1

I am currently installing python3 by hardcoding the version in my script. I am trying to figure out what I can do to always install the latest stable version of python on the windows agent.

$url = "https://www.python.org/ftp/python/3.9.10/python-3.9.10.exe"
$pythonPath = "C:/Program Files/python/python-3.9.10.exe"

If ((Test-Path C:) -and !(Test-Path "C:\Program Files\python"))
{
    New-Item -Path "C:\Program Files\" -Name "python" -ItemType "directory"
}

[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072

Invoke-WebRequest -Uri $url -OutFile $pythonPath

Above is what I am doing currently, this is working fine though but what can I do not to hardcode the version and install the latest stable python version?

Gauti11
  • 25
  • 6
  • Open this url: https://www.python.org/ftp/python/ then put it in a file. Then filter the result to get the max version of python. Then the installation exe is this format: https://www.python.org/ftp/python//python-.exe – jose_bacoy Aug 29 '22 at 18:43

2 Answers2

1

We were using @mklement0 powershell method (thanks!), but it broke on us today because Python changed their download page. (There was a warning about the fragility of the web scraping method after all...)

We did find a simple change could make it work again (only returns the url, not the version), like this:

if ((Invoke-RestMethod 'https://www.python.org/downloads/windows/') -notmatch '\bhref="(?<url>.+?\.exe)"\s*>\s*Windows installer \(64-bit\)') {
    throw "Could not determine latest Python version and download URL ... "
    exit 1
}

However, I think I stumbled upon perhaps a "better" method. In a github discussion, someone mentioned lastversion ^1 ^2 ^3, so I tried it out to see how they were getting the latest python version.

When I ran it in verbose mode (lastversion python --verbose), it was referencing this RSS/Atom feed https://github.com/python/cpython/releases.atom which I didn't know existed...

In our situation, we cannot really use lastversion because it is a python application and we are trying to determine the latest version of python and install it on a fresh system. So I thought I could use that same feed to determine the latest, and work out the link from there. Here's what I came up with. I'm sure it could be improved upon, but thought it might be useful for someone to use or build upon.

$TmpDir = "${env:SystemDrive}\Temp"
if (!(Test-Path $TmpDir)) { New-Item -ItemType Directory $TmpDir -Force }
$PyReleases = Invoke-RestMethod 'https://github.com/python/cpython/releases.atom'
# Drop the "v" and filter out versions with letters in it, cast to a version, sort descending and select the first result
$PyLatestVersion = ($PyReleases.title) -replace "^v" -notmatch "[a-z]" | Sort-Object { [version] $_ } -Descending | Select-Object -First 1
$PyLatestBaseUrl = "https://www.python.org/ftp/python/${PyLatestVersion}/"
$PyUrl = "${PyLatestBaseUrl}/python-${PyLatestVersion}-amd64.exe"
$PyPkg = $PyUrl | Split-Path -Leaf
# Also could use: $PyPkg = "python-${PyLatestVersion}-amd64.exe"
$PyVerDir = ($PyPkg -replace "\.exe" -replace "-amd64" -replace "-").Split(".")
$PyVerDir = $PyVerDir[0] + $PyVerDir[-2]
$PyVerDir = $PyVerDir.Substring(0, 1).ToUpper() + $PyVerDir.Substring(1).ToLower()
$PyCmd = "${env:ProgramFiles}\${PyVerDir}\python.exe"
Invoke-WebRequest -UseBasicParsing -Uri $PyUrl -OutFile "${TmpDir}\${PyPkg}"
Start-Process "${TmpDir}\${PyPkg}" -ArgumentList "/passive", "InstallAllUsers=1", "PrependPath=1", "Include_test=0" -Wait -NoNewWindow

Hopefully this is useful. Also, I'm open to hearing about improvements that could be made.

0

Note: If you're on a recent version of Windows 10 or you're on Windows 11, consider using winget.exe to install Python, which automatically targets the latest (stable) version:

winget install python --accept-package-agreements

Otherwise, you'll have to resort to web scraping, which is inherently brittle, however (web pages can change).

A pragmatic approach is to scrape https://www.python.org/downloads/ for the first download URL ending in .exe, which is assumed to point to the latest stable version:

if (
  (Invoke-RestMethod 'https://www.python.org/downloads/') -notmatch 
  '\bhref="(?<url>.+?\.exe)"\s*>\s*Download Python (?<version>\d+\.\d+\.\d+)'
) { throw "Could not determine latest Python version and download URL" }

# The automatic $Matches variable contains the results of the regex-
# matching operation.
$url = $Matches.url
$pythonPath = "C:/Program Files/python/python-$($Matches.version).exe"

# ... 

A comparatively more robust, but more cumbersome approach is to scrape https://www.python.org/ftp/python/ instead:

  • Since the version numbers listed there also include prerelease versions, additional checks are required to find the latest stable version.
  • This answer shows a Unix-focused implementation using a sh (bash) shell script.
mklement0
  • 382,024
  • 64
  • 607
  • 775