Windows注册表批量删除#
卸载软件后,有时仍会在注册表中留下带有相同关键词的残留项。下面使用 PowerShell 先查找并导出注册表项,再在确认列表后备份并删除。
注册表操作可能导致系统或软件异常。请只删除确定属于目标软件的项,并在操作前创建系统还原点或其他可靠备份。脚本默认先预览、再备份、最后删除,不建议跳过确认步骤。
1. 查找注册表项#
下面的脚本只搜索键名,不会删除内容。它会同时尝试 64 位和 32 位注册表视图,并将结果保存为 TXT 和 CSV 文件。
保存为 Find-WincompressRegistryKeys.ps1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| param(
[string]$Keyword = 'wincompress',
[string]$OutTxt = ".\wincompress_registry_keys.txt",
[string]$OutCsv = ".\wincompress_registry_keys.csv"
)
$IsAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
[Security.Principal.WindowsBuiltInRole]::Administrator
)
if (-not $IsAdmin) { Write-Warning "Run PowerShell as Administrator to reduce missed keys." }
$Is64OS = [Environment]::Is64BitOperatingSystem
$roots = @('HKLM','HKCU','HKCR','HKU','HKCC')
$results = New-Object System.Collections.Generic.List[string]
function Search-Keys {
param([string]$Root, [string]$Keyword, [string]$RegView)
$args = @('query', $Root, '/f', $Keyword, '/k', '/s')
if ($RegView) { $args += "/reg:$RegView" }
$out = & reg.exe @args 2>$null
if ($LASTEXITCODE -eq 0 -and $out) {
foreach ($line in $out) {
if ($line -match '^HKEY_') { $results.Add($line.Trim()) }
}
}
}
foreach ($root in $roots) {
Search-Keys -Root $root -Keyword $Keyword -RegView '64'
if ($Is64OS) { Search-Keys -Root $root -Keyword $Keyword -RegView '32' }
}
$uniq = $results | Sort-Object -Unique
if ($uniq.Count -eq 0) {
Write-Host "No registry key name contains '$Keyword'."
} else {
$uniq | Set-Content -Path $OutTxt -Encoding UTF8
$uniq | ForEach-Object { [pscustomobject]@{ RegistryKey = $_ } } |
Export-Csv -NoTypeInformation -Encoding UTF8 -Path $OutCsv
Write-Host "Found $($uniq.Count) keys."
Write-Host "Output files:`n$OutTxt`n$OutCsv"
}
|
2. 备份并删除#
确认列表中的每一项都可以删除后,保存下面的脚本为 Remove-WincompressRegistryKeys.ps1。脚本默认导出 .reg 备份,并支持 -WhatIf 预演。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
| [CmdletBinding(SupportsShouldProcess=$true)]
param(
[Parameter(Mandatory=$true)]
[string]$InputList,
[string]$BackupDir = ".\Backup_wincompress",
[switch]$NoBackup
)
if (-not (Test-Path -LiteralPath $InputList)) { throw "List not found: $InputList" }
if (-not $NoBackup) { New-Item -ItemType Directory -Force -Path $BackupDir | Out-Null }
$Is64OS = [Environment]::Is64BitOperatingSystem
$stamp = Get-Date -Format "yyyyMMdd_HHmmss"
$log = ".\remove_wincompress_$stamp.log"
"Start: $(Get-Date)" | Out-File -FilePath $log -Encoding UTF8
$keys = Get-Content -LiteralPath $InputList |
Where-Object { $_ -and ($_ -match '^HKEY_') } |
ForEach-Object { $_.Trim() } |
Sort-Object -Unique
function SafeName([string]$path) { return ($path -replace '[\\/:*?""<>|]','_') }
$success = 0
$failed = 0
foreach ($key in $keys) {
$base = Join-Path $BackupDir (SafeName $key)
if (-not $NoBackup) {
& reg.exe export "$key" "$base.x64.reg" /y /reg:64 2>$null | Out-Null
if ($LASTEXITCODE -ne 0) { "Backup FAIL (x64): $key" | Tee-Object -FilePath $log -Append | Out-Null }
if ($Is64OS) {
& reg.exe export "$key" "$base.x86.reg" /y /reg:32 2>$null | Out-Null
if ($LASTEXITCODE -ne 0) { "Backup FAIL (x86): $key" | Tee-Object -FilePath $log -Append | Out-Null }
}
}
if ($PSCmdlet.ShouldProcess("$key [x64]", 'Delete')) {
& reg.exe delete "$key" /f /reg:64 2>$null | Out-Null
if ($LASTEXITCODE -eq 0) { $success++; "Delete OK (x64): $key" | Tee-Object -FilePath $log -Append | Out-Null }
else { $failed++; "Delete FAIL (x64): $key" | Tee-Object -FilePath $log -Append | Out-Null }
}
if ($Is64OS -and $PSCmdlet.ShouldProcess("$key [x86]", 'Delete')) {
& reg.exe delete "$key" /f /reg:32 2>$null | Out-Null
if ($LASTEXITCODE -eq 0) { $success++; "Delete OK (x86): $key" | Tee-Object -FilePath $log -Append | Out-Null }
else { $failed++; "Delete FAIL (x86): $key" | Tee-Object -FilePath $log -Append | Out-Null }
}
}
"End: $(Get-Date)" | Out-File -FilePath $log -Append -Encoding UTF8
"Summary: success=$success, failed=$failed, see log: $log" | Write-Host
|
3. 使用方法#
以管理员身份打开 PowerShell,并在本次会话中允许脚本运行:
1
| Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
|
运行查找脚本,把结果输出到桌面:
1
2
3
| .\Find-WincompressRegistryKeys.ps1 -Keyword 'wincompress' `
-OutTxt "$env:USERPROFILE\Desktop\wincompress_keys.txt" `
-OutCsv "$env:USERPROFILE\Desktop\wincompress_keys.csv"
|
打开 TXT 或 CSV 文件,确认所有键都属于要卸载的软件。
先进行预演,不会真正删除:
1
2
| .\Remove-WincompressRegistryKeys.ps1 `
-InputList "$env:USERPROFILE\Desktop\wincompress_keys.txt" -WhatIf
|
确认无误后执行删除。默认会把两个注册表视图的备份保存到指定目录:
1
2
3
| .\Remove-WincompressRegistryKeys.ps1 `
-InputList "$env:USERPROFILE\Desktop\wincompress_keys.txt" `
-BackupDir "$env:USERPROFILE\Desktop\Backup_wincompress"
|
脚本文件建议保存为 ASCII 或 UTF-8(无 BOM),以兼容旧版 Windows PowerShell。PowerShell 7 通常可以直接使用 UTF-8 文件。
4. 需要 SYSTEM 权限时#
如果管理员权限仍不足,可以使用 Windows 自带的任务计划程序,以 SYSTEM 身份运行删除脚本。将下面的包装脚本与 Remove-WincompressRegistryKeys.ps1 放在同一目录,保存为 Invoke-RemoveWincompressAsSystem.ps1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
| [CmdletBinding()]
param(
[Parameter(Mandatory=$true)] [string]$InputList,
[string]$BackupDir = "$env:USERPROFILE\Desktop\Backup_wincompress",
[switch]$NoBackup
)
function Q([string]$s) { '"' + $s.Replace('"','""') + '"' }
$removeScript = Join-Path $PSScriptRoot 'Remove-WincompressRegistryKeys.ps1'
if (-not (Test-Path $removeScript)) { throw "Missing removal script." }
if (-not (Test-Path $InputList)) { throw "Input list not found." }
if (-not (Test-Path $BackupDir)) { New-Item -ItemType Directory -Force $BackupDir | Out-Null }
$ps = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"
$tokens = @('-NoProfile','-ExecutionPolicy','Bypass','-File',(Resolve-Path $removeScript).Path,
'-InputList',(Resolve-Path $InputList).Path,'-BackupDir',(Resolve-Path $BackupDir).Path)
if ($NoBackup) { $tokens += '-NoBackup' }
$argString = ($tokens | ForEach-Object { if ($_ -like '-*') { $_ } else { Q $_ } }) -join ' '
$service = New-Object -ComObject 'Schedule.Service'
$service.Connect()
$root = $service.GetFolder('\')
$taskDefinition = $service.NewTask(0)
$taskDefinition.Principal.UserId = 'SYSTEM'
$taskDefinition.Principal.LogonType = 5
$taskDefinition.Principal.RunLevel = 1
$taskDefinition.Settings.Hidden = $true
$taskDefinition.Settings.AllowDemandStart = $true
$action = $taskDefinition.Actions.Create(0)
$action.Path = $ps
$action.Arguments = $argString
$action.WorkingDirectory = Split-Path -Parent (Resolve-Path $removeScript).Path
$name = 'Temp_RemoveWincompress_' + [Guid]::NewGuid().ToString('N')
$null = $root.RegisterTaskDefinition("\$name", $taskDefinition, 6, $null, $null, 5, $null)
$task = $root.GetTask("\$name")
$running = $task.Run($null)
$deadline = (Get-Date).AddMinutes(30)
while ((Get-Date) -lt $deadline) {
try { $state = $running.State } catch { break }
if ($state -eq 4 -or $state -eq 2 -or $state -eq 3) { Start-Sleep -Seconds 2 } else { break }
}
try { $root.DeleteTask("\$name", 0) } catch {}
|
以管理员身份运行包装脚本即可。若仍无法删除,注册表项可能由 TrustedInstaller 所有;不要直接强删系统组件,先确认来源并保留完整备份。也可以使用 Sysinternals PsExec 等工具以 SYSTEM 身份运行,但第三方工具需要自行核验来源和风险。
5. 恢复备份#
需要恢复某个注册表项时,可以双击对应的 .reg 文件,或在管理员 PowerShell 中执行:
1
| reg.exe import "备份文件路径.reg"
|