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

Issue:
feature: add support for many global versions
**Is your feature request related to a problem? Please describe.**
pyenv supports setting many global versions which is nice for running Tox against several Python versions. pyenv-win already supports setting many local versions thanks to https://github.com/pyenv-win/pyenv-win/pull/224, so it's probably not a huge effort to add global support.

**Describe the solution you'd like**
Similar to https://github.com/pyenv-win/pyenv-win/pull/224, setting many global versions would need to be implemented.

**Describe alternatives you've considered**
Setting many local versions is an alternative, but I'd like to set those versions globally, so that Tox always has access to a range of Python versions.

**Additional context**
None.
bug: Setting a global or local version sets the PYENV_VERSION environment variable in powershell
**Describe the bug**
Setting a global (or local) python  version ;sets the `PYENV_VERSION` environment variable in powershell.

According to https://github.com/pyenv-win/pyenv-win/blob/ff335b88a3bc4f0182d935b75fe45f68f52e880f/pyenv-win/libexec/pyenv-local.bat#L11-L16

and https://github.com/pyenv-win/pyenv-win/blob/ff335b88a3bc4f0182d935b75fe45f68f52e880f/pyenv-win/libexec/pyenv-shell.bat#L7-L9

`PYENV_VERSION` should only be set by `pyenv shell`.

**To Reproduce**
Steps to reproduce the behavior:

Without a local or global version specified.
1. Set a local version
`pyenv local 3.7.7`
2. Set a global version
`pyenv local 3.10.0`
3. Invoke python
`pyenv exec python -v`
4. Outputs:
`Python 3.10.0`
5. and the `PYENV_VERSION` environment variable is set:
```ps
> $env:PYENV_VERSION
3.10.0
```

**Expected behavior**
The `PYENV_VERSION` environment variable should not be set by `pyenv local` or `pyenv global`. Therefore the above sequence of commands should output the local specified version: `Python 3.7.7`.

**Screenshots**
![grafik](https://user-images.githubusercontent.com/120819/136512210-a4b84211-0e57-4fc2-84f5-03e6717334b3.png)

**Desktop (please complete the following information):**
 - OS: Windows 10
 - Powershell
 - Version : `pyenv 2.64.11`

**Additional context**
I've traced this issue to 
https://github.com/pyenv-win/pyenv-win/blob/ff335b88a3bc4f0182d935b75fe45f68f52e880f/pyenv-win/bin/pyenv.ps1#L2-L14
which was introduced in #276.

I've a local fix ready and I'm happy to provide a PR to fix this issue.
bug: Setting a global or local version sets the PYENV_VERSION environment variable in powershell
**Describe the bug**
Setting a global (or local) python  version ;sets the `PYENV_VERSION` environment variable in powershell.

According to https://github.com/pyenv-win/pyenv-win/blob/ff335b88a3bc4f0182d935b75fe45f68f52e880f/pyenv-win/libexec/pyenv-local.bat#L11-L16

and https://github.com/pyenv-win/pyenv-win/blob/ff335b88a3bc4f0182d935b75fe45f68f52e880f/pyenv-win/libexec/pyenv-shell.bat#L7-L9

`PYENV_VERSION` should only be set by `pyenv shell`.

**To Reproduce**
Steps to reproduce the behavior:

Without a local or global version specified.
1. Set a local version
`pyenv local 3.7.7`
2. Set a global version
`pyenv local 3.10.0`
3. Invoke python
`pyenv exec python -v`
4. Outputs:
`Python 3.10.0`
5. and the `PYENV_VERSION` environment variable is set:
```ps
> $env:PYENV_VERSION
3.10.0
```

**Expected behavior**
The `PYENV_VERSION` environment variable should not be set by `pyenv local` or `pyenv global`. Therefore the above sequence of commands should output the local specified version: `Python 3.7.7`.

**Screenshots**
![grafik](https://user-images.githubusercontent.com/120819/136512210-a4b84211-0e57-4fc2-84f5-03e6717334b3.png)

**Desktop (please complete the following information):**
 - OS: Windows 10
 - Powershell
 - Version : `pyenv 2.64.11`

**Additional context**
I've traced this issue to 
https://github.com/pyenv-win/pyenv-win/blob/ff335b88a3bc4f0182d935b75fe45f68f52e880f/pyenv-win/bin/pyenv.ps1#L2-L14
which was introduced in #276.

I've a local fix ready and I'm happy to provide a PR to fix this issue.


Discussion / hints:
Why can't you use many local versions in your case?
I could, I'd have to set the versions for each project again though. I think a nice setup is to install and set the most common versions (probably 3.6, 3.7, 3.8, 3.9, soon 3.10) globally, create a virtualenv for a project which uses one of them, and have Tox run the test suite on whatever range is configured in `tox.ini` (which just works because all versions are set globally).

Many global versions are also supported by pyenv, so adding support to pyenv-win would get the two in sync on this subcommand again.



Fix patch:
diff --git a/.version b/.version
index b4f4ea3c..9f55b2cc 100644
--- a/.version
+++ b/.version
@@ -1,1 +1,1 @@
-2.64.11
\ No newline at end of file
+3.0
diff --git a/README.md b/README.md
index 2c02991f..69a9a0c7 100644
--- a/README.md
+++ b/README.md
@@ -243,6 +243,7 @@ This PC
 ## Usage
 
 - To view a list of python versions supported by pyenv windows: `pyenv install -l`
+- To filter the list: `pyenv install -l | findstr 3.8`
 - To install a python version:  `pyenv install 3.5.2`
    - _Note: An install wizard may pop up for some non-silent installs. You'll need to click through the wizard during installation. There's no need to change any options in it. or you can use -q for quiet installation_
    - You can also install multiple versions in one command too: `pyenv install 2.4.3 3.6.8`
@@ -281,7 +282,7 @@ Both releases can install 64bit and 32bit python versions; the difference is in
 * 64bit-train (master), i.e. pyenv version _2.64.x_
 
 ```
-> pyenv install -l 
+> pyenv install -l | findstr 3.8
 ....
 3.8.0-win32
 3.8.0
@@ -298,7 +299,7 @@ Both releases can install 64bit and 32bit python versions; the difference is in
 
 * 32bit-train, i.e. pyenv version _2.32.x_
 ```
->pyenv install -l 
+>pyenv install -l | findstr 3.8
 ....
 3.8.0
 3.8.0-amd64
@@ -338,6 +339,13 @@ Support for Python versions below 2.4 have been dropped since their installers d
 
 ## Change Log
 
+### New in 2.64.12
+- Fix [#311](https://github.com/pyenv-win/pyenv-win/issues/311): Support many global and shell versions.
+- Fix [#318](https://github.com/pyenv-win/pyenv-win/issues/318): `pyenv global` and `pyenv local` no longer affect PYENV_VERSION, which only `pyenv shell` should affect.
+- The test suite emulates a 32 bit architecture environment.
+- The test suite now also runs tests using `powershell` and `pwsh` in addition to `cmd`.
+- `pyenv shell` now works like `pyenv global` and `pyenv local` in that, on 32-bit platforms, it adds `-win32` to every supplied version if not explicitly added.
+
 ### New in 2.64.11
 - 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.
diff --git a/pyenv-win/bin/pyenv.bat b/pyenv-win/bin/pyenv.bat
index f6f0bfb9..7aa1c3e1 100644
--- a/pyenv-win/bin/pyenv.bat
+++ b/pyenv-win/bin/pyenv.bat
@@ -6,7 +6,7 @@ set "pyenv=cscript //nologo "%~dp0..\libexec\pyenv.vbs""
 
 :: if 'pyenv' called alone, then run pyenv.vbs
 if [%1]==[] (
-  %pyenv%
+  %pyenv% || goto :error
   exit /b
 )
 
@@ -25,12 +25,12 @@ for /f "%skip_arg%delims=" %%i in ('%pyenv% vname') do call :extrapath "%~dp0..\
 :: all help implemented as plugin
 if /i [%2]==[--help] goto :plugin
 if /i [%1]==[--help] (
-  call :plugin %2 %1
+  call :plugin %2 %1 || goto :error
   exit /b
 )
 if /i [%1]==[help] (
-  if [%2]==[] call :plugin help --help
-  if not [%2]==[] call :plugin %2 --help
+  if [%2]==[] call :plugin help --help || goto :error
+  if not [%2]==[] call :plugin %2 --help || goto :error
   exit /b
 )
 
@@ -41,7 +41,7 @@ for %%a in (%commands%) do (
     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% %* || goto :error
     exit /b
   )
 )
@@ -64,7 +64,7 @@ set "cmdline=%cmdline:~5%"
 :: update PATH to active version and run command
 :: endlocal needed only if cmdline sets a variable: SET FOO=BAR
 call :remove_shims_from_path
-%cmdline%
+%cmdline% ||  goto :error
 
 endlocal
 exit /b
@@ -150,7 +150,7 @@ 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 && %cmdline%
+endlocal && endlocal && %cmdline% || goto :error
 exit /b
 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 :: convert path which may have relative nodes (.. or .)
@@ -176,7 +176,7 @@ for /f "%skip_arg%delims=" %%a in ('where python') do (
   call :set_python_where %%~dpfa
 )
 call :bad_path "%python_where%"
-exit /b 1
+exit /b
 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 :: set python_where variable if empty
 :set_python_where
@@ -200,3 +200,6 @@ goto :eof
 :incrementskip
 set /a skip=%skip%+1
 goto :eof
+
+:error
+exit /b %errorlevel%
diff --git a/pyenv-win/bin/pyenv.ps1 b/pyenv-win/bin/pyenv.ps1
index b22fd648..ec3246c2 100644
--- a/pyenv-win/bin/pyenv.ps1
+++ b/pyenv-win/bin/pyenv.ps1
@@ -1,17 +1,21 @@
 
-If ($Args.Count -eq 2) {
-    $Command = $Args[0]
-    $Option = $Args[1]
-    pyenv.bat $Command $Option | Tee-Object -Variable Output
-    If ($Output.length -eq 0) {
-        If ($Option -eq "--unset") {
-            If (Test-Path Env:PYENV_VERSION) {
-                Remove-Item Env:PYENV_VERSION
-            }
-        } Else {
-            $Env:PYENV_VERSION = $Option
+If (($Args.Count -ge 2) -and ($Args[0] -eq "shell")) {
+    if ($Args[1] -eq "--help") {
+        pyenv.bat @Args
+        Exit $LastExitCode
+    } elseif ($Args[1] -eq "--unset") {
+        If (Test-Path Env:PYENV_VERSION) {
+            Remove-Item Env:PYENV_VERSION
         }
+    } else {
+        $Output = (cscript //nologo "$PSScriptRoot\..\libexec\pyenv.vbs" @Args)
+        if ($LastExitCode -ne 0) {
+            $Output -join [Environment]::NewLine
+            Exit $LastExitCode
+        }
+        $Env:PYENV_VERSION = $Output
     }
 } Else {
-    pyenv.bat $Args
+    pyenv.bat @Args
+    Exit $LastExitCode
 }
diff --git a/pyenv-win/libexec/libs/pyenv-lib.vbs b/pyenv-win/libexec/libs/pyenv-lib.vbs
index d719677d..18f09077 100644
--- a/pyenv-win/libexec/libs/pyenv-lib.vbs
+++ b/pyenv-win/libexec/libs/pyenv-lib.vbs
@@ -49,20 +49,40 @@ strDirShims  = strPyenvHome & "\shims"
 strDBFile    = strPyenvHome & "\.versions_cache.xml"
 strVerFile   = "\.python-version"
 
-Function GetCurrentVersionGlobal()
-    ' WScript.echo "kkotari: pyenv-lib.vbs get current version global..!"
-    GetCurrentVersionGlobal = Null
-
+Function GetCurrentVersionsGlobal()
+    ' WScript.echo "kkotari: pyenv-lib.vbs get current versions global..!"
     Dim fname
     Dim objFile
+    Dim line
+    ReDim versions(-1)
     fname = strPyenvHome & "\version"
     If objfs.FileExists(fname) Then
         Set objFile = objfs.OpenTextFile(fname)
-        If objFile.AtEndOfStream <> True Then
-           GetCurrentVersionGlobal = Array(objFile.ReadLine, fname)
-        End If
+        Do While objFile.AtEndOfStream <> True
+            line = objFile.ReadLine
+            If line <> "" Then
+                ReDim Preserve versions (UBound(versions) + 1)
+                versions(UBound(versions)) = Array(line, fname)
+            End If
+        Loop
         objFile.Close
     End If
+    if UBound(versions) >= 0 Then
+        GetCurrentVersionsGlobal = versions
+    Else
+        GetCurrentVersionsGlobal = Null
+    End If
+End Function
+
+Function GetFirstVersionGlobal()
+    ' WScript.echo "kkotari: pyenv-lib.vbs get first version global..!"
+    Dim versions
+    versions = GetCurrentVersionsGlobal
+    if IsNull(versions) Then
+        GetFirstVersionGlobal = Null
+    Else
+        GetFirstVersionGlobal = versions(0)
+    End If
 End Function
 
 Function GetCurrentVersionsLocal(path)
@@ -105,13 +125,34 @@ Function GetFirstVersionLocal(path)
     End If
 End Function
 
-Function GetCurrentVersionShell()
-    ' WScript.echo "kkotari: pyenv-lib.vbs get current version shell..!"
-    GetCurrentVersionShell = Null
-    Dim str
-    str = objws.Environment("Process")("PYENV_VERSION")
-    If str <> "" Then _
-        GetCurrentVersionShell = Array(str, "%PYENV_VERSION%")
+Function GetCurrentVersionsShell()
+    ' WScript.echo "kkotari: pyenv-lib.vbs get current versions shell..!"
+    Dim ver
+    Dim pyenv_version
+    ReDim versions(-1)
+    pyenv_version = objws.Environment("Process")("PYENV_VERSION")
+    If pyenv_version <> "" Then
+        For Each ver In Split(pyenv_version)
+            ReDim Preserve versions (UBound(versions) + 1)
+            versions(UBound(versions)) = Array(ver, "%PYENV_VERSION%")
+        Next
+    End If
+    if UBound(versions) >= 0 Then
+        GetCurrentVersionsShell = versions
+    Else
+        GetCurrentVersionsShell = Null
+    End If
+End Function
+
+Function GetFirstVersionShell()
+    ' WScript.echo "kkotari: pyenv-lib.vbs get first version shell..!"
+    Dim versions
+    versions = GetCurrentVersionsShell
+    if IsNull(versions) Then
+        GetFirstVersionShell = Null
+    Else
+        GetFirstVersionShell = versions(0)
+    End If
 End Function
 
 Function GetCurrentVersion()
@@ -119,9 +160,10 @@ Function GetCurrentVersion()
     Dim str
     str = GetCurrentVersionNoError
     If IsNull(str) Then
-		WScript.echo "No global python version has been set yet. Please set the global version by typing:"
+		WScript.echo "No global/local python version has been set yet. Please set the global/local version by typing:"
 		WScript.echo "pyenv global 3.7.4"
-		WScript.quit
+        WScript.echo "pyenv local 3.7.4"
+    WScript.quit 1
 	End If
 	GetCurrentVersion = str
 End Function
@@ -129,9 +171,9 @@ End Function
 Function GetCurrentVersionNoError()
     ' WScript.echo "kkotari: pyenv-lib.vbs get current version no error..!"
     Dim str
-    str = GetCurrentVersionShell
+    str = GetFirstVersionShell
     If IsNull(str) Then str = GetFirstVersionLocal(strCurrent)
-    If IsNull(str) Then str = GetCurrentVersionGlobal
+    If IsNull(str) Then str = GetFirstVersionGlobal
     GetCurrentVersionNoError = str
 End Function
 
@@ -140,9 +182,10 @@ Function GetCurrentVersions()
     Dim versions
     Set versions = GetCurrentVersionsNoError
     If versions.Count = 0 Then
-		WScript.echo "No global python version has been set yet. Please set the global version by typing:"
+		WScript.echo "No global/local python version has been set yet. Please set the global/local version by typing:"
 		WScript.echo "pyenv global 3.7.4"
-		WScript.quit
+        WScript.echo "pyenv local 3.7.4"
+    WScript.quit 1
 	End If
 	Set GetCurrentVersions = versions
 End Function
@@ -152,21 +195,27 @@ Function GetCurrentVersionsNoError()
     Dim versions
     Set versions = CreateObject("Scripting.Dictionary")
     Dim str
-    str = GetCurrentVersionShell
+    Dim v1
+    str = GetCurrentVersionsShell
     If Not(IsNull(str)) Then
-        versions.Add str(0), str(1)
+        For Each v1 in str
+            versions.Add v1(0), v1(1)
+        Next
     Else
         str = GetCurrentVersionsLocal(strCurrent)
         If Not(IsNull(str)) Then
-            Dim v1
             For Each v1 in str
                 versions.Add v1(0), v1(1)
             Next
         End If
     End If
     If IsNull(str) Then
-        str = GetCurrentVersionGlobal
-        If Not(IsNull(str)) Then versions.Add str(0), str(1)
+        str = GetCurrentVersionsGlobal
+        If Not(IsNull(str)) Then
+            For Each v1 in str
+                versions.Add v1(0), v1(1)
+            Next
+        End If
     End If
     Set GetCurrentVersionsNoError = versions
 End Function
@@ -201,22 +250,11 @@ Function GetBinDir(ver)
     If Not(IsVersion(ver) And objfs.FolderExists(str)) Then
 		WScript.Echo "pyenv specific python requisite didn't meet. Project is using different version of python."
 		WScript.Echo "Install python '"& ver &"' by typing: 'pyenv install "& ver &"'"
-		WScript.Quit
+		WScript.Quit 1
 	End If
     GetBinDir = str
 End Function
 
-' pyenv set global python version 
-Sub SetGlobalVersion(ver)
-    ' WScript.echo "kkotari: pyenv-lib.vbs set global version..!"
-    GetBinDir(ver)
-
-    With objfs.CreateTextFile(strPyenvHome &"\version" , True)
-        .WriteLine(ver)
-        .Close
-    End With
-End Sub
-
 Function GetExtensions(addPy)
     ' WScript.echo "kkotari: pyenv-lib.vbs get extensions..!"
     Dim exts
diff --git a/pyenv-win/libexec/pyenv---version.bat b/pyenv-win/libexec/pyenv---version.bat
index 2aec151d..c90f6d43 100644
--- a/pyenv-win/libexec/pyenv---version.bat
+++ b/pyenv-win/libexec/pyenv---version.bat
@@ -19,8 +19,14 @@ EXIT /B
 IF "%PYENV%" == "" (
     set version=%KNOWN_VER%
     echo PYENV variable is not set, recommended to set the variable.
-) ELSE IF EXIST %PYENV%\version (
-    set version=<"%PYENV%\version"
+    IF "%PYENV_ROOT%" == "" (
+        echo PYENV_ROOT variable is not set, recommended to set the variable.
+    )
+    IF "%PYENV_HOME%" == "" (
+        echo PYENV_HOME variable is not set, recommended to set the variable.
+    )
+) ELSE IF EXIST %PYENV%\..\.version (
+    set version=<"%PYENV%\..\.version"
     IF "%version%" == "" set version=%KNOWN_VER%
 ) ELSE (
     set version=%KNOWN_VER%
diff --git a/pyenv-win/libexec/pyenv-duplicate.bat b/pyenv-win/libexec/pyenv-duplicate.bat
index 442dc6cd..01e59e40 100644
--- a/pyenv-win/libexec/pyenv-duplicate.bat
+++ b/pyenv-win/libexec/pyenv-duplicate.bat
@@ -12,38 +12,43 @@ echo To use when you want to create a sandbox and
 echo the environment when building application-specific environment.
 EXIT /B
 )
-set src=%~1
-set dst=%~2
-set src=%src:\=_%
-set dst=%dst:\=_%
+set "src=%~1"
+set "dst=%~2"
+set "src=%src:\=_%"
+set "dst=%dst:\=_%"
 
-IF NOT EXIST %~dp0..\versions\%src%\ (
+IF NOT EXIST "%~dp0..\versions\%src%\" (
 echo %src% is not exist
-goto illegal
+goto :illegal
 )
-IF EXIST %~dp0..\versions\%dst%\ (
+IF EXIST "%~dp0..\versions\%dst%\" (
 echo %dst% is already exist
-goto illegal
+goto :illegal
 )
 IF "%dst%" == "" (
 echo new_enviroment "%dst%" is illegal env name
-goto illegal
+goto :illegal
 )
 IF "%src%" == "" (
 echo available_envirment "%src%" is illegal env name
-goto illegal
+goto :illegal
 )
 IF "%src%" == "." (
 echo available_envirment "%src%" is illegal env name
-goto illegal
+goto :illegal
 )
 IF "%src%" == ".." (
 echo available_envirment "%src%" is illegal env name
-goto illegal
+goto :illegal
 )
 
 xcopy "%~dp0..\versions\%src%" "%~dp0..\versions\%dst%\" /E /H /R /K /Y /I /F
+if errorlevel 1 (
+  exit /b 1
+)
+goto :eof
 
 :illegal
 set src=
-set dst=
\ No newline at end of file
+set dst=
+exit /b 1
\ No newline at end of file
diff --git a/pyenv-win/libexec/pyenv-export.bat b/pyenv-win/libexec/pyenv-export.bat
index 6ea6c1fc..349250bf 100644
--- a/pyenv-win/libexec/pyenv-export.bat
+++ b/pyenv-win/libexec/pyenv-export.bat
@@ -11,27 +11,32 @@ echo.
 echo To use when you want to build application-specific environment.
 EXIT /B
 )
-set src=%~1
-set src=%src:\=_%
+set "src=%~1"
+set "src=%src:\=_%"
 
-IF NOT EXIST %~dp0..\versions\%src%\ (
-echo %src% is not exist
-goto illegal
+IF NOT EXIST "%~dp0..\versions\%src%\" (
+echo %src% does not exist
+goto :illegal
 )
 IF "%src%" == "" (
 echo available_envirment "%src%" is illegal env name
-goto illegal
+goto :illegal
 )
 IF "%src%" == "." (
 echo available_envirment "%src%" is illegal env name
-goto illegal
+goto :illegal
 )
 IF "%src%" == ".." (
 echo available_envirment "%src%" is illegal env name
-goto illegal
+goto :illegal
 )
 
-xcopy "%~dp0..\versions\%src%" %2 /E /H /R /K /Y /I /F
+xcopy "%~dp0..\versions\%src%" "%~2" /E /H /R /K /Y /I /F
+if errorlevel 1 (
+  exit /b 1
+)
+goto :eof
 
 :illegal
-set src=
\ No newline at end of file
+set src=
+exit /b 1
\ No newline at end of file
diff --git a/pyenv-win/libexec/pyenv-install.vbs b/pyenv-win/libexec/pyenv-install.vbs
index 5d2ab8d0..ece130ae 100644
--- a/pyenv-win/libexec/pyenv-install.vbs
+++ b/pyenv-win/libexec/pyenv-install.vbs
@@ -36,7 +36,7 @@ Sub ShowHelp()
     WScript.Echo "  --dev          Installs precompiled standard libraries, debug symbols, and debug binaries (only applies to web installer)."
     WScript.Echo "  --help         Help, list of options allowed on pyenv install"
     WScript.Echo ""
-    WScript.Quit
+    WScript.Quit 0
 End Sub
 
 Sub EnsureFolder(path)
@@ -297,7 +297,6 @@ Sub extract(params, register)
 
     If exitCode = 0 Then
         WScript.Echo ":: [Info] :: completed! "& params(LV_Code)
-        ' SetGlobalVersion params(LV_Code)
         If register Then
             registerVersion params(LV_Code), installPath
         End If
diff --git a/pyenv-win/libexec/pyenv-shell.bat b/pyenv-win/libexec/pyenv-shell.bat
index 6e1ec887..f46a7885 100644
--- a/pyenv-win/libexec/pyenv-shell.bat
+++ b/pyenv-win/libexec/pyenv-shell.bat
@@ -1,4 +1,10 @@
 @echo off
+setlocal
+
+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 "%1" == "--help" (
   echo Usage: pyenv shell ^<version^>
@@ -12,19 +18,29 @@ if "%1" == "--help" (
 )
 
 if [%1]==[] (
-  if [%PYENV_VERSION%]==[] (
+  if "%PYENV_VERSION%"=="" (
     echo no shell-specific version configured
   ) else (
     echo %PYENV_VERSION%
   )
 
 ) else if /i [%1]==[--unset] (
-  set "PYENV_VERSION="
-
-) else if exist "%~dp0..\versions\%1" (
-  set "PYENV_VERSION=%1"
+  endlocal && set "PYENV_VERSION="
 
 ) else (
-  echo pyenv specific python requisite didn't meet. Project is using different version of python.
-  echo Install python '%1' by typing: 'pyenv install %1'
+  cscript //nologo "%~dp0pyenv.vbs" shell %* 1> nul || goto :error
+  for /f "%skip_arg%delims=" %%a in ('cscript //nologo "%~dp0pyenv.vbs" shell %*') do (
+    endlocal && set "PYENV_VERSION=%%a"
+  )
 )
+
+goto :eof
+
+:incrementskip
+set /a skip=%skip%+1
+goto :eof
+
+:error
+cscript //nologo "%~dp0pyenv.vbs" shell %*
+exit /b %errorlevel%
+
diff --git a/pyenv-win/libexec/pyenv-uninstall.vbs b/pyenv-win/libexec/pyenv-uninstall.vbs
index 1a0b42cc..b4235fd5 100644
--- a/pyenv-win/libexec/pyenv-uninstall.vbs
+++ b/pyenv-win/libexec/pyenv-uninstall.vbs
@@ -27,7 +27,7 @@ Sub ShowHelp()
     WScript.Echo ""
     WScript.Echo "See `pyenv versions` for a complete list of installed versions."
     WScript.Echo ""
-    WScript.Quit
+    WScript.Quit 0
 End Sub
 
 Sub unregister(version)
@@ -71,7 +71,7 @@ Sub main(arg)
 
     If objfs.GetFolder(strDirVers).SubFolders.Count = 0 Then
         WScript.Echo "pyenv: No valid versions of python installed."
-        WScript.Quit
+        WScript.Quit 1
     End If
 
     Dim folder
diff --git a/pyenv-win/libexec/pyenv-update.vbs b/pyenv-win/libexec/pyenv-update.vbs
index 7ee5317b..1dc14a7c 100644
--- a/pyenv-win/libexec/pyenv-update.vbs
+++ b/pyenv-win/libexec/pyenv-update.vbs
@@ -25,7 +25,7 @@ Sub ShowHelp()
     WScript.Echo
     WScript.Echo "Updates the internal database of python installer URL's."
     WScript.Echo
-    WScript.Quit
+    WScript.Quit 0
 End Sub
 
 Sub EnsureBaseURL(ByRef html, ByVal URL)
diff --git a/pyenv-win/libexec/pyenv.vbs b/pyenv-win/libexec/pyenv.vbs
index b36be20b..57220671 100644
--- a/pyenv-win/libexec/pyenv.vbs
+++ b/pyenv-win/libexec/pyenv.vbs
@@ -121,7 +121,7 @@ Sub CommandWhich(arg)
     For Each version In versions
 
         If Not objfs.FolderExists(strDirVers &"\"& version) Then
-            WScript.Echo "pyenv: version `"& version &"' is not installed (set by "& version &")"
+            WScript.Echo "pyenv: version '"& version &"' is not installed (set by "& version &")"
             WScript.Quit 1
         End If
 
@@ -318,11 +318,14 @@ Sub CommandGlobal(arg)
     ' WScript.echo "kkotari: pyenv.vbs command global..!"
     Dim ver
     If arg.Count < 2 Then
-        ver = GetCurrentVersionGlobal()
-        If IsNull(ver) Then
+        Dim currentVersions
+        currentVersions = GetCurrentVersionsGlobal
+        If IsNull(currentVersions) Then
             WScript.Echo "no global version configured"
         Else
-            WScript.Echo ver(0)
+            For Each ver in currentVersions
+                WScript.Echo ver(0)
+            Next
         End If
     Else
         If arg(1) = "--unset" Then
@@ -330,9 +333,26 @@ Sub CommandGlobal(arg)
             objfs.DeleteFile strPyenvHome &"\version", True
             Exit Sub
         Else
-            ver = Check32Bit(arg(1))
-            SetGlobalVersion ver
+            Dim versionCount
+            versionCount = arg.Count - 1
+            ReDim globalVersions(versionCount - 1)
+            Dim i
+            For i = 0 To versionCount - 1
+                globalVersions(i) = Check32Bit(arg(i + 1))
+                GetBinDir(globalVersions(i))
+            Next
+        End If
+
+        Dim ofile
+        If objfs.FileExists(strPyenvHome &"\version") Then
+            Set ofile = objfs.OpenTextFile(strPyenvHome &"\version", 2)
+        Else
+            Set ofile = objfs.CreateTextFile(strPyenvHome &"\version", True)
         End If
+        For Each ver in globalVersions
+            ofile.WriteLine(ver)
+        Next
+        ofile.Close()
     End If
 End Sub
 
@@ -378,6 +398,29 @@ Sub CommandLocal(arg)
     End If
 End Sub
 
+Sub CommandShell(arg)
+    ' WScript.echo "kkotari: pyenv.vbs command shell..!"
+    Dim ver
+    If arg.Count < 2 Then
+        WScript.Echo "Not enough parameters passed to pyenv.vbs shell"
+    Else
+        If arg(1) = "--unset" Then
+            Exit Sub
+        Else
+            Dim versionCount
+            versionCount = arg.Count - 1
+            ReDim shellVersions(versionCount - 1)
+            Dim i
+            For i = 0 To versionCount - 1
+                shellVersions(i) = Check32Bit(arg(i + 1))
+                GetBinDir(shellVersions(i))
+            Next
+        End If
+
+        WScript.Echo Join(shellVersions, " ")
+    End If
+End Sub
+
 Sub CommandVersion(arg)
     ' WScript.echo "kkotari: pyenv.vbs command version..!"
     If Not objfs.FolderExists(strDirVers) Then objfs.CreateFolder(strDirVers)
@@ -464,6 +507,7 @@ Sub main(arg)
            Case "rehash"       CommandRehash(arg)
            Case "global"       CommandGlobal(arg)
            Case "local"        CommandLocal(arg)
+           Case "shell"        CommandShell(arg)
            Case "version"      CommandVersion(arg)
            Case "vname"        CommandVersionNameShort(arg)
            Case "version-name" CommandVersionName(arg)
diff --git a/requirements_dev.txt b/requirements_dev.txt
index ab02b9d2..5e87bf45 100644
--- a/requirements_dev.txt
+++ b/requirements_dev.txt
@@ -1,6 +1,6 @@
-pytest-cov==2.8.1
-pytest==5.3.0
-coveralls==1.7.0
-coverage==4.5.4
+pytest-cov==2.12.1
+pytest==6.2.5
+coveralls==3.2.0
+coverage==5.5
 pathlib==1.0.1
-tempenv==0.2.0
\ No newline at end of file
+tempenv==1.0.0
\ No newline at end of file


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 00000000..59f13f94
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,129 @@
+import sys
+
+import pytest
+from tempenv import TemporaryEnvironment
+
+import os
+import subprocess
+from pathlib import Path
+from test_pyenv_helpers import touch, pyenv_setup, do_run
+
+
+@pytest.fixture()
+def shell():
+    return "cmd"
+
+
+@pytest.fixture()
+def shell_ext(shell):
+    if shell == 'cmd':
+        return '.bat'
+    if shell in ['powershell', 'pwsh']:
+        return '.ps1'
+
+
+@pytest.fixture()
+def src_path():
+    return Path(__file__).parent.parent
+
+
+@pytest.fixture()
+def local_path(tmp_path):
+    return tmp_path / 'local dir with spaces'
+
+
+@pytest.fixture()
+def pyenv_path(tmp_path):
+    return tmp_path / 'pyenv dir with spaces'
+
+
+@pytest.fixture()
+def bin_path(pyenv_path):
+    return pyenv_path / 'bin'
+
+
+@pytest.fixture()
+def shims_path(pyenv_path):
+    return pyenv_path / 'shims'
+
+
+@pytest.fixture()
+def pyenv_file(shell, bin_path, shell_ext):
+    pyenv_file = str(Path(bin_path, 'pyenv' + shell_ext))
+    if shell in ['powershell', 'pwsh']:
+        pyenv_file = pyenv_file.replace(' ', '` ')
+    return pyenv_file
+
+
+@pytest.fixture(scope='session', autouse=True, params=['AMD64', 'X86'],
+                ids=['PYENV_FORCE_ARCH=AMD64', 'PYENV_FORCE_ARCH=X86'])
+def arch(request):
+    arch = request.param
+    if request.session.testsfailed:
+        pytest.skip(f'Skipping PYENV_FORCE_ARCH={arch} since at lease one test failed for PYENV_FORCE_ARCH=AMD64')
+    with(TemporaryEnvironment({'PYENV_FORCE_ARCH': arch})):
+        yield arch
+
+
+@pytest.fixture()
+def settings(arch):
+    # This needs to return a callable so that `__new__` of Native and its siblings gets called after `PYENV_FORCE_ARCH`
+    # has been set by the `arch` fixture instead of `__new__` being called during pytest's collection phase.
+    return lambda: {}
+
+
+@pytest.fixture(autouse=True)
+def tmp_pyenv(tmp_path, pyenv_path, local_path, bin_path, shims_path, settings, arch):
+    settings = settings()
+    touch(tmp_path / '.python-version')
+    settings['pyenv_path'] = pyenv_path
+    settings['local_path'] = local_path
+    os.mkdir(pyenv_path)
+    os.mkdir(local_path)
+    pyenv_setup(settings)
+    prev_cwd = os.getcwd()
+    os.chdir(local_path)
+    yield
+    os.chdir(prev_cwd)
+
+
+@pytest.fixture()
+def run_args(shell):
+    if shell == 'cmd':
+        return ['cmd', '/d', '/c', 'call']
+    if shell == 'powershell':
+        return ['powershell', '-Command']
+    if shell == 'pwsh':
+        return ['pwsh', '-Command']
+
+
+@pytest.fixture()
+def run(run_args, pyenv_path, bin_path, shims_path):
+    environ = os.environ.copy()
+    for key in ['PYENV', 'PYENV_ROOT', 'PYENV_HOME']:
+        if key in environ:
+            environ[key] = str(pyenv_path)
+            environ['PATH'] = environ['PATH'].replace(str(Path(os.environ[key])), environ[key])
+
+    def run(*args, env={}):
+        env = {**environ, **env}
+        args = run_args + list(args)
+        return do_run(*args, env=env)
+
+    with TemporaryEnvironment({'PATH': environ['PATH']}):
+        return run
+
+
+@pytest.fixture()
+def pyenv(tmp_pyenv, pyenv_file, run):
+    class PyEnv:
+        def __getattr__(self, item):
+            def command(*args, **kwargs):
+                return run(pyenv_file, item, *args, **kwargs)
+
+            return command
+
+        def __call__(self, *args, **kwargs):
+            return run(pyenv_file, *args, **kwargs)
+
+    return PyEnv()
diff --git a/tests/test_pyenv.py b/tests/test_pyenv.py
index 8743d975..ca6f5206 100644
--- a/tests/test_pyenv.py
+++ b/tests/test_pyenv.py
@@ -1,49 +1,36 @@
-import pytest
-import sys
-import subprocess
-from pathlib import Path
-
-
-class TestPyenvBase:
-    @pytest.fixture
-    def setup(self):
-        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))
-        sys.path.append(str(shims_path))
-        yield bin_path
-
-
-class TestPyenv(TestPyenvBase):    
-    def test_check_pyenv_path(self, setup):
-        assert setup.exists() is True
-        assert str(setup) in sys.path
-    
-    def test_check_pyenv_version(self, setup):
-        ver_path = str(setup.parent.parent / '.version')
-        version = open(ver_path).read().strip()
-        result = subprocess.run(['pyenv'], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-        assert version in str(result.stdout, "utf-8")
-    
-    def test_check_pyenv_features_list(self, setup):
-        result = subprocess.run(['pyenv'], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-        result = str(result.stdout, "utf-8")
-        assert 'commands' in result
-        assert 'duplicate' in result
-        assert 'local' in result
-        assert 'global' in result
-        assert 'shell' in result
-        assert 'install' in result
-        assert 'uninstall' in result
-        assert 'rehash' in result
-        assert 'version' in result
-        assert 'vname' in result
-        assert 'versions' in result
-        assert 'version-name' in result
-        assert 'exec' in result
-        assert 'which' in result
-        assert 'whence' in result
-    
-    def test_check_pyenv_help(self, setup):
-        pass
+
+
+def test_check_pyenv_path(bin_path, run):
+    assert bin_path.exists() is True
+    assert str(bin_path) in run('echo', '%PATH%')[0]
+
+
+def test_check_pyenv_version(src_path, pyenv):
+    ver_path = str(src_path / '.version')
+    version = open(ver_path).read().strip()
+    stdout, stderr = pyenv()
+    assert version in stdout
+
+
+def test_check_pyenv_features_list(pyenv):
+    result, stderr = pyenv()
+    assert stderr == ''
+    assert 'commands' in result
+    assert 'duplicate' in result
+    assert 'local' in result
+    assert 'global' in result
+    assert 'shell' in result
+    assert 'install' in result
+    assert 'uninstall' in result
+    assert 'rehash' in result
+    assert 'version' in result
+    assert 'vname' in result
+    assert 'versions' in result
+    assert 'version-name' in result
+    assert 'exec' in result
+    assert 'which' in result
+    assert 'whence' in result
+
+
+def test_check_pyenv_help():
+    pass
diff --git a/tests/test_pyenv_feature_commands.py b/tests/test_pyenv_feature_commands.py
index 9ff7bb3b..6836e66f 100644
--- a/tests/test_pyenv_feature_commands.py
+++ b/tests/test_pyenv_feature_commands.py
@@ -1,15 +1,11 @@
-import subprocess
-from test_pyenv import TestPyenvBase
 
 
-class TestPyenvFeatureCommands(TestPyenvBase):
-    def test_check_pyenv_commands_list(self, setup):
-        # TODO: assert the list of commands
-        result = subprocess.run(['pyenv'], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-        result = str(result.stdout, "utf-8")
-        pass
+def test_check_pyenv_commands_list(pyenv):
+    # TODO: assert the list of commands
+    stdout, stderr = pyenv()
+    pass
 
-    def test_check_pyenv_commands_help(self, setup):
-        # TODO: assert the help result
-        pass
 
+def test_check_pyenv_commands_help():
+    # TODO: assert the help result
+    pass
diff --git a/tests/test_pyenv_feature_duplicate.py b/tests/test_pyenv_feature_duplicate.py
index c7bb99dc..6bd321e8 100644
--- a/tests/test_pyenv_feature_duplicate.py
+++ b/tests/test_pyenv_feature_duplicate.py
@@ -1,15 +1,11 @@
-import subprocess
-from test_pyenv import TestPyenvBase
 
 
-class TestPyenvFeatureDuplicate(TestPyenvBase):
-    def test_check_pyenv_duplicate(self, setup):
-        # TODO: assert the list of commands
-        result = subprocess.run(['pyenv'], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-        result = str(result.stdout, "utf-8")
-        pass
+def test_check_pyenv_duplicate(pyenv):
+    # TODO: assert the list of commands
+    stdout, stderr = pyenv()
+    pass
 
-    def test_check_pyenv_commands_help(self, setup):
-        # TODO: assert the help result
-        pass
 
+def test_check_pyenv_commands_help():
+    # TODO: assert the help result
+    pass
diff --git a/tests/test_pyenv_feature_exec.py b/tests/test_pyenv_feature_exec.py
index fa7aae10..6e73aae2 100644
--- a/tests/test_pyenv_feature_exec.py
+++ b/tests/test_pyenv_feature_exec.py
@@ -1,18 +1,34 @@
 import os
-import subprocess
 import sys
 import pytest
 from pathlib import Path
 from tempenv import TemporaryEnvironment
-from test_pyenv import TestPyenvBase
-from test_pyenv_helpers import TempPyEnv
 
+from test_pyenv_helpers import Native
 
-class TestPyenvFeatureExec(TestPyenvBase):
-    _temp_env = None
-    _temp_path = None
-    ctx = None
 
+@pytest.fixture()
+def settings():
+    return lambda: {
+        'versions': [Native('3.7.7'), Native('3.8.9'), Native('3.10.0')],
+        'global_ver': Native('3.7.7'),
+        'local_ver': [Native('3.7.7'), Native('3.8.9')]
+    }
+
+
+@pytest.fixture()
+def env(pyenv_path):
+    env = {"PATH": f"{os.path.dirname(sys.executable)};" \
+                     f"{str(Path(pyenv_path, 'bin'))};" \
+                     f"{str(Path(pyenv_path, 'shims'))};" \
+                     f"{os.environ['PATH']}"}
+    environment = TemporaryEnvironment(env)
+    with environment:
+        yield env
+
+
+@pytest.fixture(autouse=True)
+def remove_python_exe(pyenv, pyenv_path, settings):
     """
     We do not have any python version installed.
     But we prepend the path with sys.executable dir.
@@ -20,137 +36,120 @@ class TestPyenvFeatureExec(TestPyenvBase):
     This method allows us to execute python.exe.
     But it cannot be used to play with many python versions.
     """
-    @classmethod
-    def setup_class(cls):
-        settings = {
-            'versions': ['3.7.7', '3.8.9', '3.10.0'],
-            'global_ver': '3.7.7',
-            'local_ver': "3.7.7\n3.8.9\n",
-        }
-        cls._temp_env = TempPyEnv(settings)
-        cls.ctx = cls._temp_env.context
-        cls._temp_path = TemporaryEnvironment({"PATH": f"{os.path.dirname(sys.executable)};"
-                                                       f"{str(Path(cls.ctx.pyenv_path, 'bin'))};"
-                                                       f"{str(Path(cls.ctx.pyenv_path, 'shims'))};"
-                                                       f"{os.environ['PATH']}"})
-        cls._temp_path.__enter__()
-        cls.ctx.pyenv('rehash')
-        for v in settings['versions']:
-            os.unlink(str(Path(cls.ctx.pyenv_path, fr'versions\{v}\python.exe')))
-
-    @classmethod
-    def teardown_class(cls):
-        cls._temp_path.__exit__(None)
-        cls._temp_env.cleanup()
-
-    @pytest.mark.parametrize(
-        "command",
-        [
-            lambda path: [str(path / "bin" / "pyenv.bat"), "exec", "python"],
-            lambda path: [str(path / "shims" / "python.bat")],
-        ],
-        ids=["pyenv exec", "python shim"],
-    )
-    @pytest.mark.parametrize(
-        "arg",
-        [
-            "Hello",
-            "Hello World",
-            "Hello 'World'",
-            'Hello "World"',  # " is escaped as \" by python
-            "Hello %World%",
-            # "Hello %22World%22",
-            "Hello !World!",
-            "Hello #World#",
-            "Hello World'",
-            'Hello World"',
-            "Hello ''World'",
-            'Hello ""World"',
-        ],
-        ids=[
-            "One Word",
-            "Two Words",
-            "Single Quote",
-            "Double Quote",
-            "Percentage",
-            # "Escaped",
-            "Exclamation Mark",
-            "Pound",
-            "One Single Quote",
-            "One Double Quote",
-            "Imbalance Single Quote",
-            "Imbalance Double Quote",
-        ]
-    )
-    def test_exec_arg(self, setup, command, arg):
-        with TemporaryEnvironment({'World': 'Earth'}):
-            stdout, stderr = self.__class__.ctx.exec([
-                *command(self.__class__.ctx.pyenv_path),
-                "-c",
-                "import sys; print(sys.argv[1])",
-                arg
-            ])
-            assert (stdout, stderr) == (arg.replace('%World%', 'Earth'), "")
-
-    @pytest.mark.parametrize(
-        "args",
-        [
-            ["--help", "exec"],
-            ["help", "exec"],
-            ["exec", "--help"],
-        ],
-        ids=[
-            "--help exec",
-            "help exec",
-            "exec --help",
-        ]
+    pyenv.rehash()
+    for v in settings()['versions']:
+        os.unlink(str(pyenv_path / 'versions' / v / 'python.exe'))
+
+
+@pytest.mark.parametrize(
+    "command",
+    [
+        lambda path: [str(path / "bin" / "pyenv.bat"), "exec", "python"],
+        lambda path: [str(path / "shims" / "python.bat")],
+    ],
+    ids=["pyenv exec", "python shim"],
+)
+@pytest.mark.parametrize(
+    "arg",
+    [
+        "Hello",
+        "Hello World",
+        "Hello 'World'",
+        'Hello "World"',  # " is escaped as \" by python
+        "Hello %World%",
+        # "Hello %22World%22",
+        "Hello !World!",
+        "Hello #World#",
+        "Hello World'",
+        'Hello World"',
+        "Hello ''World'",
+        'Hello ""World"',
+    ],
+    ids=[
+        "One Word",
+        "Two Words",
+        "Single Quote",
+        "Double Quote",
+        "Percentage",
+        # "Escaped",
+        "Exclamation Mark",
+        "Pound",
+        "One Single Quote",
+        "One Double Quote",
+        "Imbalance Single Quote",
+        "Imbalance Double Quote",
+    ]
+)
+def test_exec_arg(command, arg, env, pyenv_path, run):
+    env['World'] = 'Earth'
+    stdout, stderr = run(
+        *command(pyenv_path),
+        "-c",
+        "import sys; print(sys.argv[1])",
+        arg,
+        env=env
     )
-    def test_exec_help(self, setup, args):
-        stdout, stderr = self.__class__.ctx.pyenv(args)
-        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")
-        tmp_bat = str(Path(self.__class__.ctx.local_path, "tmp.bat"))
-        with open(tmp_bat, "w") as f:
-            # must chain commands because env var is lost when cmd ends
-            print(f'@echo %PATH%', file=f)
-            print(f'@call "{python}" -V>nul', file=f)
-            print(f'@echo %PATH%', file=f)
-        args = ["call", tmp_bat]
-        stdout, stderr = self.__class__.ctx.exec(args)
-        # \x0c: generated by cls in cmd AutoRun
-        stdout = stdout.splitlines()
-        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;"
-                rf"{self.__class__.ctx.pyenv_path}\versions\3.7.7\Scripts;"
-                rf"{self.__class__.ctx.pyenv_path}\versions\3.8.9;"
-                rf"{self.__class__.ctx.pyenv_path}\versions\3.8.9\Scripts;"
-            )
+    assert (stdout, stderr) == (arg.replace('%World%', 'Earth'), "")
+
+
+@pytest.mark.parametrize(
+    "args",
+    [
+        ["--help", "exec"],
+        ["help", "exec"],
+        ["exec", "--help"],
+    ],
+    ids=[
+        "--help exec",
+        "help exec",
+        "exec --help",
+    ]
+)
+def test_exec_help(args, env, pyenv):
+    stdout, stderr = pyenv(*args, env=env)
+    assert ("\r\n".join(stdout.splitlines()[:1]), stderr) == (pyenv_exec_help(), "")
+
+
+def test_path_not_updated(pyenv_path, local_path, env, run):
+    python = str(pyenv_path / "shims" / "python.bat")
+    tmp_bat = str(Path(local_path, "tmp.bat"))
+    with open(tmp_bat, "w") as f:
+        # must chain commands because env var is lost when cmd ends
+        print(f'@echo %PATH%', file=f)
+        print(f'@call "{python}" -V>nul', file=f)
+        print(f'@echo %PATH%', file=f)
+    stdout, stderr = run("call", tmp_bat, env=env)
+    path = os.environ['PATH']
+    assert (stdout, stderr) == (f"{path}\r\n{path}", "")
+
+
+def test_many_paths(pyenv_path, env, pyenv):
+    stdout, stderr = pyenv.exec('python', '-c', "import os; print(os.environ['PATH'])", env=env)
+    assert stderr == ""
+    assert stdout.startswith(
+        (
+            rf"{pyenv_path}\versions\{Native('3.7.7')};"
+            rf"{pyenv_path}\versions\{Native('3.7.7')}\Scripts;"
+            rf"{pyenv_path}\versions\{Native('3.8.9')};"
+            rf"{pyenv_path}\versions\{Native('3.8.9')}\Scripts;"
         )
-        args = ['exec', 'version']
-        assert self.__class__.ctx.pyenv(args) == ("3.7.7", "")
-
-    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.'
-            )
+    )
+    assert pyenv.exec('version.bat') == ("3.7.7", "")
+
+
+def test_bat_shim(pyenv):
+    assert pyenv.exec('hello') == ("Hello world!", "")
+
+
+def test_removes_shims_from_path(pyenv):
+    assert pyenv.exec('python310') == (
+        '',
+        (
+            "'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 b017f23a..9e1d9c37 100644
--- a/tests/test_pyenv_feature_global.py
+++ b/tests/test_pyenv_feature_global.py
@@ -1,5 +1,6 @@
-from test_pyenv import TestPyenvBase
-from test_pyenv_helpers import not_installed_output, run_pyenv_test
+import pytest
+
+from test_pyenv_helpers import not_installed_output, global_python_versions, Native, Arch
 
 
 def pyenv_global_help():
@@ -7,54 +8,62 @@ def pyenv_global_help():
             f"       pyenv global --unset")
 
 
-class TestPyenvFeatureGlobal(TestPyenvBase):
-    def test_global_help(self, setup):
-        def commands(ctx):
-            for args in [
-                ["--help", "global"],
-                ["help", "global"],
-                ["global", "--help"],
-            ]:
-                stdout, stderr = ctx.pyenv(args)
-                assert ("\r\n".join(stdout.splitlines()[:2]), stderr) == (pyenv_global_help(), "")
-        run_pyenv_test({}, commands)
-
-    def test_global_no_version(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv("global") == ("no global version configured", "")
-        run_pyenv_test({}, commands)
-
-    def test_global_version_defined(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv("global") == ("3.8.9", "")
-        run_pyenv_test({'global_ver': "3.8.9"}, commands)
-
-    def test_global_set_installed_version(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv(["global", "3.7.7"]) == ("", "")
-            assert ctx.pyenv("global") == ("3.7.7", "")
-        settings = {
-            'versions': ["3.7.7", "3.8.9"],
-            'global_ver': "3.8.9"
-        }
-        run_pyenv_test(settings, commands)
-
-    def test_global_set_unknown_version(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv(["global", "3.7.8"]) == (not_installed_output("3.7.8"), "")
-        settings = {
-            'versions': ["3.8.9"],
-            'global_ver': "3.8.9",
-        }
-        run_pyenv_test(settings, commands)
-
-    def test_global_unset(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv(["global", "--unset"]) == ("", "")
-            assert ctx.pyenv("global") == ("no global version configured", "")
-        settings = {
-            'versions': ["3.8.9"],
-            'global_ver': "3.8.9",
-        }
-        run_pyenv_test(settings, commands)
+def test_global_help(pyenv):
+    for args in [
+        ["--help", "global"],
+        ["help", "global"],
+        ["global", "--help"],
+    ]:
+        stdout, stderr = pyenv(*args)
+        assert ("\r\n".join(stdout.splitlines()[:2]), stderr) == (pyenv_global_help(), "")
+
+
+def test_global_no_version(pyenv):
+    assert pyenv("global") == ("no global version configured", "")
+
+
+@pytest.mark.parametrize('settings', [lambda: {'global_ver': Native("3.8.9")}])
+def test_global_version_defined(pyenv):
+    assert pyenv("global") == (Native("3.8.9"), "")
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'versions': [Native("3.7.7"), Native("3.8.9")],
+        'global_ver': Native("3.8.9")
+    }])
+def test_global_set_installed_version(pyenv):
+    assert pyenv("global", Arch("3.7.7")) == ("", "")
+    assert pyenv("global") == (Native("3.7.7"), "")
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'versions': [Native("3.8.9")],
+        'global_ver': Native("3.8.9"),
+    }])
+def test_global_set_unknown_version(pyenv):
+    assert pyenv("global", Arch("3.7.8")) == (not_installed_output(Native("3.7.8")), "")
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'versions': [Native("3.8.9")],
+        'global_ver': Native("3.8.9"),
+    }])
+def test_global_unset(pyenv):
+    assert pyenv("global", "--unset") == ("", "")
+    assert pyenv("global") == ("no global version configured", "")
+
+
+@pytest.mark.parametrize('settings', [lambda: {'versions': [Native("3.7.7"), Native("3.8.9")]}])
+def test_global_set_many_versions(pyenv_path, pyenv):
+    assert pyenv('global', Arch("3.7.7"), Arch("3.8.9")) == ("", "")
+    assert global_python_versions(pyenv_path) == "\n".join([Native('3.7.7'), Native('3.8.9')])
+
+
+@pytest.mark.parametrize('settings', [lambda: {'versions': [Native("3.7.7")]}])
+def test_global_set_many_versions_one_not_installed(pyenv):
+    assert pyenv('global', Arch("3.7.7"), Arch("3.8.9")) == (not_installed_output(Native("3.8.9")), "")
+
 
+@pytest.mark.parametrize('settings', [lambda: {'global_ver': [Native('3.7.7'), Native('3.8.9')]}])
+def test_global_many_versions_defined(pyenv):
+    assert pyenv('global') == ("\r\n".join([Native('3.7.7'), Native('3.8.9')]), "")
diff --git a/tests/test_pyenv_feature_help.py b/tests/test_pyenv_feature_help.py
index c4f62ed8..09030be2 100644
--- a/tests/test_pyenv_feature_help.py
+++ b/tests/test_pyenv_feature_help.py
@@ -1,11 +1,6 @@
-from test_pyenv import TestPyenvBase
-from test_pyenv_helpers import run_pyenv_test
 
 
-class TestPyenvFeatureGlobal(TestPyenvBase):
-    def test_help(self, setup):
-        def commands(ctx):
-            stdout, stderr = ctx.pyenv("help")
-            stdout = "\r\n".join(stdout.splitlines()[:2])
-            assert (stdout.strip(), stderr) == ("Usage: pyenv <command> [<args>]", "")
-        run_pyenv_test({}, commands)
+def test_help(pyenv):
+    stdout, stderr = pyenv.help()
+    stdout = "\r\n".join(stdout.splitlines()[:2])
+    assert (stdout.strip(), stderr) == ("Usage: pyenv <command> [<args>]", "")
diff --git a/tests/test_pyenv_feature_install.py b/tests/test_pyenv_feature_install.py
index 4ca70c9d..6e091aa9 100644
--- a/tests/test_pyenv_feature_install.py
+++ b/tests/test_pyenv_feature_install.py
@@ -1,37 +1,34 @@
 import subprocess
-from test_pyenv import TestPyenvBase
 
 
-class TestPyenvFeatureInstall(TestPyenvBase):
-    def test_check_pyenv_install_list(self, setup):
-        result = subprocess.run(['pyenv', 'install', '-l'], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-        result = str(result.stdout, "utf-8")
-        assert "Mirror: https://www.python.org/ftp/python" in result
-        assert "2.7.17-win32" in result
-        assert "2.7.17" in result
-        assert "3.1.4-win32" in result
-        assert "3.1.4" in result
-        assert "3.2.5-win32" in result
-        assert "3.2.5" in result
-        assert "3.3.5-win32" in result
-        assert "3.3.5" in result
-        assert "3.4.4-win32" in result
-        assert "3.4.4" in result
-        assert "3.5.4-win32" in result
-        assert "3.5.4" in result
-        assert "3.6.8-win32" in result
-        assert "3.6.8" in result
-        assert "3.7.7-win32" in result
-        assert "3.7.7" in result
-        assert "3.8.2-win32" in result
-        assert "3.8.2" in result
-        assert "3.9.0-win32" in result
-        assert "3.9.0" in result
-        assert "3.9.1-win32" in result
-        assert "3.9.1" in result
-        assert "3.10.2-win32" in result
-        assert "3.10.2" in result
-    def test_check_pyenv_installation(self, setup):
-        # TODO: tracking the logs of installation and checking the folder
-        pass
+def test_check_pyenv_install_list(pyenv):
+    result, stderr = pyenv.install('-l')
+    assert stderr == ""
+    assert "Mirror: https://www.python.org/ftp/python" in result
+    assert "2.7.17-win32" in result
+    assert "2.7.17" in result
+    assert "3.1.4-win32" in result
+    assert "3.1.4" in result
+    assert "3.2.5-win32" in result
+    assert "3.2.5" in result
+    assert "3.3.5-win32" in result
+    assert "3.3.5" in result
+    assert "3.4.4-win32" in result
+    assert "3.4.4" in result
+    assert "3.5.4-win32" in result
+    assert "3.5.4" in result
+    assert "3.6.8-win32" in result
+    assert "3.6.8" in result
+    assert "3.7.7-win32" in result
+    assert "3.7.7" in result
+    assert "3.8.2-win32" in result
+    assert "3.8.2" in result
+    assert "3.9.0-win32" in result
+    assert "3.9.0" in result
+    assert "3.9.1-win32" in result
+    assert "3.9.1" in result
 
+
+def test_check_pyenv_installation():
+    # TODO: tracking the logs of installation and checking the folder
+    pass
diff --git a/tests/test_pyenv_feature_local.py b/tests/test_pyenv_feature_local.py
index 1f291677..6ca8fe61 100644
--- a/tests/test_pyenv_feature_local.py
+++ b/tests/test_pyenv_feature_local.py
@@ -1,45 +1,42 @@
-from test_pyenv import TestPyenvBase
-from test_pyenv_helpers import local_python_versions, not_installed_output, run_pyenv_test
-
-
-class TestPyenvFeatureLocal(TestPyenvBase):
-    def test_no_local_version(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv("local") == ("no local version configured for this directory", "")
-        run_pyenv_test({}, commands)
-
-    def test_local_version_defined(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv("local") == ("3.8.9", "")
-        run_pyenv_test({'local_ver': "3.8.9"}, commands)
-
-    def test_local_set_installed_version(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv(["local", "3.7.7"]) == ("", "")
-            assert ctx.pyenv("local") == ("3.7.7", "")
-        settings = {
-            'versions': ["3.7.7", "3.8.9"],
-            'local_ver': "3.8.9",
-        }
-        run_pyenv_test(settings, commands)
-
-    def test_local_set_unknown_version(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv(["local", "3.7.8"]) == (not_installed_output("3.7.8"), "")
-        run_pyenv_test({'versions': ["3.8.9"]}, commands)
-
-    def test_local_set_many_versions(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv(["local", "3.7.7", "3.8.9"]) == ("", "")
-            assert local_python_versions(ctx.local_path) == "3.7.7\n3.8.9"
-        run_pyenv_test({'versions': ["3.7.7", "3.8.9"]}, commands)
-
-    def test_local_set_many_versions_one_not_installed(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv(["local", "3.7.7", "3.8.9"]) == (not_installed_output("3.8.9"), "")
-        run_pyenv_test({'versions': ["3.7.7"]}, commands)
-
-    def test_local_many_versions_defined(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv("local") == ("3.7.7\r\n3.8.9", "")
-        run_pyenv_test({'local_ver': "3.7.7\n3.8.9\n"}, commands)
+import pytest
+
+from test_pyenv_helpers import local_python_versions, not_installed_output, Native, Arch
+
+
+def test_no_local_version(pyenv):
+    assert pyenv.local() == ("no local version configured for this directory", "")
+
+
+@pytest.mark.parametrize('settings', [lambda: {'local_ver': Native("3.8.9")}])
+def test_local_version_defined(pyenv):
+    assert pyenv.local() == (Native("3.8.9"), "")
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+    'versions': [Native("3.7.7"), Native("3.8.9")],
+    'local_ver': Native("3.8.9"),
+}])
+def test_local_set_installed_version(pyenv):
+    assert pyenv.local(Arch("3.7.7")) == ("", "")
+    assert pyenv.local() == (Native("3.7.7"), "")
+
+
+@pytest.mark.parametrize('settings', [lambda: {'versions': [Native("3.8.9")]}])
+def test_local_set_unknown_version(pyenv):
+    assert pyenv.local(Arch("3.7.8")) == (not_installed_output(Native("3.7.8")), "")
+
+
+@pytest.mark.parametrize('settings', [lambda: {'versions': [Native("3.7.7"), Native("3.8.9")]}])
+def test_local_set_many_versions(local_path, pyenv):
+    assert pyenv.local(Arch("3.7.7"), Arch("3.8.9")) == ("", "")
+    assert local_python_versions(local_path) == "\n".join([Native('3.7.7'), Native('3.8.9')])
+
+
+@pytest.mark.parametrize('settings', [lambda: {'versions': [Native("3.7.7")]}])
+def test_local_set_many_versions_one_not_installed(pyenv):
+    assert pyenv.local(Arch("3.7.7"), Arch("3.8.9")) == (not_installed_output(Native("3.8.9")), "")
+
+
+@pytest.mark.parametrize('settings', [lambda: {'local_ver': [Native('3.7.7'), Native('3.8.9')]}])
+def test_local_many_versions_defined(pyenv):
+    assert pyenv.local() == ("\r\n".join([Native('3.7.7'), Native('3.8.9')]), "")
diff --git a/tests/test_pyenv_feature_rehash.py b/tests/test_pyenv_feature_rehash.py
index cab53e97..79817d54 100644
--- a/tests/test_pyenv_feature_rehash.py
+++ b/tests/test_pyenv_feature_rehash.py
@@ -1,13 +1,12 @@
+import pytest
 from packaging import version
 from pathlib import Path
-from tempenv import TemporaryEnvironment
-from test_pyenv import TestPyenvBase
-from test_pyenv_helpers import python_exes, script_exes, run_pyenv_test
+from test_pyenv_helpers import python_exes, script_exes, Native
 
 
 def assert_shims(pyenv_path, ver):
     shims_path = Path(pyenv_path, 'shims')
-    ver = version.parse(ver)
+    ver = version.parse(ver.version)
     suffixes = [f'{ver.major}', f'{ver.major}{ver.minor}', f'{ver.major}.{ver.minor}']
     all_exes = [Path(n).stem for n in list(python_exes(suffixes)) + list(script_exes(ver))]
     all_shims = [n + s for n in all_exes for s in ['', '.bat']]
@@ -15,50 +14,39 @@ def assert_shims(pyenv_path, ver):
         assert shims_path.joinpath(s).is_file()
 
 
-class TestPyenvFeatureRehash(TestPyenvBase):
-    def test_rehash_no_version(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv('rehash') == (
-                "No version installed. Please install one with 'pyenv install <version>'.",
-                ""
-            )
-        run_pyenv_test({}, commands)
+def test_rehash_no_version(pyenv):
+    assert pyenv.rehash() == (
+        "No version installed. Please install one with 'pyenv install <version>'.",
+        ""
+    )
 
-    def test_rehash_global_version(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv('rehash') == ("", "")
-            assert_shims(ctx.pyenv_path, '3.8.6')
-            assert_shims(ctx.pyenv_path, '3.8.7')
-            assert_shims(ctx.pyenv_path, '3.10.2')
-        settings = {
-            'versions': ['3.8.6', '3.8.7','3.10.2'],
-            'global_ver': '3.8.6',
-        }
-        run_pyenv_test(settings, commands)
 
-    def test_rehash_local_version(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv('rehash') == ("", "")
-            assert_shims(ctx.pyenv_path, '3.8.6')
-            assert_shims(ctx.pyenv_path, '3.9.1')
-            assert_shims(ctx.pyenv_path, '3.10.2')
-        settings = {
-            'versions': ['3.8.6', '3.9.1','3.10.2'],
-            'global_ver': '3.8.6',
-            'local_ver': '3.9.1'
-        }
-        run_pyenv_test(settings, commands)
+@pytest.mark.parametrize('settings', [lambda: {
+    'versions': [Native('3.8.6'), Native('3.8.7')],
+    'global_ver': Native('3.8.6'),
+}])
+def test_rehash_global_version(pyenv_path, pyenv):
+    assert pyenv.rehash() == ("", "")
+    assert_shims(pyenv_path, Native('3.8.6'))
+    assert_shims(pyenv_path, Native('3.8.7'))
 
-    def test_rehash_shell_version(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv('rehash') == ("", "")
-        global_ver = '3.7.5'
-        local_ver = '3.8.6'
-        shell_ver = '3.9.1'
-        settings = {
-            'versions': [global_ver, local_ver, shell_ver],
-            'global_ver': global_ver,
-            'local_ver': local_ver
-        }
-        with TemporaryEnvironment({"PYENV_VERSION": shell_ver}):
-            run_pyenv_test(settings, commands)
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'versions': [Native('3.8.6'), Native('3.9.1')],
+        'global_ver': Native('3.8.6'),
+        'local_ver': Native('3.9.1')
+    }])
+def test_rehash_local_version(pyenv_path, pyenv):
+    assert pyenv.rehash() == ("", "")
+    assert_shims(pyenv_path, Native('3.8.6'))
+    assert_shims(pyenv_path, Native('3.9.1'))
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'versions': [Native('3.7.5'), Native('3.8.6'), Native('3.9.1')],
+        'global_ver': Native('3.7.5'),
+        'local_ver': Native('3.8.6')
+    }])
+def test_rehash_shell_version(pyenv):
+    env = {"PYENV_VERSION": Native('3.9.1')}
+    assert pyenv.rehash(env=env) == ("", "")
diff --git a/tests/test_pyenv_feature_shell.py b/tests/test_pyenv_feature_shell.py
index 2e3463a1..a48d43a9 100644
--- a/tests/test_pyenv_feature_shell.py
+++ b/tests/test_pyenv_feature_shell.py
@@ -1,8 +1,16 @@
-import subprocess
+import pytest
+
+import shutil
 from pathlib import Path
-from tempenv import TemporaryEnvironment
-from test_pyenv import TestPyenvBase
-from test_pyenv_helpers import not_installed_output, run_pyenv_test
+from test_pyenv_helpers import not_installed_output, Native, Arch
+
+
+@pytest.fixture(scope="module", params=["cmd", "powershell", "pwsh"])
+def shell(request):
+    shell = request.param
+    if shutil.which(shell) is None:
+        pytest.skip(f"the shell '{shell}' was not found")
+    return shell
 
 
 def pyenv_shell_help():
@@ -10,46 +18,84 @@ def pyenv_shell_help():
             f"       pyenv shell --unset")
 
 
-class TestPyenvFeatureShell(TestPyenvBase):
-    def test_shell_help(self, setup):
-        def commands(ctx):
-            for args in [
-                ["--help", "shell"],
-                ["help", "shell"],
-                ["shell", "--help"],
-            ]:
-                stdout, stderr = ctx.pyenv(args)
-                assert ("\r\n".join(stdout.splitlines()[:2]), stderr) == (pyenv_shell_help(), "")
-        run_pyenv_test({}, commands)
-
-    def test_no_shell_version(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv("shell") == ("no shell-specific version configured", "")
-        with TemporaryEnvironment({"PYENV_VERSION": ""}):
-            run_pyenv_test({}, commands)
-
-    def test_shell_version_defined(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv("shell") == ("3.9.2", "")
-        with TemporaryEnvironment({"PYENV_VERSION": "3.9.2"}):
-            run_pyenv_test({}, commands)
-
-    def test_shell_set_installed_version(self, setup):
-        def commands(ctx):
-            pyenv_bat = Path(ctx.pyenv_path, r'bin\pyenv.bat')
-            tmp_bat = str(Path(ctx.local_path, "tmp.bat"))
-            with open(tmp_bat, "w") as f:
-                # must chain commands because env var is lost when cmd ends
-                print(f'@call "{pyenv_bat}" shell 3.7.7 && call "{pyenv_bat}" shell', file=f)
-            args = ['cmd', '/d', '/c', 'call', tmp_bat]
-            result = subprocess.run(args, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-            output = str(result.stdout, "utf-8").strip()
-            assert output == "3.7.7"
-
-        with TemporaryEnvironment({"PYENV_VERSION": "3.8.9"}):
-            run_pyenv_test({'versions': ["3.7.7", "3.8.9"]}, commands)
-
-    def test_shell_set_unknown_version(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv(["shell", "3.7.8"]) == (not_installed_output("3.7.8"), "")
-        run_pyenv_test({'versions': ["3.8.9"]}, commands)
+def test_shell_help(pyenv):
+    for args in [
+        ["--help", "shell"],
+        ["help", "shell"],
+        ["shell", "--help"],
+    ]:
+        stdout, stderr = pyenv(*args)
+        assert ("\r\n".join(stdout.splitlines()[:2]), stderr) == (pyenv_shell_help(), "")
+
+
+def test_no_shell_version(pyenv):
+    env = {"PYENV_VERSION": ""}
+    assert pyenv.shell(env=env) == ("no shell-specific version configured", "")
+
+
+def test_shell_version_defined(pyenv):
+    env = {"PYENV_VERSION": Native("3.9.2")}
+    assert pyenv.shell(env=env) == (Native("3.9.2"), "")
+
+
+@pytest.mark.parametrize('settings', [lambda: {'versions': [Native("3.7.7"), Native("3.8.9")]}])
+def test_shell_set_installed_version(local_path, shell, shell_ext, run):
+    env = {"PYENV_VERSION": Native("3.8.9")}
+    tmp_bat = str(Path(local_path, "tmp" + shell_ext))
+    with open(tmp_bat, "w") as f:
+        # must chain commands because env var is lost when cmd ends
+        if shell == 'cmd':
+            print(f'@call pyenv shell {Arch("3.7.7")} && call pyenv shell', file=f)
+        if shell in ['powershell', 'pwsh']:
+            tmp_bat = tmp_bat.replace(' ', '` ')
+            print(f'& pyenv shell {Arch("3.7.7")}; & pyenv shell', file=f)
+    stdout, stderr = run(tmp_bat, env=env)
+    assert (stdout, stderr) == (Native("3.7.7"), "")
+
+
+@pytest.mark.parametrize('settings', [lambda: {'versions': [Native("3.8.9")]}])
+def test_shell_set_unknown_version(pyenv):
+    assert pyenv.shell(Native("3.7.8")) == (not_installed_output(Native("3.7.8")), "")
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'versions': [Native("3.7.7"), Native("3.8.9")],
+        'global_ver': Native("3.7.7"),
+        'local_ver': Native("3.7.7"),
+    }])
+def test_shell_unset_unaffected(local_path, shell, shell_ext, run):
+    env = {"PYENV_VERSION": Native("3.8.9")}
+    tmp_bat = str(Path(local_path, "tmp" + shell_ext))
+    with open(tmp_bat, "w") as f:
+        # must chain commands because env var is lost when cmd ends
+        if shell == 'cmd':
+            print(f'@call pyenv global --unset && call pyenv local --unset && call pyenv shell', file=f)
+        if shell in ['powershell', 'pwsh']:
+            tmp_bat = tmp_bat.replace(' ', '` ')
+            print(f'pyenv global --unset; pyenv local --unset; pyenv shell', file=f)
+    stdout, stderr = run(tmp_bat, env=env)
+    assert (stdout, stderr) == (Native("3.8.9"), "")
+
+
+@pytest.mark.parametrize('settings', [lambda: {'versions': [Native("3.7.7"), Native("3.8.9")]}])
+def test_shell_set_many_versions(local_path, shell, shell_ext, run):
+    tmp_bat = str(Path(local_path, "tmp" + shell_ext))
+    with open(tmp_bat, "w") as f:
+        # must chain commands because env var is lost when cmd ends
+        if shell == 'cmd':
+            print(f'@call pyenv shell {Arch("3.7.7")} {Arch("3.8.9")} && call pyenv shell', file=f)
+        if shell in ['powershell', 'pwsh']:
+            tmp_bat = tmp_bat.replace(' ', '` ')
+            print(f'pyenv shell {Arch("3.7.7")} {Arch("3.8.9")}; pyenv shell', file=f)
+    stdout, stderr = run(tmp_bat)
+    assert (stdout, stderr) == (" ".join([Native('3.7.7'), Native('3.8.9')]), "")
+
+
+@pytest.mark.parametrize('settings', [lambda: {'versions': [Native("3.7.7")]}])
+def test_shell_set_many_versions_one_not_installed(pyenv):
+    assert pyenv.shell(Arch("3.7.7"), Arch("3.8.9")) == (not_installed_output(Native("3.8.9")), "")
+
+
+def test_shell_many_versions_defined(pyenv):
+    env = {'PYENV_VERSION': " ".join([Native('3.7.7'), Native('3.8.9')])}
+    assert pyenv.shell(env=env) == (" ".join([Native('3.7.7'), Native('3.8.9')]), "")
diff --git a/tests/test_pyenv_feature_uninstall.py b/tests/test_pyenv_feature_uninstall.py
index dd34bcb5..e69de29b 100644
--- a/tests/test_pyenv_feature_uninstall.py
+++ b/tests/test_pyenv_feature_uninstall.py
@@ -1,6 +0,0 @@
-from test_pyenv import TestPyenvBase
-
-
-class TestPyenvFeatureUninstall(TestPyenvBase):
-    pass
-
diff --git a/tests/test_pyenv_feature_version.py b/tests/test_pyenv_feature_version.py
index 3624207e..0aea2dd9 100644
--- a/tests/test_pyenv_feature_version.py
+++ b/tests/test_pyenv_feature_version.py
@@ -1,92 +1,87 @@
+import pytest
+
 import os
-import tempfile
 from pathlib import Path
-from tempenv import TemporaryEnvironment
-from test_pyenv import TestPyenvBase
-from test_pyenv_helpers import run_pyenv_test, touch
+from test_pyenv_helpers import touch, Native
 
 
 def pyenv_version_help():
     return "Usage: pyenv version"
 
 
-class TestPyenvFeatureVersion(TestPyenvBase):
-    def test_version_help(self, setup):
-        def commands(ctx):
-            for args in [
-                ["--help", "version"],
-                ["help", "version"],
-                ["version", "--help"],
-            ]:
-                stdout, stderr = ctx.pyenv(args)
-                assert ("\r\n".join(stdout.splitlines()[:2]).strip(), stderr) == (pyenv_version_help(), "")
-        run_pyenv_test({}, commands)
-
-    def test_no_version(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv("version") == (
-                (
-                    "No global python version has been set yet. "
-                    "Please set the global version by typing:\r\n"
-                    "pyenv global 3.7.4"
-                ),
-                ""
-            )
-        run_pyenv_test({}, commands)
-
-    def test_global_version(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv("version") == (rf'3.7.2 (set by {ctx.pyenv_path}\version)', "")
-        run_pyenv_test({'global_ver': "3.7.2"}, commands)
-
-    def test_one_local_version(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv("version") == (rf'3.9.1 (set by {ctx.local_path}\.python-version)', "")
-        settings = {
-            'global_ver': "3.7.2",
-            'local_ver': "3.9.1"
-        }
-        run_pyenv_test(settings, commands)
-
-    def test_shell_version(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv("version") == ("3.9.2 (set by %PYENV_VERSION%)", "")
-        settings = {
-            'global_ver': "3.7.5",
-            'local_ver': "3.8.6",
-        }
-        with TemporaryEnvironment({"PYENV_VERSION": "3.9.2"}):
-            run_pyenv_test(settings, commands)
-
-    def test_many_local_versions(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv("version") == (
-                (
-                    f'3.8.8 (set by {ctx.local_path}\\.python-version)\r\n'
-                    f'3.9.1 (set by {ctx.local_path}\\.python-version)'
-                ),
-                ""
-            )
-        settings = {
-            'global_ver': "3.7.2",
-            'local_ver': "3.8.8\n3.9.1\n"
-        }
-        run_pyenv_test(settings, commands)
-
-    def test_bad_path(self, setup):
-        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{ctx.local_path}\\python.exe\x1b[91m version '
-                            f'before pyenv in PATH.\x1b[0m\r\n'
-                            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, stderr) == (expected, "")
-
-        run_pyenv_test({'global_ver': "3.7.2"}, commands)
+def test_version_help(pyenv):
+    for args in [
+        ["--help", "version"],
+        ["help", "version"],
+        ["version", "--help"],
+    ]:
+        stdout, stderr = pyenv(*args)
+        assert ("\r\n".join(stdout.splitlines()[:2]).strip(), stderr) == (pyenv_version_help(), "")
+
+
+def test_no_version(pyenv):
+    assert pyenv.version() == (
+        (
+            "No global/local python version has been set yet. "
+            "Please set the global/local version by typing:\r\n"
+            "pyenv global 3.7.4\r\n"
+            "pyenv local 3.7.4"
+        ),
+        ""
+    )
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'global_ver': Native("3.7.4")
+    }])
+def test_global_version(pyenv_path, pyenv):
+    assert pyenv.version() == (rf'{Native("3.7.4")} (set by {pyenv_path}\version)', "")
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'global_ver': Native("3.7.4"),
+        'local_ver': Native("3.9.1")
+    }])
+def test_one_local_version(local_path, pyenv):
+    assert pyenv.version() == (rf'{Native("3.9.1")} (set by {local_path}\.python-version)', "")
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'global_ver': Native("3.7.5"),
+        'local_ver': Native("3.8.6"),
+    }])
+def test_shell_version(pyenv):
+    env = {"PYENV_VERSION": Native("3.9.2")}
+    assert pyenv.version(env=env) == (f"{Native('3.9.2')} (set by %PYENV_VERSION%)", "")
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'global_ver': Native("3.7.4"),
+        'local_ver': [Native("3.8.8"), Native("3.9.1")]
+    }])
+def test_many_local_versions(local_path, pyenv):
+    assert pyenv.version() == (
+        (
+            f'{Native("3.8.8")} (set by {local_path}\\.python-version)\r\n'
+            f'{Native("3.9.1")} (set by {local_path}\\.python-version)'
+        ),
+        ""
+    )
+
+
+@pytest.mark.parametrize('settings', [lambda: {'global_ver': Native("3.7.4")}])
+def test_bad_path(local_path, pyenv_path, pyenv):
+    touch(Path(local_path, 'python.exe'))
+    touch(Path(pyenv_path, r'shims\python.bat'))
+    env = {"PATH": f"{local_path};{os.environ['PATH']}"}
+    stdout, stderr = pyenv.version(env=env)
+    expected = (f'\x1b[91mFATAL: Found \x1b[95m{local_path}\\python.exe\x1b[91m version '
+                f'before pyenv in PATH.\x1b[0m\r\n'
+                f'\x1b[91mPlease remove \x1b[95m{local_path}\\\x1b[91m from '
+                f'PATH for pyenv to work properly.\x1b[0m\r\n'
+                f'{Native("3.7.4")} (set by {pyenv_path}\\version)')
+    # Fix 8.3 mismatch in GitHub actions
+    stdout = stdout.replace('RUNNER~1', 'runneradmin')
+    expected = expected.replace('RUNNER~1', 'runneradmin')
+    assert (stdout, stderr) == (expected, "")
+
diff --git a/tests/test_pyenv_feature_version_name.py b/tests/test_pyenv_feature_version_name.py
index 31ff38cf..df239755 100644
--- a/tests/test_pyenv_feature_version_name.py
+++ b/tests/test_pyenv_feature_version_name.py
@@ -1,84 +1,72 @@
 import pytest
-from tempenv import TemporaryEnvironment
-from test_pyenv_helpers import run_pyenv_test
 
+from test_pyenv_helpers import Native
 
-def test_version_name_help():
-    def commands(ctx):
-        for args in [
-            ["--help", "version-name"],
-            ["help", "version-name"],
-            ["version-name", "--help"],
-        ]:
-            stdout, stderr = ctx.pyenv(args)
-            stdout = "\r\n".join(stdout.splitlines()[:2]).strip()
-            assert (stdout, stderr) == ("Usage: pyenv version-name", "")
-    run_pyenv_test({}, commands)
 
+def test_version_name_help(pyenv):
+    for args in [
+        ["--help", "version-name"],
+        ["help", "version-name"],
+        ["version-name", "--help"],
+    ]:
+        stdout, stderr = pyenv(*args)
+        stdout = "\r\n".join(stdout.splitlines()[:2]).strip()
+        assert (stdout, stderr) == ("Usage: pyenv version-name", "")
 
-def test_vname_help():
-    def commands(ctx):
-        for args in [
-            ["--help", "vname"],
-            ["help", "vname"],
-            ["vname", "--help"],
-        ]:
-            stdout, stderr = ctx.pyenv(args)
-            stdout = "\r\n".join(stdout.splitlines()[:2]).strip()
-            assert (stdout, stderr) == ("Usage: pyenv vname", "")
-    run_pyenv_test({}, commands)
+
+def test_vname_help(pyenv):
+    for args in [
+        ["--help", "vname"],
+        ["help", "vname"],
+        ["vname", "--help"],
+    ]:
+        stdout, stderr = pyenv(*args)
+        stdout = "\r\n".join(stdout.splitlines()[:2]).strip()
+        assert (stdout, stderr) == ("Usage: pyenv vname", "")
 
 
 @pytest.mark.parametrize("command", ['version-name', 'vname'])
-def test_no_version(command):
-    def commands(ctx):
-        assert ctx.pyenv(command) == (
-            (
-                "No global python version has been set yet. "
-                "Please set the global version by typing:\r\n"
-                "pyenv global 3.7.4"
-            ),
-            ""
-        )
-    run_pyenv_test({}, commands)
+def test_no_version(command, pyenv):
+    assert pyenv(command) == (
+        (
+            "No global/local python version has been set yet. "
+            "Please set the global/local version by typing:\r\n"
+            "pyenv global 3.7.4\r\n"
+            "pyenv local 3.7.4"
+        ),
+        ""
+    )
 
 
+@pytest.mark.parametrize("settings", [lambda: {'global_ver': Native("3.7.4")}])
 @pytest.mark.parametrize("command", ['version-name', 'vname'])
-def test_global_version(command):
-    def commands(ctx):
-        assert ctx.pyenv(command) == ("3.7.2", "")
-    run_pyenv_test({'global_ver': "3.7.2"}, commands)
+def test_global_version(command, pyenv):
+    assert pyenv(command) == (Native("3.7.4"), "")
 
 
+@pytest.mark.parametrize("settings", [lambda: {
+        'global_ver': Native("3.7.4"),
+        'local_ver': Native("3.9.1")
+    }])
 @pytest.mark.parametrize("command", ['version-name', 'vname'])
-def test_one_local_version(command):
-    def commands(ctx):
-        assert ctx.pyenv(command) == ("3.9.1", "")
-    settings = {
-        'global_ver': "3.7.2",
-        'local_ver': "3.9.1"
-    }
-    run_pyenv_test(settings, commands)
+def test_one_local_version(command, pyenv):
+    assert pyenv(command) == (Native("3.9.1"), "")
 
 
+@pytest.mark.parametrize('settings', [lambda: {
+        'global_ver': Native("3.7.5"),
+        'local_ver': Native("3.8.6"),
+    }])
 @pytest.mark.parametrize("command", ['version-name', 'vname'])
-def test_shell_version(command):
-    def commands(ctx):
-        assert ctx.pyenv(command) == ("3.9.2", "")
-    settings = {
-        'global_ver': "3.7.5",
-        'local_ver': "3.8.6",
-    }
-    with TemporaryEnvironment({"PYENV_VERSION": "3.9.2"}):
-        run_pyenv_test(settings, commands)
+def test_shell_version(command, pyenv):
+    env = {"PYENV_VERSION": Native("3.9.2")}
+    assert pyenv(command, env=env) == (Native("3.9.2"), "")
 
 
+@pytest.mark.parametrize('settings', [lambda: {
+        'global_ver': Native("3.7.4"),
+        'local_ver': [Native("3.8.8"), Native("3.9.1")]
+    }])
 @pytest.mark.parametrize("command", ['version-name', 'vname'])
-def test_many_local_versions(command):
-    def commands(ctx):
-        assert ctx.pyenv(command) == ("3.8.8\r\n3.9.1", "")
-    settings = {
-        'global_ver': "3.7.2",
-        'local_ver': "3.8.8\n3.9.1\n"
-    }
-    run_pyenv_test(settings, commands)
+def test_many_local_versions(command, pyenv):
+    assert pyenv(command) == ("\r\n".join([Native("3.8.8"), Native("3.9.1")]), "")
diff --git a/tests/test_pyenv_feature_versions.py b/tests/test_pyenv_feature_versions.py
index 80e6f1e0..1e6ad0b5 100644
--- a/tests/test_pyenv_feature_versions.py
+++ b/tests/test_pyenv_feature_versions.py
@@ -1,138 +1,127 @@
-from tempenv import TemporaryEnvironment
-from test_pyenv import TestPyenvBase
-from test_pyenv_helpers import run_pyenv_test
-
-
-class TestPyenvFeatureVersions(TestPyenvBase):
-    def test_list_no_version(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv("versions") == ("", "")
-        run_pyenv_test({}, commands)
-
-    def test_list_all_versions(self, setup):
-        def commands(ctx):
-            stdout, stderr = ctx.pyenv("versions")
-            for v in versions:
-                assert v in stdout
-            assert stderr == ""
-        versions = ['3.7.4', '3.8.5']
-        run_pyenv_test({'versions': versions}, commands)
-
-    def test_list_current_global_version(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv("versions") == (
-                (
-                    f"  3.6.5\r\n"
-                    f"* 3.7.7 (set by {ctx.pyenv_path}\\version)\r\n"
-                    f"  3.9.1"
-                ),
-                ""
-            )
-        settings = {
-            'versions': ["3.6.5", "3.7.7", "3.9.1"],
-            'global_ver': "3.7.7"
-        }
-        run_pyenv_test(settings, commands)
-
-    def test_list_current_local_version(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv("versions") == (
-                (
-                    f"* 3.6.5 (set by {ctx.local_path}\\.python-version)\r\n"
-                    f"  3.7.7\r\n"
-                    f"  3.9.1"
-                ),
-                ""
-            )
-        settings = {
-            'versions': ["3.6.5", "3.7.7", "3.9.1"],
-            'global_ver': "3.7.7",
-            'local_ver': "3.6.5"
-        }
-        run_pyenv_test(settings, commands)
-
-    def test_list_current_local_many_versions(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv("versions") == (
-                (
-                    f"* 3.6.5 (set by {ctx.local_path}\\.python-version)\r\n"
-                    f"* 3.7.7 (set by {ctx.local_path}\\.python-version)\r\n"
-                    f"  3.9.1"
-                ),
-                ""
-            )
-        settings = {
-            'versions': ["3.6.5", "3.7.7", "3.9.1"],
-            'global_ver': "3.9.1",
-            'local_ver': "3.6.5\n3.7.7\n"
-        }
-        run_pyenv_test(settings, commands)
-
-    def test_list_current_shell_version(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv("versions") == (
-                (
-                    f"  3.6.5\r\n"
-                    f"  3.7.7\r\n"
-                    f"* 3.9.1 (set by %PYENV_VERSION%)"
-                ),
-                ""
-            )
-        settings = {
-            'versions': ["3.6.5", "3.7.7", "3.9.1"],
-            'global_ver': "3.7.7",
-            'local_ver': "3.6.5"
-        }
-        with TemporaryEnvironment({"PYENV_VERSION": "3.9.1"}):
-            run_pyenv_test(settings, commands)
-
-    def test_list_uninstalled_current_global_version(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv("versions") == (
-                (
-                    f"  3.6.5\r\n"
-                    f"  3.7.7\r\n"
-                    f"  3.9.1"
-                ),
-                ""
-            )
-        settings = {
-            'versions': ["3.6.5", "3.7.7", "3.9.1"],
-            'global_ver': "3.7.5"
-        }
-        run_pyenv_test(settings, commands)
-
-    def test_list_uninstalled_local_version(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv("versions") == (
-                (
-                    f"  3.6.5\r\n"
-                    f"  3.7.7\r\n"
-                    f"  3.9.1"
-                ),
-                ""
-            )
-        settings = {
-            'versions': ["3.6.5", "3.7.7", "3.9.1"],
-            'global_ver': "3.7.7",
-            'local_ver': "3.6.1"
-        }
-        run_pyenv_test(settings, commands)
-
-    def test_list_uninstalled_shell_version(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv("versions") == (
-                (
-                    f"  3.6.5\r\n"
-                    f"  3.7.7\r\n"
-                    f"  3.9.1"
-                ),
-                ""
-            )
-        settings = {
-            'versions': ["3.6.5", "3.7.7", "3.9.1"],
-            'global_ver': "3.7.7",
-            'local_ver': "3.6.5"
-        }
-        with TemporaryEnvironment({"PYENV_VERSION": "3.9.2"}):
-            run_pyenv_test(settings, commands)
+import pytest
+
+from test_pyenv_helpers import Native
+
+
+def test_list_no_version(pyenv):
+    assert pyenv.versions() == ("", "")
+
+
+@pytest.mark.parametrize('settings', [lambda: {'versions': [Native('3.7.4'), Native('3.8.5')]}])
+def test_list_all_versions(settings, pyenv):
+    stdout, stderr = pyenv.versions()
+    for v in settings()['versions']:
+        assert v in stdout
+    assert stderr == ""
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'versions': [Native("3.6.5"), Native("3.7.7"), Native("3.9.1")],
+        'global_ver': Native("3.7.7")
+    }])
+def test_list_current_global_version(pyenv_path, pyenv):
+    assert pyenv.versions() == (
+        (
+            f"  {Native('3.6.5')}\r\n"
+            f"* {Native('3.7.7')} (set by {pyenv_path}\\version)\r\n"
+            f"  {Native('3.9.1')}"
+        ),
+        ""
+    )
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'versions': [Native("3.6.5"), Native("3.7.7"), Native("3.9.1")],
+        'global_ver': Native("3.7.7"),
+        'local_ver': Native("3.6.5")
+    }])
+def test_list_current_local_version(local_path, pyenv):
+    assert pyenv.versions() == (
+        (
+            f"* {Native('3.6.5')} (set by {local_path}\\.python-version)\r\n"
+            f"  {Native('3.7.7')}\r\n"
+            f"  {Native('3.9.1')}"
+        ),
+        ""
+    )
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'versions': [Native("3.6.5"), Native("3.7.7"), Native("3.9.1")],
+        'global_ver': Native("3.9.1"),
+        'local_ver': [Native('3.6.5'), Native('3.7.7')]
+    }])
+def test_list_current_local_many_versions(local_path, pyenv):
+    assert pyenv.versions() == (
+        (
+            f"* {Native('3.6.5')} (set by {local_path}\\.python-version)\r\n"
+            f"* {Native('3.7.7')} (set by {local_path}\\.python-version)\r\n"
+            f"  {Native('3.9.1')}"
+        ),
+        ""
+    )
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'versions': [Native("3.6.5"), Native("3.7.7"), Native("3.9.1")],
+        'global_ver': Native("3.7.7"),
+        'local_ver': Native("3.6.5")
+    }])
+def test_list_current_shell_version(pyenv):
+    env = {"PYENV_VERSION": Native("3.9.1")}
+    assert pyenv.versions(env=env) == (
+        (
+            f"  {Native('3.6.5')}\r\n"
+            f"  {Native('3.7.7')}\r\n"
+            f"* {Native('3.9.1')} (set by %PYENV_VERSION%)"
+        ),
+        ""
+    )
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'versions': [Native("3.6.5"), Native("3.7.7"), Native("3.9.1")],
+        'global_ver': Native("3.7.5")
+    }])
+def test_list_uninstalled_current_global_version(pyenv):
+    assert pyenv.versions() == (
+        (
+            f"  {Native('3.6.5')}\r\n"
+            f"  {Native('3.7.7')}\r\n"
+            f"  {Native('3.9.1')}"
+        ),
+        ""
+    )
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'versions': [Native("3.6.5"), Native("3.7.7"), Native("3.9.1")],
+        'global_ver': Native("3.7.7"),
+        'local_ver': Native("3.6.1")
+    }])
+def test_list_uninstalled_local_version(pyenv):
+    assert pyenv.versions() == (
+        (
+            f"  {Native('3.6.5')}\r\n"
+            f"  {Native('3.7.7')}\r\n"
+            f"  {Native('3.9.1')}"
+        ),
+        ""
+    )
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'versions': [Native("3.6.5"), Native("3.7.7"), Native("3.9.1")],
+        'global_ver': Native("3.7.7"),
+        'local_ver': Native("3.6.5")
+    }])
+def test_list_uninstalled_shell_version(pyenv):
+    env = {"PYENV_VERSION": Native("3.9.2")}
+    assert pyenv.versions(env=env) == (
+        (
+            f"  {Native('3.6.5')}\r\n"
+            f"  {Native('3.7.7')}\r\n"
+            f"  {Native('3.9.1')}"
+        ),
+        ""
+    )
diff --git a/tests/test_pyenv_feature_whence.py b/tests/test_pyenv_feature_whence.py
index b3f75977..322a4f23 100644
--- a/tests/test_pyenv_feature_whence.py
+++ b/tests/test_pyenv_feature_whence.py
@@ -1,5 +1,6 @@
-from test_pyenv import TestPyenvBase
-from test_pyenv_helpers import run_pyenv_test
+import pytest
+
+from test_pyenv_helpers import Native
 
 
 def pyenv_whence_usage():
@@ -9,29 +10,30 @@ def pyenv_whence_usage():
             f"selected. To obtain python version of executable, use `pyenv whence pip'.")
 
 
-class TestPyenvFeatureWhence(TestPyenvBase):
-    def test_whence_no_arg(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv("whence") == (pyenv_whence_usage(), "")
-            assert ctx.pyenv(["whence", "--help"]) == (pyenv_whence_usage(), "")
-            assert ctx.pyenv(["--help", "whence"]) == (pyenv_whence_usage(), "")
-            assert ctx.pyenv(["help", "whence"]) == (pyenv_whence_usage(), "")
-        run_pyenv_test({'versions': ['3.7.7']}, commands)
-
-    def test_whence_major(self, setup):
-        def commands(ctx):
-            for name in ['python', 'python3', 'pip3']:
-                assert ctx.pyenv(["whence", name]) == ('3.7.7\r\n3.8.2\r\n3.8.7\r\n3.9.1', "")
-        run_pyenv_test({'versions': ['3.7.7', '3.8.2', '3.8.7', '3.9.1']}, commands)
-
-    def test_whence_major_minor(self, setup):
-        def commands(ctx):
-            for name in ['python38', 'python3.8', 'pip3.8']:
-                assert ctx.pyenv(["whence", name]) == ('3.8.2\r\n3.8.7', "")
-        run_pyenv_test({'versions': ['3.7.7', '3.8.2', '3.8.7', '3.9.1']}, commands)
-
-    def test_whence_not_found(self, setup):
-        def commands(ctx):
-            for name in ['unknown3.8']:
-                assert ctx.pyenv(["whence", name]) == ("", "")
-        run_pyenv_test({'versions': ['3.7.7', '3.8.2', '3.8.7', '3.9.1']}, commands)
+@pytest.mark.parametrize('settings', [lambda: {'versions': [Native('3.7.7')]}])
+def test_whence_no_arg(pyenv):
+    assert pyenv.whence() == (pyenv_whence_usage(), "")
+    assert pyenv.whence("--help") == (pyenv_whence_usage(), "")
+    assert pyenv("--help", "whence") == (pyenv_whence_usage(), "")
+    assert pyenv("help", "whence") == (pyenv_whence_usage(), "")
+
+
+@pytest.mark.parametrize('settings',
+                         [lambda: {'versions': [Native('3.7.7'), Native('3.8.2'), Native('3.8.7'), Native('3.9.1')]}])
+def test_whence_major(pyenv):
+    for name in ['python', 'python3', 'pip3']:
+        assert pyenv.whence(name) == ("\r\n".join([Native('3.7.7'), Native('3.8.2'), Native('3.8.7'), Native('3.9.1')]), "")
+
+
+@pytest.mark.parametrize('settings',
+                         [lambda: {'versions': [Native('3.7.7'), Native('3.8.2'), Native('3.8.7'), Native('3.9.1')]}])
+def test_whence_major_minor(pyenv):
+    for name in ['python38', 'python3.8', 'pip3.8']:
+        assert pyenv.whence(name) == ("\r\n".join([Native('3.8.2'), Native('3.8.7')]), "")
+
+
+@pytest.mark.parametrize('settings',
+                         [lambda: {'versions': [Native('3.7.7'), Native('3.8.2'), Native('3.8.7'), Native('3.9.1')]}])
+def test_whence_not_found(pyenv):
+    for name in ['unknown3.8']:
+        assert pyenv.whence(name) == ("", "")
diff --git a/tests/test_pyenv_feature_which.py b/tests/test_pyenv_feature_which.py
index 06b77bca..2d5a7506 100644
--- a/tests/test_pyenv_feature_which.py
+++ b/tests/test_pyenv_feature_which.py
@@ -1,6 +1,6 @@
-from tempenv import TemporaryEnvironment
-from test_pyenv import TestPyenvBase
-from test_pyenv_helpers import run_pyenv_test
+import pytest
+
+from test_pyenv_helpers import Native
 
 
 def assert_paths_equal(actual, expected):
@@ -14,167 +14,159 @@ def pyenv_which_usage():
             f"selected. To obtain the full path, use `pyenv which pip'.")
 
 
-class TestPyenvFeatureWhich(TestPyenvBase):
-    def test_which_no_arg(self, setup):
-        def commands(ctx):
-            assert ctx.pyenv("which") == (pyenv_which_usage(), "")
-            assert ctx.pyenv(["which", "--help"]) == (pyenv_which_usage(), "")
-            assert ctx.pyenv(["--help", "which"]) == (pyenv_which_usage(), "")
-            assert ctx.pyenv(["help", "which"]) == (pyenv_which_usage(), "")
-        run_pyenv_test({'versions': ['3.7.7']}, commands)
-
-    def test_which_exists_is_global(self, setup):
-        def commands(ctx):
-            for name in ['python', 'python3', 'python38', 'pip3', 'pip3.8']:
-                sub_dir = '' if 'python' in name else 'Scripts\\'
-                stdout, stderr = ctx.pyenv(["which", name])
-                assert_paths_equal(stdout, rf'{ctx.pyenv_path}\versions\3.8.5\{sub_dir}{name}.exe')
-                assert stderr == ""
-        settings = {
-            'versions': ['3.8.5'],
-            'global_ver': '3.8.5'
-        }
-        run_pyenv_test(settings, commands)
-
-    def test_which_exists_is_local(self, setup):
-        def commands(ctx):
-            for name in ['python', 'python3', 'python38', 'pip3', 'pip3.8']:
-                sub_dir = '' if 'python' in name else 'Scripts\\'
-                stdout, stderr = ctx.pyenv(["which", name])
-                assert_paths_equal(stdout, rf'{ctx.pyenv_path}\versions\3.8.5\{sub_dir}{name}.exe')
-                assert stderr == ""
-        settings = {
-            'versions': ['3.8.5'],
-            'local_ver': '3.8.5'
-        }
-        run_pyenv_test(settings, commands)
-
-    def test_which_exists_is_shell(self, setup):
-        def commands(ctx):
-            for name in ['python', 'python3', 'python38', 'pip3', 'pip3.8']:
-                sub_dir = '' if 'python' in name else 'Scripts\\'
-                stdout, stderr = ctx.pyenv(["which", name])
-                assert_paths_equal(stdout, rf'{ctx.pyenv_path}\versions\3.8.5\{sub_dir}{name}.exe')
-                assert stderr == ""
-        with TemporaryEnvironment({"PYENV_VERSION": "3.8.5"}):
-            run_pyenv_test({'versions': ['3.8.5']}, commands)
-
-    def test_which_exists_is_global_not_installed(self, setup):
-        def commands(ctx):
-            for name in ['python', 'python3', 'python38', 'pip3', 'pip3.8']:
-                assert ctx.pyenv(["which", name]) == ("pyenv: version `3.8.5' is not installed (set by 3.8.5)", "")
-        run_pyenv_test({'global_ver': '3.8.5'}, commands)
-
-    def test_which_exists_is_local_not_installed(self, setup):
-        def commands(ctx):
-            for name in ['python', 'python3', 'python38', 'pip3', 'pip3.8']:
-                assert ctx.pyenv(["which", name]) == ("pyenv: version `3.8.5' is not installed (set by 3.8.5)", "")
-        run_pyenv_test({'local_ver': '3.8.5'}, commands)
-
-    def test_which_exists_is_shell_not_installed(self, setup):
-        def commands(ctx):
-            for name in ['python', 'python3', 'python38', 'pip3', 'pip3.8']:
-                assert ctx.pyenv(["which", name]) == ("pyenv: version `3.8.5' is not installed (set by 3.8.5)", "")
-        with TemporaryEnvironment({"PYENV_VERSION": "3.8.5"}):
-            run_pyenv_test({}, commands)
-
-    def test_which_exists_is_global_other_version(self, setup):
-        def commands(ctx):
-            for name in ['python38', 'pip3.8']:
-                assert ctx.pyenv(["which", name]) == (
-                    (
-                        f"pyenv: {name}: command not found\r\n"
-                        f"\r\n"
-                        f"The '{name}' command exists in these Python versions:\r\n"
-                        f"  3.8.2\r\n"
-                        f"  3.8.6\r\n"
-                        f"  "
-                    ),
-                    ""
-                )
-        settings = {
-            'versions': ['3.8.2', '3.8.6', '3.9.1'],
-            'global_ver': '3.9.1'
-        }
-        run_pyenv_test(settings, commands)
-
-    def test_which_exists_is_local_other_version(self, setup):
-        def commands(ctx):
-            for name in ['python38', 'pip3.8']:
-                assert ctx.pyenv(["which", name]) == (
-                    (
-                        f"pyenv: {name}: command not found\r\n"
-                        f"\r\n"
-                        f"The '{name}' command exists in these Python versions:\r\n"
-                        f"  3.8.2\r\n"
-                        f"  3.8.6\r\n"
-                        f"  "
-                    ),
-                    ""
-                )
-        settings = {
-            'versions': ['3.8.2', '3.8.6', '3.9.1'],
-            'local_ver': '3.9.1'
-        }
-        run_pyenv_test(settings, commands)
-
-    def test_which_exists_is_shell_other_version(self, setup):
-        def commands(ctx):
-            for name in ['python38', 'python3.8', 'pip3.8']:
-                assert ctx.pyenv(["which", name]) == (
-                    (
-                        f"pyenv: {name}: command not found\r\n"
-                        f"\r\n"
-                        f"The '{name}' command exists in these Python versions:\r\n"
-                        f"  3.8.2\r\n"
-                        f"  3.8.6\r\n"
-                        f"  "
-                    ),
-                    ""
-                )
-        settings = {
-            'versions': ['3.8.2', '3.8.6', '3.9.1'],
-        }
-        with TemporaryEnvironment({"PYENV_VERSION": "3.9.1"}):
-            run_pyenv_test(settings, commands)
-
-    def test_which_command_not_found(self, setup):
-        def commands(ctx):
-            for name in ['unknown3.8']:
-                assert ctx.pyenv(["which", name]) == (f"pyenv: {name}: command not found", "")
-        settings = {
-            'versions': ['3.8.6'],
-            'global_ver': '3.8.6'
-        }
-        run_pyenv_test(settings, commands)
-
-    def test_which_no_version_defined(self, setup):
-        def commands(ctx):
-            for name in ['python']:
-                assert ctx.pyenv(["which", name]) == (
-                    (
-                        "No global python version has been set yet. "
-                        "Please set the global version by typing:\r\n"
-                        "pyenv global 3.7.4"
-                    ),
-                    ""
-                )
-        run_pyenv_test({'versions': ['3.8.6']}, commands)
-
-    def test_which_many_local_versions(self, setup):
-        def commands(ctx):
-            cases = [
-                ('python37', r'3.7.7\python37.exe'),
-                ('python38', r'3.8.2\python38.exe'),
-                ('pip3.7', r'3.7.7\Scripts\pip3.7.exe'),
-                ('pip3.8', r'3.8.2\Scripts\pip3.8.exe'),
-            ]
-            for (name, path) in cases:
-                stdout, stderr = ctx.pyenv(["which", name])
-                assert_paths_equal(stdout, rf'{ctx.pyenv_path}\versions\{path}')
-                assert stderr == ""
-        settings = {
-            'versions': ['3.7.7', '3.8.2', '3.9.1'],
-            'local_ver': '3.7.7\n3.8.2\n'
-        }
-        run_pyenv_test(settings, commands)
+@pytest.mark.parametrize('settings', [lambda: {'versions': [Native('3.7.7')]}])
+def test_which_no_arg(pyenv):
+    assert pyenv.which() == (pyenv_which_usage(), "")
+    assert pyenv.which("--help") == (pyenv_which_usage(), "")
+    assert pyenv("--help", "which") == (pyenv_which_usage(), "")
+    assert pyenv("help", "which") == (pyenv_which_usage(), "")
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'versions': [Native('3.8.5')],
+        'global_ver': Native('3.8.5')
+    }])
+def test_which_exists_is_global(pyenv_path, pyenv):
+    for name in ['python', 'python3', 'python38', 'pip3', 'pip3.8']:
+        sub_dir = '' if 'python' in name else 'Scripts\\'
+        stdout, stderr = pyenv.which(name)
+        assert_paths_equal(stdout, rf'{pyenv_path}\versions\{Native("3.8.5")}\{sub_dir}{name}.exe')
+        assert stderr == ""
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'versions': [Native('3.8.5')],
+        'local_ver': Native('3.8.5')
+    }])
+def test_which_exists_is_local(pyenv_path, pyenv):
+    for name in ['python', 'python3', 'python38', 'pip3', 'pip3.8']:
+        sub_dir = '' if 'python' in name else 'Scripts\\'
+        stdout, stderr = pyenv.which(name)
+        assert_paths_equal(stdout, rf'{pyenv_path}\versions\{Native("3.8.5")}\{sub_dir}{name}.exe')
+        assert stderr == ""
+
+
+@pytest.mark.parametrize('settings', [lambda: {'versions': [Native('3.8.5')]}])
+def test_which_exists_is_shell(pyenv_path, pyenv):
+    env = {"PYENV_VERSION": Native("3.8.5")}
+    for name in ['python', 'python3', 'python38', 'pip3', 'pip3.8']:
+        sub_dir = '' if 'python' in name else 'Scripts\\'
+        stdout, stderr = pyenv.which(name, env=env)
+        assert_paths_equal(stdout, rf'{pyenv_path}\versions\{Native("3.8.5")}\{sub_dir}{name}.exe')
+        assert stderr == ""
+
+
+@pytest.mark.parametrize('settings', [lambda: {'global_ver': Native('3.8.5')}])
+def test_which_exists_is_global_not_installed(pyenv):
+    for name in ['python', 'python3', 'python38', 'pip3', 'pip3.8']:
+        assert pyenv.which(name) == (f"pyenv: version '{Native('3.8.5')}' is not installed (set by {Native('3.8.5')})", "")
+
+
+@pytest.mark.parametrize('settings', [lambda: {'local_ver': Native('3.8.5')}])
+def test_which_exists_is_local_not_installed(pyenv):
+    for name in ['python', 'python3', 'python38', 'pip3', 'pip3.8']:
+        assert pyenv.which(name) == (f"pyenv: version '{Native('3.8.5')}' is not installed (set by {Native('3.8.5')})", "")
+
+
+def test_which_exists_is_shell_not_installed(pyenv):
+    env = {"PYENV_VERSION": Native("3.8.5")}
+    for name in ['python', 'python3', 'python38', 'pip3', 'pip3.8']:
+        assert pyenv.which(name, env=env) == (f"pyenv: version '{Native('3.8.5')}' is not installed (set by {Native('3.8.5')})", "")
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'versions': [Native('3.8.2'), Native('3.8.6'), Native('3.9.1')],
+        'global_ver': Native('3.9.1')
+    }])
+def test_which_exists_is_global_other_version(pyenv):
+    for name in ['python38', 'pip3.8']:
+        assert pyenv.which(name) == (
+            (
+                f"pyenv: {name}: command not found\r\n"
+                f"\r\n"
+                f"The '{name}' command exists in these Python versions:\r\n"
+                f"  {Native('3.8.2')}\r\n"
+                f"  {Native('3.8.6')}\r\n"
+                f"  "
+            ),
+            ""
+        )
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'versions': [Native('3.8.2'), Native('3.8.6'), Native('3.9.1')],
+        'local_ver': Native('3.9.1')
+    }])
+def test_which_exists_is_local_other_version(pyenv):
+    for name in ['python38', 'pip3.8']:
+        assert pyenv.which(name) == (
+            (
+                f"pyenv: {name}: command not found\r\n"
+                f"\r\n"
+                f"The '{name}' command exists in these Python versions:\r\n"
+                f"  {Native('3.8.2')}\r\n"
+                f"  {Native('3.8.6')}\r\n"
+                f"  "
+            ),
+            ""
+        )
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'versions': [Native('3.8.2'), Native('3.8.6'), Native('3.9.1')],
+    }])
+def test_which_exists_is_shell_other_version(pyenv):
+    env = {"PYENV_VERSION": Native("3.9.1")}
+    for name in ['python38', 'python3.8', 'pip3.8']:
+        assert pyenv.which(name, env=env) == (
+            (
+                f"pyenv: {name}: command not found\r\n"
+                f"\r\n"
+                f"The '{name}' command exists in these Python versions:\r\n"
+                f"  {Native('3.8.2')}\r\n"
+                f"  {Native('3.8.6')}\r\n"
+                f"  "
+            ),
+            ""
+        )
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'versions': [Native('3.8.6')],
+        'global_ver': Native('3.8.6')
+    }])
+def test_which_command_not_found(pyenv):
+    for name in ['unknown3.8']:
+        assert pyenv.which(name) == (f"pyenv: {name}: command not found", "")
+
+
+@pytest.mark.parametrize('settings', [lambda: {'versions': [Native('3.8.6')]}])
+def test_which_no_version_defined(pyenv):
+    for name in ['python']:
+        assert pyenv.which(name) == (
+            (
+                "No global/local python version has been set yet. "
+                "Please set the global/local version by typing:\r\n"
+                "pyenv global 3.7.4\r\n"
+                "pyenv local 3.7.4"
+            ),
+            ""
+        )
+
+
+@pytest.mark.parametrize('settings', [lambda: {
+        'versions': [Native('3.7.7'), Native('3.8.2'), Native('3.9.1')],
+        'local_ver': [Native('3.7.7'), Native('3.8.2')]
+    }])
+def test_which_many_local_versions(pyenv_path, pyenv):
+    cases = [
+        ('python37', rf'{Native("3.7.7")}\python37.exe'),
+        ('python38', rf'{Native("3.8.2")}\python38.exe'),
+        ('pip3.7', rf'{Native("3.7.7")}\Scripts\pip3.7.exe'),
+        ('pip3.8', rf'{Native("3.8.2")}\Scripts\pip3.8.exe'),
+    ]
+    for (name, path) in cases:
+        stdout, stderr = pyenv.which(name)
+        assert_paths_equal(stdout, rf'{pyenv_path}\versions\{path}')
+        assert stderr == ""
+
diff --git a/tests/test_pyenv_helpers.py b/tests/test_pyenv_helpers.py
index ffde6f94..e383b5e0 100644
--- a/tests/test_pyenv_helpers.py
+++ b/tests/test_pyenv_helpers.py
@@ -1,7 +1,6 @@
 import os
 import shutil
 import subprocess
-import tempfile
 from contextlib import contextmanager
 from packaging import version
 from pathlib import Path
@@ -40,14 +39,18 @@ def touch(exe):
 
 
 def pyenv_setup(settings):
-    pyenv_path, local_path, versions, global_ver, local_ver =\
-        settings['pyenv_path'],\
-        settings['local_path'],\
-        settings.get('versions', None),\
-        settings.get("global_ver", None),\
+    pyenv_path, local_path, versions, global_ver, local_ver = \
+        settings['pyenv_path'], \
+        settings['local_path'], \
+        settings.get('versions', None), \
+        settings.get("global_ver", None), \
         settings.get('local_ver', None)
     if versions is None:
         versions = []
+    if isinstance(global_ver, list):
+        global_ver = '\n'.join(global_ver)
+    if isinstance(local_ver, list):
+        local_ver = '\n'.join(local_ver)
     src_path = Path(__file__).resolve().parents[1].joinpath('pyenv-win')
     dirs = [r'bin', r'libexec\libs', r'shims', r'versions']
     for d in dirs:
@@ -55,7 +58,10 @@ def pyenv_setup(settings):
     _, _, libexec_files = next(os.walk(src_path.joinpath('libexec')))
     for f in libexec_files:
         shutil.copy(Path(src_path, 'libexec', f), Path(pyenv_path, 'libexec', f))
-    files = [r'bin\pyenv.bat',
+    files = [r'.versions_cache.xml',
+             r'..\.version',
+             r'bin\pyenv.bat',
+             r'bin\pyenv.ps1',
              r'libexec\pyenv-shell.bat',
              r'libexec\libs\pyenv-install-lib.vbs',
              r'libexec\libs\pyenv-lib.vbs']
@@ -81,7 +87,7 @@ def create_scripts(path):
             print(f"@echo {ver.major}.{ver.minor}.{ver.micro}", file=batch)
 
     for v in versions:
-        ver = version.parse(v)
+        ver = version.parse(v.version)
         version_path = create_pythons(versions_dir.joinpath(v))
         create_scripts(version_path.joinpath('Scripts'))
     if global_ver is not None:
@@ -92,67 +98,63 @@ def create_scripts(path):
             print(local_ver, file=f)
 
 
-class PyenvContext:
-    def __init__(self, exec, pyenv, pyenv_path, local_path):
-        self.exec = exec
-        self.pyenv = pyenv
-        self.pyenv_path = pyenv_path
-        self.local_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)
-        os.mkdir(local_path)
-        pyenv_setup(settings)
-        self.prev_cwd = os.getcwd()
-        os.chdir(local_path)
-        self.bat = Path(pyenv_path, r'bin\pyenv.bat')
-        self.context = PyenvContext(self.exec, self.pyenv, pyenv_path, local_path)
-
-    def pyenv(self, pyenv_args=None):
-        args = [str(self.bat)]
-        if pyenv_args is not None:
-            if isinstance(pyenv_args, list):
-                args = args + pyenv_args
-            else:
-                args.append(pyenv_args)
-        return self.exec(args)
-
-    def exec(self, args):
-        args = ['cmd', '/d', '/c', 'call', *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").rpartition('\x0c')[2].strip("\r\n")
-        return stdout, stderr
-
-    def cleanup(self):
-        os.chdir(self.prev_cwd)
-        self.tmp_path.cleanup()
-
-    def __enter__(self):
-        return self.context
-
-    def __exit__(self, exc_type, exc_val, exc_tb):
-        self.cleanup()
-
-
-def run_pyenv_test(settings, commands):
-    with TempPyEnv(settings) as context:
-        commands(context)
-
-
 def not_installed_output(ver):
     return (f"pyenv specific python requisite didn't meet. "
             f"Project is using different version of python.\r\n"
             f"Install python '{ver}' by typing: 'pyenv install {ver}'")
 
 
+def global_python_versions(path):
+    with open(Path(path, 'version'), mode='r') as f:
+        return f.read().strip()
+
+
 def local_python_versions(path):
     with open(Path(path, '.python-version'), mode='r') as f:
         return f.read().strip()
+
+
+def do_run(*args, env={}):
+    args = list(args)
+    result = subprocess.run(args, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
+    stderr = str(result.stderr, "utf-8").strip()
+    # \x0c: generated by cls in cmd AutoRun
+    stdout = str(result.stdout, "utf-8").rpartition('\x0c')[2].strip("\r\n")
+    return stdout, stderr
+
+
+class Arch(str):
+    version = None
+
+    def __new__(cls, content):
+        self = super().__new__(cls, content)
+        self.version = content
+        return self
+
+
+class Native(Arch):
+    def __new__(cls, content):
+        ver = content
+        if os.environ['PYENV_FORCE_ARCH'] == 'X86':
+            content = content + '-win32'
+        self = super().__new__(cls, content)
+        self.version = ver
+        return self
+
+
+class X86(Arch):
+    def __new__(cls, content):
+        ver = content
+        content = content + '-win32'
+        self = super().__new__(cls, content)
+        self.version = ver
+        return self
+
+
+class Amd64(Arch):
+    def __new__(cls, content):
+        ver = content
+        content = content + '-amd64'
+        self = super().__new__(cls, content)
+        self.version = ver
+        return self