$ErrorActionPreference = "Stop"
$ProgressPreference = 'SilentlyContinue'
$PythonVersion = "3.11"
$PythonFullVersion = "3.11.4"
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$UtilsScript = Join-Path $ScriptDir "utils.ps1"
. $UtilsScript
function Compare-Version {
param(
[string]$Version1,
[string]$Version2
)
$V1Match = $Version1 -match "(\d+)\.(\d+)\.(\d+)"
$V2Match = $Version2 -match "(\d+)\.(\d+)\.(\d+)"
if (-not $V1Match -or -not $V2Match) {
return $null
}
$V1Major = [int]$Matches[1]
$V1Minor = [int]$Matches[2]
$V1Patch = [int]$Matches[3]
$null = $Version2 -match "(\d+)\.(\d+)\.(\d+)"
$V2Major = [int]$Matches[1]
$V2Minor = [int]$Matches[2]
$V2Patch = [int]$Matches[3]
if ($V1Major -gt $V2Major) { return 1 }
if ($V1Major -lt $V2Major) { return -1 }
if ($V1Minor -gt $V2Minor) { return 1 }
if ($V1Minor -lt $V2Minor) { return -1 }
if ($V1Patch -gt $V2Patch) { return 1 }
if ($V1Patch -lt $V2Patch) { return -1 }
return 0
}
function Test-PythonInstalled {
param(
[string]$RequiredFullVersion = $PythonFullVersion,
[ref]$PythonPath = $null
)
$FoundPath = Search-PythonExecutable
if ($FoundPath) {
try {
$InstalledVersion = & $FoundPath --version 2>&1
if ($InstalledVersion -match "Python 3\.11") {
$Comparison = Compare-Version -Version1 $InstalledVersion -Version2 "Python $RequiredFullVersion"
if ($null -ne $Comparison -and $Comparison -ge 0) {
if ($PythonPath) {
$PythonPath.Value = $FoundPath
}
return $true
} else {
Write-Log "WARN" "Found Python $InstalledVersion but required version is >= $RequiredFullVersion"
}
} elseif ($InstalledVersion -match "Python 3\.(\d+)") {
$MinorVersion = [int]$Matches[1]
if ($MinorVersion -gt 11) {
if ($PythonPath) {
$PythonPath.Value = $FoundPath
}
Write-Log "INFO" "Found Python $InstalledVersion (>= 3.11.4), using it"
return $true
}
}
} catch {
}
}
return $false
}
function Search-PythonExecutable {
$SearchPaths = @(
"C:\Program Files\Python$PythonVersion\python.exe",
"C:\Program Files (x86)\Python$PythonVersion\python.exe",
"$env:LOCALAPPDATA\Programs\Python\Python$PythonVersion\python.exe",
"C:\Python$PythonVersion\python.exe",
"C:\Program Files\Python$($PythonVersion.Replace('.', ''))\python.exe",
"C:\Program Files (x86)\Python$($PythonVersion.Replace('.', ''))\python.exe",
"$env:LOCALAPPDATA\Programs\Python\Python$($PythonVersion.Replace('.', ''))\python.exe"
)
foreach ($Path in $SearchPaths) {
if (Test-Path $Path) {
try {
$Version = & $Path --version 2>&1
if ($Version -match "Python 3\.11") {
$Comparison = Compare-Version -Version1 $Version -Version2 "Python $PythonFullVersion"
if ($null -ne $Comparison -and $Comparison -ge 0) {
Write-Log "INFO" "Found Python executable at: $Path (Version: $Version, >= $PythonFullVersion)"
return $Path
} else {
Write-Log "INFO" "Found Python at $Path but version is $Version, required: >= $PythonFullVersion"
}
} elseif ($Version -match "Python 3\.(\d+)") {
$MinorVersion = [int]$Matches[1]
if ($MinorVersion -gt 11) {
Write-Log "INFO" "Found Python executable at: $Path (Version: $Version, >= $PythonFullVersion)"
return $Path
} else {
Write-Log "INFO" "Found Python at $Path but version is $Version, required: >= $PythonFullVersion"
}
} elseif ($Version -match "Python") {
Write-Log "INFO" "Found Python at $Path but version is $Version, required: >= $PythonFullVersion"
}
} catch {
}
}
}
$null = Get-Command python -ErrorAction SilentlyContinue
if ($?) {
try {
$PythonPath = (Get-Command python).Source
if (Test-Path $PythonPath) {
if ($PythonPath -like "*WindowsApps*\python.exe") {
Write-Log "INFO" "Skipping Microsoft Store Python alias at: $PythonPath"
} else {
try {
$Version = & $PythonPath --version 2>&1
if ($Version -match "Python 3\.11") {
$Comparison = Compare-Version -Version1 $Version -Version2 "Python $PythonFullVersion"
if ($null -ne $Comparison -and $Comparison -ge 0) {
Write-Log "INFO" "Found Python executable in PATH: $PythonPath (Version: $Version, >= $PythonFullVersion)"
return $PythonPath
} else {
Write-Log "INFO" "Found Python in PATH at $PythonPath but version is $Version, required: >= $PythonFullVersion"
}
} elseif ($Version -match "Python 3\.(\d+)") {
$MinorVersion = [int]$Matches[1]
if ($MinorVersion -gt 11) {
Write-Log "INFO" "Found Python executable in PATH: $PythonPath (Version: $Version, >= $PythonFullVersion)"
return $PythonPath
} else {
Write-Log "INFO" "Found Python in PATH at $PythonPath but version is $Version, required: >= $PythonFullVersion"
}
} elseif ($Version -match "Python") {
Write-Log "INFO" "Found Python in PATH at $PythonPath but version is $Version, required: >= $PythonFullVersion"
}
} catch {
}
}
}
} catch {
}
}
$null = Get-Command python3 -ErrorAction SilentlyContinue
if ($?) {
try {
$PythonPath = (Get-Command python3).Source
if (Test-Path $PythonPath) {
if ($PythonPath -like "*WindowsApps*\python3.exe") {
Write-Log "INFO" "Skipping Microsoft Store Python3 alias at: $PythonPath"
} else {
try {
$Version = & $PythonPath --version 2>&1
if ($Version -match "Python 3\.11") {
$Comparison = Compare-Version -Version1 $Version -Version2 "Python $PythonFullVersion"
if ($null -ne $Comparison -and $Comparison -ge 0) {
Write-Log "INFO" "Found Python3 executable in PATH: $PythonPath (Version: $Version, >= $PythonFullVersion)"
return $PythonPath
} else {
Write-Log "INFO" "Found Python3 in PATH at $PythonPath but version is $Version, required: >= $PythonFullVersion"
}
} elseif ($Version -match "Python 3\.(\d+)") {
$MinorVersion = [int]$Matches[1]
if ($MinorVersion -gt 11) {
Write-Log "INFO" "Found Python3 executable in PATH: $PythonPath (Version: $Version, >= $PythonFullVersion)"
return $PythonPath
} else {
Write-Log "INFO" "Found Python3 in PATH at $PythonPath but version is $Version, required: >= $PythonFullVersion"
}
} elseif ($Version -match "Python") {
Write-Log "INFO" "Found Python3 in PATH at $PythonPath but version is $Version, required: >= $PythonFullVersion"
}
} catch {
}
}
}
} catch {
}
}
Write-Log "INFO" "Searched all common Python installation paths but Python 3.11 not found"
return $null
}
function Install-Python {
Write-Log "INFO" "Starting automatic installation of Python $PythonVersion"
$TempDir = Join-Path $env:TEMP "PythonInstall"
if (-not (Test-Path $TempDir)) {
New-Item -ItemType Directory -Path $TempDir | Out-Null
}
$InstallerUrl = "https://www.python.org/ftp/python/$PythonFullVersion/python-$PythonFullVersion-amd64.exe"
$InstallerPath = Join-Path $TempDir "python-$PythonFullVersion-amd64.exe"
Write-Log "INFO" "Downloading Python installer from: $InstallerUrl"
try {
Invoke-WebRequest -Uri $InstallerUrl -OutFile $InstallerPath -UseBasicParsing
Write-Log "SUCCESS" "Python installer downloaded successfully"
} catch {
Write-Log "ERROR" "Failed to download Python installer: $($_.Exception.Message)"
exit 1
}
Write-Log "INFO" "Installing Python silently..."
try {
Start-Process -FilePath $InstallerPath -ArgumentList "/quiet InstallAllUsers=1 PrependPath=1 Include_test=0" -Wait
Write-Log "SUCCESS" "Python installed successfully"
} catch {
Write-Log "ERROR" "Failed to install Python: $($_.Exception.Message)"
exit 1
}
Remove-Item -Path $TempDir -Recurse -Force -ErrorAction SilentlyContinue
Write-Log "INFO" "Waiting for Python installation to complete..."
Start-Sleep -Seconds 10
Write-Log "INFO" "Refreshing environment variables..."
$env:PATH = [System.Environment]::GetEnvironmentVariable("PATH", [System.EnvironmentVariableTarget]::Machine) + ";" + [System.Environment]::GetEnvironmentVariable("PATH", [System.EnvironmentVariableTarget]::User)
$Retries = 3
$PythonExePath = $null
for ($i = 1; $i -le $Retries; $i++) {
Write-Log "INFO" "Searching for Python executable (attempt $i/$Retries)..."
$PythonExePath = Search-PythonExecutable
if ($PythonExePath) {
break
}
Write-Log "INFO" "Python executable not found, waiting 5 seconds and trying again..."
Start-Sleep -Seconds 5
}
if ($PythonExePath) {
$PythonDir = Split-Path -Parent $PythonExePath
$ScriptsDir = Join-Path $PythonDir "Scripts"
if ($env:PATH -notlike "*$PythonDir*") {
$env:PATH = "$PythonDir;$ScriptsDir;" + $env:PATH
}
try {
$Version = & $PythonExePath --version 2>&1
Write-Log "INFO" "Python version check: $Version"
} catch {
Write-Log "WARN" "Failed to check Python version via direct path: $($_.Exception.Message)"
}
} else {
Write-Log "ERROR" "Python executable not found after installation"
Write-Log "INFO" "Please check if Python was installed correctly and try again"
Write-Log "INFO" "Common Python installation paths:"
Write-Log "INFO" "- C:\Program Files\Python$PythonVersion\python.exe"
Write-Log "INFO" "- C:\Program Files (x86)\Python$PythonVersion\python.exe"
Write-Log "INFO" "- $env:LOCALAPPDATA\Programs\Python\Python$PythonVersion\python.exe"
exit 1
}
}
function Install-UvIfNeeded {
try {
$UvCmd = Get-Command uv -ErrorAction Stop
Write-Log "INFO" "uv already installed: $($UvCmd.Source)"
} catch {
Write-Log "INFO" "uv not found, installing via official installer script..."
$UvInstallScriptUrl = "https://astral.sh/uv/install.ps1"
$UvInstallScriptPath = Join-Path $env:TEMP "install-uv.ps1"
try {
Invoke-WebRequest -Uri $UvInstallScriptUrl -OutFile $UvInstallScriptPath -UseBasicParsing
powershell -NoProfile -ExecutionPolicy Bypass -File $UvInstallScriptPath
if ($LASTEXITCODE -ne 0) {
Write-Log "ERROR" "uv installer exited with code: $LASTEXITCODE"
exit 1
}
} catch {
Write-Log "ERROR" "Failed to install uv: $($_.Exception.Message)"
exit 1
} finally {
Remove-Item -Path $UvInstallScriptPath -Force -ErrorAction SilentlyContinue
}
}
$UvPathCandidates = @(
(Join-Path $env:USERPROFILE ".local\bin"),
(Join-Path $env:USERPROFILE ".cargo\bin")
)
foreach ($UvCandidate in $UvPathCandidates) {
if ((Test-Path $UvCandidate) -and ($env:PATH -notlike "*$UvCandidate*")) {
$env:PATH = "$UvCandidate;$env:PATH"
}
}
Test-Command "uv"
Write-Log "SUCCESS" "uv is available ($(uv --version 2>&1))"
}
$ProxyConfigPath = Join-Path $PSScriptRoot "user_config.ps1"
if (Test-Path $ProxyConfigPath) {
try {
. $ProxyConfigPath
} catch {
Write-Log "WARN" "Failed to load proxy config file: $ProxyConfigPath, Error: $($_.Exception.Message)"
}
}
Write-Log "INFO" "=== Checking Python $PythonFullVersion and uv Installation Status ==="
$PythonExePath = Search-PythonExecutable
$PythonInstalled = $false
if ($PythonExePath) {
try {
$Version = & $PythonExePath --version 2>&1
if ($Version -match "Python 3\.11") {
$Comparison = Compare-Version -Version1 $Version -Version2 "Python $PythonFullVersion"
if ($null -ne $Comparison -and $Comparison -ge 0) {
Write-Log "SUCCESS" "Python >= $PythonFullVersion found, Version: $Version"
$PythonInstalled = $true
} else {
Write-Log "WARN" "Python 3.11 found but version is $Version, required: >= $PythonFullVersion"
$PythonInstalled = $false
}
} elseif ($Version -match "Python 3\.(\d+)") {
$MinorVersion = [int]$Matches[1]
if ($MinorVersion -gt 11) {
Write-Log "SUCCESS" "Python >= $PythonFullVersion found, Version: $Version"
$PythonInstalled = $true
} else {
Write-Log "WARN" "Python found but version is $Version, required: >= $PythonFullVersion"
$PythonInstalled = $false
}
} else {
Write-Log "WARN" "Python found but version is $Version, required: >= $PythonFullVersion"
$PythonInstalled = $false
}
} catch {
Write-Log "WARN" "Python found but failed to check version: $($_.Exception.Message)"
$PythonInstalled = $false
}
}
if (-not $PythonInstalled) {
$TestPath = $null
$PythonInstalled = Test-PythonInstalled -RequiredFullVersion $PythonFullVersion -PythonPath ([ref]$TestPath)
if ($PythonInstalled -and $TestPath) {
$PythonExePath = $TestPath
Write-Log "INFO" "Python found via Test-PythonInstalled at: $PythonExePath"
}
}
if (-not $PythonInstalled) {
Write-Log "WARN" "Python $PythonFullVersion not installed"
Install-Python
$PythonExePath = Search-PythonExecutable
if ($PythonExePath) {
try {
$Version = & $PythonExePath --version 2>&1
if ($Version -match "Python 3\.11") {
$Comparison = Compare-Version -Version1 $Version -Version2 "Python $PythonFullVersion"
if ($null -ne $Comparison -and $Comparison -ge 0) {
Write-Log "SUCCESS" "Python installed successfully, Version: $Version (>= $PythonFullVersion)"
$PythonInstalled = $true
} else {
Write-Log "ERROR" "Python installed but version is $Version, required: >= $PythonFullVersion"
exit 1
}
} elseif ($Version -match "Python 3\.(\d+)") {
$MinorVersion = [int]$Matches[1]
if ($MinorVersion -gt 11) {
Write-Log "SUCCESS" "Python installed successfully, Version: $Version (>= $PythonFullVersion)"
$PythonInstalled = $true
} else {
Write-Log "ERROR" "Python installed but version is $Version, required: >= $PythonFullVersion"
exit 1
}
} else {
Write-Log "ERROR" "Python installed but version is $Version, required: >= $PythonFullVersion"
exit 1
}
} catch {
Write-Log "ERROR" "Python installation failed - cannot verify version"
exit 1
}
} else {
Write-Log "ERROR" "Python installation failed - executable not found"
exit 1
}
}
if ($PythonInstalled) {
try {
$PythonVersionOutput = & $PythonExePath --version 2>&1
Write-Log "SUCCESS" "Python installed successfully! Version: $PythonVersionOutput"
} catch {
Write-Log "ERROR" "Failed to verify Python installation: $($_.Exception.Message)"
exit 1
}
}
Install-UvIfNeeded
if (-not $PythonExePath) {
Write-Log "ERROR" "Python executable path not found after installation check"
exit 1
}
if (-not (Test-Path $PythonExePath)) {
Write-Log "ERROR" "Python executable path does not exist: $PythonExePath"
exit 1
}
$env:PYTHON_EXE_PATH = $PythonExePath
Write-Output "PYTHON_EXE_PATH=$PythonExePath"
Write-Log "SUCCESS" "=== Operation Completed ==="
exit 0