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