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:
@@ -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."
|
||||
|
||||
Reference in New Issue
Block a user