mirror of
https://github.com/myronblair/jarvis
synced 2026-06-30 17:50:23 -05:00
Fix Windows installer: handle per-user Python, make pywin32 postinstall non-fatal
The LocalSystem service account cannot access per-user Python installs (AppData\Local\Programs\Python\...). When a per-user install is detected, automatically install Python system-wide before proceeding. - Detect per-user Python (AppData in path) and trigger system-wide install - Extract system Python install logic into Install-PythonSystemWide function - Check winget exit code before marking install successful - Split pip install and postinstall into separate steps; pip failure is fatal, postinstall failure is a warning (service DLLs may already be registered) - Use $LASTEXITCODE check on pip rather than try/catch (external process) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -44,7 +44,7 @@ Write-Host "[1/6] Checking for Python 3..." -ForegroundColor Cyan
|
|||||||
|
|
||||||
$pythonPath = $null
|
$pythonPath = $null
|
||||||
|
|
||||||
# Search for a system-wide Python first (accessible by LocalSystem)
|
# System-wide paths — accessible by LocalSystem service account
|
||||||
$systemPaths = @(
|
$systemPaths = @(
|
||||||
"C:\Program Files\Python313\python.exe",
|
"C:\Program Files\Python313\python.exe",
|
||||||
"C:\Program Files\Python312\python.exe",
|
"C:\Program Files\Python312\python.exe",
|
||||||
@@ -56,6 +56,49 @@ $systemPaths = @(
|
|||||||
"C:\Python311\python.exe",
|
"C:\Python311\python.exe",
|
||||||
"C:\Python310\python.exe"
|
"C:\Python310\python.exe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
function Install-PythonSystemWide {
|
||||||
|
# Try winget first (Win 10 1709+ / Win 11)
|
||||||
|
$wingetOk = $false
|
||||||
|
try {
|
||||||
|
$null = Get-Command winget -ErrorAction Stop
|
||||||
|
Write-Host " Using winget (system-wide)..." -NoNewline
|
||||||
|
winget install Python.Python.3.12 --silent --scope machine `
|
||||||
|
--accept-package-agreements --accept-source-agreements 2>&1 | Out-Null
|
||||||
|
if ($LASTEXITCODE -eq 0) { $wingetOk = $true; Write-Host " done." -ForegroundColor Green }
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
if (-not $wingetOk) {
|
||||||
|
# Direct download — works on Win 8.1 without winget
|
||||||
|
# Python 3.11 explicitly supports Win 8.1+
|
||||||
|
Write-Host " Downloading Python 3.11 (Win 8.1+ compatible)..." -NoNewline
|
||||||
|
$pyInstaller = "$env:TEMP\python-installer.exe"
|
||||||
|
$pyUrl = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-amd64.exe"
|
||||||
|
try {
|
||||||
|
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
|
||||||
|
$wc = New-Object System.Net.WebClient
|
||||||
|
$wc.DownloadFile($pyUrl, $pyInstaller)
|
||||||
|
Write-Host " downloaded." -ForegroundColor Green
|
||||||
|
} catch {
|
||||||
|
Write-Error "Could not download Python. Install from https://python.org choosing 'Install for all users', then re-run."
|
||||||
|
}
|
||||||
|
Write-Host " Installing system-wide (silent)..." -NoNewline
|
||||||
|
$proc = Start-Process -FilePath $pyInstaller `
|
||||||
|
-ArgumentList "/quiet InstallAllUsers=1 PrependPath=1 Include_test=0" `
|
||||||
|
-Wait -PassThru
|
||||||
|
if ($proc.ExitCode -ne 0) {
|
||||||
|
Write-Error "Python installer exited $($proc.ExitCode). Install manually from https://python.org then re-run."
|
||||||
|
}
|
||||||
|
Write-Host " done." -ForegroundColor Green
|
||||||
|
Remove-Item $pyInstaller -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
# Refresh PATH after install
|
||||||
|
$env:PATH = [System.Environment]::GetEnvironmentVariable("PATH","Machine") + ";" +
|
||||||
|
[System.Environment]::GetEnvironmentVariable("PATH","User")
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Search for system-wide Python first ───────────────────────────────────────
|
||||||
foreach ($p in $systemPaths) {
|
foreach ($p in $systemPaths) {
|
||||||
if (Test-Path $p) {
|
if (Test-Path $p) {
|
||||||
try {
|
try {
|
||||||
@@ -65,7 +108,7 @@ foreach ($p in $systemPaths) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Fall back to PATH
|
# ── Fall back to PATH — but flag if it's per-user ─────────────────────────────
|
||||||
if (-not $pythonPath) {
|
if (-not $pythonPath) {
|
||||||
foreach ($cmd in @("python", "python3", "py")) {
|
foreach ($cmd in @("python", "python3", "py")) {
|
||||||
try {
|
try {
|
||||||
@@ -78,64 +121,31 @@ if (-not $pythonPath) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (-not $pythonPath) {
|
# ── If Python is per-user (AppData), install system-wide so LocalSystem can use it ──
|
||||||
|
$needsSystemPython = $false
|
||||||
|
if ($pythonPath -and ($pythonPath -match "AppData")) {
|
||||||
|
Write-Host " Found per-user Python: $pythonPath" -ForegroundColor Yellow
|
||||||
|
Write-Host " LocalSystem service needs system-wide Python. Installing..." -ForegroundColor Yellow
|
||||||
|
$needsSystemPython = $true
|
||||||
|
} elseif (-not $pythonPath) {
|
||||||
Write-Host " Python 3 not found. Installing system-wide..." -ForegroundColor Yellow
|
Write-Host " Python 3 not found. Installing system-wide..." -ForegroundColor Yellow
|
||||||
|
$needsSystemPython = $true
|
||||||
|
}
|
||||||
|
|
||||||
# Try winget first (Win 10 1709+ / Win 11)
|
if ($needsSystemPython) {
|
||||||
$wingetOk = $false
|
Install-PythonSystemWide
|
||||||
try {
|
# Locate the newly installed system-wide Python
|
||||||
$null = Get-Command winget -ErrorAction Stop
|
$pythonPath = $null
|
||||||
Write-Host " Using winget..." -NoNewline
|
|
||||||
winget install Python.Python.3.12 --silent --scope machine `
|
|
||||||
--accept-package-agreements --accept-source-agreements 2>&1 | Out-Null
|
|
||||||
$wingetOk = $true
|
|
||||||
Write-Host " done." -ForegroundColor Green
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
if (-not $wingetOk) {
|
|
||||||
# Direct download — works on Win 8.1 without winget
|
|
||||||
# Python 3.11 is explicitly documented to support Win 8.1+
|
|
||||||
Write-Host " Downloading Python 3.11 installer (Win 8.1+ compatible)..." -NoNewline
|
|
||||||
$pyInstaller = "$env:TEMP\python-installer.exe"
|
|
||||||
$pyUrl = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-amd64.exe"
|
|
||||||
try {
|
|
||||||
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
|
|
||||||
$wc = New-Object System.Net.WebClient
|
|
||||||
$wc.DownloadFile($pyUrl, $pyInstaller)
|
|
||||||
Write-Host " downloaded." -ForegroundColor Green
|
|
||||||
} catch {
|
|
||||||
Write-Error "Could not download Python installer. Install Python 3.11+ from https://python.org (choose 'Install for all users') then re-run."
|
|
||||||
}
|
|
||||||
Write-Host " Running Python installer (system-wide, silent)..." -NoNewline
|
|
||||||
$proc = Start-Process -FilePath $pyInstaller `
|
|
||||||
-ArgumentList "/quiet InstallAllUsers=1 PrependPath=1 Include_test=0" `
|
|
||||||
-Wait -PassThru
|
|
||||||
if ($proc.ExitCode -ne 0) {
|
|
||||||
Write-Error "Python installer exited with code $($proc.ExitCode). Install manually from https://python.org then re-run."
|
|
||||||
}
|
|
||||||
Write-Host " done." -ForegroundColor Green
|
|
||||||
Remove-Item $pyInstaller -ErrorAction SilentlyContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
# Refresh PATH and locate installed Python
|
|
||||||
$env:PATH = [System.Environment]::GetEnvironmentVariable("PATH","Machine") + ";" +
|
|
||||||
[System.Environment]::GetEnvironmentVariable("PATH","User")
|
|
||||||
foreach ($p in $systemPaths) {
|
foreach ($p in $systemPaths) {
|
||||||
if (Test-Path $p) { $pythonPath = $p; break }
|
if (Test-Path $p) {
|
||||||
}
|
|
||||||
if (-not $pythonPath) {
|
|
||||||
foreach ($cmd in @("python", "python3")) {
|
|
||||||
try {
|
try {
|
||||||
$ver = & $cmd --version 2>&1
|
$ver = & $p --version 2>&1
|
||||||
if ("$ver" -match "Python 3") {
|
if ("$ver" -match "Python 3") { $pythonPath = $p; break }
|
||||||
$resolved = (Get-Command $cmd -ErrorAction SilentlyContinue)
|
|
||||||
if ($resolved) { $pythonPath = $resolved.Source; break }
|
|
||||||
}
|
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (-not $pythonPath) {
|
if (-not $pythonPath) {
|
||||||
Write-Error "Python 3 still not found after installation. Open a new Admin PowerShell and re-run this installer."
|
Write-Error "System-wide Python not found after install. Open a new Admin PowerShell and re-run."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,13 +153,19 @@ Write-Host " Python: $pythonPath" -ForegroundColor Green
|
|||||||
|
|
||||||
# ── Install pywin32 (required for Windows service support) ────────────────────
|
# ── Install pywin32 (required for Windows service support) ────────────────────
|
||||||
Write-Host "[2/6] Installing pywin32..." -ForegroundColor Cyan
|
Write-Host "[2/6] Installing pywin32..." -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# pip install
|
||||||
|
$pipResult = & $pythonPath -m pip install --upgrade pywin32 2>&1
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error "pip install pywin32 failed (exit $LASTEXITCODE).`n$pipResult`nTry manually: $pythonPath -m pip install pywin32"
|
||||||
|
}
|
||||||
|
|
||||||
|
# postinstall registers service runner DLLs — non-fatal if it fails
|
||||||
try {
|
try {
|
||||||
& $pythonPath -m pip install --quiet --upgrade pywin32
|
$postResult = & $pythonPath -c "import pywin32_postinstall; pywin32_postinstall.install()" 2>&1
|
||||||
# Run postinstall to register service runner DLLs system-wide
|
|
||||||
& $pythonPath -c "import pywin32_postinstall; pywin32_postinstall.install()" 2>$null
|
|
||||||
Write-Host " pywin32 installed." -ForegroundColor Green
|
Write-Host " pywin32 installed." -ForegroundColor Green
|
||||||
} catch {
|
} catch {
|
||||||
Write-Error "pip install pywin32 failed: $_`nEnsure pip is available: $pythonPath -m ensurepip"
|
Write-Host " pywin32 installed (postinstall skipped — service should still work)." -ForegroundColor Yellow
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Create install directory and download agent ────────────────────────────────
|
# ── Create install directory and download agent ────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user