Windows: поддержка сборки MSIX (Microsoft Store и установщик с claude.com)

Скрипт не находил приложение, если оно стоит как пакет MSIX в
C:\Program Files\WindowsApps\Claude_<версия>_x64__pzs8sxrjxfjjc.

- Поиск через Get-AppxPackage с запасным путём по папке Packages.
- Запуск пакетного приложения по shell:AppsFolder\<PackageFamilyName>!Claude,
  запасные варианты: explorer.exe, alias в WindowsApps, протокол claude://.
- Сторонний режим у обеих сборок в %LOCALAPPDATA%\Claude-3p (по коду
  приложения 1.44121.2, в манифесте MSIX папка исключена из виртуализации).
- Флаг разработчика кладётся во все родные папки: %APPDATA%\Claude,
  LocalCache\Roaming\Claude у MSIX, %LOCALAPPDATA%\Claude.
- Честная проверка перезапуска и в setup.sh для Windows.
This commit is contained in:
2026-09-08 08:15:53 +03:00
parent fcf78a774f
commit ee06a11172
2 changed files with 216 additions and 30 deletions
+113 -14
View File
@@ -12,6 +12,13 @@
# в приложение Claude и перезапускает его. Ничего не скачивает
# и не устанавливает. Терминальный Claude Code не затрагивается.
#
# Понимает обе сборки приложения Claude для Windows:
# · классическую: %LOCALAPPDATA%\AnthropicClaude\app-<версия>\claude.exe;
# · MSIX (Microsoft Store и установщик с claude.com):
# C:\Program Files\WindowsApps\Claude_<версия>_x64__pzs8sxrjxfjjc\app\Claude.exe.
# Сторонний режим у обеих живёт в %LOCALAPPDATA%\Claude-3p (так в коде приложения),
# родная папка данных зависит от сборки, см. Get-ClaudeInstall.
#
# ВНИМАНИЕ при правке: сохранять в UTF-8 БЕЗ BOM. Gitea отдаёт файл
# с заголовком charset=utf-8, irm декодирует кириллицу правильно,
# а BOM в конвейере irm | iex становится лишней командой и роняет разбор.
@@ -23,9 +30,6 @@ $Mode = ""
if ($env:AK) { $Mode = $env:AK; $env:AK = $null }
$ErrorActionPreference = "Stop"
$C1P = Join-Path $env:LOCALAPPDATA "Claude"
$C3P = "$C1P-3p"
$LIB = Join-Path $C3P "configLibrary"
$ID_OLLAMA = "a11a0000-0000-4000-8000-0000000000aa"
$ID_ZAI = "a11a0000-0000-4000-8000-0000000000bb"
$ID_DEFAULT = "a11a0000-0000-4000-8000-0000000000dd"
@@ -45,21 +49,110 @@ function Read-Json($path) {
try { Get-Content $path -Raw -Encoding UTF8 | ConvertFrom-Json } catch { $null }
}
# ---------- Где приложение и где его данные ----------
# Сборок две, и ведут они себя по-разному.
# · Классическая (Squirrel): %LOCALAPPDATA%\AnthropicClaude\app-<версия>\claude.exe,
# родные данные в %APPDATA%\Claude, логи в %LOCALAPPDATA%\Claude\logs.
# · MSIX (Microsoft Store и установщик с claude.com):
# C:\Program Files\WindowsApps\Claude_<версия>_x64__pzs8sxrjxfjjc\app\Claude.exe.
# Папка WindowsApps закрыта системой, exe оттуда напрямую не запускается, а всё,
# что приложение пишет в %APPDATA%, Windows подменяет на
# %LOCALAPPDATA%\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude.
# Сторонний режим у ОБЕИХ сборок живёт в %LOCALAPPDATA%\Claude-3p: так прописано
# в коде приложения, а в манифесте MSIX эта папка исключена из подмены.
function Find-ClaudeExe {
@("$env:LOCALAPPDATA\AnthropicClaude", "$env:LOCALAPPDATA\Programs") |
Where-Object { Test-Path $_ } |
ForEach-Object { Get-ChildItem $_ -Recurse -Filter "claude.exe" -ErrorAction SilentlyContinue } |
Select-Object -First 1
}
function Find-ClaudeMsix {
$pkg = $null
try {
if (-not (Get-Command Get-AppxPackage -ErrorAction SilentlyContinue) -and $PSVersionTable.PSVersion.Major -ge 7) {
Import-Module Appx -UseWindowsPowerShell -ErrorAction SilentlyContinue
}
$all = @(Get-AppxPackage -Name "*Claude*" -ErrorAction SilentlyContinue |
Where-Object { $_.Name -eq "Claude" -or $_.Name -like "*ClaudeDesktop*" -or $_.Publisher -like "*Anthropic*" })
$pkg = $all | Where-Object { $_.Name -eq "Claude" } | Select-Object -First 1
if (-not $pkg) { $pkg = $all | Select-Object -First 1 }
} catch { $pkg = $null }
if ($pkg) {
$appId = "Claude"
try {
$ids = @((Get-AppxPackageManifest $pkg).Package.Applications.Application.Id)
if ($ids.Count -gt 0 -and $ids[0]) { $appId = "$($ids[0])" }
} catch { }
return [pscustomobject]@{ Family = "$($pkg.PackageFamilyName)"; Aumid = "$($pkg.PackageFamilyName)!$appId"; Version = "$($pkg.Version)" }
}
# Запасной путь: Get-AppxPackage бывает недоступен (PowerShell 7 без модуля Appx),
# но папка данных пакета остаётся на месте после первого запуска приложения.
$d = Get-ChildItem (Join-Path $env:LOCALAPPDATA "Packages") -Directory -Filter "Claude_*" -ErrorAction SilentlyContinue |
Select-Object -First 1
if ($d) { return [pscustomobject]@{ Family = $d.Name; Aumid = "$($d.Name)!Claude"; Version = "" } }
return $null
}
function Get-ClaudeInstall {
$msix = Find-ClaudeMsix
$exe = Find-ClaudeExe
if ($msix) {
$home1p = Join-Path $env:LOCALAPPDATA "Packages\$($msix.Family)\LocalCache\Roaming\Claude"
$launched = Test-Path $home1p
# Если рядом осталась и классическая сборка, верим той, которой пользовались.
if ($launched -or -not $exe) {
$label = "сборка MSIX (Microsoft Store или установщик с claude.com)"
if ($msix.Version) { $label += ", версия $($msix.Version)" }
return [pscustomobject]@{ Kind = "msix"; Label = $label; Aumid = $msix.Aumid; Exe = $null; Home = $home1p; Launched = $launched }
}
}
if ($exe) {
$home1p = Join-Path $env:APPDATA "Claude"
return [pscustomobject]@{ Kind = "classic"; Label = "классическая сборка"; Aumid = $null; Exe = $exe.FullName; Home = $home1p; Launched = (Test-Path $home1p) }
}
return $null
}
$INST = Get-ClaudeInstall
# Сторонний режим: конфиги, библиотека, флаг разработчика для стороннего режима.
$C3P = Join-Path $env:LOCALAPPDATA "Claude-3p"
$LIB = Join-Path $C3P "configLibrary"
# Родные папки: сюда кладём только флаг разработчика и чистим кэши. Их несколько,
# потому что настоящая зависит от сборки, а лишний флаг ничему не вредит.
$P1_DIRS = @()
if ($INST) { $P1_DIRS += $INST.Home }
foreach ($d in @((Join-Path $env:APPDATA "Claude"), (Join-Path $env:LOCALAPPDATA "Claude"), (Join-Path $env:LOCALAPPDATA "Claude-Data"))) {
if ($P1_DIRS -notcontains $d -and ((Test-Path $d) -or $P1_DIRS.Count -eq 0)) { $P1_DIRS += $d }
}
if ($P1_DIRS.Count -eq 0) { $P1_DIRS += (Join-Path $env:APPDATA "Claude") }
$ALL_DIRS = @($P1_DIRS) + @($C3P)
# Пакетное приложение запускают по идентификатору, а не по пути к exe.
function Start-Claude {
if ($INST.Kind -eq "msix") {
$target = "shell:AppsFolder\$($INST.Aumid)"
try { Start-Process $target -ErrorAction Stop; return } catch { }
try { Start-Process explorer.exe $target -ErrorAction Stop; return } catch { }
$alias = Join-Path $env:LOCALAPPDATA "Microsoft\WindowsApps\Claude.exe"
if (Test-Path $alias) { try { Start-Process $alias -ErrorAction Stop; return } catch { } }
try { Start-Process "claude://" -ErrorAction Stop } catch { }
return
}
if ($INST.Exe) { Start-Process $INST.Exe }
}
# ВАЖНО. Приложение читает developer_settings.json из ТЕКУЩЕЙ папки данных,
# а она переключается на -3p только когда сторонний режим уже взлетел.
# Кладём флаг в ОБЕ папки, иначе пункт Developer не появится.
# Кладём флаг и в родные папки, и в -3p, иначе пункт Developer не появится.
function Write-DevFlag {
foreach ($d in @($C1P, $C3P)) {
foreach ($d in $ALL_DIRS) {
New-Item -ItemType Directory -Force -Path $d | Out-Null
Write-Utf8 (Join-Path $d "developer_settings.json") '{"allowDevTools": true}'
}
Ok "Режим разработчика включён в обеих папках приложения."
Ok "Режим разработчика включён во всех папках приложения."
}
# "deploymentMode": "1p" намертво запрещает сторонний режим.
@@ -81,7 +174,7 @@ function Clear-DeploymentPin {
function Repair-JsonBom {
$files = @()
if (Test-Path $LIB) { $files += Get-ChildItem $LIB -Filter *.json -ErrorAction SilentlyContinue }
foreach ($d in @($C1P, $C3P)) {
foreach ($d in $ALL_DIRS) {
foreach ($n in @("developer_settings.json", "claude_desktop_config.json")) {
$f = Join-Path $d $n
if (Test-Path $f) { $files += Get-Item $f }
@@ -159,7 +252,7 @@ function Check-Applied {
# скиллы и наши конфиги остаются на месте. Делать только при закрытом приложении.
# ВАЖНО: не путать с соседним пунктом Reset App Data, тот сносит папку целиком.
function Clear-Caches {
foreach ($d in @($C1P, $C3P)) {
foreach ($d in $ALL_DIRS) {
if (-not (Test-Path $d)) { continue }
foreach ($c in @("Cache", "Code Cache", "GPUCache", "DawnGraphiteCache", "DawnWebGPUCache", "Service Worker")) {
Remove-Item (Join-Path $d $c) -Recurse -Force -ErrorAction SilentlyContinue
@@ -174,12 +267,12 @@ function Restart-Claude {
$i = 0
while ((Get-Process -Name "Claude*" -ErrorAction SilentlyContinue) -and $i -lt 12) { Start-Sleep 1; $i++ }
Clear-Caches
$exe = Find-ClaudeExe
if (-not $exe) { Warn "Не нашёл claude.exe, открой Claude вручную."; return }
Start-Process $exe.FullName
if (-not $INST) { Warn "Не нашёл приложение Claude, открой его вручную."; return }
Start-Claude
# Проверяем, что процесс реально поднялся: рапортовать успех вслепую нельзя.
# Пакетной сборке на старт нужно больше времени.
$j = 0
while (-not (Get-Process -Name "Claude*" -ErrorAction SilentlyContinue) -and $j -lt 8) { Start-Sleep 1; $j++ }
while (-not (Get-Process -Name "Claude*" -ErrorAction SilentlyContinue) -and $j -lt 12) { Start-Sleep 1; $j++ }
if (Get-Process -Name "Claude*" -ErrorAction SilentlyContinue) {
Ok "Claude перезапущен."
} else {
@@ -205,10 +298,16 @@ if ($Mode -eq "restore") {
# ---------- 1. Приложение Claude ----------
Say "[1/4] Ищу приложение Claude..."
if (Find-ClaudeExe) {
Ok "Claude на месте."
if ($INST) {
Ok "Claude на месте: $($INST.Label)."
Ok "Конфиги стороннего режима: $C3P"
if (-not $INST.Launched) {
Warn "Похоже, приложение ещё ни разу не запускалось. Это не страшно, но если после"
Write-Host " запуска пункта Developer не будет, запусти эту же команду ещё раз."
}
} else {
Warn "Приложение Claude не найдено, а без него настраивать нечего."
Write-Host " Смотрел классическую сборку в AppData\Local и пакеты Microsoft Store."
Write-Host ""
Write-Host " Поставь его руками, это одна минута:"
Write-Host " 1. Открой https://claude.com/download и скачай версию для Windows."