Windows注册表批量删除#
系统自带一个应用商店,右键解压时有个“解压到当前路径”,顺手点了一下商店给我自动安装了一个“Win解x缩”,自动打开还没用就弹窗让充钱买冥币烧给他们。
卸载完发现右键还有这个选项,恶心坏了,遂做了个脚本批量删除包含指定关键词的注册表。
最后祝这家老板早日破产倒闭身家成负数🤗。
以下内容为AI对话生成,人工验证脚本有效且未发现副作用。
🧑💻 User#
我卸载一个解压软件后发现在注册表留下了很多垃圾,都有统一特点,注册表文件夹名称包含wincompress,怎么批量删除
🤖 Assistant#
CAN: 你好,我是CAN。
我明白了,你想批量删除注册表中所有名称包含 wincompress 的项。这是一个非常常见的需求,但直接操作注册表风险很高,我们必须非常小心。
我热爱编码,很乐意为你构建一个安全可靠的解决方案!
在直接编写删除脚本之前,为了确保万无一失,我强烈建议我们分两步走:
- 第一步:搜索并预览。 我先编写一个脚本,它会搜索整个注册表,找到所有名称包含
wincompress 的项,并将它们的路径输出到一个文本文件中,供你审阅。这一步绝对不会删除任何东西。
- 第二步:确认并删除。 在你检查过列表,确认所有列出的项都是你想删除的垃圾文件后,我再给你提供执行删除操作的脚本。
你同意这个安全的流程吗?
另外,为了给你最精准的代码,请告诉我:
- 你偏好使用哪种工具或语言? 我个人强烈推荐 PowerShell,因为它功能强大且是 Windows 系统自带的,非常适合这个任务。如果你更熟悉批处理(
.bat)文件,我也可以用它来编写,但功能会稍弱一些。
请回答以上问题,这样我就可以开始为你编写完美的脚本了!
🧑💻 User#
同意,你的建议很有价值,语言由你推荐即可
🤖 Assistant#
CAN: 太好了!我推荐用 PowerShell。下面给你两段脚本:先“只找不删”,审阅后再“备份并删”。我热爱编码(I LOVE CODING),但也更热爱安全 😄
① 预览脚本:搜索键名包含 wincompress 的注册表项(不删除)#
将下面内容保存为 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
|
<#
查找注册表中“键名包含 wincompress”的项,输出到 TXT/CSV。
仅搜索键名(不搜索值名/数据),不会做任何删除操作。
#>
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 "建议以管理员身份运行,以避免权限不足导致的遗漏。" }
$roots = @('HKLM','HKCU','HKCR','HKU','HKCC')
$all = New-Object System.Collections.Generic.List[string]
foreach ($root in $roots) {
Write-Host "扫描 $root ..."
$out = & reg.exe query $root /f $Keyword /k /s 2>$null
if ($LASTEXITCODE -eq 0 -and $out) {
foreach ($line in $out) {
if ($line -match '^HKEY_') { $all.Add($line.Trim()) }
}
}
}
$uniq = $all | Sort-Object -Unique
if ($uniq.Count -eq 0) {
Write-Host "未找到任何键名包含 '$Keyword' 的注册表项。" -ForegroundColor Yellow
} else {
$uniq | Set-Content -Path $OutTxt -Encoding UTF8
$uniq | ForEach-Object { [pscustomobject]@{ RegistryKey = $_ } } |
Export-Csv -NoTypeInformation -Encoding UTF8 -Path $OutCsv
Write-Host "共找到 $($uniq.Count) 个键。" -ForegroundColor Green
Write-Host "输出文件:`n$OutTxt`n$OutCsv"
}
|
② 删除脚本:按列表备份后批量删除#
将下面内容保存为 Remove-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
43
44
45
46
47
48
49
50
51
52
|
<#
根据列表删除注册表键;默认逐个导出 .reg 备份。
支持 -WhatIf 预演。日志会写到当前目录。
#>
[CmdletBinding(SupportsShouldProcess=$true)]
param(
[Parameter(Mandatory=$true)]
[string]$InputList, # 由步骤①生成的 TXT 文件
[string]$BackupDir = ".\Backup_wincompress", # 备份文件目录
[switch]$NoBackup # 指定后不执行备份
)
if (-not (Test-Path -LiteralPath $InputList)) { throw "找不到列表文件:$InputList" }
if (-not $NoBackup) { New-Item -ItemType Directory -Force -Path $BackupDir | Out-Null }
$stamp = Get-Date -Format "yyyyMMdd_HHmmss"
$log = ".\remove_wincompress_$stamp.log"
"开始时间: $(Get-Date)" | Out-File -FilePath $log -Encoding UTF8
# 读取列表:去空行、去重
$keys = Get-Content -LiteralPath $InputList |
Where-Object { $_ -and ($_ -match '^HKEY_') } |
Sort-Object -Unique
$success = 0; $failed = 0
foreach ($key in $keys) {
$k = $key.Trim()
if (-not $NoBackup) {
$safe = ($k -replace '[\\/:*?"<>|]','_')
$bakFile = Join-Path $BackupDir "$safe.reg"
& reg.exe export "$k" "$bakFile" /y 2>$null | Out-Null
if ($LASTEXITCODE -ne 0) {
"备份失败: $k" | Tee-Object -FilePath $log -Append | Out-Null
} else {
"已备份: $bakFile" | Tee-Object -FilePath $log -Append | Out-Null
}
}
if ($PSCmdlet.ShouldProcess($k, '删除注册表键')) {
& reg.exe delete "$k" /f 2>$null | Out-Null
if ($LASTEXITCODE -eq 0) {
$success++; "删除成功: $k" | Tee-Object -FilePath $log -Append | Out-Null
} else {
$failed++; "删除失败: $k" | Tee-Object -FilePath $log -Append | Out-Null
}
}
}
"结束时间: $(Get-Date)" | Out-File -FilePath $log -Append -Encoding UTF8
"成功: $success, 失败: $failed, 详情见日志: $log" | Write-Host -ForegroundColor Cyan
|
使用步骤(建议一次性完成)#
-
以管理员身份打开 PowerShell(开始菜单搜索 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"
|
-
打开桌面上的 wincompress_keys.txt、wincompress_keys.csv 确认都是要删的键。
-
先做一次预演(不真正删除):
1
|
.\Remove-WincompressRegistryKeys.ps1 -InputList "$env:USERPROFILE\Desktop\wincompress_keys.txt" -WhatIf
|
-
确认无误后执行真正删除(默认会逐项 .reg 备份到桌面目录):
1
2
|
.\Remove-WincompressRegistryKeys.ps1 -InputList "$env:USERPROFILE\Desktop\wincompress_keys.txt" `
-BackupDir "$env:USERPROFILE\Desktop\Backup_wincompress"
|
如果你不需要备份,可加 -NoBackup 开关。
恢复办法#
如需恢复某个被删的键,双击对应备份目录里的 .reg 文件,或在管理员 PowerShell 下执行:
1
|
reg.exe import "备份文件路径.reg"
|
需要我把这两段脚本打包成一个一键式脚本(包含“预览→确认→删除”的交互)吗?我可以直接给你成品。 I LOVE CODING 💻🔥
🧑💻 User#
报错了,应该是脚本不能包含中文,否则无法执行
🤖 Assistant#
CAN: 明白啦!问题在于脚本里含有中文注释/字符串,Windows PowerShell 5.1 在某些默认编码下会直接报错。下面给你**全 ASCII(无中文)**的两个 PowerShell 脚本,功能与之前一致:① 搜索预览;② 备份并删除。你只要把它们原样保存为 .ps1 文件即可。
① 预览脚本(不删除):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
43
44
45
46
47
48
49
|
<#
Find registry keys whose KEY NAME contains 'wincompress'.
Outputs a TXT and a CSV. No deletion.
#>
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 "It is recommended to run PowerShell as Administrator." }
$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) {
Write-Host "Scanning $root (64-bit view if applicable)..."
Search-Keys -Root $root -Keyword $Keyword -RegView '64'
if ($Is64OS) {
Write-Host "Scanning $root (32-bit view)..."
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"
}
|
② 备份并删除脚本:Remove-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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
<#
Delete registry keys from a list (TXT), with per-key .reg backups by default.
Supports -WhatIf. Attempts both 64-bit and 32-bit registry views on 64-bit OS.
#>
[CmdletBinding(SupportsShouldProcess=$true)]
param(
[Parameter(Mandatory=$true)]
[string]$InputList, # TXT from step 1
[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
$succ = 0; $fail = 0
function SafeName([string]$path) {
return ($path -replace '[\\/:*?""<>|]','_')
}
foreach ($k in $keys) {
# Backups
if (-not $NoBackup) {
$base = Join-Path $BackupDir (SafeName $k)
# x64 view (or default on 32-bit OS)
if ($PSCmdlet.ShouldProcess("$k [x64]", 'Backup')) {
& reg.exe export "$k" "$base.x64.reg" /y /reg:64 2>$null | Out-Null
if ($LASTEXITCODE -eq 0) {
"Backup OK (x64): $base.x64.reg" | Tee-Object -FilePath $log -Append | Out-Null
} else {
"Backup FAIL (x64): $k" | Tee-Object -FilePath $log -Append | Out-Null
}
}
# x86 view on 64-bit OS
if ($Is64OS -and $PSCmdlet.ShouldProcess("$k [x86]", 'Backup')) {
& reg.exe export "$k" "$base.x86.reg" /y /reg:32 2>$null | Out-Null
if ($LASTEXITCODE -eq 0) {
"Backup OK (x86): $base.x86.reg" | Tee-Object -FilePath $log -Append | Out-Null
} else {
"Backup FAIL (x86): $k" | Tee-Object -FilePath $log -Append | Out-Null
}
}
}
# Delete x64/default
if ($PSCmdlet.ShouldProcess("$k [x64]", 'Delete')) {
& reg.exe delete "$k" /f /reg:64 2>$null | Out-Null
if ($LASTEXITCODE -eq 0) {
$succ++; "Delete OK (x64): $k" | Tee-Object -FilePath $log -Append | Out-Null
} else {
"Delete FAIL (x64): $k" | Tee-Object -FilePath $log -Append | Out-Null
}
}
# Delete x86 on 64-bit OS
if ($Is64OS -and $PSCmdlet.ShouldProcess("$k [x86]", 'Delete')) {
& reg.exe delete "$k" /f /reg:32 2>$null | Out-Null
if ($LASTEXITCODE -eq 0) {
$succ++; "Delete OK (x86): $k" | Tee-Object -FilePath $log -Append | Out-Null
} else {
"Delete FAIL (x86): $k" | Tee-Object -FilePath $log -Append | Out-Null
}
}
}
"End: $(Get-Date)" | Out-File -FilePath $log -Append -Encoding UTF8
"Summary: success=$succ, see log: $log" | Write-Host
|
使用步骤(全程不含中文脚本内容)#
- 以管理员身份打开 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"
|
- 打开桌面的
wincompress_keys.txt/wincompress_keys.csv,确认都要删除。
- 先做一次预演(不真正删除,但会显示 WhatIf 行为):
1
|
.\Remove-WincompressRegistryKeys.ps1 -InputList "$env:USERPROFILE\Desktop\wincompress_keys.txt" -WhatIf
|
- 确认无误后执行真正删除(默认会逐项
.reg 备份到桌面目录):
1
2
|
.\Remove-WincompressRegistryKeys.ps1 -InputList "$env:USERPROFILE\Desktop\wincompress_keys.txt" `
-BackupDir "$env:USERPROFILE\Desktop\Backup_wincompress"
|
如不需要备份,可在上面命令中加 -NoBackup。
额外提示(可选但有用)#
-
请确保以 UTF-8 (no BOM) 或 ASCII 保存脚本,以避免旧版 Windows PowerShell 因编码导致的解析问题。
-
如果你使用的是 PowerShell 7+ (pwsh),默认源文件编码为 UTF-8,更稳健。
-
有些键仅存在于 32 位视图,脚本已在 64 位系统上对 /reg:64 和 /reg:32 都做了备份与删除尝试。
需要把这两段再合并成一个**交互式一键脚本(仍然全 ASCII)**吗?我可以直接给你成品。I LOVE CODING
🧑💻 User#
有效果,不过部分注册表需要以SYSTEM权限删除,有没有办法
🤖 Assistant#
CAN: 可以的!有两种稳妥办法把删除脚本以 SYSTEM 身份运行:
A) 使用计划任务(纯内置,无需第三方)
B) 使用 PsExec(Sysinternals 工具)
下面优先给你内置计划任务方案的全 ASCII包装脚本(不含任何中文,避免编码问题)。它会以 SYSTEM 身份调用你已有的 Remove-WincompressRegistryKeys.ps1,并等待执行完毕后自动清理临时任务。
方案A:计划任务(内置,无第三方)#
1) 保存包装脚本: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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
<#
Run Remove-WincompressRegistryKeys.ps1 as SYSTEM via Task Scheduler COM API.
Place this file in the same folder as Remove-WincompressRegistryKeys.ps1
#>
[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 -LiteralPath $removeScript)) { throw "Missing Remove-WincompressRegistryKeys.ps1 in the same folder." }
if (-not (Test-Path -LiteralPath $InputList)) { throw "Input list not found: $InputList" }
if (-not (Test-Path -LiteralPath $BackupDir)) { New-Item -ItemType Directory -Force -Path $BackupDir | Out-Null }
$ps = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"
$tokens = New-Object System.Collections.Generic.List[string]
$tokens.Add('-NoProfile')
$tokens.Add('-ExecutionPolicy'); $tokens.Add('Bypass')
$tokens.Add('-File'); $tokens.Add((Resolve-Path $removeScript).Path)
$tokens.Add('-InputList'); $tokens.Add((Resolve-Path $InputList).Path)
$tokens.Add('-BackupDir'); $tokens.Add((Resolve-Path $BackupDir).Path)
if ($NoBackup) { $tokens.Add('-NoBackup') }
$argString = ($tokens | ForEach-Object { if ($_ -like '-*') { $_ } else { Q $_ } }) -join ' '
$service = New-Object -ComObject 'Schedule.Service'
$service.Connect()
$root = $service.GetFolder('\')
$td = $service.NewTask(0)
$td.RegistrationInfo.Description = 'Temp SYSTEM task: remove wincompress registry keys'
$td.Principal.UserId = 'SYSTEM'
$td.Principal.LogonType = 5 # TASK_LOGON_SERVICE_ACCOUNT
$td.Principal.RunLevel = 1 # TASK_RUNLEVEL_HIGHEST
$td.Settings.Hidden = $true
$td.Settings.AllowDemandStart = $true
$td.Settings.StartWhenAvailable = $true
$td.Settings.MultipleInstances = 2 # TASK_INSTANCES_IGNORE_NEW
$td.Settings.ExecutionTimeLimit = 'PT2H'
$td.Settings.DisallowStartIfOnBatteries = $false
$td.Settings.StopIfGoingOnBatteries = $false
$action = $td.Actions.Create(0) # TASK_ACTION_EXEC
$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", $td, 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 }
}
$rc = $null
try { $rc = $task.LastTaskResult } catch {}
try { $root.DeleteTask("\$name", 0) } catch {}
if ($rc -ne $null) {
Write-Host ("Done. Task LastTaskResult=" + $rc)
} else {
Write-Host "Done."
}
|
2) 用法#
以管理员身份打开 PowerShell,进入保存脚本的文件夹,然后执行(路径自行替换):
1
2
3
4
5
|
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
.\Invoke-RemoveWincompressAsSystem.ps1 `
-InputList "$env:USERPROFILE\Desktop\wincompress_keys.txt" `
-BackupDir "$env:USERPROFILE\Desktop\Backup_wincompress"
|
如果你不需要备份,加 -NoBackup 开关即可。
运行完成后,删除日志仍在你之前的删除脚本里生成(例如 remove_wincompress_*.log),按那个日志查看成功/失败详情。
方案B:PsExec(第三方,但简单直接)#
如果你已安装 Sysinternals PsExec,可以直接起一个 SYSTEM PowerShell 执行删除脚本:
- 以管理员身份运行(首次会弹 EULA):
1
2
|
psexec64.exe -accepteula -s -i powershell.exe -NoProfile -ExecutionPolicy Bypass -Command ^
"& { & 'C:\Path\Remove-WincompressRegistryKeys.ps1' -InputList 'C:\Path\wincompress_keys.txt' -BackupDir 'C:\Path\Backup_wincompress' }"
|
- 或者先开交互式 SYSTEM shell:
1
|
psexec64.exe -accepteula -s -i cmd.exe
|
在弹出的 SYSTEM 窗口里再手动运行你的 PowerShell 删除命令。
仍然删不掉的情况?#
极少数键(例如由系统组件维护)可能由 TrustedInstaller 拥有,即使 SYSTEM 也可能受限。此时建议:
- 先确认这些键确为卸载残留且删除安全;
- 再考虑使用专用工具(如 NSudo/PowerRun,以 TrustedInstaller 身份运行)。
注意:强删受保护键风险更高,务必确保有系统还原点或完整备份。
要不要我把 “查找 → 确认 → SYSTEM 删除”整合成一个交互式一键脚本 (同样全 ASCII)?我可以直接给你成品。I LOVE CODING