Администраторы время от времени должны удалять старые профили пользователей (уволенные пользователи, неактивные пользователи, и т.д.) в каталоге C:\Users на рабочих станциях и серверах Windows. Чаще всего с задачей очисткой профилей пользователей Windows сталкиваются на терминальных серверах RDS (Remote Desktop Services).
Основная проблема терминальных серверов – постоянный рост размеров каталогов профилей пользователей на диске. Частично эта проблема решается политиками квотирования размера профиля пользователя с помощью FSRM или NTFS квот, использованием профилей типа FSLogix или User Profile Disk, перемещаемыми папками и т.д. Но при большом количестве RDS пользователей в папке C:\Users со временем накапливается огромное количество каталогов с неиспользуемыми профилями пользователей.
Как вручную удалить профиль пользователя в Windows?
В Windows вы можете вручную удалить профиль пользователя через панель управления.
- Откройте Advanced System Settings (команда
SystemPropertiesAdvanced
) -> User Profiles -> Settings; - В этом окне перечислен список всех профилей пользователей (локальных и доменных), которые хранятся на этом компьютере. Размер каждого профиля пользователя на диске указан в столбце Size.
- Выберите пользователя, чей профиль нужно удалить и нажмите кнопку Delete.
В Windows 11/10 и Windows Server 2022/2019 вы можете удалить профили пользователей с диска через приложение Settings. Перейдите в раздел Accounts -> Access work and school (или выполните команду быстрого доступа
ms-settings:otherusers
). Выберите пользователя и нажмите Remove чтобы удалить его данные с компьютера.
При корректном удалении профиля пользователя с диска будет удален каталог профиля в C:\Users и запись о пользователе в реестре.
Многие начинающиеся администраторы пытаются вручную удалить каталог с профилем пользователя из папки C:\Users. В этом случае нужно обязательно вручную удалить информацию о профиле из реестра Windows:
- Откройте редактор реестра
regedit.exe
; - Перейдите в ветку HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList
- Для каждого пользователя, выполнившего локальный вход в систему (этот метод входа должен быть разрешен пользователю настройками параметра Allow log on locally в GPO), создается отдельная ветка с SID пользователя в качестве имени;
- Вы можете найти раздел реестра, соответствующий пользователю по SID, или можете вручную просмотреть содержимое всех вложенных разделв, пока не найдете раздел, в котором значение ProfileImagePath указывает на каталог с профилем пользователя на диске (например,
C:\Users\kbuldogov
); - Удалите данный раздел реестра, чтобы завершить корректное удаление профиля.
Также вы можете удалить профиль конкретного пользователя с помощью PowerShell:
Get-CimInstance -Class Win32_UserProfile | Where-Object { $_.LocalPath.split(‘\’)[-1] -eq 'kbuldogov' } | Remove-CimInstance
Эта команда удалит как каталог на диске, так и ссылку на профиль пользователя kbuldogov в реестре HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList.
Можно удалить профиль пользователя на удаленном компьютере с помощью PowerShell Remoting и командлета Invoke-Command:
$compname="wks21s32"
$user = "kbuldogov"
Invoke-Command -ComputerName $compname -ScriptBlock {
param($user)
Get-CimInstance -Class Win32_UserProfile | Where-Object { $_.LocalPath.split(‘\’)[-1] -eq $user } | Remove-CimInstance
} -ArgumentList $user
Групповая политика для автоматической очистки старых профилей
В Windows есть специальный параметр групповой политики для автоматического удаления старых профилей пользователей старше xx дней. Вы можете включить этот параметр с помощью локального редактора GPO (
gpedit.msc
) или с помощью консоли управления доменными GPO (
gpmc.msc
). В этом примере на назначим политику автоматической очистки профилей на хосты в ферме RDS, которые вынесены в отдельный контейнер (Organizational Unit) Active Directory.
- Найдите OU с компьютерами/серверами, на который вы хотите применить политику очистки старых профилей пользователей. Щелкните по OU и выберите Create a GPO in this domain and Link it here;
- Укажите имя политики и отредактируйте GPO;
- Перейдите в раздел Конфигурация компьютера -> Административные шаблоны -> Система -> Профили пользователей (Computer Configuration -> Administrative Templates -> System -> User Profiles);
- Откройте параметр “Удалять при перезагрузке системы профили пользователей по истечении указанного числа дней” (Delete user profiles older than a specified number days on system restart);
- Включите политику и укажите через сколько дней профиль пользователя считается неактивным и “Служба профилей пользователей Windows” можно автоматически удалить такой профиль при следующей перезагрузке. Обычно тут стоит указать не менее 45-90 дней;
- После применения новых настроек групповых политк, служба User Profile Services на ваших серверах Windows будет автоматически удалять старые профили пользователей. Удаление выполняется при перезагрузке сервера.
Другой недостаток — вы не можете запретить удаление определенных профилей, например, локальных учетных записей, администраторов и т.д.
В версиях до Windows 11/10 и Windows Server 2022/2019 эта политика работала некорректно. Дело в том, что неактивноть профиля пользователя ранее определялась по дате именения файла NTUSER.dat. При установке обновлений Windows, служба Trusted Installer может менять дату изменения файла NTUSER.dat в профиле каждого пользователя. В результате служба Win32_UserProfile считает, что профиль использовался недавно.
В современных версиях Windows эта политика проверяет активность профиля пользователей по параметрам LocalProfileUnloadTimeLow и LocalProfileUnloadTimeHigh в ветке
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\<User Sid>
.
$profilelist = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" foreach ($p in $profilelist) { try { $objUser = (New-Object System.Security.Principal.SecurityIdentifier($p.PSChildName)).Translate([System.Security.Principal.NTAccount]).value } catch { $objUser = "[UNKNOWN]" } Remove-Variable -Force LTH,LTL,UTH,UTL -ErrorAction SilentlyContinue $LTH = '{0:X8}' -f (Get-ItemProperty -Path $p.PSPath -Name LocalProfileLoadTimeHigh -ErrorAction SilentlyContinue).LocalProfileLoadTimeHigh $LTL = '{0:X8}' -f (Get-ItemProperty -Path $p.PSPath -Name LocalProfileLoadTimeLow -ErrorAction SilentlyContinue).LocalProfileLoadTimeLow $UTH = '{0:X8}' -f (Get-ItemProperty -Path $p.PSPath -Name LocalProfileUnloadTimeHigh -ErrorAction SilentlyContinue).LocalProfileUnloadTimeHigh $UTL = '{0:X8}' -f (Get-ItemProperty -Path $p.PSPath -Name LocalProfileUnloadTimeLow -ErrorAction SilentlyContinue).LocalProfileUnloadTimeLow $LoadTime = if ($LTH -and $LTL) { [datetime]::FromFileTime("0x$LTH$LTL") } else { $null } $UnloadTime = if ($UTH -and $UTL) { [datetime]::FromFileTime("0x$UTH$UTL") } else { $null } [pscustomobject][ordered]@{ User = $objUser SID = $p.PSChildName Loadtime = $LoadTime UnloadTime = $UnloadTime } }
PowerShell скрипт для удаления старых профилей пользователей в Windows
Вы можете удалять профили неактивных или заблокированных пользователей с помощью скрипта PowerShell.
Сначала попробуем подсчитать размер профиля каждого пользователя в папке C:\Users c помощью простого скрипта из статьи “Вывести размер папок с помощью PowerShell”:
gci -force ‘C:\Users\’-ErrorAction SilentlyContinue | Where { !($_.Attributes -match " ReparsePoint") }| ? { $_ -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 Гб).
Следующий PowerShell скрипт выведет список подробную информацию о профилях пользователей, которые не обновлялись более 60 дней. Скрипт сконвертирует SID пользователя в имя, посчитает размер профиля каждого пользователя и выведет все в таблице:
$allprofilesinfo = @() $OldProfiles=Get-WMIObject -class Win32_UserProfile | Where {(!$_.Special) -and ($_.ConvertToDateTime($_.LastUseTime) -lt (Get-Date).AddDays(-60))} Foreach ($OldProfile in $OldProfiles) {$objSID = New-Object System.Security.Principal.SecurityIdentifier ($OldProfile.SID) $objUser = $objSID.Translate( [System.Security.Principal.NTAccount]) $userinfo = New-Object PSObject -Property @{ userName = $objUser.Value ProfilePath = $OldProfile.localpath LastUsedDate = $OldProfile.ConvertToDateTime($OldProfile.LastUseTime) FolderSize = "{0:N2} GB" -f ((gci –force $OldProfile.localpath –Recurse -ErrorAction SilentlyContinue| measure Length -s).sum / 1Gb) } $allprofilesinfo += $userinfo } $allprofilesinfo
Чтобы удалить все эти профили достаточно добавить перенаправить список на команду Remove-WmiObject (перед использование скрипта удаления желательно несколько раз перепроверить его вывод с помощью параметра –WhatIf ):
Get-WMIObject -class Win32_UserProfile | Where {(!$_.Special) -and (!$_.Loaded) -and ($_.ConvertToDateTime($_.LastUseTime) -lt (Get-Date).AddDays(-30))} | Remove-WmiObject –WhatIf
Как мы уже упомянули выше, при установке некоторых обновлений Windows, служба Trusted installer может менять дату изменения файла NTUSER.dat в профиле каждого пользователя.
На скриншоте выше видно, что все профили были изменены примерно в одно и тоже время. Проверьте дату последней установки обновлений в Windows:
gwmi win32_quickfixengineering |sort installedon |select InstalledOn -Last 1
Или с помощью модуля PSWindowsUpdate:
Get-WUHistory | Select-Object -First 20
Скорее всего она совпадет с датой изменения профилей. Поэтому в старых версиях Windows можно получить список неактивных профилей с помощью другого скрипта, который проверяет атрибуту lastwritetime каталога пользователя:
$USERS= (Get-ChildItem -directory -force 'C:\Users' | Where { ((Get-Date) — $_.lastwritetime).days -ge 60 } | % {'c:\users\' + $_.Name})
foreach ($User in $USERS) {
Get-WMIObject -class Win32_UserProfile | Where {(!$_.Special) -and (!$_.Loaded) -and ($_.LocalPath -eq $User)} | 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 Русская там пусто(
Разобрался, не хватало прав для чтения этого атрибута.
У меня та же ошибка.
Команда «Get-WMIObject -class Win32_UserProfile|select LastUseTime» выдаёт:
LastUseTime
-----------
20221212041018.000000+000
20221214040351.761000+000
20221212041017.890000+000
Та же ошибка.. Подскажите, как решили
Спасибо
привет.
мой переделанный скриптик:
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 …
не могу вкурить в чем проблема
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)
{
Remove-Item -WhatIf -Recurse -Force $LocalProfile
Write-host $LocalProfile «профиль будет удален» -ForegroundColor Magenta
}
} -ComputerName …
А не проще использовать перемещаемые профиля и перенаправленные папки?
в десятке перемещаемые профили могут быть несовместимы с приложениями из майкрософт стор. казалось бы и хрен с ними но у нас бухгалтерия взбунтовалась помтоу что виндовый калькулятор так тоже не работает 🙂 и еще кое что
Они тоже могут быть неактуальными.
gci -force ‘C:\Users\’-ErrorAction SilentlyContinue | Where { !($_.Attributes -match «ReparsePoint») }| ? { $_ -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)
исключит символьные ссылки — более точно
День добрый.
А как можно удалить все вложенные папки и файлы внутри каталогов пользователей, кроме Desktop,Documents,Downloads,Music,Pictures,Videos?
К примеру чтобы в каталоге:
c:\Users\Ivanov_I
Удалились все папки и файлы кроме папок Desktop,Documents,Downloads,Music,Pictures,Videos?
На PowerShell это реализуется несложно.
Вот вам для зацепки.
Get-ChildItem -Path C:\TestFolder -Exclude SubFolder1,SubFolder2 | Remove-Item -Recurse -Force
Так не получается, он не удаляет рекурсивно.
Предположим есть несколько учетных записей на куче компов в домене с в профиле каждого такой комплект каталогов:
c:\Users\Ivanov_I\AppData\
c:\Users\Ivanov_I\Application Data\
c:\Users\Ivanov_I\Contacts\
c:\Users\Ivanov_I\Cookies\
c:\Users\Ivanov_I\Desktop\
c:\Users\Ivanov_I\Favorites\
c:\Users\Ivanov_I\Links\
c:\Users\Ivanov_I\Local Settings\
c:\Users\Ivanov_I\NetHood\
c:\Users\Ivanov_I\PrintHood\
c:\Users\Ivanov_I\Recent\
c:\Users\Ivanov_I\Saved Games\
c:\Users\Ivanov_I\Searches\
c:\Users\Ivanov_I\SendTo\
c:\Users\Ivanov_I\Web\
Мне нужно удалить в КАЖДОМ каталоге пользователя все каталоги кроме
Desktop\
Documents\
Downloads\
Music\
Pictures\
Video\
Я бы так сделал. Не забудьте убрать whatif
$userslist=Get-ChildItem C:\users
foreach ($user in $userslist)
{
Get-ChildItem -Path $user.FullName -Exclude Desktop,Documents,Downloads,Music,Pictures,Video} | Remove-Item -Recurse -Force -whatif
}
Народ помогите, плиз. Так все норм, выдает список, которые больше года не входили. с WhatIf показывает выполнение операции, удаление каталогов. но, убираю whatif и не может их удалить, нет доступа. Пробовал в планировщик от имени СИСТЕМА, результат нулевой.
$ExcludedUsers = «.NET v4.5», «.NET v4.5 Classic», «Администратор», «MSSQL$MICROSOFT##WID», «Default User», «Public», «All Users», «Default», «Все пользователи»
dir -directory -force C:\Users -Exclude $ExcludedUsers | ? { ((Get-Date) — $_.lastwritetime).days -ge 365 } | rm -Recurse -Force -WhatIf
Скрипт спотыкается на одном профиле, или в принципе не может удалить ни одного? Профили точно никем не используются? Может там антивирус или еще кто держит их открытыми.
Попробуйте удалить любой профиль так:
Get-Item C:\Users\someuser|rm -Recurse -Force
Вообще не может ничего удалить. Пробовал и так, нацеливая на определенный профиль. Итог тот же. Нет доступа. Вручную удаляются.
Порыскав в инете, есть такие статьи, где сказано, что PowerShell не может удалять профили, только cmd
rm : Отказано в доступе по пути «C:\Users\пользователь».
строка:1 знак:111
+ … { ((Get-Date) — $_.lastwritetime).days -ge 365 } | rm -Recurse -Force
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : PermissionDenied: (C:\Users\пользователь:String) [Remove-Item], UnauthorizedAccessException
+ FullyQualifiedErrorId : RemoveItemUnauthorizedAccessError,Microsoft.PowerShell.Commands.RemoveItemCommand
«Подкрутил», работает на ура:
$USERS= (Get-ChildItem -directory -force ‘C:\Users’ | Where { ((Get-Date) — $_.lastwritetime).days -ge 60 } | % {‘c:\users\’ + $_.Name})
foreach ($User in $USERS) {
Get-WMIObject -class Win32_UserProfile | Where {(!$_.Special) -and (!$_.Loaded) -and ($_.LocalPath -eq $User)} | Remove-WmiObject }
а с UPD есть похожее решение?
icm -cn GTE78 {gcim Win32_UserProfile | ? { $_.LocalPath.split(‘\’)[-1] -eq ‘kbuldogov’ } | rcim}
На перемещаемых профилях какая то проблема с отработкой скрипта. ошибка «Exception calling «ConvertToDateTime» with «1» argument(s): «Exception calling «ToDateTime» with «1» argument». Если профиль локальный, то всё работает.Нашел еще утилиту по удалению профилей «Delprof2 – User Profile Deletion Tool». Удаляются профиля.
Функция подойдет только для локальных профилей…
Т.е. у вас получилось как-то автоматизировать удаление старых перемещаемых профилей через Delprof2? ПРишлите пример параметров