# Compatible with Windows PowerShell 2.0. Reuse a verified full Python install. param([switch]$ProbeOnly) $ErrorActionPreference = 'Stop' $kitRoot = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path) $logDirectory = Join-Path $kitRoot 'logs' [void][IO.Directory]::CreateDirectory($logDirectory) $runId = Get-Date -Format 'yyyyMMdd-HHmmss-fff' $setupLog = Join-Path $logDirectory ('setup-' + $runId + '.log') $utf8 = New-Object System.Text.UTF8Encoding($true) [IO.File]::WriteAllText($setupLog, '', $utf8) $env:PYTHONUTF8 = '1' $env:PYTHONIOENCODING = 'utf-8' $env:PYTHONHOME = $null $env:PYTHONPATH = $null [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false) $targetBits = 32 if ($env:PROCESSOR_ARCHITECTURE -eq 'AMD64' -or $env:PROCESSOR_ARCHITEW6432 -eq 'AMD64') { $targetBits = 64 } function Write-Log([string]$message) { Write-Host $message [IO.File]::AppendAllText($setupLog, $message + [Environment]::NewLine, $utf8) } function Invoke-Logged([string]$executable, [string[]]$arguments) { # PowerShell 2 treats native stderr as ErrorRecord; preserve it in the log # without mistaking harmless warnings for a failed process. $savedPreference = $ErrorActionPreference $ErrorActionPreference = 'Continue' try { $global:LASTEXITCODE = $null & $executable @arguments 2>&1 | ForEach-Object { Write-Log ([string]$_) } $code = $global:LASTEXITCODE } finally { $ErrorActionPreference = $savedPreference } if ($null -eq $code) { return 1 } return $code } function Test-Python([string]$candidate) { if (-not (Test-Path -LiteralPath $candidate -PathType Leaf)) { return $false } Write-Log ('Checking Python: ' + $candidate) $probe = 'import sys, struct, venv, ensurepip, ssl, sqlite3; print(sys.version); print(sys.executable); sys.exit(0 if sys.version_info[:3] == (3,8,10) and struct.calcsize(''P'')*8 == ' + $targetBits + ' else 3)' $code = Invoke-Logged $candidate @('-I', '-X', 'utf8', '-c', $probe) if ($code -ne 0) { Write-Log ('Python rejected; exit code: ' + $code) } return ($code -eq 0) } function Find-Python { $manualFile = Join-Path $kitRoot 'python_path.txt' if (Test-Path -LiteralPath $manualFile) { $manualPath = [IO.File]::ReadAllText($manualFile).Trim().Trim('"') if (Test-Python $manualPath) { return $manualPath } throw 'python_path.txt must contain the full path to a working Python 3.8.10 of the required architecture.' } $candidates = New-Object System.Collections.ArrayList [void]$candidates.Add((Join-Path $kitRoot '.python38\python.exe')) # Python's official installer registers the full interpreter, even when # its files live in an older source kit directory. foreach ($registryRoot in @('HKCU:\Software\Python\PythonCore', 'HKCU:\Software\WOW6432Node\Python\PythonCore', 'HKLM:\Software\Python\PythonCore', 'HKLM:\Software\WOW6432Node\Python\PythonCore')) { foreach ($tag in @('3.8', '3.8-32')) { $keyPath = Join-Path $registryRoot ($tag + '\InstallPath') try { if (Test-Path -LiteralPath $keyPath) { $key = Get-Item -LiteralPath $keyPath $executable = [string]$key.GetValue('ExecutablePath') $directory = [string]$key.GetValue('') if ($executable) { [void]$candidates.Add($executable) } if ($directory) { [void]$candidates.Add((Join-Path $directory 'python.exe')) } } } catch { Write-Log ('Registry lookup skipped: ' + $keyPath + ' : ' + $_.Exception.Message) } } } foreach ($candidate in ($candidates | Select-Object -Unique)) { if (Test-Python $candidate) { return $candidate } } return $null } try { Write-Log ('Setup log: ' + $setupLog) Write-Log ('Package: ' + $kitRoot) Write-Log ('Windows: ' + [Environment]::OSVersion.VersionString) Write-Log ('PowerShell: ' + $PSVersionTable.PSVersion.ToString()) Write-Log ('Required Python: 3.8.10, ' + $targetBits + '-bit') $python = Find-Python if ($ProbeOnly) { if ($python) { Write-Log ('FOUND reusable Python: ' + $python) } else { Write-Log 'No working matching Python was found. No installation was attempted.' } Write-Log 'DIAGNOSTIC COMPLETE. No dependencies or system settings were changed.' exit 0 } if (-not $python) { $installerName = 'python-3.8.10.exe' if ($targetBits -eq 64) { $installerName = 'python-3.8.10-amd64.exe' } $installer = Join-Path $kitRoot ('installers\' + $installerName) if (-not (Test-Path -LiteralPath $installer)) { throw ('Installer missing: ' + $installer) } $installerLog = Join-Path $logDirectory ('python-installer-' + $runId + '.log') $targetDirectory = Join-Path $kitRoot '.python38' Write-Log ('No reusable Python found. Starting installer: ' + $installer) Write-Log ('Installer log: ' + $installerLog) $installerArgs = @('/passive', '/log', ('"' + $installerLog + '"'), 'InstallAllUsers=0', ('TargetDir="' + $targetDirectory + '"'), 'Include_launcher=0', 'InstallLauncherAllUsers=0', 'PrependPath=0', 'Include_doc=0', 'Include_test=0', 'Include_tcltk=0', 'Shortcuts=0', 'AssociateFiles=0') $process = Start-Process -FilePath $installer -ArgumentList $installerArgs -Wait -PassThru -WindowStyle Hidden $installerCode = $process.ExitCode Write-Log ('Python installer exit code: ' + $installerCode) if ($installerCode -eq 3010 -or $installerCode -eq 1641) { Write-Log 'Windows requested a restart. Restart the computer and run 01_install_env.cmd again.' exit $installerCode } if ($installerCode -ne 0) { throw ('Python installer failed with exit code ' + $installerCode + '. See ' + $installerLog) } # Maintenance mode may leave Python in its original registered folder. $python = Find-Python if (-not $python) { throw 'Installer returned success but no matching Python was found. Keep logs; do not uninstall the old working environment.' } } Write-Log ('Using existing Python: ' + $python) Write-Log 'Keep this Python directory while using the build environment.' $buildScript = Join-Path $kitRoot 'win7\build.py' $code = Invoke-Logged $python @('-I', '-X', 'utf8', $buildScript, 'setup') Write-Log ('Environment setup exit code: ' + $code) if ($code -ne 0) { exit $code } Write-Log 'SETUP COMPLETE. Run 02_build_exe.cmd next.' exit 0 } catch { Write-Log ('FAILED: ' + $_.Exception.Message) Write-Log ('Details: ' + $_.ScriptStackTrace) Write-Log ('Keep the logs folder. Setup log: ' + $setupLog) exit 1 }