| <# | | .SYNOPSIS | | Fixes Claude Desktop on Windows that keeps breaking with | | "This app can't open" / silently quits on launch. | | | | .DESCRIPTION | | Newer Claude Desktop MSIX (Store) builds bundle vk_swiftshader.dll. | | On machines that enforce Code Integrity, Windows blocks that DLL inside | | the MSIX container (event 3033: "did not meet the Microsoft signing level | | requirements"). That crashes the GPU process (0x060C201E), which makes | | Windows flag the whole package as "Modified, NeedsRemediation", so the | | next launch fails with 0x3CFC. Repair/reinstall only fixes it for a few | | minutes, then it self-corrupts again. | | | | This script sidesteps the bug by deploying the NON-MSIX (Squirrel) build | | to %LOCALAPPDATA%\AnthropicClaude, which lives OUTSIDE the MSIX container, | | so the signing enforcement never applies. Same Claude, no self-corruption. | | | | What it touches (full transparency): | |
- Downloads an official Squirrel .nupkg from downloads.claude.ai | |
- Extracts it to %LOCALAPPDATA%\AnthropicClaude\app-<version>\ | |
- Creates Desktop + Start Menu shortcuts to Claude.exe | |
- Registers the claude:// protocol handler (HKCU) so OAuth / Google | | sign-in redirects back into the app instead of looping forever on | | the sign-in screen | |
- (optional -Lock) adds "0.0.0.0 downloads.claude.ai" to the hosts file | | to stop it auto-updating back into the broken MSIX build (needs admin) | |
- (optional -RemoveBadMsix) uninstalls the broken MSIX package | |
- (optional -MigrateData) copies your old MSIX chat/session data over |
|
so your history isn't "wiped" after the switch |
|
|
|
It does NOT delete your Claude data / sign-in (that lives elsewhere). |
|
|
|
.PARAMETER Version |
|
Specific version to install, e.g. 1.22209.3. Omit to auto-detect the |
|
latest from the RELEASES manifest. |
|
|
|
.PARAMETER Lock |
|
Also lock the version via the hosts file so Claude can't auto-update back |
|
into the broken MSIX build. Requires an elevated (Administrator) shell. |
|
|
|
.PARAMETER RemoveBadMsix |
|
Also uninstall the broken MSIX (Store) package. |
|
|
|
.PARAMETER MigrateData |
|
Copy your existing chat/session data from the old MSIX package folder |
|
into the new build so your history carries over (the two builds use |
|
different data directories, so a fresh switch otherwise looks "wiped"). |
|
|
|
.EXAMPLE |
|
.\Fix-ClaudeDesktop.ps1 |
|
Deploy the latest Squirrel build and make shortcuts. |
|
|
|
.EXAMPLE |
|
.\Fix-ClaudeDesktop.ps1 -Lock -RemoveBadMsix |
|
(Run as Admin) Deploy, remove the broken MSIX, and lock the version. |
|
|
|
.EXAMPLE |
|
.\Fix-ClaudeDesktop.ps1 -Version 1.22209.3 |
|
Pin a specific known-good version. |
|
|
|
.NOTES |
|
Author : Cheng (building Wuwei - wuweiai.io) |
|
License : MIT. Use at your own risk, no warranty. |
|
The manual steps are documented in the thread; this just automates them. |
|
#> |
|
|
|
param( |
|
[string]$Version = "", |
|
[switch]$Lock, |
|
[switch]$RemoveBadMsix, |
|
[switch]$MigrateData # copy old MSIX chat/session data into the new build |
|
) |
|
|
|
$ErrorActionPreference = "Stop" |
|
|
|
|
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {} |
|
|
|
$base = "https://downloads.claude.ai/releases/win32/x64" |
|
$root = Join-Path $env:LOCALAPPDATA "AnthropicClaude" |
|
$hostFile = "$env:SystemRoot\System32\drivers\etc\hosts" |
|
|
|
function Info($m){ Write-Host "[] $m" -ForegroundColor Cyan } |
|
function Good($m){ Write-Host "[OK] $m" -ForegroundColor Green } |
|
function Warn($m){ Write-Host "[!] $m" -ForegroundColor Yellow } |
|
|
|
function Test-Admin { |
|
$id = [Security.Principal.WindowsIdentity]::GetCurrent() |
|
(New-Object Security.Principal.WindowsPrincipal($id)).IsInRole( |
|
[Security.Principal.WindowsBuiltInRole]::Administrator) |
|
} |
|
|
|
Write-Host "" |
|
Write-Host " Fix-ClaudeDesktop - non-MSIX Squirrel deploy" -ForegroundColor White |
|
Write-Host " by Cheng | wuweiai.io" -ForegroundColor DarkGray |
|
Write-Host "" |
|
|
|
|
|
$wasLocked = $false |
|
try { |
|
if ((Get-Content $hostFile -ErrorAction SilentlyContinue) -match 'downloads.claude.ai') { |
|
$wasLocked = $true |
|
} |
|
} catch {} |
|
|
|
if ($wasLocked) { |
|
if (Test-Admin) { |
|
Info "Version lock found in hosts; temporarily unlocking to download..." |
|
(Get-Content $hostFile) | |
|
Where-Object { $_ -notmatch 'downloads.claude.ai' -and $_ -notmatch 'Claude desktop lock' } | |
|
Set-Content $hostFile -Encoding ASCII |
|
ipconfig /flushdns | Out-Null |
|
Start-Sleep -Seconds 1 |
|
} else { |
|
Warn "hosts has a version lock (0.0.0.0 downloads.claude.ai) and you're NOT admin." |
|
Warn "The download will fail. Re-run in an Administrator PowerShell, or remove that line first." |
|
} |
|
} |
|
|
|
|
if (-not $Version) { |
|
Info "Detecting latest version from RELEASES..." |
|
$rel = (Invoke-WebRequest -UseBasicParsing "$base/RELEASES").Content |
|
if ($rel -match 'AnthropicClaude-([0-9.]+)-full.nupkg') { $Version = $Matches[1] } |
|
if (-not $Version) { throw "Could not detect version from RELEASES." } |
|
} |
|
Info "Target version: $Version" |
|
|
|
|
$tmp = Join-Path $env:TEMP "AnthropicClaude-$Version-full.nupkg" |
|
$url = "$base/AnthropicClaude-$Version-full.nupkg" |
|
Info "Down $url" |
|
Invoke-WebRequest -UseBasicParsing $url -OutFile $tmp |
|
Good ("Downloaded {0} MB" -f [math]::Round((Get-Item $tmp).Length / 1MB)) |
|
|
|
|
$dst = Join-Path $root "app-$Version" |
|
if (Test-Path $dst) { Remove-Item $dst -Recurse -Force } |
|
New-Item -ItemType Directory -Path $dst -Force | Out-Null |
|
|
|
Info "Extracting to $dst" |
|
Add-Type -AssemblyName System.IO.Compression.FileSystem |
|
$zip = [System.IO.Compression.ZipFile]::OpenRead($tmp) |
|
try { |
|
foreach ($e in $zip.Entries) { |
|
if ($e.FullName -notlike "lib/net45/") { continue } |
|
if ($e.FullName.EndsWith("/")) { continue } |
|
$relPath = $e.FullName.Substring("lib/net45/".Length) |
|
$target = Join-Path $dst $relPath |
|
$dir = Split-Path $target -Parent |
|
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } |
|
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($e, $target, $true) |
|
} |
|
} finally { |
|
$zip.Dispose() |
|
} |
|
|
|
$exe = Join-Path $dst "Claude.exe" |
|
if (-not (Test-Path $exe)) { throw "Claude.exe not found after extract - the package layout may have changed." } |
|
Good "Deployed Claude.exe (non-MSIX)" |
|
|
|
|
if ($RemoveBadMsix) { |
|
$pkg = Get-AppxPackage claude -ErrorAction SilentlyContinue |
|
if ($pkg) { |
|
Info "Removing broken MSIX package: $($pkg.PackageFullName)" |
|
Get-Process | Where-Object { $.Path -like 'WindowsApps\Claude' } | |
|
Stop-Process -Force -ErrorAction SilentlyContinue |
|
$pkg | Remove-AppxPackage -ErrorAction SilentlyContinue |
|
Good "MSIX package removed" |
|
} else { |
|
Info "No MSIX Claude package found (nothing to remove)." |
|
} |
|
} |
|
|
|
|
$ws = New-Object -ComObject WScript.Shell |
|
foreach ($loc in @( |
|
[Environment]::GetFolderPath('Desktop'), |
|
(Join-Path $env:APPDATA 'Microsoft\Windows\Start Menu\Programs') |
|
)) { |
|
$lnk = $ws.CreateShortcut((Join-Path $loc 'Claude.lnk')) |
|
$lnk.TargetPath = $exe |
|
$lnk.WorkingDirectory = $dst |
|
$lnk.IconLocation = "$exe,0" |
|
$lnk.Save() |
|
} |
|
Good "Created Desktop and Start Menu shortcuts" |
|
|
|
|
|
|
|
|
|
|
try { |
|
$cmd = "
"$exe""%1"" | | New-Item -Path 'HKCU:\Software\Classes\claude' -Force | Out-Null | | New-ItemProperty -Path 'HKCU:\Software\Classes\claude' -Name '(default)' -Value 'URL:claude' -PropertyType String -Force | Out-Null | | New-ItemProperty -Path 'HKCU:\Software\Classes\claude' -Name 'URL Protocol' -Value '' -PropertyType String -Force | Out-Null | | New-Item -Path 'HKCU:\Software\Classes\claude\shell\open\command' -Force | Out-Null | | New-ItemProperty -Path 'HKCU:\Software\Classes\claude\shell\open\command' -Name '(default)' -Value $cmd -PropertyType String -Force | Out-Null | | Good "Registered claude:// protocol handler -> OAuth / Google sign-in will redirect back into the app" | | } catch { | | Warn "Could not register claude:// handler ($($.Exception.Message)). Sign-in still works via email + verification code." | | } | | | | | | | | try { | | $reg = (Get-ItemProperty 'HKCU:\Software\Classes\claude\shell\open\command' -ErrorAction Stop).'(default)' | | if ($reg -like "$dst") { | | Good "Verified claude:// -> this build. Google / OAuth sign-in will land back in the app." | | } else { | | Warn "claude:// points at a different path ($reg). Repointing to this build..." | | New-ItemProperty -Path 'HKCU:\Software\Classes\claude\shell\open\command' -Name '(default)' -Value ""$exe""%1"" -PropertyType String -Force | Out-Null | | Good "claude:// repointed to $exe" | | } | | } catch { | | Warn "claude:// handler not found after registration; sign-in may loop. Re-run the script, or sign in with email + verification code." | | } | | | | | | | | | if ($MigrateData) { | | $newData = Join-Path $env:APPDATA 'Claude' | | $msixRoot = Join-Path $env:LOCALAPPDATA 'Packages' | | $src = $null | | if (Test-Path $msixRoot) { | | $src = Get-ChildItem $msixRoot -Directory -Filter 'Claude_' -ErrorAction SilentlyContinue | | | ForEach-Object { Join-Path $.FullName 'LocalCache\Roaming\Claude' } | | | Where-Object { Test-Path $ } | Select-Object -First 1 | | } | | if ($src) { | | Info "Migrating old MSIX data:n from $srcn to $newData" | | if (Test-Path $newData) { | | $bak = "$newData.bak-$(Get-Date -Format yyyyMMdd-HHmmss)" | | Copy-Item $newData $bak -Recurse -Force -ErrorAction SilentlyContinue | | Info "Backed up existing new-build data to $bak" | | } | | New-Item -ItemType Directory -Path $newData -Force | Out-Null | | Copy-Item (Join-Path $src '') $newData -Recurse -Force -ErrorAction SilentlyContinue | | Good "Migrated old chat/session data. Your history should reappear after launch." | | } else { | | Warn "No old MSIX data folder found under $msixRoot (nothing to migrate)." | | } | | } | | | | | | if ($Lock -or $wasLocked) { | | if (Test-Admin) { | | try { | | $lines = Get-Content $hostFile | | | Where-Object { $_ -notmatch 'downloads.claude.ai' -and $_ -notmatch 'Claude desktop lock' } | | $lines += '# Claude desktop lock version (avoid bad MSIX auto-update)' | | $lines += '0.0.0.0 downloads.claude.ai' | | Set-Content -Path $hostFile -Value $lines -Encoding ASCII | | ipconfig /flushdns | Out-Null | | Good "Version locked via hosts. Remove that line to allow updates later." | | } catch { | | Warn "Lock failed - run PowerShell as Administrator to edit hosts. (Fix still works without lock.)" | | } | | } else { | | Warn "Skipping version lock: not admin. (Fix still works; re-run as admin with -Lock.)" | | } | | } | | | | Write-Host "" | | Good "Done. Launch Claude from the new Desktop shortcut." | | Info "It now runs from: $dst" | | Info "That path is outside the MSIX container, so no more NeedsRemediation self-corruption." | | Write-Host "" | | | | Start-Process $exe |