Удаление профилей пользователей с сервера с помощью PowerShell

Опубликовано: 29.11.2013
Автор: Виталий Бочкарев
Поддержать автора статьи по этой ссылке
Доступна новая версия скрипта. Подробности в статье "Удаление старых файлов на сервере Windows (версия 2)".

На терминальных серверах существует проблема с автоматическим удалением перемещаемых профилей (roaming profiles), если в терминальной сессии происходил сбой какой-нибудь программы - временные файлы программы блокируют удаление профиля с терминального сервера. При следующем входе в терминальную сессию для пользователя создастся новый профиль с каким-то суфиксом, так как старый неудаленный профиль не даст корректно переместить профиль с файлового сервера на терминальный. Со временем таких неудаленных профилей накопится очень много, что замусорит систему.

Для решения вышеописанной проблемы рекомендую использовать скрипт удаления профилей с терминального сервера, который должен быть запущен сразу же после перезагрузки сервера, когда терминальные сессии к серверу еще не установлены, то есть когда на сервере по определению не может быть профилей пользователей.

Внимание! Ни в коем случае нельзя удалять профиль пользователя (или его часть), если открыта сессия пользователя к терминальному серверу. В этом случае при завершении сессии пользователя на файловый сервер вернется неправильная версия профиля.
Внимание! На терминальных серверах без перемещаемых профилей данная проблема не возникает - применять скрипт удаления профилей не нужно. Для освобождения места на дисках нужно очищать временные файлы в каждом профиле, когда пользователь не подключен к серверу.

Ниже приведен код скрипта удаления профилей Remove-Profiles.ps1.

# Получение пути к скрипту
$ScriptFolder = $MyInvocation.MyCommand.Path.SubString(0,($MyInvocation.MyCommand.Path.Length - `
 $MyInvocation.MyCommand.Name.Length))
# Формирование пути к лог-файлу
$LogFile = $ScriptFolder + $MyInvocation.MyCommand.Name.SubString(0,($MyInvocation.MyCommand.Name.Length `
 - 4)) + ".log"
# Создание лог-файла
Out-File -FilePath $LogFile

# Указание папки профилей
$ProfileFolder = "C:\Users"
# Указание служебных папок
$ExcludedProfiles = "Default", "Default User", "Public", "All Users", "Administrator"

# Получение объектов из папки профилей
$SubDirs= Get-ChildItem $ProfileFolder -Force

# Обработка объектов из папки профилей
ForEach ($Dir in $SubDirs) {
   $LogMessage = $Nothing
   # Проверка, что объект существует и это папка
   If (Test-Path $Dir.FullName -PathType Container) {
       # Проверка, что профиль - это не служебная папка
       $NotDeleteFlag = $False
       ForEach ($ExcludedProfile in $ExcludedProfiles) {
           If ($Dir.Name -eq $ExcludedProfile) {
               $NotDeleteFlag = $True
           }
       }
       # Удаление профиля
       If ($NotDeleteFlag -eq $False) {
           # Формирование события для лога
           $LogMessage = get-date -uformat "%d.%m.%Y %H:%M:%S"
           $LogMessage += "`t"
           $LogMessage += $Dir.FullName + " is deleted."
           # Удаление папки
           #Remove-Item -Path $Dir.FullName -Force -Recurse
           $CmdLine = "cmd /c RD /S /Q """ + $Dir.FullName + """"
           Invoke-Expression -command $CmdLine
       }
       Else {
           # Формирование события для лога
           $LogMessage = get-date -uformat "%d.%m.%Y %H:%M:%S"
           $LogMessage += "`t"
           $LogMessage += $Dir.FullName + " is skipped as a service folder."
       }
   }
   Else {
       # Формирование события для лога
       $LogMessage = get-date -uformat "%d.%m.%Y %H:%M:%S"
       $LogMessage += "`t"
       $LogMessage += $Dir.FullName + " is skipped as a file."
   }
   # Запись события в лог-файл
   $LogMessage | Out-File -FilePath $LogFile -Append
}

Обращаю внимание, что в скрипте закомментирована строка удаления папок средствами PowerShell

Remove-Item -Path $Dir.FullName -Force -Recurse

Это сделано по причине того, что PowerShell не может удалить папки, на которые у пользователя нет прав, даже если оболочка запущена с повышенными административными правами. Проблема решается вызовом обычной командной строки с командой RD, которая не имеет указанной проблемы.

$CmdLine = "cmd /c RD /S /Q """ + $Dir.FullName + """"
Invoke-Expression -command $CmdLine

Перед использованием скрипта на сервере нужно разрешить выполнение неподписанных сценариев командой

Set-ExecutionPolicy -ExecutionPolicy "RemoteSigned"

Проверить текущую политику выполнения сценариев можно командой

Get-ExecutionPolicy

Чтобы корректно удалять профили, нужно чтобы все сессии этих профилей были завершены и перемещаемые профили вернулись на файловый сервер, если это возможно. Форсировать завершение сессий и отправку профилей на файловый сервер можно перезагрузкой сервера. В планировщике заданий Windows нужно поставить 2 задачи:
- задачу на перезагрузку сервера раз в неделю перед началом рабочего дня;
- задачу на выполнение скрипта от имени системы с повышенными административными правами при загрузке сервера.

Запуск скрипта лучше осуществлять через командный файл

powershell.exe "%~DP0Remove-Profiles.ps1"

После выполнения сценария все профили, которые зависли на терминальном сервере будут стерты. А копии профилей с правильной конфигурацией останутся на файловом сервере. То есть при входе в терминальную сессию пользователь получит последнюю правильную копию своего профиля.