本文提供了一个脚本,用于检索配置文件年龄和选择性地删除过期副本。
适用于: Windows Server 2016 及更高版本、Windows 11、Windows 10
存在组策略“删除系统重启时早于指定天数的用户配置文件”,用于删除用户配置文件的过期副本。
自 Windows 10、Windows Server 2019 及更高版本以来,已使用注册表中存储的时间戳。 这比使用配置文件 NTUSER 的新技术文件系统 (NTFS) 时间戳的旧操作系统 (OS) 版本中的方法更可靠。DAT 文件,因为此文件可由加载用户注册表的任何软件更新。
此脚本允许你体验策略将执行的操作,或使用不同的参数运行策略。 例如,可以使用较低的未使用间隔清理配置文件。 该脚本将仅清理脚本运行时未加载的配置文件。
重要
此示例脚本在任何Microsoft标准支持计划或服务下都不受支持。
示例脚本按原样提供,没有任何保证。 Microsoft进一步否认所有默示担保,包括不限于适销性或针对特定用途的适用性的任何默示担保。
示例脚本和文档的使用或性能导致的整个风险依然存在。 在任何情况下,不得Microsoft、其作者或参与创建、制作或交付脚本的任何其他人对任何损害负责(包括但不限于业务利润损失、业务中断、业务中断、业务信息损失或其他经济损失)因使用或无法使用示例脚本或文档而导致的损害。 即使Microsoft被告知这种损害的可能性。
重要
使用注册表信息的远程执行需要在目标计算机上允许 Windows 远程管理(WinRM)。
###############################################################################
#
# Usage:
# Powershell -file ADDelProf.ps1 [-Days #] [-UserDomain <domainname>] [-UserName <username>] [-RoamingOnly] [-UseNtfs] [-Delete] [-Quiet] [-ComputerName]
#
# -Days = Number of days a profile has not been used to consider for deletion (default 0)
# -UserDomain = Domain pattern to match to consider for deletion (default "*")
# -UserName = Username pattern to match to consider for deletion (default "*")
# -RoamingOnly = Determines if only profiles that would be roaming should be deleted. Default is to do all unloaded profiles.
# -UseNtfs = Determines if using reg-entries or NTFS timestamp on ntuser.dat. Default is registry.
# -Delete = Determines if profiles will be deleted or reported. Default is to just report.
# -Quiet = Prompts or not, default is to prompt.
# -ComputerName = Computer to delete profiles against. Default is the local machine. Needs WinRM to work so we can invoke registry read remotely.
#
# Example:
# To delete profiles that haven't been used in 60 or more days on the local machine
# Powershell -file ADDelProf.ps1 -Days 60 -Delete True
#
# Parameters definition
Param(
$Days = 0,
[string]$UserDomain = "*",
[string]$UserName = "*",
[switch]$RoamingOnly = $false,
[switch]$UseNtfs = $false,
[switch]$Delete = $false,
[switch]$Quiet = $false,
[string]$ComputerName = "."
)
[void][System.Reflection.Assembly]::Load('System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a')
[void][System.Reflection.Assembly]::Load('System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')
Function ConvertToDate
{
param(
[uint32]$lowpart,
[uint32]$highpart
)
$ft64 = ( [UInt64]$highpart -shl 32) -bor $lowpart
[datetime]::FromFileTime( $ft64 )
}
Write-Host "Parameters for deletion:"
Write-Host "Target Computer.:" $ComputerName
Write-Host "Days Not used...:" $Days
Write-Host "Domain..........:" $UserDomain
Write-Host "UserName........:" $UserName
Write-Host "Roaming Only....:" $RoamingOnly
Write-Host "UseNtfs.........:" $UseNtfs
Write-Host "Delete..........:" $Delete
Write-Host "Quiet Mode......:" $Quiet
Write-Host ""
Write-Host "To change parameters, provide command line switches."
Write-Host "E.g. to delete roaming profile copies older than 30 days: ADDelProf.ps1 -Days 30 -Roaming True -Delete $True"
#Setup Prompt Parameters
$title = "Delete Profile"
$message = "Delete user profile?"
$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Deletes the profile."
$no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Does not delete the profile."
$all = New-Object System.Management.Automation.Host.ChoiceDescription "&All", "Switches to 'quiet mode' and deletes remaining matching profiles."
$cancel = New-Object System.Management.Automation.Host.ChoiceDescription "&Cancel", "Exits the script."
$options = [System.Management.Automation.Host.ChoiceDescription[]]($no, $yes, $all, $cancel)
#Get Unloaded profiles and maybe ones that are only roaming
$Filter = "Loaded = 'False' and Special = 'False'"
if( $RoamingOnly ) { $Filter = "$Filter and RoamingPreference = 'True'" }
$UnloadedProfiles = gwmi win32_userprofile -filter $Filter -computername $ComputerName -ErrorAction stop
if( -not ($UnloadedProfiles -eq $null) ){
#Match Profiles to delete
Write-Host "Matching Profiles....."
$UnloadedProfiles | ForEach-Object {
$CurUserSid = $_.sid
$CurUserObj = [wmi]"\\$ComputerName\root\cimv2:Win32_SID.SID='$CurUserSid'"
$CurUserDomain = $CurUserObj.ReferencedDomainName
if( $CurUserDomain -eq "" ) { $CurUserDomain = "<Unresolved>" }
$CurUserName = $CurUserObj.AccountName
if( $CurUserName -eq "" ) { $CurUserName = "<Unresolved>" }
$CurUser = "$CurUserDomain\$CurUserName"
if( $UseNtfs ) {
if( $_.lastusetime ) {
$CurUserDaysOld = (((Get-Date) - [System.Management.ManagementDateTimeConverter]::ToDateTime($_.lastusetime)).days)
} else {
$CurUserDaysOld = 99999
}
}
else {
#Use registry keys
$profilelistsidkey = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$CurUserSid"
$SaveErrorActionPreference = $ErrorActionPreference
$ErrorActionPreference = "SilentlyContinue"
$localprofileunloadtimelow = 0
$localprofileunloadtimehigh = 0
if( $ComputerName -eq ".") {
$localprofileunloadtimelow = Get-ItemPropertyValue -Path $profilelistsidkey -Name LocalProfileUnLoadTimeLow
$localprofileunloadtimehigh = Get-ItemPropertyValue -Path $profilelistsidkey -Name LocalProfileUnLoadTimeHigh
}
else {
$RemSession = New-PSSession -ComputerName $ComputerName
$result = Invoke-Command -Session $RemSession {
Get-ItemProperty $Using:profilelistsidkey -Name LocalProfileLoadTimeLow, LocalProfileLoadTimeHigh
}
$localprofileunloadtimelow = $result.LocalProfileLoadTimeLow
$localprofileunloadtimehigh = $result.LocalProfileLoadTimeHigh
}
$lastusetime = ConvertToDate $localprofileunloadtimelow $localprofileunloadtimehigh
$ErrorActionPreference = $SaveErrorActionPreference
if( $lastusetime ) {
$CurUserDaysOld = ((Get-Date) - $lastusetime).days
}
else {
$CurUserDaysOld = 99999
}
}
if (
($CurUserDaysOld -ge $Days) -and
($CurUserDomain -like $UserDomain) -and
($CurUserName -like $UserName)
){
Write-Host "Age:" $CurUserDaysOld "-" $CurUser "-" $_.Sid "- Path:" $_.localpath
if( $Delete ){
$bDelete = $false
if( $Quiet ){
$bDelete = $true
} else {
$result = $host.ui.PromptForChoice($title, $message, $options, 0)
if( $result -eq 3 ) {
"Exiting Script"
Exit
} elseif( $result -eq 0 ) {
" Skipping profile."
} else {
$bDelete = $true
if( $result -eq 2 ) { $Quiet = $true }
}
}
if( $bDelete ) {
Write-Host " Deleting profile - " -NoNewline
try {
$_.Delete()
write-host "Successfully deleted profile." -ForegroundColor green
}
catch {
write-host "Error occured deleting profile. It may have been only partially deleted." -ForegroundColor red
}
}
}
}
}
}
"Completed!"