5REM --------------------------------------------------------------------
6REM --- PATCH 1 FOR PYENV-VIRTUALENV 2025-07-14 ---
7REM --------------------------------------------------------------------
10if not defined LOG_LEVEL goto log_level1
11 set /a LOG_LEVEL=%LOG_LEVEL%
17REM Determine pyenv root folder
18if not defined PYENV_ROOT goto undefined0
19if not exist "%PYENV_ROOT%" goto nonexist0
22 echo ␛[101mCRITICAL Cannot find "PYENV_ROOT" environment variable.␛[0m
23 echo ␛[37mINFO Check/install/configure "pyenv". Then try again.␛[0m
26 echo ␛[101mCRITICAL Cannot find environment "pyenv root" directory "%PYENV_ROOT%".␛[0m
27 echo ␛[37mINFO Check/repair/configure "pyenv". Then try again.␛[0m
31REM NOTE: In addition all substrings "%~dp0..\" has been replaced
32REM by "%PYENV_ROOT%" to make this script independent from its location
33REM on the system hard disk.
35REM --------------------------------------------------------------------
37set "pyenv=cscript //nologo "%PYENV_ROOT%libexec\pyenv.vbs""
39:: if 'pyenv' called alone, then run pyenv.vbs
41 %pyenv% || goto :error
46for /f "delims=␌" %%i in ('echo skip') do (call :incrementskip)
47if [%skip%]==[0] set "skip_arg="
48if not [%skip%]==[0] set "skip_arg=skip=%skip% "
50if /i [%1%2]==[version] call :check_path
52:: use pyenv.vbs to aid resolving absolute path of "active" version into 'bindir'
55for /f "%skip_arg%delims=" %%i in ('%pyenv% vname') do call :extrapath "%PYENV_ROOT%versions\%%i"
57:: Add %AppData% Python Scripts to %extrapaths%.
58for /F "tokens=1,2 delims=-" %%i in ('%pyenv% vname') do (
59 if /i "%%j" == "win32" (
60 for /F "tokens=1,2,3 delims=." %%a in ("%%i") do (
61 set "extrapaths=%extrapaths%%AppData%\Python\Python%%a%%b-32\Scripts;"
64 for /F "tokens=1,2,3 delims=." %%a in ("%%i") do (
65 set "extrapaths=%extrapaths%%AppData%\Python\Python%%a%%b\Scripts;"
70:: all help implemented as plugin
71if /i [%2]==[--help] goto :plugin
73 call :plugin %2 %1 || goto :error
77 if [%2]==[] call :plugin help --help || goto :error
78 if not [%2]==[] call :plugin %2 --help || goto :error
82:: let pyenv.vbs handle these
83set "commands=rehash global local version vname version-name versions commands shims which whence help --help"
84for %%a in (%commands%) do (
86 rem endlocal not really needed here since above commands do not set any variable
87 rem endlocal closed automatically with exit
88 rem no need to update PATH either
89 %pyenv% %* || goto :error
94:: jump to plugin or fall to exec
95if /i not [%1]==[exec] goto :plugin
96:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
99if not exist "%bindir%" (
100 echo No global/local python version has been set yet. Please set the global/local version by typing:
101 echo pyenv global 3.7.4
102 echo pyenv local 3.7.4
107set cmdline=%cmdline:~5%
109:: update PATH to active version and run command
110:: endlocal needed only if cmdline sets a variable: SET FOO=BAR
111call :remove_shims_from_path
112%cmdline% || goto :error
116:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
117:remove_shims_from_path
118set "python_shims=%PYENV_ROOT%shims"
119call :normalizepath "%python_shims%" python_shims
121set "path=%extrapaths%"
123:: arcane magic courtesy of StackOverflow question 5471556
124:: https://stackoverflow.com/a/7940444/381865
125setlocal DisableDelayedExpansion
126:: escape all special characters
127set "_path=%_path:"=""%"
128set "_path=%_path:^=^^%"
129set "_path=%_path:&=^&%"
130set "_path=%_path:|=^|%"
131set "_path=%_path:<=^<%"
132set "_path=%_path:>=^>%"
133set "_path=%_path:;=^;^;%"
134:: the 'missing' quotes below are intended
135set _path=%_path:""="%
136:: " => ""Q (like quote)
137set "_path=%_path:"=""Q%"
138:: ;; => "S"S (like semicolon)
139set "_path=%_path:;;="S"S%"
140set "_path=%_path:^;^;=;%"
141set "_path=%_path:""="%"
142setlocal EnableDelayedExpansion
145set "_path=!_path:"Q=!"
147for %%a in ("!_path:"S"S=";"!") do (
153 if /i not "%%~dpfa"=="%python_shims%" call :append_to_path %%~dpfa
158:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
162:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
164set "exe=%PYENV_ROOT%libexec\pyenv-%1"
166call :normalizepath %exe% exe
168if exist "%exe%.bat" (
169 set "exe=call "%exe%.bat""
171) else if exist "%exe%.cmd" (
172 set "exe=call "%exe%.cmd""
174) else if exist "%exe%.vbs" (
175 set "exe=cscript //nologo "%exe%.vbs""
177) else if exist "%exe%.lnk" (
178 set "exe=start '' "%exe%.bat""
180 REM --------------------------------------------------------------------
181 REM --- PATCH 2 FOR PYENV-VIRTUALENV 2025-07-14 ---
182 REM --------------------------------------------------------------------
183 if %LOG_LEVEL% leq 15 echo ␛[94mVERBOSE Cannot find executable "%exe%.*".␛[0m
184 if %LOG_LEVEL% leq 15 echo ␛[94mVERBOSE Redirecting command "pyenv %1" to related plugin ...␛[0m
185 if %LOG_LEVEL% leq 15 echo ␛[94mVERBOSE Working around entropy caused by deviations from 'pyenv-virtualenv' common command design ...␛[0m
186 REM Determine the plugin name
187 REM NOTE: The "virtualenv" command will be redirected to 'pyenv-virtualenv'.
188 if "%1"=="virtualenv" goto redirect_to_virtualenv
189 REM NOTE: The "virtualenvs" command will be redirected to 'pyenv-virtualenv'.
190 if "%1"=="virtualenvs" goto redirect_to_virtualenv
191 REM NOTE: The "activate" command will be redirected to 'pyenv-virtualenv'.
192 if "%1"=="activate" goto redirect_to_virtualenv
193 REM NOTE: The "deactivate" command will be redirected to 'pyenv-virtualenv'.
194 if "%1"=="deactivate" goto redirect_to_virtualenv
196 :redirect_to_virtualenv
197 set "PLUGIN_NAME=virtualenv"
200 if %LOG_LEVEL% leq 15 echo ␛[94mVERBOSE Detecting plugin name in command name "%1" ...␛[0m
201 for /f "tokens=1 delims=-" %%a in ("%1") do set "PLUGIN_NAME=%%a"
202 REM NOTE: All commands starting with "venv-" will be redirected to "pyenv-virtualenv".
203 if "%PLUGIN_NAME%"=="venv" goto redirect_to_virtualenv
204 REM NOTE: All other commands will be forwarded to other installed plugins if available.
207 if %LOG_LEVEL% leq 15 echo ␛[94mVERBOSE Plugin name: "%PLUGIN_NAME%".␛[0m
208 if %LOG_LEVEL% leq 15 echo ␛[94mVERBOSE Plugin command: "%1".␛[0m
209 REM Forward the command to the detected plugin
210 set "exe=%PYENV_ROOT%plugins\pyenv-%PLUGIN_NAME%\libexec\pyenv-%1"
211 call :normalizepath %exe% exe
212 if %LOG_LEVEL% leq 15 echo ␛[94mVERBOSE Plugin call: "%exe%".␛[0m
213 REM Calculate path to existing executable only
214 if exist "%exe%.bat" (
215 set "exe=call "%exe%.bat""
216 ) else if exist "%exe%.cmd" (
217 set "exe=call "%exe%.cmd""
218 ) else if exist "%exe%.vbs" (
219 set "exe=cscript //nologo "%exe%.vbs""
220 ) else if exist "%exe%.lnk" (
221 set "exe=start '' "%exe%.bat""
224 if %LOG_LEVEL% leq 15 echo ␛[94mVERBOSE Cannot find executable "%exe%.*".␛[0m
225 echo pyenv: no such command '%1'
226 REM Cancel with error level 1
229 REM The following 2 lines are obsolete now and has been commented:
230 REM echo pyenv: no such command '%1'
233 REM ------------------------------------------------------------------
236:: replace first arg with %exe%
238set cmdline=%cmdline:^=^^%
239set cmdline=%cmdline:!=^!%
245if not [%arg1%]==[] goto :loop_len
247setlocal enabledelayedexpansion
248set cmdline=!exe! !cmdline:~%len%!
249:: run command (no need to update PATH for plugins)
250:: endlocal needed to ensure exit will not automatically close setlocal
251:: otherwise PYTHON_VERSION will be lost
252endlocal && endlocal && %cmdline% || goto :error
254:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
255:: convert path which may have relative nodes (.. or .)
256:: to its absolute value so can be used in PATH
260:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
261:: compute list of paths to add for all activated python versions
263call :normalizepath %1 bindir
264set "extrapaths=%extrapaths%%bindir%;%bindir%\Scripts;%bindir%\bin;"
266:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
267:: check pyenv python shim is first in PATH
269set "python_shim=%PYENV_ROOT%shims\python.bat"
270if not exist "%python_shim%" goto :eof
271call :normalizepath "%python_shim%" python_shim
273for /f "%skip_arg%delims=" %%a in ('where python') do (
274 if /i "%python_shim%"=="%%~dpfa" goto :eof
275 call :set_python_where %%~dpfa
277call :bad_path "%python_where%"
279:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
280:: set python_where variable if empty
282if "%python_where%"=="" set "python_where=%*"
284:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
285:: tell bad PATH and exit
289echo ␛[91mFATAL: Found ␛[95m%bad_python%␛[91m version before pyenv in PATH.␛[0m
290echo ␛[91mPlease remove ␛[95m%bad_dir%␛[91m from PATH for pyenv to work properly.␛[0m
292:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
293:: if AutoExec/AutoRun is configured for cmd it probably ends with the `cls` command
294:: meaning there will be a Form Feed (U+000C) included in the output.
295:: so we add it as a dilimiter so that we can skip x number of lines.
296:: we find out how many to skip and pass that tot the skip option of the for loop,
297:: EXCEPT skip=0 gives errors...
298:: so we prepend every command with `echo skip` to force skip being at least 1