@echo off REM Get our local path and args before delayed expansion - allows % and ! set "thisDir=%~dp0" set "args=%*" setlocal enableDelayedExpansion REM Setup initial vars set "script_name=" set /a tried=0 set "toask=yes" set "pause_on_error=yes" set "py2v=" set "py2path=" set "py3v=" set "py3path=" set "pypath=" set "targetpy=3" REM use_py3: REM TRUE = Use if found, use py2 otherwise REM FALSE = Use py2 REM FORCE = Use py3 set "use_py3=TRUE" REM We'll parse if the first argument passed is REM --install-python and if so, we'll just install REM Can optionally take a version number as the REM second arg - i.e. --install-python 3.13.1 set "just_installing=FALSE" set "user_provided=" REM Get the system32 (or equivalent) path call :getsyspath "syspath" REM Make sure the syspath exists if "!syspath!" == "" ( if exist "%SYSTEMROOT%\system32\cmd.exe" ( if exist "%SYSTEMROOT%\system32\reg.exe" ( if exist "%SYSTEMROOT%\system32\where.exe" ( REM Fall back on the default path if it exists set "ComSpec=%SYSTEMROOT%\system32\cmd.exe" set "syspath=%SYSTEMROOT%\system32\" ) ) ) if "!syspath!" == "" ( cls echo ### ### echo # Missing Required Files # echo ### ### echo. echo Could not locate cmd.exe, reg.exe, or where.exe echo. echo Please ensure your ComSpec environment variable is properly configured and echo points directly to cmd.exe, then try again. echo. echo Current CompSpec Value: "%ComSpec%" echo. echo Press [enter] to quit. pause > nul exit /b 1 ) ) if "%~1" == "--install-python" ( set "just_installing=TRUE" set "user_provided=%~2" goto installpy ) goto checkscript :checkscript REM Check for our script first set "looking_for=!script_name!" if "!script_name!" == "" ( set "looking_for=%~n0.py or %~n0.command" set "script_name=%~n0.py" if not exist "!thisDir!\!script_name!" ( set "script_name=%~n0.command" ) ) if not exist "!thisDir!\!script_name!" ( cls echo ### ### echo # Target Not Found # echo ### ### echo. echo Could not find !looking_for!. echo Please make sure to run this script from the same directory echo as !looking_for!. echo. echo Press [enter] to quit. pause > nul exit /b 1 ) goto checkpy :checkpy call :updatepath for /f "USEBACKQ tokens=*" %%x in (`!syspath!where.exe python 2^> nul`) do ( call :checkpyversion "%%x" "py2v" "py2path" "py3v" "py3path" ) for /f "USEBACKQ tokens=*" %%x in (`!syspath!where.exe python3 2^> nul`) do ( call :checkpyversion "%%x" "py2v" "py2path" "py3v" "py3path" ) for /f "USEBACKQ tokens=*" %%x in (`!syspath!where.exe py 2^> nul`) do ( call :checkpylauncher "%%x" "py2v" "py2path" "py3v" "py3path" ) REM Walk our returns to see if we need to install if /i "!use_py3!" == "FALSE" ( set "targetpy=2" set "pypath=!py2path!" ) else if /i "!use_py3!" == "FORCE" ( set "pypath=!py3path!" ) else if /i "!use_py3!" == "TRUE" ( set "pypath=!py3path!" if "!pypath!" == "" set "pypath=!py2path!" ) if not "!pypath!" == "" ( goto runscript ) if !tried! lss 1 ( if /i "!toask!"=="yes" ( REM Better ask permission first goto askinstall ) else ( goto installpy ) ) else ( cls echo ### ### echo # Python Not Found # echo ### ### echo. REM Couldn't install for whatever reason - give the error message echo Python is not installed or not found in your PATH var. echo Please go to https://www.python.org/downloads/windows/ to echo download and install the latest version, then try again. echo. echo Make sure you check the box labeled: echo. echo "Add Python X.X to PATH" echo. echo Where X.X is the py version you're installing. echo. echo Press [enter] to quit. pause > nul exit /b 1 ) goto runscript :checkpylauncher REM Attempt to check the latest python 2 and 3 versions via the py launcher for /f "USEBACKQ tokens=*" %%x in (`%~1 -2 -c "import sys; print(sys.executable)" 2^> nul`) do ( call :checkpyversion "%%x" "%~2" "%~3" "%~4" "%~5" ) for /f "USEBACKQ tokens=*" %%x in (`%~1 -3 -c "import sys; print(sys.executable)" 2^> nul`) do ( call :checkpyversion "%%x" "%~2" "%~3" "%~4" "%~5" ) goto :EOF :checkpyversion set "version="&for /f "tokens=2* USEBACKQ delims= " %%a in (`"%~1" -V 2^>^&1`) do ( REM Ensure we have a version number call :isnumber "%%a" if not "!errorlevel!" == "0" goto :EOF set "version=%%a" ) if not defined version goto :EOF if "!version:~0,1!" == "2" ( REM Python 2 call :comparepyversion "!version!" "!%~2!" if "!errorlevel!" == "1" ( set "%~2=!version!" set "%~3=%~1" ) ) else ( REM Python 3 call :comparepyversion "!version!" "!%~4!" if "!errorlevel!" == "1" ( set "%~4=!version!" set "%~5=%~1" ) ) goto :EOF :isnumber set "var="&for /f "delims=0123456789." %%i in ("%~1") do set var=%%i if defined var (exit /b 1) exit /b 0 :comparepyversion REM Exits with status 0 if equal, 1 if v1 gtr v2, 2 if v1 lss v2 for /f "tokens=1,2,3 delims=." %%a in ("%~1") do ( set a1=%%a set a2=%%b set a3=%%c ) for /f "tokens=1,2,3 delims=." %%a in ("%~2") do ( set b1=%%a set b2=%%b set b3=%%c ) if not defined a1 set a1=0 if not defined a2 set a2=0 if not defined a3 set a3=0 if not defined b1 set b1=0 if not defined b2 set b2=0 if not defined b3 set b3=0 if %a1% gtr %b1% exit /b 1 if %a1% lss %b1% exit /b 2 if %a2% gtr %b2% exit /b 1 if %a2% lss %b2% exit /b 2 if %a3% gtr %b3% exit /b 1 if %a3% lss %b3% exit /b 2 exit /b 0 :askinstall cls echo ### ### echo # Python Not Found # echo ### ### echo. echo Python !targetpy! was not found on the system or in the PATH var. echo. set /p "menu=Would you like to install it now? [y/n]: " if /i "!menu!"=="y" ( REM We got the OK - install it goto installpy ) else if "!menu!"=="n" ( REM No OK here... set /a tried=!tried!+1 goto checkpy ) REM Incorrect answer - go back goto askinstall :installpy REM This will attempt to download and install python set /a tried=!tried!+1 cls echo ### ### echo # Downloading Python # echo ### ### echo. set "release=!user_provided!" if "!release!" == "" ( REM No explicit release set - get the latest from python.org echo Gathering latest version... powershell -command "[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12;(new-object System.Net.WebClient).DownloadFile('https://www.python.org/downloads/windows/','%TEMP%\pyurl.txt')" REM Extract it if it's gzip compressed powershell -command "$infile='%TEMP%\pyurl.txt';$outfile='%TEMP%\pyurl.temp';try{$input=New-Object System.IO.FileStream $infile,([IO.FileMode]::Open),([IO.FileAccess]::Read),([IO.FileShare]::Read);$output=New-Object System.IO.FileStream $outfile,([IO.FileMode]::Create),([IO.FileAccess]::Write),([IO.FileShare]::None);$gzipStream=New-Object System.IO.Compression.GzipStream $input,([IO.Compression.CompressionMode]::Decompress);$buffer=New-Object byte[](1024);while($true){$read=$gzipstream.Read($buffer,0,1024);if($read -le 0){break};$output.Write($buffer,0,$read)};$gzipStream.Close();$output.Close();$input.Close();Move-Item -Path $outfile -Destination $infile -Force}catch{}" if not exist "%TEMP%\pyurl.txt" ( if /i "!just_installing!" == "TRUE" ( echo - Failed to get info exit /b 1 ) else ( goto checkpy ) ) pushd "%TEMP%" :: Version detection code slimmed by LussacZheng (https://github.com/corpnewt/gibMacOS/issues/20) for /f "tokens=9 delims=< " %%x in ('findstr /i /c:"Latest Python !targetpy! Release" pyurl.txt') do ( set "release=%%x" ) popd REM Let's delete our txt file now - we no longer need it del "%TEMP%\pyurl.txt" if "!release!" == "" ( if /i "!just_installing!" == "TRUE" ( echo - Failed to get python version exit /b 1 ) else ( goto checkpy ) ) echo Located Version: !release! ) else ( echo User-Provided Version: !release! REM Update our targetpy to reflect the first number of REM our release for /f "tokens=1 delims=." %%a in ("!release!") do ( call :isnumber "%%a" if "!errorlevel!" == "0" ( set "targetpy=%%a" ) ) ) echo Building download url... REM At this point - we should have the version number. REM We can build the url like so: "https://www.python.org/ftp/python/[version]/python-[version]-amd64.exe" set "url=https://www.python.org/ftp/python/!release!/python-!release!-amd64.exe" set "pytype=exe" if "!targetpy!" == "2" ( set "url=https://www.python.org/ftp/python/!release!/python-!release!.amd64.msi" set "pytype=msi" ) echo - !url! echo Downloading... REM Now we download it with our slick powershell command powershell -command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; (new-object System.Net.WebClient).DownloadFile('!url!','%TEMP%\pyinstall.!pytype!')" REM If it doesn't exist - we bail if not exist "%TEMP%\pyinstall.!pytype!" ( if /i "!just_installing!" == "TRUE" ( echo - Failed to download python installer exit /b 1 ) else ( goto checkpy ) ) REM It should exist at this point - let's run it to install silently echo Running python !pytype! installer... pushd "%TEMP%" if /i "!pytype!" == "exe" ( echo - pyinstall.exe /quiet PrependPath=1 Include_test=0 Shortcuts=0 Include_launcher=0 pyinstall.exe /quiet PrependPath=1 Include_test=0 Shortcuts=0 Include_launcher=0 ) else ( set "foldername=!release:.=!" echo - msiexec /i pyinstall.msi /qb ADDLOCAL=ALL TARGETDIR="%LocalAppData%\Programs\Python\Python!foldername:~0,2!" msiexec /i pyinstall.msi /qb ADDLOCAL=ALL TARGETDIR="%LocalAppData%\Programs\Python\Python!foldername:~0,2!" ) popd set "py_error=!errorlevel!" echo Installer finished with status: !py_error! echo Cleaning up... REM Now we should be able to delete the installer and check for py again del "%TEMP%\pyinstall.!pytype!" REM If it worked, then we should have python in our PATH REM this does not get updated right away though - let's try REM manually updating the local PATH var call :updatepath if /i "!just_installing!" == "TRUE" ( echo. echo Done. ) else ( goto checkpy ) exit /b :runscript REM Python found cls REM Checks the args gathered at the beginning of the script. REM Make sure we're not just forwarding empty quotes. set "arg_test=!args:"=!" if "!arg_test!"=="" ( "!pypath!" "!thisDir!!script_name!" ) else ( "!pypath!" "!thisDir!!script_name!" !args! ) if /i "!pause_on_error!" == "yes" ( if not "%ERRORLEVEL%" == "0" ( echo. echo Script exited with error code: %ERRORLEVEL% echo. echo Press [enter] to exit... pause > nul ) ) goto :EOF :undouble REM Helper function to strip doubles of a single character out of a string recursively set "string_value=%~2" :undouble_continue set "check=!string_value:%~3%~3=%~3!" if not "!check!" == "!string_value!" ( set "string_value=!check!" goto :undouble_continue ) set "%~1=!check!" goto :EOF :updatepath set "spath=" set "upath=" for /f "USEBACKQ tokens=2* delims= " %%i in (`!syspath!reg.exe query "HKCU\Environment" /v "Path" 2^> nul`) do ( if not "%%j" == "" set "upath=%%j" ) for /f "USEBACKQ tokens=2* delims= " %%i in (`!syspath!reg.exe query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v "Path" 2^> nul`) do ( if not "%%j" == "" set "spath=%%j" ) if not "%spath%" == "" ( REM We got something in the system path set "PATH=%spath%" if not "%upath%" == "" ( REM We also have something in the user path set "PATH=%PATH%;%upath%" ) ) else if not "%upath%" == "" ( set "PATH=%upath%" ) REM Remove double semicolons from the adjusted PATH call :undouble "PATH" "%PATH%" ";" goto :EOF :getsyspath REM Helper method to return a valid path to cmd.exe, reg.exe, and where.exe by REM walking the ComSpec var - will also repair it in memory if need be REM Strip double semi-colons call :undouble "temppath" "%ComSpec%" ";" REM Dirty hack to leverage the "line feed" approach - there are some odd side REM effects with this. Do not use this variable name in comments near this REM line - as it seems to behave erradically. (set LF=^ %=this line is empty=% ) REM Replace instances of semi-colons with a line feed and wrap REM in parenthesis to work around some strange batch behavior set "testpath=%temppath:;=!LF!%" REM Let's walk each path and test if cmd.exe, reg.exe, and where.exe exist there set /a found=0 for /f "tokens=* delims=" %%i in ("!testpath!") do ( REM Only continue if we haven't found it yet if not "%%i" == "" ( if !found! lss 1 ( set "checkpath=%%i" REM Remove "cmd.exe" from the end if it exists if /i "!checkpath:~-7!" == "cmd.exe" ( set "checkpath=!checkpath:~0,-7!" ) REM Pad the end with a backslash if needed if not "!checkpath:~-1!" == "\" ( set "checkpath=!checkpath!\" ) REM Let's see if cmd, reg, and where exist there - and set it if so if EXIST "!checkpath!cmd.exe" ( if EXIST "!checkpath!reg.exe" ( if EXIST "!checkpath!where.exe" ( set /a found=1 set "ComSpec=!checkpath!cmd.exe" set "%~1=!checkpath!" ) ) ) ) ) ) goto :EOF