На рабочих станциях и серверах Windows, особенно на терминальных серверах RDS (Remote Desktop Services), периодически возникает необходимость очистки каталога C:\Users от старых профилей пользователей (уволенные пользователи, пользователи, которые долго не используют сервер и т.д.).
Основная проблема терминальных серверов – постоянный рост размеров каталогов профилей пользователей на диске. Частично эта проблема решается политиками квотирования размера профиля пользователя с помощью FSRM или NTFS квот, перемещаемыми папками и т.д. Но при большом количестве пользователей терминального сервера в папке C:\Users со временем накапливается огромное количество каталогов с ненужными профилями пользователей.
Ручное удаление профиля пользователя в Windows
Многие начинающиеся администраторы пытаются вручную удалить каталог с профилем пользователя из папки C:\Users. Так можно делать, если вы после удаления папки вручную удалите раздел профиля пользователя со ссылкой на каталог в ветке реестра HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\ CurrentVersion\ProfileList. Правильный ручной способ удаления профиля пользователя в Windows – открыть свойства системы, перейти в Advanced System Settings -> User Profiles -> Settings, выбрать в списке пользователя (в столбце Size указан размер профиля пользователя) и нажать кнопку Удалить.
Но это ручной способ, а хочется автоматизации.
Групповая политика автоматического удаления старых профилей
В Windows есть встроенная групповая политика для автоматического удаления старых профилей пользователей старше xx дней. Эта политика находится в разделе Конфигурация компьютера -> Административные шаблоны -> Система -> Профили пользователей (Computer Configuration -> Administrative Templates -> System -> User Profiles) и называется “Удалять при перезагрузке системы профили пользователей по истечении указанного числа дней” (Delete user profiles older than a specified number days on system restart). Вы можете включить этот параметр в локальном редакторе политик (gpedit.msc) или с помощью доменных политик из консоли GPMC.msc.
Включите политику и укажите через сколько дней профиль пользователя считается неактивным и “Служба профилей пользователей Windows” можно автоматически удалить такой профиль при следующей перезагрузке. Обычно тут стоит указать не менее 45-90 дней.
Основные проблемы такого способа автоматической очистки профилей – ожидание перезагрузки сервера и неизбирательность (вы не можете запретить удаление определенных профилей, например, локальных учетных записей, администраторов и т.д.). Также эта политика может не работать, если некоторое стороннее ПО (чаще всего это антивирус) обращается к файлу NTUSER.DAT в профилях пользователей и обновляет дату последнего использования.
Очистка сервера от старых профилей пользователей с помощью PowerShell
Вместо использования рассмотренной выше политики автоматической очистки профилей, вы можете использовать простой PowerShell скрипт для поиска и удаления профилей неактивных или заблокированных пользователей.
Сначала попробуем подсчитать размер профиля каждого пользователя в папке C:\Users c помощью простого скрипта из статьи “Вывести размер папок с помощью PowerShell”:
gci -force 'C:\Users'-ErrorAction SilentlyContinue | ? { $_ -is [io.directoryinfo] } | % {
$len = 0
gci -recurse -force $_.fullname -ErrorAction SilentlyContinue | % { $len += $_.length }
$_.fullname, '{0:N2} GB' -f ($len / 1Gb)
$sum = $sum + $len
}
“Общий размер профилей”,'{0:N2} GB' -f ($sum / 1Gb)
Итого суммарный размер всех профилей пользователей в каталоге C:\Users около 22 Гб.
Теперь выведем список пользователей, профиль которых не использовался более 60 дней. Для поиска можно использовать значение поля профиля LastUseTime.
Get-WMIObject -class Win32_UserProfile | Where {(!$_.Special) -and ($_.ConvertToDateTime($_.LastUseTime) -lt (Get-Date).AddDays(-60))}| Measure-Object
У меня на терминальном сервере оказалось 143 профиля неактивных пользователей (общим размером около 10 Гб).
Чтобы удалить все эти профили достаточно добавить перенаправить список на команду Remove-WmiObject (перед использование скрипта удаления желательно несколько раз перепроверить его вывод с помощью параметра –WhatIf ):
Get-WMIObject -class Win32_UserProfile | Where {(!$_.Special) -and (!$_.Loaded) -and ($_.ConvertToDateTime($_.LastUseTime) -lt (Get-Date).AddDays(-30))} | Remove-WmiObject –WhatIf
Чтобы не удалять профили некоторых пользователей, например, специальные аккаунты System и Network Service, учетную запись локального администратора, пользователей с активными сессиями, список аккаунтов-исключений), нужно модифицировать скрипт следующим образом:
#Список аккаунтов, чьи профили нельзя удалять
$ExcludedUsers ="Public","zenoss","svc",”user_1”,”user_2”
$LocalProfiles=Get-WMIObject -class Win32_UserProfile | Where {(!$_.Special) -and (!$_.Loaded) -and ($_.ConvertToDateTime($_.LastUseTime) -lt (Get-Date).AddDays(-60))}
foreach ($LocalProfile in $LocalProfiles)
{
if (!($ExcludedUsers -like $LocalProfile.LocalPath.Replace("C:\Users\","")))
{
$LocalProfile | Remove-WmiObject
Write-host $LocalProfile.LocalPath, "профиль удален” -ForegroundColor Magenta
}
}
Вы можете настроить запуск этого скрипта через shutdown скрипт групповой политики или по расписанию заданием планировщика. (перед настройкой автоматического удаления профилей внимательно протестируйте скрипт в своей среде!).
Можно модифицировать скрипт, чтобы автоматически удалять пользователи всех пользователей, которые добавлены в определенную группу AD (например, группа DisabledUsers):
$users = Get-ADGroupMember -Identity DisabledUsers | Foreach {$_.Sid.Value}
$profiles = Get-WmiObject Win32_UserProfile
$profiles | Where {$users -eq $_.Sid} | Foreach {$_.Delete()}
Добрый день.
Данное решение может не работать.
Trusted installer может менять дату изменения файла NTUSER.dat после установки накопительных обновлений.
Win32_UserProfile и служба профилей отслеживает именно дату изменения именно этого файла (Это легко проверить путем изменения даты).
В 2019 и 10 RS5 данное поведение исправлено.
Спасибо за полезное уточнение! Все верно, отслеживание активности профиля выполняется по дате модификации ntuser.dat
Что делать если пользователь не заходил в систему, а файл ntuser.dat изменен?
Поможет ли использование Get-ChildItem -directory -force «C:\Users» | Where { ((Get-Date) — $_.lastwritetime).days -ge 60 } или все-таки необходимо обходиться другими методами?
Выше указан вариант с обновлением файла профиля при установке security обновлений.
Ваш вариант с выбором папок с датой модификации через posh в принципе ничего. В моем случае он действительно выбрал профили старых пользователей.
Супер, спасибо!
Exception calling «ConvertToDateTime» with «1» argument(s): «Exception calling «ToDateTime» with «1» argument(s):
«Specified argument was out of the range of valid values.
Parameter name: dmtfDate»»
At C:\distr\userdel1.ps1:3 char:64
+ … le | Where {(!$_.Special) -and (!$_.Loaded) -and ($_.ConvertToDateTim …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ScriptMethodRuntimeException
ошибочка 🙁
Какая версия Windows? И язык ОС? Подозреваю, что русский.
OS Name: Microsoft Windows Server 2016 Standard
OS Version: 10.0.14393 N/A Build 14393
Product ID: 00377-60000-00000-AA934
System Manufacturer: Microsoft Corporation
System Model: Virtual Machine
System Type: x64-based PC
Processor(s): 1 Processor(s) Installed.
BIOS Version: Microsoft Corporation Hyper-V UEFI Release v1.0, 26.11.2012
Input Locale: ru;Russian
Hotfix(s): 6 Hotfix(s) Installed.
[01]: KB3199986
[02]: KB3202790
[03]: KB4091664
[04]: KB4132216
[05]: KB4457146
[06]: KB4471321
Current language for non-Unicode programs:
Russian (Russia)
Терминалка цитриксоая
Посмотрите, что у вас указано в атрибуте LastUseTime:
Get-WMIObject -class Win32_UserProfile|select LastUseTime
скрипт целиком
$ExcludedUsers =»Public»,»Administrator»,»CitrixTelemetryService»
$LocalProfiles=Get-WMIObject -class Win32_UserProfile | Where {(!$_.Special) -and (!$_.Loaded) -and ($_.ConvertToDateTime($_.LastUseTime) -lt (Get-Date).AddDays(-60))}
foreach ($LocalProfile in $LocalProfiles)
{
if (!($ExcludedUsers -like $LocalProfile.LocalPath.Replace(«C:\Users\»,»»)))
{
$LocalProfile | Remove-WmiObject
Write-host $LocalProfile.LocalPath, «профиль удален” -ForegroundColor Magenta
}
}
У меня Вин7 Русская там пусто(
Разобрался, не хватало прав для чтения этого атрибута.
Та же ошибка.. Подскажите, как решили
Спасибо
привет.
мой переделанный скриптик:
Invoke-Command -ScriptBlock {
#Список аккаунтов, чьи профили нельзя удалять
$ExcludedUsers =»Public»
$LocalProfiles=Get-ChildItem -directory -force «C:\Users» -Exclude $ExcludedUsers | Where { ((Get-Date) — $_.lastwritetime).days -ge 90 }
foreach ($LocalProfile in $LocalProfiles)
{
$LocalProfile| Remove-Item -Recurse -Force
Write-host $LocalProfile «профиль удален” -ForegroundColor Magenta
}
} -ComputerName …
выдает ошибку:
Тег, указанный в запросе, и тег в буфере точки повторной обработки не совпадают
+ CategoryInfo : NotSpecified: (:) [Remove-Item], Win32Exception
+ FullyQualifiedErrorId : System.ComponentModel.Win32Exception,Microsoft.PowerShell.Commands.RemoveItemCommand
+ PSComputerName …
не могу вкурить в чем проблема