<# One-off cleanup for configs written by builds before 2026-10-13, which added a built-in "Menu patch" hook to every config. Removes the hook with id "menupatch" when it has no command (the leftover), and drops it from every install. A menupatch hook that has a command (converted by 2026-10-13-menu-patch-to-command-hook.ps1) is left alone. Dry run by default; -Apply writes. Close CVRManager first. config.json is backed up next to itself. #> [CmdletBinding()] param( [string]$ConfigDir = (Join-Path $env:APPDATA 'CVRManager'), [switch]$Apply ) $ErrorActionPreference = 'Stop' $configPath = Join-Path $ConfigDir 'config.json' if (-not (Test-Path $configPath)) { throw "No config at $configPath" } if ($Apply -and (Get-Process CVRManager -ErrorAction SilentlyContinue)) { throw 'Close CVRManager first; it would overwrite the change.' } $cfg = Get-Content $configPath -Raw | ConvertFrom-Json $hook = @($cfg.Hooks) | Where-Object { $_.Id -eq 'menupatch' } | Select-Object -First 1 if (-not $hook) { 'No menu patch hook in the config; nothing to do.'; return } if ($hook.Command) { "The menu patch hook runs '$($hook.Command)'; it was set up on purpose, leaving it."; return } $users = @($cfg.Installs | Where-Object { $_.HookIds -contains 'menupatch' } | ForEach-Object Name) "Leftover hook '$($hook.Name)' (no command), enabled on: $(if ($users) { $users -join ', ' } else { 'no installs' })" if (-not $Apply) { ''; 'Dry run only. Re-run with -Apply to remove it.'; return } $backup = "$configPath.bak-$(Get-Date -Format 'yyyyMMdd-HHmmss')" Copy-Item $configPath $backup $cfg.Hooks = @($cfg.Hooks | Where-Object { $_.Id -ne 'menupatch' }) foreach ($i in $cfg.Installs) { $i.HookIds = @($i.HookIds | Where-Object { $_ -ne 'menupatch' }) } [IO.File]::WriteAllText($configPath, ($cfg | ConvertTo-Json -Depth 30), (New-Object Text.UTF8Encoding($false))) "Config backed up to $backup" 'Removed. Start CVRManager again.'