Repository: pyenv-win/pyenv-win (language: python)

Issue:
"python3.bat" recursively calls itself when the actual python version has no "python3.exe"
**Describe the bug**

If default version is python 2, it has no "python3.exe" executable.
If using virtualenv of python 3, the virtualenv's "Script" has no "python3.exe" executable.

In these situations, "python3.bat" calls "pyenv.bat exec python3.bat", and the following line:

```
%cmddir%%cmdline%
```

ends up being:

```
python3
```

because `%cmddir%` is just empty.

then the recursion happens.

To avoid this, in the `:exec` subroutine of `pyenv.bat`, I added one line to check if `%cmddir%` is empty, if so, just abort:

```batch
if exist %bindir%\%2 set cmddir=%bindir%\
if exist %bindir%\%2.exe set cmddir=%bindir%\
if exist %bindir%\%2.bat set cmddir=%bindir%\
REM Add the following
if [%cmddir%]==[] (echo %2 Not found & exit /b)
```



Correctly handle spaces in the path of pyenv.bat.
Fixes #259

Depends on #306 
bug: pyenv rehash introduces bug with virtualenvwrapper
**Describe the bug**
I installed pyenv-win, and subsequently installed a new Python with version 3.7. In Python 3.7, I pip-installed `virtualenvwrapper-win`. Following the instructions on virtualenvwrapper, I tried running a command like `mkvirtualenv`, which returned "mkvirtualenv is not a recognized command". Then I remembered I had to rehash pyenv after the install, and so did `pyenv rehahs`. Now running mkvirtualenv gives "Scripts is not a recognized command".
I also tried uninstalling and reinstalling virtualenvwrapper, with rehash right after both uninstall and reinstall, but the same error occurrs (see screenshot).

**To Reproduce**
Steps to reproduce the behavior:
1. Install pyenv-win and new Python version, switch to that Python version with `pyenv shell 3.7.6`. Confirm correct Python version is active.
2. Install virtualenvwrapper-win via `pip install virtualenvwrapper-win`
3. Rehahs pyenv-win with `pyenv rehahs`
4. See error

**Expected behavior**
To be able to run `mkvirtualenv <env>` or other commands related to virtualenvwrapper

**Screenshots**
![image](https://user-images.githubusercontent.com/6236646/126139732-b8dae242-f08b-48fb-996f-18814857bc42.png)

**Desktop (please complete the following information):**
 - OS: Windows 10
 - Tools used: Command Prompt (cmd)
 - Version: Pyenv-win version 2.64.8

**Additional context**
I also tried finding the file "virtualenvwrapper.sh", fillowing the instructions on virtualenvwrapper, but that file is nowhere to be found, I only find a "virtualenvwrapper.bat" inside `[...]\.pyenv\pyenv-win\versions\3.7.6\Scripts`.


Discussion / hints:
I have created a new branch called 285 with the change you mention which resulted 23 failed test cases. We need to come up with new approach to resolve it.  
======================= 23 failed, 82 passed in 32.24s ========================
GitHub Action [Link](https://github.com/pyenv-win/pyenv-win/runs/3116161963?check_suite_focus=true)
Spent some time on it this weekend. I use a hack in exec UT. With this fix, I cannot use the hack anymore, hence not possible to test `pyen exec`. Will push a PR whenever I get an idea 💡 to fix this.

It is related to the other issue about mkvirtualenv failing with a bad error message when installed in a Python version not currently active.

All this is a consequence of the big exec refactoring months ago, but I prefer current code as we can run multiple pyenv commands in parallel with no harm. Above examples show that current limitation is that unexpected execs (shims redirecting to unavailable batches or exe) do not properly fail (here: one infinite loop and a bad error message).

Just got an idea for another hack so that unit tests would still work: somehow be able to identify that we are in a unit test context and in this case, skip the fix required for current issue. Will give it a try to see how this goes...

Maybe related..? https://github.com/davidmarble/virtualenvwrapper-win/issues/112

virtualenvwrapper-win is a windows implementation of virtualenvwrapper and therefore does not use the virtualenvwrapper.sh file, instead it installs its implementation (a number of .bat files) in the Scripts directory of the system python, e.g.
```
(dev35) go|c:\Program Files (x86)\Python35-32\Scripts> dir *.bat
 Volume in drive C is OS
 Volume Serial Number is 4C4D-147C

 Directory of c:\Program Files (x86)\Python35-32\Scripts

11/07/2018  12:52 AM             1,805 add2virtualenv.bat
08/05/2017  11:51 PM                83 cd-.bat
11/07/2018  12:52 AM               823 cdproject.bat
08/05/2017  11:51 PM               368 cdsitepackages.bat
08/05/2017  11:51 PM               350 cdvirtualenv.bat
11/07/2018  12:52 AM               495 folder_delete.bat
08/05/2017  11:51 PM               997 lssitepackages.bat
08/05/2017  11:51 PM               267 lsvirtualenv.bat
11/07/2018  12:52 AM             1,601 mkproject.bat
02/16/2020  10:47 PM             9,158 mkvirtualenv.bat
11/07/2018  12:52 AM             1,050 rmvirtualenv.bat
11/07/2018  12:52 AM             2,248 setprojectdir.bat
08/05/2017  11:51 PM               422 toggleglobalsitepackages.bat
11/07/2018  12:52 AM             2,326 virtualenvwrapper.bat
11/07/2018  12:52 AM             1,608 vwenv.bat
11/07/2018  12:52 AM               557 whereis.bat
11/07/2018  12:52 AM             1,495 workon.bat
              17 File(s)         25,653 bytes
```
Could it be that pyenv-win doesn't handle paths with spaces or parenthesis?
Hei Bjorn, thank you for the comment., and for the clarifications about the .bat files in place of the .sh. That made it much clearer :)
I have checked the other issue you linked (which also prompted me to edit the above by specifying I installed pyenv-win). It may be related, but it's hard for me to say.

To give some additional info that you asked in the other issue: I have a Python 3.9 installed "from scratch" with the python.org downloadable, which gave me `pip` through which I installed `pyenv-win`, and later the Python 3.7 as described above. virtualenvwrapper-win is installed only in Python 3.7, but at the same time that version is the only one pyenv-win can see, checking with the command `pyenv versions`.

There are no spaces or parenthesis on the path. The full path to the virtualwrapper executables (.bat) is `C:\Users\filippor\.pyenv\pyenv-win\versions\3.7.6\Scripts`. As far as I understand reading the doc for `pyenv exec`, the part of the path up to `[...]\3.7.6` is automatically added by the pyenv-win's shims, so that then when I run the command `mkvirtualenv`, the command actually being run is `Scripts\mkvirtualenv`. It seems like the Command Prompt interprets the "Scripts" part as the command name, instead of the directory into which to search.
To check if that works, I manually `cd` myself into `C:\Users\filippor\.pyenv\pyenv-win\versions\3.7.6` and then ran `Scripts\mkvirtualenv`, and the command gets executed normally and I can create the virtualenv. Same goes for other commands given by virtualenvwrapper.
Weirdly, I have checked the shims for, say, `pip`, and it's made in the same way, but `pip` works fine. So I am a bit stumbled here.


I cannot reproduce:

```batch
pyenv install 3.7.7
pyenv local 3.7.7
pip install virtualenvwrapper-win
mkvirtualenv -h
```

> Usage: mkvirtualenv [mkvirtualenv-options] [virtualenv-options] DEST_DIR
> 
>   DEST_DIR              The name of the envirnment to create (must be last).
> 
> The new environment is automatically activated after being
> initialized.
> 
> mkvirtualenv options:
> ...
OK, I can reproduce when I run the command with the wrong Python activated:

> C:\Users\ded $ mkvirtualenv
> 'Scripts' is not recognized as an internal or external command,
> operable program or batch file.

Following your steps, mkvirtualenv is only available for one Python version, the one that was activated when `pip` run.
So this works only when you activate the right Python version.

I'll see what I can do to improve the error (should be something like command not found).
Hi dedale, note that you used `pyenv local 3.7.7` instead of `pyenv shell` as in the issue posted above. See screenshot below, which I just reproduced:
![image](https://user-images.githubusercontent.com/6236646/126313439-718fbcf6-a757-416b-9029-72f305be91b3.png)

EDIT - After dedale's second answer: It seems to me that the correct Python version is active in the screenshot above, that I verify with `pyenv shell`; it might be a conflict between the global/local/shell Python versions being used, perhaps? However, both `pyenv global` and `pyenv local` return "No global (local) version configured", so the only one active in my shell is the one from `pyenv shell`, which seems to be the right one.
> **Describe the bug**
> I installed pyenv-win, and subsequently installed a new Python with version 3.7. In Python 3.7, I pip-installed `virtualenvwrapper-win`. Following the instructions on virtualenvwrapper, I tried running a command like `mkvirtualenv`, which returned "mkvirtualenv is not a recognized command". Then I remembered I had to rehash pyenv after the install, and so did `pyenv rehahs`. Now running mkvirtualenv gives "Scripts is not a recognized command".
> I also tried uninstalling and reinstalling virtualenvwrapper, with rehash right after both uninstall and reinstall, but the same error occurrs (see screenshot).
> 
> **To Reproduce**
> Steps to reproduce the behavior:
> 
> 1. Install pyenv-win and new Python version, switch to that Python version with `pyenv shell 3.7.6`. Confirm correct Python version is active.
> 2. Install virtualenvwrapper-win via `pip install virtualenvwrapper-win`
> 3. Rehahs pyenv-win with `pyenv rehahs`
> 4. See error
> 
> **Expected behavior**
> To be able to run `mkvirtualenv <env>` or other commands related to virtualenvwrapper
> 
> **Screenshots**
> ![image](https://user-images.githubusercontent.com/6236646/126139732-b8dae242-f08b-48fb-996f-18814857bc42.png)
> 
> **Desktop (please complete the following information):**
> 
> * OS: Windows 10
> * Tools used: Command Prompt (cmd)
> * Version: Pyenv-win version 2.64.8
> 
> **Additional context**
> I also tried finding the file "virtualenvwrapper.sh", fillowing the instructions on virtualenvwrapper, but that file is nowhere to be found, I only find a "virtualenvwrapper.bat" inside `[...]\.pyenv\pyenv-win\versions\3.7.6\Scripts`.

**To Reproduce**
Step 1. Is wrong. Why? find the below explanation.
`pyenv shell <version>` doesn't confirm correct python version. It sets the default python shell to the given version even you configure the `pyenv local` the python shell (interpreter) uses the default which is configured. 

If you wanted to validate the python is set, use following commands `pyenv version` or `pyenv versions` or `pyenv version-name`
To set use following commands `pyenv local <version>` or `pyenv global <version>` 
Hi Kirankotari,
No, step 1 is correct. `pyenv shell <version>` sets the version of Python for the current shell. To confirm the active Python version one can then use `pyenv shell`,i.e. without the `<version>` argument, as explained in the help function. You can also see this in the screenshot I posted right above your reply. Perhaps I should have been more specific.
After setting the Python version with step 1, the same version (i.e. 3.7.6) is shown as the active one by all of the commands `pyenv shell`, `pyenv version`, `pyenv version-name`, and `pyenv versions` (marked with a star).

Fix patch:
diff --git a/README.md b/README.md
index da242324..06437aa7 100644
--- a/README.md
+++ b/README.md
@@ -223,8 +223,14 @@ Now follow the steps to "[finish the installation](#finish-the-installation)".
 ## Change Log
 
 ### New in 2.64.11
-- Fix [#259](https://github.com/pyenv-win/pyenv-win/issues/259): Correctly handle spaces in `pyenv` path
+- Fix [#287](https://github.com/pyenv-win/pyenv-win/issues/287): Prevent infinite recursion by removing the shims directory from the path.
+- Fix [#259](https://github.com/pyenv-win/pyenv-win/issues/259): Correctly handle spaces in `pyenv` path.
 - Fix [#305](https://github.com/pyenv-win/pyenv-win/issues/305): Fix `exec` preferring the last version listed in `.python-version` instead of the first.
+  - **Note:** `pyenv rehash` must be called after upgrading. Expect the following error message if you don't:
+    ```
+    'Scripts' is not recognized as an internal or external command,
+    operable program or batch file.
+    ```
 
 ### New in 2.64.10
 - Check `PATH` in `pyenv version` to report other Python versions.
diff --git a/pyenv-win/bin/pyenv.bat b/pyenv-win/bin/pyenv.bat
index f2e58b33..d6862964 100644
--- a/pyenv-win/bin/pyenv.bat
+++ b/pyenv-win/bin/pyenv.bat
@@ -2,20 +2,25 @@
 setlocal
 chcp 1250 >nul
 
-set "__pyenv_pyenv=cscript //nologo "%~dp0..\libexec\pyenv.vbs""
+set "pyenv=cscript //nologo "%~dp0..\libexec\pyenv.vbs""
 
 :: if 'pyenv' called alone, then run pyenv.vbs
 if [%1]==[] (
-  %__pyenv_pyenv%
+  %pyenv%
   exit /b
 )
 
+set "skip=-1"
+for /f "delims=" %%i in ('echo skip') do (call :incrementskip)
+if [%skip%]==[0] set "skip_arg="
+if not [%skip%]==[0] set "skip_arg=skip=%skip% "
+
 if /i [%1%2]==[version] call :check_path
 
-:: use pyenv.vbs to aid resolving absolute path of "active" version into '__pyenv_bindir'
-set "__pyenv_bindir="
-set "__pyenv_extrapaths="
-for /f %%i in ('%__pyenv_pyenv% vname') do call :extrapath "%~dp0..\versions\%%i"
+:: use pyenv.vbs to aid resolving absolute path of "active" version into 'bindir'
+set "bindir="
+set "extrapaths="
+for /f "%skip_arg%delims=" %%i in ('%pyenv% vname') do call :extrapath "%~dp0..\versions\%%i"
 
 :: all help implemented as plugin
 if /i [%2]==[--help] goto :plugin
@@ -30,13 +35,13 @@ if /i [%1]==[help] (
 )
 
 :: let pyenv.vbs handle these
-set "__pyenv_commands=rehash global local version vname version-name versions commands shims which whence help --help"
-for %%a in (%__pyenv_commands%) do (
+set "commands=rehash global local version vname version-name versions commands shims which whence help --help"
+for %%a in (%commands%) do (
   if /i [%1]==[%%a] (
     rem endlocal not really needed here since above commands do not set any variable
     rem endlocal closed automatically with exit
     rem no need to update PATH either
-    %__pyenv_pyenv% %*
+    %pyenv% %*
     exit /b
   )
 )
@@ -46,57 +51,103 @@ if /i not [%1]==[exec] goto :plugin
 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 :exec
 
-if not exist "%__pyenv_bindir%" (
+if not exist "%bindir%" (
   echo No global python version has been set yet. Please set the global version by typing:
   echo pyenv global 3.7.2
   exit /b
 )
 
-set "__pyenv_cmdline=%*"
-set "__pyenv_cmdline=%__pyenv_cmdline:~5%"
+set "cmdline=%*"
+set "cmdline=%cmdline:~5%"
 :: update PATH to active version and run command
 :: endlocal needed only if cmdline sets a variable: SET FOO=BAR
-set "path=%__pyenv_extrapaths%%path%"
-%__pyenv_cmdline%
+call :remove_shims_from_path
+%cmdline%
 endlocal
 exit /b
 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+:remove_shims_from_path
+set "python_shims=%~dp0..\shims"
+call :normalizepath "%python_shims%" python_shims
+set "_path=%path%"
+set "path=%extrapaths%"
+
+:: arcane magic courtesy of StackOverflow question 5471556
+:: https://stackoverflow.com/a/7940444/381865
+setlocal DisableDelayedExpansion
+:: escape all special characters
+set "_path=%_path:"=""%"
+set "_path=%_path:^=^^%"
+set "_path=%_path:&=^&%"
+set "_path=%_path:|=^|%"
+set "_path=%_path:<=^<%"
+set "_path=%_path:>=^>%"
+set "_path=%_path:;=^;^;%"
+:: the 'missing' quotes below are intended
+set _path=%_path:""="%
+:: " => ""Q (like quote)
+set "_path=%_path:"=""Q%"
+:: ;; => "S"S (like semicolon)
+set "_path=%_path:;;="S"S%"
+set "_path=%_path:^;^;=;%"
+set "_path=%_path:""="%"
+setlocal EnableDelayedExpansion
+
+:: "Q => <empty>
+set "_path=!_path:"Q=!"
+:: "S"S => ";"
+for %%a in ("!_path:"S"S=";"!") do (
+  if "!!"=="" (
+    endlocal
+    endlocal
+  )
+  if %%a neq "" (
+    if /i not "%%~dpfa"=="%python_shims%" call :append_to_path %%~dpfa
+  )
+)
+
+exit /b
+:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+:append_to_path
+set "path=%path%%*;"
+exit /b
+:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 :plugin
-set "__pyenv_exe=%~dp0..\libexec\pyenv-%1"
+set "exe=%~dp0..\libexec\pyenv-%1"
 rem TODO needed?
-call :normalizepath %__pyenv_exe% __pyenv_exe
+call :normalizepath %exe% exe
 
-if exist "%__pyenv_exe%.bat" (
-  set "__pyenv_exe=call "%__pyenv_exe%.bat""
+if exist "%exe%.bat" (
+  set "exe=call "%exe%.bat""
 
-) else if exist "%__pyenv_exe%.cmd" (
-  set "__pyenv_exe=call "%__pyenv_exe%.cmd""
+) else if exist "%exe%.cmd" (
+  set "exe=call "%exe%.cmd""
 
-) else if exist "%__pyenv_exe%.vbs" (
-  set "__pyenv_exe=cscript //nologo "%__pyenv_exe%.vbs""
+) else if exist "%exe%.vbs" (
+  set "exe=cscript //nologo "%exe%.vbs""
 
 ) else (
   echo pyenv: no such command '%1'
   exit /b 1
 )
 
-:: replace first arg with %__pyenv_exe%
-set "__pyenv_cmdline=%*"
-set "__pyenv_cmdline=%__pyenv_cmdline:^=^^%"
-set "__pyenv_cmdline=%__pyenv_cmdline:!=^!%"
-set "__pyenv_arg1=%1"
-set "__pyenv_len=1"
+:: replace first arg with %exe%
+set "cmdline=%*"
+set "cmdline=%cmdline:^=^^%"
+set "cmdline=%cmdline:!=^!%"
+set "arg1=%1"
+set "len=1"
 :loop_len
-set /a __pyenv_len=%__pyenv_len%+1
-set "__pyenv_arg1=%__pyenv_arg1:~1%"
-if not [%__pyenv_arg1%]==[] goto :loop_len
+set /a len=%len%+1
+set "arg1=%arg1:~1%"
+if not [%arg1%]==[] goto :loop_len
 
 setlocal enabledelayedexpansion
-set "__pyenv_cmdline=!__pyenv_exe! !__pyenv_cmdline:~%__pyenv_len%!"
+set "cmdline=!exe! !cmdline:~%len%!"
 :: run command (no need to update PATH for plugins)
 :: endlocal needed to ensure exit will not automatically close setlocal
 :: otherwise PYTHON_VERSION will be lost
-endlocal && endlocal && %__pyenv_cmdline%
+endlocal && endlocal && %cmdline%
 exit /b
 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 :: convert path which may have relative nodes (.. or .)
@@ -107,32 +158,42 @@ goto :eof
 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 :: compute list of paths to add for all activated python versions
 :extrapath
-call :normalizepath %1 __pyenv_bindir
-set "__pyenv_extrapaths=%__pyenv_extrapaths%%__pyenv_bindir%;%__pyenv_bindir%\Scripts;"
+call :normalizepath %1 bindir
+set "extrapaths=%extrapaths%%bindir%;%bindir%\Scripts;"
 goto :eof
 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 :: check pyenv python shim is first in PATH
 :check_path
-set "__pyenv_python_shim=%~dp0..\shims\python.bat"
-if not exist "%__pyenv_python_shim%" goto :eof
-call :normalizepath "%__pyenv_python_shim%" __pyenv_python_shim
-set "__pyenv_python_where="
-for /f "delims=" %%f in ('where python') do call :set_python_where "%%f"
-:: On recent Windows versions, where finds python shim with shebang first
-if /i "%__pyenv_python_shim%"=="%__pyenv_python_where:~1,-1%.bat" goto :eof
-if /i "%__pyenv_python_shim%"=="%__pyenv_python_where:~1,-1%" goto :eof
-call :bad_path %__pyenv_python_where%
+set "python_shim=%~dp0..\shims\python.bat"
+if not exist "%python_shim%" goto :eof
+call :normalizepath "%python_shim%" python_shim
+set "python_where="
+for /f "%skip_arg%delims=" %%a in ('where python') do (
+  if /i "%python_shim%"=="%%~dpfa" goto :eof
+  call :set_python_where %%~dpfa
+)
+call :bad_path "%python_where%"
 exit /b 1
 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
-:: set __pyenv_python_where variable if empty
+:: set python_where variable if empty
 :set_python_where
-if [%__pyenv_python_where%]==[] set "__pyenv_python_where="%~1""
+if "%python_where%"=="" set "python_where=%*"
 goto :eof
 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 :: tell bad PATH and exit
 :bad_path
-set "__pyenv_bad_python=%~1"
-set "__pyenv_bad_dir=%~dp1"
-echo [91mFATAL: Found [95m%__pyenv_bad_python%[91m version before pyenv in PATH.[0m
-echo [91mPlease remove [95m%__pyenv_bad_dir%[91m from PATH for pyenv to work properly.[0m
-goto :eof
\ No newline at end of file
+set "bad_python=%~1"
+set "bad_dir=%~dp1"
+echo [91mFATAL: Found [95m%bad_python%[91m version before pyenv in PATH.[0m
+echo [91mPlease remove [95m%bad_dir%[91m from PATH for pyenv to work properly.[0m
+goto :eof
+:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+:: if AutoExec/AutoRun is configured for cmd it probably ends with the `cls` command
+:: meaning there will be a Form Feed (U+000C) included in the output.
+:: so we add it as a dilimiter so that we can skip x number of lines.
+:: we find out how many to skip and pass that tot the skip option of the for loop,
+:: EXCEPT skip=0 gives errors...
+:: so we prepend every command with `echo skip` to force skip being at least 1
+:incrementskip
+set /a skip=%skip%+1
+goto :eof


Test patch:
diff --git a/tests/test_pyenv.py b/tests/test_pyenv.py
index 3ea48bf2..8743d975 100644
--- a/tests/test_pyenv.py
+++ b/tests/test_pyenv.py
@@ -7,7 +7,7 @@
 class TestPyenvBase:
     @pytest.fixture
     def setup(self):
-        pyenv_path = Path.cwd()
+        pyenv_path = Path(__file__).parent.parent
         bin_path = pyenv_path / 'pyenv-win' / 'bin'
         shims_path = pyenv_path / 'pyenv-win' / 'shims'
         sys.path.append(str(bin_path))
diff --git a/tests/test_pyenv_feature_exec.py b/tests/test_pyenv_feature_exec.py
index f5f8e01f..fa7aae10 100644
--- a/tests/test_pyenv_feature_exec.py
+++ b/tests/test_pyenv_feature_exec.py
@@ -23,7 +23,7 @@ class TestPyenvFeatureExec(TestPyenvBase):
     @classmethod
     def setup_class(cls):
         settings = {
-            'versions': ['3.7.7', '3.8.9'],
+            'versions': ['3.7.7', '3.8.9', '3.10.0'],
             'global_ver': '3.7.7',
             'local_ver': "3.7.7\n3.8.9\n",
         }
@@ -90,8 +90,7 @@ def test_exec_arg(self, setup, command, arg):
                 "import sys; print(sys.argv[1])",
                 arg
             ])
-            assert stdout == arg.replace('%World%', 'Earth')
-            assert stderr == ""
+            assert (stdout, stderr) == (arg.replace('%World%', 'Earth'), "")
 
     @pytest.mark.parametrize(
         "args",
@@ -108,8 +107,7 @@ def test_exec_arg(self, setup, command, arg):
     )
     def test_exec_help(self, setup, args):
         stdout, stderr = self.__class__.ctx.pyenv(args)
-        assert "\r\n".join(stdout.splitlines()[:1]) == pyenv_exec_help()
-        assert stderr == ""
+        assert ("\r\n".join(stdout.splitlines()[:1]), stderr) == (pyenv_exec_help(), "")
 
     def test_path_not_updated(self, setup):
         python = str(self.__class__.ctx.pyenv_path / "shims" / "python.bat")
@@ -123,12 +121,12 @@ def test_path_not_updated(self, setup):
         stdout, stderr = self.__class__.ctx.exec(args)
         # \x0c: generated by cls in cmd AutoRun
         stdout = stdout.splitlines()
-        assert stdout[0] == stdout[1]
-        assert stderr == ""
+        assert (stdout[1], stderr) == (stdout[0], "")
 
     def test_many_paths(self, setup):
         args = ['exec', 'python', '-c', "import os; print(os.environ['PATH'])"]
         stdout, stderr = self.__class__.ctx.pyenv(args)
+        assert stderr == ""
         assert stdout.startswith(
             (
                 rf"{self.__class__.ctx.pyenv_path}\versions\3.7.7;"
@@ -137,7 +135,6 @@ def test_many_paths(self, setup):
                 rf"{self.__class__.ctx.pyenv_path}\versions\3.8.9\Scripts;"
             )
         )
-        assert stderr == ""
         args = ['exec', 'version']
         assert self.__class__.ctx.pyenv(args) == ("3.7.7", "")
 
@@ -145,6 +142,15 @@ def test_bat_shim(self, setup):
         args = ['exec', 'hello']
         assert self.__class__.ctx.pyenv(args) == ("Hello world!", "")
 
+    def test_removes_shims_from_path(self, setup):
+        args = ['exec', 'python310']
+        assert self.__class__.ctx.pyenv(args) == (
+            '',
+            (
+                "'python310' is not recognized as an internal or external command,\r\n"
+                'operable program or batch file.'
+            )
+        )
 
 def pyenv_exec_help():
     return "Usage: pyenv exec <command> [arg1 arg2...]"
diff --git a/tests/test_pyenv_feature_global.py b/tests/test_pyenv_feature_global.py
index a4d0905d..b017f23a 100644
--- a/tests/test_pyenv_feature_global.py
+++ b/tests/test_pyenv_feature_global.py
@@ -16,8 +16,7 @@ def commands(ctx):
                 ["global", "--help"],
             ]:
                 stdout, stderr = ctx.pyenv(args)
-                assert "\r\n".join(stdout.splitlines()[:2]) == pyenv_global_help()
-                assert stderr == ""
+                assert ("\r\n".join(stdout.splitlines()[:2]), stderr) == (pyenv_global_help(), "")
         run_pyenv_test({}, commands)
 
     def test_global_no_version(self, setup):
diff --git a/tests/test_pyenv_feature_help.py b/tests/test_pyenv_feature_help.py
index 2c8f9291..c4f62ed8 100644
--- a/tests/test_pyenv_feature_help.py
+++ b/tests/test_pyenv_feature_help.py
@@ -7,6 +7,5 @@ def test_help(self, setup):
         def commands(ctx):
             stdout, stderr = ctx.pyenv("help")
             stdout = "\r\n".join(stdout.splitlines()[:2])
-            assert stdout.strip() == "Usage: pyenv <command> [<args>]"
-            assert stderr == ""
+            assert (stdout.strip(), stderr) == ("Usage: pyenv <command> [<args>]", "")
         run_pyenv_test({}, commands)
diff --git a/tests/test_pyenv_feature_shell.py b/tests/test_pyenv_feature_shell.py
index 419f2b94..2e3463a1 100644
--- a/tests/test_pyenv_feature_shell.py
+++ b/tests/test_pyenv_feature_shell.py
@@ -19,8 +19,7 @@ def commands(ctx):
                 ["shell", "--help"],
             ]:
                 stdout, stderr = ctx.pyenv(args)
-                assert "\r\n".join(stdout.splitlines()[:2]) == pyenv_shell_help()
-                stderr == ""
+                assert ("\r\n".join(stdout.splitlines()[:2]), stderr) == (pyenv_shell_help(), "")
         run_pyenv_test({}, commands)
 
     def test_no_shell_version(self, setup):
diff --git a/tests/test_pyenv_feature_version.py b/tests/test_pyenv_feature_version.py
index bc45fa71..03ac5b71 100644
--- a/tests/test_pyenv_feature_version.py
+++ b/tests/test_pyenv_feature_version.py
@@ -19,8 +19,7 @@ def commands(ctx):
                 ["version", "--help"],
             ]:
                 stdout, stderr = ctx.pyenv(args)
-                assert "\r\n".join(stdout.splitlines()[:2]).strip() == pyenv_version_help()
-                assert stderr == ""
+                assert ("\r\n".join(stdout.splitlines()[:2]).strip(), stderr) == (pyenv_version_help(), "")
         run_pyenv_test({}, commands)
 
     def test_no_version(self, setup):
@@ -75,21 +74,19 @@ def commands(ctx):
         run_pyenv_test(settings, commands)
 
     def test_bad_path(self, setup):
-        with tempfile.TemporaryDirectory() as tmp_path:
-            def commands(ctx):
+        def commands(ctx):
+            touch(Path(ctx.local_path, 'python.exe'))
+            with TemporaryEnvironment({"PATH": f"{ctx.local_path};{os.environ['PATH']}"}):
                 touch(Path(ctx.pyenv_path, r'shims\python.bat'))
                 stdout, stderr = ctx.pyenv("version")
-                expected = (f'\x1b[91mFATAL: Found \x1b[95m{tmp_path}\\python.exe\x1b[91m version '
+                expected = (f'\x1b[91mFATAL: Found \x1b[95m{ctx.local_path}\\python.exe\x1b[91m version '
                             f'before pyenv in PATH.\x1b[0m\r\n'
-                            f'\x1b[91mPlease remove \x1b[95m{tmp_path}\\\x1b[91m from '
+                            f'\x1b[91mPlease remove \x1b[95m{ctx.local_path}\\\x1b[91m from '
                             f'PATH for pyenv to work properly.\x1b[0m\r\n'
                             f'3.7.2 (set by {ctx.pyenv_path}\\version)')
                 # Fix 8.3 mismatch in GitHub actions
                 stdout = stdout.replace('RUNNER~1', 'runneradmin')
                 expected = expected.replace('RUNNER~1', 'runneradmin')
-                assert stdout == expected
-                assert stderr == ""
+                assert (stdout, stderr) == (expected, "")
 
-            touch(Path(tmp_path, 'python.exe'))
-            with TemporaryEnvironment({"PATH": f"{tmp_path};{os.environ['PATH']}"}):
-                run_pyenv_test({'global_ver': "3.7.2"}, commands)
+        run_pyenv_test({'global_ver': "3.7.2"}, commands)
diff --git a/tests/test_pyenv_feature_version_name.py b/tests/test_pyenv_feature_version_name.py
index 98b37708..c46ce8e6 100644
--- a/tests/test_pyenv_feature_version_name.py
+++ b/tests/test_pyenv_feature_version_name.py
@@ -12,8 +12,7 @@ def commands(ctx):
         ]:
             stdout, stderr = ctx.pyenv(args)
             stdout = "\r\n".join(stdout.splitlines()[:2]).strip()
-            assert stdout == "Usage: pyenv version-name"
-            assert stderr == ""
+            assert (stdout, stderr) == ("Usage: pyenv version-name", "")
     run_pyenv_test({}, commands)
 
 
@@ -26,8 +25,7 @@ def commands(ctx):
         ]:
             stdout, stderr = ctx.pyenv(args)
             stdout = "\r\n".join(stdout.splitlines()[:2]).strip()
-            assert stdout == "Usage: pyenv vname"
-            assert stderr == ""
+            assert (stdout, stderr) == ("Usage: pyenv vname", "")
     run_pyenv_test({}, commands)
 
 
diff --git a/tests/test_pyenv_helpers.py b/tests/test_pyenv_helpers.py
index 1bde0842..ffde6f94 100644
--- a/tests/test_pyenv_helpers.py
+++ b/tests/test_pyenv_helpers.py
@@ -103,6 +103,7 @@ def __init__(self, exec, pyenv, pyenv_path, local_path):
 class TempPyEnv:
     def __init__(self, settings):
         self.tmp_path = tempfile.TemporaryDirectory()
+        touch(Path(self.tmp_path.name, '.python-version'))
         settings['pyenv_path'] = pyenv_path = Path(self.tmp_path.name, 'pyenv dir with spaces')
         settings['local_path'] = local_path = Path(self.tmp_path.name, 'local dir with spaces')
         os.mkdir(pyenv_path)
@@ -127,7 +128,7 @@ def exec(self, args):
         result = subprocess.run(args, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
         stderr = str(result.stderr, "utf-8").strip()
         # \x0c: generated by cls in cmd AutoRun
-        stdout = str(result.stdout, "utf-8").strip("\r\n\x0c")
+        stdout = str(result.stdout, "utf-8").rpartition('\x0c')[2].strip("\r\n")
         return stdout, stderr
 
     def cleanup(self):