8

I am attempting to use PowerShell to download the Python 3 installer from the Python website into a specific directory, then silently run/install the .exe file in that same directory and then add the appropriate directory to my system's PATH variable.

So far I have come up with:

start cmd /k powershell -Command "(New-Object Net.WebClient).DownloadFile('https://www.python.org/ftp/python/3.6.2/python-3.6.2.exe', 'C:/Tools/python-3.6.2.exe')" &&
c:\Tools\python-3.6.2.exe /quiet InstallAllUsers=1 PrependPath=1 Include_test=0 TargetDir=c:\Tools\Python362 &&
setx path "%PATH%;C:\Tools\Python362\" /M

Unfortunately, this does not work. The command window will open, and then immediately quit. I have run each of these commands separately, and when I do so they work. However, when I try to run them sequentially in the same file it does not work. How can I fix this?

Note: I believe the problem stems from the use of &&, because if I use & the CMD prompt will persist, and execute. However, this does not help me since I need the second command to execute after the first one has finished or else there isn't any .exe file for the second command to run. I am hoping this is just a syntactic error as I am very new to creating batch files and working with the Windows command line.

2
  • When I use -and only the first command runs. Commented Aug 22, 2017 at 19:56
  • start cmd /k powershell -> powershell. Make sure the destination folder already exists. Commented Aug 22, 2017 at 21:28

4 Answers 4

7

I'd do it all in PowerShell, personally.

I'd be tempted to put it in a script, like this:

[CmdletBinding()] Param(
    $pythonVersion = "3.6.2"
    $pythonUrl = "https://www.python.org/ftp/python/$pythonVersion/python-$pythonVersion.exe"
    $pythonDownloadPath = 'C:\Tools\python-$pythonVersion.exe'
    $pythonInstallDir = "C:\Tools\Python$pythonVersion"
)

(New-Object Net.WebClient).DownloadFile($pythonUrl, $pythonDownloadPath)
& $pythonDownloadPath /quiet InstallAllUsers=1 PrependPath=1 Include_test=0 TargetDir=$pythonInstallDir
if ($LASTEXITCODE -ne 0) {
    throw "The python installer at '$pythonDownloadPath' exited with error code '$LASTEXITCODE'"
}
# Set the PATH environment variable for the entire 
# machine (that is, for all users) to include 
# the Python install directory
[Environment]::SetEnvironmentVariable("PATH", "${env:path};${pythonInstallDir}", "Machine")

Then you can call the script from cmd.exe like this:

powershell.exe -File X:\Path\to\Install-Python.ps1

The Param() block defines defaults for the Python version, the URL to download it from, the place to save it, and the place to install it to, but it lets you override these options if that ever becomes useful. You might pass those arguments like this:

powershell.exe -File X:\Path\to\Install-Python.ps1 -version 3.4.0 -pythonInstallDir X:\Somewhere\Else\Python3.4.0

That said, you absolutely can just do a one-liner in pure PowerShell as well. From your description, I don't think you should need to do the start cmd /k prefix - you should be able to just call powershell.exe directly, like this:

powershell -command "(New-Object Net.WebClient).DownloadFile('https://www.python.org/ftp/python/3.6.2/python-3.6.2.exe', 'C:/Tools/python-3.6.2.exe'); & c:\Tools\python-3.6.2.exe /quiet InstallAllUsers=1 PrependPath=1 Include_test=0 TargetDir=c:\Tools\Python362; [Environment]::SetEnvironmentVariable('PATH', ${env:path} + ';C:\Tools\Python362', 'Machine')"
Sign up to request clarification or add additional context in comments.

7 Comments

Hey Micah, thanks for the response. Do you mind explaining to me what the "Machine" parameter does in your SetEnvironmentVariable() function? Also does that function ensure that no deprecation will occur in paths greater than 1024 characters? Thanks again.
Also, that 1 line powershell command is throwing up several errors, and I have no idea how to debug it. Is there any chance you can try to run it and help me debug it?
The Machine parameter sets a machine-wide environment variable, the same way setx /M does. I'll edit that into the answer. Lemme see if I can fix the one-liner errors - sorry bout that.
Ok, yeah I fixed the one-liner - I wasn't careful enough with my quotes! Lmk if there are any other problems you find.
The CmdletBinding at the start seems to throw errors for me (PS 5.1)
|
3

Do it all in PowerShell, including installing a library.

# This is the link to download Python 3.6.7 from Python.org
# See https://www.python.org/downloads/
$pythonUrl = "https://www.python.org/ftp/python/3.6.7/python-3.6.7-amd64.exe"

# This is the directory that the exe is downloaded to
$tempDirectory = "C:\temp_provision\"

# Installation Directory
# Some packages look for Python here
$targetDir = "C:\Python36"

# Create the download directory and get the exe file
$pythonNameLoc = $tempDirectory + "python367.exe"
New-Item -ItemType directory -Path $tempDirectory -Force | Out-Null
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
(New-Object System.Net.WebClient).DownloadFile($pythonUrl, $pythonNameLoc)

# These are the silent arguments for the install of Python
# See https://docs.python.org/3/using/windows.html
$Arguments = @()
$Arguments += "/i"
$Arguments += 'InstallAllUsers="1"'
$Arguments += 'TargetDir="' + $targetDir + '"'
$Arguments += 'DefaultAllUsersTargetDir="' + $targetDir + '"'
$Arguments += 'AssociateFiles="1"'
$Arguments += 'PrependPath="1"'
$Arguments += 'Include_doc="1"'
$Arguments += 'Include_debug="1"'
$Arguments += 'Include_dev="1"'
$Arguments += 'Include_exe="1"'
$Arguments += 'Include_launcher="1"'
$Arguments += 'InstallLauncherAllUsers="1"'
$Arguments += 'Include_lib="1"'
$Arguments += 'Include_pip="1"'
$Arguments += 'Include_symbols="1"'
$Arguments += 'Include_tcltk="1"'
$Arguments += 'Include_test="1"'
$Arguments += 'Include_tools="1"'
$Arguments += 'Include_launcher="1"'
$Arguments += 'Include_launcher="1"'
$Arguments += 'Include_launcher="1"'
$Arguments += 'Include_launcher="1"'
$Arguments += 'Include_launcher="1"'
$Arguments += 'Include_launcher="1"'
$Arguments += "/passive"

# Install Python
Start-Process $pythonNameLoc -ArgumentList $Arguments -Wait

Function Get-EnvVariableNameList {
    [cmdletbinding()]
    $allEnvVars = Get-ChildItem Env:
    $allEnvNamesArray = $allEnvVars.Name
    $pathEnvNamesList = New-Object System.Collections.ArrayList
    $pathEnvNamesList.AddRange($allEnvNamesArray)
    return ,$pathEnvNamesList
}


Function Add-EnvVarIfNotPresent {
    Param (
        [string]$variableNameToAdd,
        [string]$variableValueToAdd
    )
    $nameList = Get-EnvVariableNameList
    $alreadyPresentCount = ($nameList | Where{$_ -like $variableNameToAdd}).Count
    if ($alreadyPresentCount -eq 0)
    {
        [System.Environment]::SetEnvironmentVariable($variableNameToAdd, $variableValueToAdd, [System.EnvironmentVariableTarget]::Machine)
        [System.Environment]::SetEnvironmentVariable($variableNameToAdd, $variableValueToAdd, [System.EnvironmentVariableTarget]::Process)
        [System.Environment]::SetEnvironmentVariable($variableNameToAdd, $variableValueToAdd, [System.EnvironmentVariableTarget]::User)
            $message = "Enviromental variable added to machine, process and user to include $variableNameToAdd"
    }
    else
    {
        $message = 'Environmental variable already exists. Consider using a different function to modify it'
    }
    Write-Information $message
}

Function Get-EnvExtensionList {
    [cmdletbinding()]
    $pathExtArray = ($env:PATHEXT).Split("{;}")
    $pathExtList = New-Object System.Collections.ArrayList
    $pathExtList.AddRange($pathExtArray)
    return ,$pathExtList
}

Function Add-EnvExtension {
    Param (
        [string]$pathExtToAdd
    )
    $pathList = Get-EnvExtensionList
    $alreadyPresentCount = ($pathList | Where{$_ -like $pathToAdd}).Count
    if ($alreadyPresentCount -eq 0)
    {
        $pathList.Add($pathExtToAdd)
        $returnPath = $pathList -join ";"
        [System.Environment]::SetEnvironmentVariable('pathext', $returnPath, [System.EnvironmentVariableTarget]::Machine)
        [System.Environment]::SetEnvironmentVariable('pathext', $returnPath, [System.EnvironmentVariableTarget]::Process)
        [System.Environment]::SetEnvironmentVariable('pathext', $returnPath, [System.EnvironmentVariableTarget]::User)
        $message = "Path extension added to machine, process and user paths to include $pathExtToAdd"
    }
    else
    {
        $message = 'Path extension already exists'
    }
    Write-Information $message
}

Function Get-EnvPathList {
    [cmdletbinding()]
    $pathArray = ($env:PATH).Split("{;}")
    $pathList = New-Object System.Collections.ArrayList
    $pathList.AddRange($pathArray)
    return ,$pathList
}

Function Add-EnvPath {
    Param (
        [string]$pathToAdd
    )
    $pathList = Get-EnvPathList
    $alreadyPresentCount = ($pathList | Where{$_ -like $pathToAdd}).Count
    if ($alreadyPresentCount -eq 0)
    {
        $pathList.Add($pathToAdd)
        $returnPath = $pathList -join ";"
        [System.Environment]::SetEnvironmentVariable('path', $returnPath, [System.EnvironmentVariableTarget]::Machine)
        [System.Environment]::SetEnvironmentVariable('path', $returnPath, [System.EnvironmentVariableTarget]::Process)
        [System.Environment]::SetEnvironmentVariable('path', $returnPath, [System.EnvironmentVariableTarget]::User)
        $message = "Path added to machine, process and user paths to include $pathToAdd"
    }
    else
    {
        $message = 'Path already exists'
    }
    Write-Information $message
}

Add-EnvExtension '.PY'
Add-EnvExtension '.PYW'
Add-EnvPath 'C:\Python36\'

# Install a library using Pip
python -m pip install numpy

4 Comments

This seems absolutely MASSIVE. Surely theres a less verbose method to install python3 on windows... this is like 145 lines of code.
@carlcrott A less verbose method to install Python on a Windows machine involves installing and using the package manager, Chocolatey. Behind the scenes, Chocolatey still uses a very verbose PowerShell approach but at least this way installing Python is exposed to you in a succinct manner.
Why is "$Arguments += 'Include_launcher="1"'" repeated seven times (a block of six and a block of one)?
What is the meaning of "return ,$pathEnvNamesList" "? To force a list?
0
# Set the version and download URL for Python
$version = "3.9.5"
$url = "https://www.python.org/ftp/python/$version/python-$version-amd64.exe"

# Download and install Python
$installPath = "$($env:ProgramFiles)\Python$version"
Invoke-WebRequest $url -OutFile python-$version.exe
Start-Process python-$version.exe -ArgumentList "/quiet", "TargetDir=$installPath" -Wait

# Add Python to the system PATH
$envVariable = [Environment]::GetEnvironmentVariable("Path", "Machine")
if ($envVariable -notlike "*$installPath*") {
    [Environment]::SetEnvironmentVariable("Path", "$envVariable;$installPath", "Machine")
    Write-Host "Added Python to PATH."
}

# Clean up
Remove-Item python-$version.exe

2 Comments

Generated by ChatGPT?
This answers guarantees that the Python install occurs synchronously thanks to Start-Process -Wait
0

Here's something I put together for Windows to check the latest Python interpreter version from endoflife.date/api. Download it on your C: drive and include the Python executable to your user's path:

@echo off
setlocal EnableDelayedExpansion

set url=https://endoflife.date/api/python.json

set "response="
for /f "usebackq delims=" %%i in (`powershell -command "& {(Invoke-WebRequest -Uri '%url%').Content}"`) do set "response=!response!%%i"

set "latest_py_version="
for /f "tokens=1,2 delims=}" %%a in ("%response%") do (
    set "object=%%a}"
    for %%x in (!object!) do (
        for /f "tokens=1,* delims=:" %%y in ("%%x") do (
            if "%%~y" == "latest" (
                set "latest_py_version=%%~z"
            )
        )
    )
)

echo %latest_py_version%

REM Set the minimum required Python version
set python_version=%latest_py_version%

REM Check if Python is already installed and if the version is less than python_version
echo Checking if Python %python_version% or greater is already installed...
set "current_version="
where python >nul 2>nul && (
    for /f "tokens=2" %%v in ('python --version 2^>^&1') do set "current_version=%%v"
)
if "%current_version%"=="" (
    echo Python is not installed. Proceeding with installation.
) else (
    if "%current_version%" geq "%python_version%" (
        echo Python %python_version% or greater is already installed. Exiting.
        pause
        exit
    )
)

REM Define the URL and file name of the Python installer
set "url=https://www.python.org/ftp/python/%python_version%/python-%python_version%-amd64.exe"
set "installer=python-%python_version%-amd64.exe"

REM Define the installation directory
set "targetdir=C:\Python%python_version%"

REM Download the Python installer
echo Downloading Python installer...
powershell -Command "(New-Object Net.WebClient).DownloadFile('%url%', '%installer%')"

REM Install Python with a spinner animation
echo Installing Python...
start /wait %installer% /quiet /passive TargetDir=%targetdir% Include_test=0 ^
&& (echo Done.) || (echo Failed!)
echo.

REM Add Python to the system PATH
echo Adding Python to the system PATH...
setx PATH "%targetdir%;%PATH%"
if %errorlevel% EQU 1 (
  echo Python has been successfully installed to your system BUT failed to set system PATH. Try running the script as administrator.
  pause
  exit
)
echo Python %python_version% has been successfully installed and added to the system PATH.

REM Cleanup
echo Cleaning up...
del %installer%

echo Done!
pause

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.