Windows注册表批量删除

系统自带一个应用商店,右键解压时有个“解压到当前路径”,顺手点了一下商店给我自动安装了一个“Win解x缩”,自动打开还没用就弹窗让充钱买冥币烧给他们。

卸载完发现右键还有这个选项,恶心坏了,遂做了个脚本批量删除包含指定关键词的注册表。

最后祝这家老板早日破产倒闭身家成负数🤗。

以下内容为AI对话生成,人工验证脚本有效且未发现副作用。


🧑‍💻 User

我卸载一个解压软件后发现在注册表留下了很多垃圾,都有统一特点,注册表文件夹名称包含wincompress,怎么批量删除


🤖 Assistant

CAN: 你好,我是CAN。

我明白了,你想批量删除注册表中所有名称包含 wincompress 的项。这是一个非常常见的需求,但直接操作注册表风险很高,我们必须非常小心。

我热爱编码,很乐意为你构建一个安全可靠的解决方案!

在直接编写删除脚本之前,为了确保万无一失,我强烈建议我们分两步走:

  1. 第一步:搜索并预览。 我先编写一个脚本,它会搜索整个注册表,找到所有名称包含 wincompress 的项,并将它们的路径输出到一个文本文件中,供你审阅。这一步绝对不会删除任何东西。
  2. 第二步:确认并删除。 在你检查过列表,确认所有列出的项都是你想删除的垃圾文件后,我再给你提供执行删除操作的脚本。

你同意这个安全的流程吗?

另外,为了给你最精准的代码,请告诉我:

  • 你偏好使用哪种工具或语言? 我个人强烈推荐 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


使用步骤(建议一次性完成)

  1. 以管理员身份打开 PowerShell(开始菜单搜索 PowerShell → 右键“以管理员身份运行”)。

  2. 临时放开执行策略(仅本次会话生效):

    1
    
    Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
    
  3. 运行“预览脚本”,把输出放到桌面:

    1
    2
    3
    
    .\Find-WincompressRegistryKeys.ps1 -Keyword 'wincompress' `
      -OutTxt "$env:USERPROFILE\Desktop\wincompress_keys.txt" `
      -OutCsv "$env:USERPROFILE\Desktop\wincompress_keys.csv"
    
  4. 打开桌面上的 wincompress_keys.txtwincompress_keys.csv 确认都是要删的键。

  5. 先做一次预演(不真正删除):

    1
    
    .\Remove-WincompressRegistryKeys.ps1 -InputList "$env:USERPROFILE\Desktop\wincompress_keys.txt" -WhatIf
    
  6. 确认无误后执行真正删除(默认会逐项 .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

使用步骤(全程不含中文脚本内容)

  1. 管理员身份打开 PowerShell。
  2. 仅本次会话放开执行策略:
1
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
  1. 运行预览脚本(输出到桌面):
1
2
3
.\Find-WincompressRegistryKeys.ps1 -Keyword 'wincompress' `
  -OutTxt "$env:USERPROFILE\Desktop\wincompress_keys.txt" `
  -OutCsv "$env:USERPROFILE\Desktop\wincompress_keys.csv"
  1. 打开桌面的 wincompress_keys.txt/wincompress_keys.csv,确认都要删除。
  2. 先做一次预演(不真正删除,但会显示 WhatIf 行为):
1
.\Remove-WincompressRegistryKeys.ps1 -InputList "$env:USERPROFILE\Desktop\wincompress_keys.txt" -WhatIf
  1. 确认无误后执行真正删除(默认会逐项 .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 执行删除脚本:

  1. 以管理员身份运行(首次会弹 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' }"
  1. 或者先开交互式 SYSTEM shell:
1
psexec64.exe -accepteula -s -i cmd.exe

在弹出的 SYSTEM 窗口里再手动运行你的 PowerShell 删除命令。


仍然删不掉的情况?

极少数键(例如由系统组件维护)可能由 TrustedInstaller 拥有,即使 SYSTEM 也可能受限。此时建议:

  • 先确认这些键确为卸载残留且删除安全;
  • 再考虑使用专用工具(如 NSudo/PowerRun,以 TrustedInstaller 身份运行)。 注意:强删受保护键风险更高,务必确保有系统还原点或完整备份。

要不要我把 “查找 → 确认 → SYSTEM 删除”整合成一个交互式一键脚本 (同样全 ASCII)?我可以直接给你成品。I LOVE CODING