PowerShell清理Windows旧用户脚本卡顿问题排查求助
问题背景
我需要编写一款可在企业电脑运行的PowerShell脚本,实现Windows旧用户配置文件清理功能,具体需求包括:
- 获取本地用户列表
- 排除指定系统/业务账户
- 删除90天未登录用户的配置文件(需同时清理对应注册表项)
我对PowerShell不太熟悉,先后写了初始版本和更新版本的脚本,但不清楚脚本运行卡顿的原因,希望帮忙排查。
初始脚本
$User = get-childitem -Path C:\Users -Name #Current Date for comparison $Cutoff = (Get-Date).AddDays(-90) #Cut Public and Administrator accounts from the list $Userlist = @() foreach ($User in $Users) { if ($User -in "Public", "Administrator", "administrator", ".Net v4.5", ".Net v4.5 classic", ".Default", "itadmin", "it-admin", "cpsi") { # Skip the user if their name is in the exclusion list continue } try { $Userlist.Add($User) } catch { Write-Host "Could not add the user $User to the Userlist." } } #Gather all Login events in the $events variable $Events = Get-WinEvent -FilterHashtable @{ LogName='Security'; Id='4624'; StartTime=(Get-Date).AddDays(-90)} $LogList = @{} $user -lt ($cutoff) #Loop through the previously gathered list is users foreach ($User in $Userlist){ #Filter the Events for ones performed by the user and store it in $Logins $Logins = $Events| Where {$_.Message -like "*$User*" -and $_.Message -like "*@tchospital.local*"} #Output line ($Logins | Measure).Count will output the number of times the user login during the currently available security logs $LoginCount = ($Logins | Measure).Count if($LoginCount -eq 0){ $RemovedUserList =@($RemovedUserList + $User) $UserFolder = "C:\Users\$User" Get-WmiObject -Class Win32_UserProfile | Where LocalPath -eq $UserFolder | Remove-WmiObject} else{ continue } } $CutOut = Write-Output "Cutoff date is $Cutoff" $UserOut = Write-Output "Found users are: $Userlist" $Today = Get-Date $RunDate= Write-Output "Rundate is: $Today" if (!($RemovedUserList)){ $RemovedUser = Write-Output "No one has been removed" } else{ $RemovedUser = Write-Output "$RemovedUserList has been removed" }
更新后脚本
# Get a list of user folders in C:\Users $UserNames = Get-ChildItem -Path C:\Users -Name # Set the cutoff date to 90 days ago $Cutoff = (Get-Date).AddDays(-90) # Initialize user lists $Userlist = @() $RemovedUserList = @() # Loop through each user folder foreach ($UserName in $UserNames) { # List of excluded user names $ExcludedUsers = @("Public", "Administrator", "administrator", ".Net v4.5", ".Net v4.5 classic", ".Default", "itadmin", "it-admin", "cpsi") # Check if the current user name is in the excluded list if ($UserName -in $ExcludedUsers) { Write-Host "Excluded user: $UserName" continue } # Add the user to the $Userlist if it's not excluded $Userlist += $UserName } # Get security events for logins in the last 90 days $Events = Get-WinEvent -FilterHashtable @{ LogName='Security'; Id='4624'; StartTime=$Cutoff } # Loop through each user in the $Userlist foreach ($UserName in $Userlist) { $Logins = $Events | Where-Object { $_.Message -like "*$UserName*" -and $_.Message -like "*@tchospital.local*" } $LoginCount = ($Logins | Measure-Object).Count if ($LoginCount -eq 0) { $RemovedUserList += $UserName $UserFolder = "C:\Users\$UserName" try { Get-WmiObject -Class Win32_UserProfile | Where-Object LocalPath -eq $UserFolder | Remove-WmiObject -ErrorAction Stop Write-Host "Removed user profile for: $UserName" } catch { Write-Host "Failed to remove user profile for: $UserName" Write-Host "Error: $_" } } } # Output the results to files in the root of the C drive $Cutoff | Out-File -Append -FilePath "C:\cutoff.txt" $Userlist | Out-File -Append -FilePath "C:\userlist.txt" $Today = Get-Date $Today | Out-File -Append -FilePath "C:\runlog.txt" if ($RemovedUserList.Count -eq 0) { "No users were removed" | Out-File -Append -FilePath "C:\runlog.txt" } else { "Skipped users: $($RemovedUserList -join ', ')" | Out-File -Append -FilePath "C:\runlog.txt" }
卡顿原因排查
1. 安全事件处理效率极低
Get-WinEvent获取的90天内4624登录事件量可能极大,后续循环每个用户都要全量遍历事件集,用-like做全文模糊匹配:
- 用户数量越多,重复扫描事件的次数越多
- 模糊匹配
Message字段是性能黑洞,事件量上万时卡顿会非常明显
2. WMI查询重复执行
每次删除用户时都调用Get-WmiObject -Class Win32_UserProfile全量查询配置文件,再过滤路径,重复调用会浪费大量系统资源。
3. 数组拼接方式低效
用$Userlist += $UserName和$RemovedUserList += $UserName拼接数组,每次都会创建新数组,用户数量多的时候会累积性能开销。
4. 初始脚本的语法错误
初始脚本存在变量名写错($User = get-childitem...却循环$Users)、无意义代码($user -lt ($cutoff))等问题,虽不直接导致卡顿,但会引发逻辑错误和不必要的处理。
优化后的脚本
# 配置参数 $ExcludedUsers = @("Public", "Administrator", ".Net v4.5", ".Net v4.5 classic", ".Default", "itadmin", "it-admin", "cpsi") $Cutoff = (Get-Date).AddDays(-90) $DomainSuffix = "@tchospital.local" # 获取C:\Users下的用户文件夹,排除指定账户(统一转小写避免大小写问题) $Userlist = Get-ChildItem -Path C:\Users -Name | Where-Object { $lowerName = $_.ToLower() !($ExcludedUsers | Where-Object { $_.ToLower() -eq $lowerName }) } # 预加载所有用户配置文件,避免重复查询 $allUserProfiles = Get-WmiObject -Class Win32_UserProfile # 高效处理登录事件:提取事件中用户名,构建哈希表存储最近登录时间 $loginEvents = Get-WinEvent -FilterHashtable @{ LogName='Security' Id='4624' StartTime=$Cutoff } -ErrorAction SilentlyContinue # 构建用户最近登录时间哈希表(只保留目标域用户) $userLastLogin = @{} foreach ($event in $loginEvents) { # 直接从事件Properties提取用户名,比解析Message快 $username = $event.Properties[5].Value if ($username.EndsWith($DomainSuffix)) { $userShortName = $username.Replace($DomainSuffix, "") $eventTime = $event.TimeCreated # 只保留最新的登录时间 if (-not $userLastLogin.ContainsKey($userShortName) -or $eventTime -gt $userLastLogin[$userShortName]) { $userLastLogin[$userShortName] = $eventTime } } } # 遍历用户列表,处理未登录用户 $RemovedUserList = @() foreach ($userName in $Userlist) { # 检查用户是否有90天内的登录记录 if (-not $userLastLogin.ContainsKey($userName)) { $RemovedUserList += $userName $userFolder = "C:\Users\$userName" # 从预加载的配置文件中查找并删除 $profileToRemove = $allUserProfiles | Where-Object { $_.LocalPath -eq $userFolder } if ($profileToRemove) { try { $profileToRemove.Remove($true) # $true表示同时删除注册表和文件夹 Write-Host "成功删除用户配置文件: $userName" } catch { Write-Host "删除用户配置文件失败: $userName,错误信息: $_" } } else { Write-Host "未找到用户$userName的配置文件记录" } } } # 写入统一日志 $logPath = "C:\ProfileCleanupLog.txt" "=== 清理日志 - $(Get-Date) ===" | Out-File -FilePath $logPath -Append "截止日期: $Cutoff" | Out-File -FilePath $logPath -Append "扫描用户列表: $($Userlist -join ', ')" | Out-File -FilePath $logPath -Append if ($RemovedUserList.Count -eq 0) { "本次清理无用户被移除" | Out-File -FilePath $logPath -Append } else { "被移除用户: $($RemovedUserList -join ', ')" | Out-File -FilePath $logPath -Append } "============================" | Out-File -FilePath $logPath -Append
优化点说明
- 事件处理优化:一次性提取事件中的用户名,用哈希表存储最新登录时间,避免多次遍历事件集
- WMI预加载:只查询一次所有用户配置文件,后续直接过滤,减少WMI调用次数
- 用户名匹配优化:统一转小写避免大小写问题,直接用事件
Properties提取用户名,比解析Message快数倍 - 删除逻辑优化:用
Win32_UserProfile的Remove($true)方法,自动删除文件夹和注册表项,无需单独处理
内容的提问来源于stack exchange,提问作者Sloan
相关产品推荐
相关产品推荐

