脚本:检索配置文件期限并选择性地删除过期副本

本文提供了一个脚本,用于检索配置文件年龄和选择性地删除过期副本。

适用于: 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!"