Skip to content

Instantly share code, notes, and snippets.

@JamesDawson
Last active December 25, 2024 15:31
Show Gist options
  • Save JamesDawson/4d90e7fcc535c582c617ed553feaf35d to your computer and use it in GitHub Desktop.
Save JamesDawson/4d90e7fcc535c582c617ed553feaf35d to your computer and use it in GitHub Desktop.
Setup python, pyenv-win and poetry on Windows
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[string] $PythonVersion
)
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
Set-StrictMode -Version Latest
function _runCommand {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, Position=0)]
[string] $Command,
[switch] $PassThru
)
if ($PassThru) {
$res = Invoke-Expression $Command
}
else {
Invoke-Expression $Command
}
if ($LASTEXITCODE -ne 0) {
$msg = "'$Command' reported a non-zero status code [$LASTEXITCODE] - check previous output{0}"
if ($PassThru) {
Write-Error ($msg -f "`n$res")
}
else {
Write-Error ($msg -f ".")
}
}
if ($PassThru) {
return $res
}
}
function _addToUserPath {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, Position=0)]
[string] $AppName,
[Parameter(Mandatory=$true, Position=1)]
[string[]] $PathsToAdd
)
$currentPathEntries = $env:PATH -split ";" | Select-Object -Unique | Where-Object { ![string]::IsNullOrEmpty($_) }
$missingPathEntries = @()
foreach ($pathToAdd in $PathsToAdd) {
Write-Verbose "Checking PATH entry for $pathToAdd"
if ($pathToAdd -notin $currentPathEntries) {
Write-Verbose "Not found, will add to PATH"
$missingPathEntries += $pathToAdd
}
}
if ($missingPathEntries.Count -gt 0) {
Write-Host "$($AppName): Updating %PATH%..." -f Green
# Update the user-scoped PATH environment variable
$currentUserPaths = [System.Environment]::GetEnvironmentVariable("PATH", [System.EnvironmentVariableTarget]::User) -split ";" | Select-Object -Unique | Where-Object { ![string]::IsNullOrEmpty($_) }
$updatedUserPath = $missingPathEntries + $currentUserPaths
[System.Environment]::SetEnvironmentVariable("PATH", ($updatedUserPath -join ";").TrimEnd(";"), [System.EnvironmentVariableTarget]::User)
# Update PATH in the current session, so we don't need to restart the console
$env:PATH = ($missingPathEntries + $currentPathEntries) -join ";"
}
else {
Write-Host "$($AppName): PATH already setup." -f Cyan
}
}
Write-Host "#################################################" -f White
Write-Host "## Python, pyenv & poetry Windows setup script ##" -f White
Write-Host "#################################################" -f White
# Install pyenv
if (!(Test-Path $HOME/.pyenv)) {
# Explicitly check whether running Windows PowerShell, as '$IsWindows' is only available for PowerShell Core
if ($PSVersionTable.PSEdition -eq 'Desktop' -or $IsWindows) {
Write-Host "pyenv: Installing for Windows..." -f Green
if (!(Get-Command git -ErrorAction Ignore)) {
Write-Error "Git is required to install pyenv. Please install git and re-run this script."
}
& git clone https://github.com/pyenv-win/pyenv-win.git $HOME/.pyenv
if ($LASTEXITCODE -ne 0) {
Write-Error "git reported a non-zero status code [$LASTEXITCODE] - check previous output."
}
}
else {
Write-Error "This script currently only supports Windows."
}
}
else {
Write-Host "pyenv: Already installed." -f Cyan
}
# Add pyenv to PATH
_addToUserPath "pyenv" @(
"$HOME\.pyenv\pyenv-win\bin"
"$HOME\.pyenv\pyenv-win\shims"
)
# Install default pyenv python version
$pyenvVersions = _runCommand "pyenv versions" -PassThru | Select-String $PythonVersion
if (!($pyenvVersions)) {
Write-Host "pyenv: Installing python version $PythonVersion..." -f Green
_runCommand "pyenv install $PythonVersion"
}
else {
Write-Host "pyenv: Python version $PythonVersion already installed." -f Cyan
}
# Set pyenv global version
$globalPythonVersion = _runCommand "pyenv global" -PassThru
if ($globalPythonVersion -ne $PythonVersion) {
Write-Host "pyenv: Setting global python version: $PythonVersion" -f Green
_runCommand "pyenv global $PythonVersion"
}
else {
Write-Host "pyenv: Global python version already set: $globalPythonVersion" -f Cyan
}
if (!(Get-Command poetry -ErrorAction Ignore)) {
$downloadPath = "$HOME/Downloads/install-poetry.py"
Write-Host "python-poetry: Installing..." -f Green
Invoke-WebRequest -Uri "https://install.python-poetry.org" `
-UseBasicParsing `
-OutFile $downloadPath
try {
_runCommand "pyenv exec python `"$downloadPath`""
}
finally {
Remove-Item $downloadPath
}
}
else {
Write-Host "python-poetry: Already installed." -f Cyan
}
# Add poetry to PATH
_addToUserPath "python-poetry" @("$HOME\AppData\Roaming\Python\Scripts")
# Test poetry is available
_runCommand "poetry --version"
Write-Host "####################" -f Green
Write-Host "## Setup Complete ##" -f Green
Write-Host "####################" -f Green
@Fastjur
Copy link

Fastjur commented Apr 19, 2024

The $IsWindows variable is not defined for me on windows 10.

@JamesDawson
Copy link
Author

JamesDawson commented Apr 22, 2024

Hi @Fastjur, thanks for the comment.

If you run $PSVersionTable, does this show you are running on PSVersion v5.1? This is the original 'Windows PowerShell' rather than the cross-platform 'PowerShell Core' version - unfortunately $IsWindows is only available in the latter as v5.1 isn't getting updated now. However, the intention was for this script to work on both editions so thanks for letting me know.

I've updated the gist to handle this properly, so hopefully it should work for you now?

@the4amfriend
Copy link

Hey @JamesDawson I had issues running the code on Windows 11

PS C:\Users\Shiva.Subramanian\Dropbox\PC\Documents\Shiva\gitrep> ./setup-pyenv-poetry-windows.ps1

cmdlet setup-pyenv-poetry-windows.ps1 at command pipeline position 1
Supply values for the following parameters:
PythonVersion: 3.11.9
#################################################
## Python, pyenv & poetry Windows setup script ##
#################################################
pyenv: Installing for Windows...
Cloning into 'C:\Users\Shiva.Subramanian/.pyenv'...
remote: Enumerating objects: 3227, done.
remote: Counting objects: 100% (517/517), done.
remote: Compressing objects: 100% (118/118), done.
remote: Total 3227 (delta 450), reused 413 (delta 399), pack-reused 2710
Receiving objects: 100% (3227/3227), 3.61 MiB | 9.58 MiB/s, done.
Resolving deltas: 100% (2103/2103), done.
pyenv: Updating %PATH%...
'cscript' is not recognized as an internal or external command,
operable program or batch file.
'cscript' is not recognized as an internal or external command,
operable program or batch file.
'cscript' is not recognized as an internal or external command,
operable program or batch file.
_runCommand : 'pyenv versions' reported a non-zero status code [1] - check previous output
At C:\Users\Shiva.Subramanian\Dropbox\PC\Documents\Shiva\gitrep\setup-pyenv-poetry-windows.ps1:104 char:18
+ $pyenvVersions = _runCommand "pyenv versions" -PassThru | Select-Stri ...
+                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,_runCommand

PS C:\Users\Shiva.Subramanian\Dropbox\PC\Documents\Shiva\gitrep>

I checked on another terminal where I was able to run cscript just fine but I suspect there's a bug in the way the path is added (where it loses old configuration so it can't run cscript anymore)

@JamesDawson
Copy link
Author

Thanks for the comment @the4amfriend, I've not looked at this specific issue (I run Windows 11 too), but I have just updated the gist to prepend to the path instead of appending, following a comment elsewhere. Can you let me know if this has any effect on your issue?

@the4amfriend
Copy link

Sorry @JamesDawson I fixed the path manually and moved on. Not sure if running the script again will impact my current environment?

@tab1tha
Copy link

tab1tha commented Jun 7, 2024

Sorry @JamesDawson I fixed the path manually and moved on. Not sure if running the script again will impact my current environment?

@the4amfriend how did you fix the path?

@the4amfriend
Copy link

I just manually set my path within the same command prompt.

@tab1tha
Copy link

tab1tha commented Jun 7, 2024

I just manually set my path within the same command prompt.

I tried editing the environment variables through the UI and adding the paths to pyenv-win/bin and pyenv-win/shims (lines 99-100) but that still didn't work.

If it isn't much trouble, could you share the command that you ran in the command prompt to set your path?
What location did you add to PATH? @the4amfriend

@gtheys
Copy link

gtheys commented Jul 6, 2024

I just manually set my path within the same command prompt.

I tried editing the environment variables through the UI and adding the paths to pyenv-win/bin and pyenv-win/shims (lines 99-100) but that still didn't work.

If it isn't much trouble, could you share the command that you ran in the command prompt to set your path? What location did you add to PATH? @the4amfriend

I have the same problem and am quite new to windows. Could use a pointer her too :)

@toolsche
Copy link

toolsche commented Sep 6, 2024

Hi, I have the same problem with cscript not being found. The script overwrites previously set PATH variables loaded from machine scope by just getting those of the current user.

@fsuchy
Copy link

fsuchy commented Sep 24, 2024

I had the exact same problem. I checked my path variable in the shell I ran the script, and I could see that pyenv-win/bin and pyenv-win/shims prepended, but the rest was somehow messed up. Rather than fix the path in that shell, I opened a new shell and ran the script again. That appears to have worked, at least I saw no errors.
This is all new to me, so I cannot yet say everything is fully functional.

@armbck
Copy link

armbck commented Oct 17, 2024

One way of fixing the cscript error (Win 10). To me opening in a new shell did not fix this one.

  1. Get the current PATH variable content
    [System.Environment]::GetEnvironmentVariable("PATH", [System.EnvironmentVariableTarget]::User)

  2. Reset the PATH with added path to cscript C:\windows\system32\ <- must have the backward slash at the end
    (modify according to your environment paths from the previous command)
    [System.Environment]::SetEnvironmentVariable("PATH","C:\Users\NNN.pyenv\pyenv-win\shims;C:\Users\xxx.pyenv\pyenv-win\bin;C:\Users\NNN\AppData\Roaming\pypoetry;C:\windows\system32\", [System.EnvironmentVariableTarget]::User)

  3. Reload the Path environment variable to the PS
    $env:PATH = [System.Environment]::GetEnvironmentVariable("PATH", [System.EnvironmentVariableTarget]::User)

  4. Run the setup again
    .\setup-pyenv-poetry-windows.ps1

@JamesDawson
Copy link
Author

Thanks for your comments @tab1tha, @the4amfriend, @gtheys, @toolsche, @fsuchy, @armbck

I finally got around to revisiting this and have fixed (hopefully!) the PATH handling - the change should ensure that your existing PATH entries are left intact, regardless of their scope.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment