pyshim
A deterministic, context-aware Python shim for Windows that lets you control which Python interpreter is used by apps, projects, and background tools — without breaking the global environment.
Overview
pyshim is a lightweight command router that sits in front of python.exe.
It intercepts all calls to python and dynamically decides which interpreter to run based on context and configuration.
This is especially useful when:
- Multiple Python versions (e.g., 3.8, 3.11, 3.12) are installed.
- You want per-project or per-app version pinning.
- You need background tools and scripts to consistently use the same interpreter as your shell session.
- You don’t want to fight Windows’ confusing PATH order or
py launcherbehavior.
How It Works
When you call python, pyshim resolves the appropriate interpreter using this priority chain:
-
One-shot flag If called with
python --interpreter "SPEC" -- [args], that spec is used for this invocation only. -
Session override If the environment variable
PYSHIM_INTERPRETERis set (viaUse-Pythonwithout-Persist), that spec is used. -
App-target override If the environment variable
PYSHIM_TARGETis set (e.g.,MyApp), pyshim checks forC:\bin\shims\python@MyApp.env. -
Project-level pin If a
.python-versionfile exists in the current directory or any parent, that spec is used. -
Global persistence file If
C:\bin\shims\python.envexists (and persistence isn’t disabled), pyshim uses that. -
Fallback chain If no specific interpreter is found, pyshim falls back to:
py -3.12 → py -3 → conda run -n base python → real python.exe → (error if none found)Note: The system requires the Windows Python Launcher (
py.exe) or Conda to be installed. The fallback intentionally usespy.exeand searches for realpython.exeoutside the shim directory to avoid infinite recursion (sincepython.batIS thepythoncommand in your PATH).
Prerequisites
- Install the Windows Python Launcher (
py.exe) unless you already have it. Run any modern Python installer from python.org and tick “Install launcher for all users” during setup. pyshim’s fallback chain expectspy.exe(or Conda) to be present. -
Install PowerShell 7 (pwsh). On Windows 11, run:
winget install --id Microsoft.PowerShell --source wingetThe Microsoft Store package or the MSI from GitHub works too—just make sure
pwsh.exeends up on your PATH. Windows PowerShell 5.x is not enough for pyshim’s module helpers. -
(Recommended) Set an execution policy for your account so profile scripts can run:
Set-ExecutionPolicy -Scope CurrentUser RemoteSignedExecute that inside PowerShell 7; you can tighten it again once profiles are configured.
Install (Recommended)
- Download the Windows installer from the latest releases:
- Grab
Pyshim.Setup.exe. The WinForms bootstrapper requires a single UAC approval and bundles the latest shims, module, and helper scripts.
- Grab
- Run
Pyshim.Setup.exe(UAC prompt expected) and pick the actions you want:- Keep
C:\bin\shimsat the front of both the machine and user PATH values. - Copy the shims, refresh the shared Conda environments, and insert the guarded
Enable-PyshimProfileblock for CurrentUser and AllUsers scopes. - The log window mirrors every command so you can see exactly what changed; clear any checkbox to skip that step.
- Keep
- Re-run the installer anytime to repair an existing deployment or refresh PATH/profile wiring. The operations are idempotent, so nothing is duplicated.
Manual Install (PowerShell)
Only take this path when you cannot run the GUI installer (locked-down servers, offline images, etc.). You are responsible for the work the installer normally automates:
- Stage the payload – Download the latest release asset or clone the repo, then copy
python.bat,pip.bat,pythonw.bat,pyshim.psm1, the Conda helper scripts, andUninstall-Pyshim.ps1intoC:\bin\shims(create the directory first). - Wire up PATH – Ensure
C:\bin\shimssits at the end of your user PATH at minimum ([Environment]::SetEnvironmentVariable('Path', '<existing>;C:\bin\shims','User')). Update the current$env:Pathso open shells can see it immediately. - Trust the module for the session – In PowerShell 7 run
Import-Module 'C:\bin\shims\pyshim.psm1' -DisableNameChecking -ErrorAction SilentlyContinue -WarningAction SilentlyContinueto load the helper cmdlets. - Persist the auto-import – Execute
Enable-PyshimProfile(add-Scope AllUsersAllHostsand run elevated if you need system-wide coverage,-IncludeWindowsPowerShellfor legacy shells). This inserts the guarded import block that the installer normally writes. - Refresh managed Conda envs (optional) – Run
Refresh-CondaPythons -IgnoreMissingorInstall-CondaPythonsfrom the shim directory if you rely on the curatedpy310..py314interpreters. Supply-CondaPathwhen detection fails.
Following every step above reproduces the installer’s behaviour; skipping any of them means you are also skipping that piece of automation.
Need a headless option for CI or fleet tools? Use dist/Install-Pyshim.ps1 -WritePath -Confirm:$false, which performs the same actions as the GUI but stays scriptable.
PowerShell Installer Caveats
-
Even though the script is Authenticode-signed, Windows still honors the local execution policy. If your policy blocks unsigned scripts, start by unblocking and verifying the file:
Unblock-File .\Install-Pyshim.ps1 Get-AuthenticodeSignature .\Install-Pyshim.ps1 | Format-List Status,StatusMessage,SignerCertificate -
Run the installer from an elevated PowerShell 7 session so it can touch
C:\bin\shims, machine PATH, and AllUsers profiles:Set-ExecutionPolicy -Scope Process Bypass -Force pwsh -NoLogo -Command "powershell.exe -ExecutionPolicy Bypass -File .\Install-Pyshim.ps1 -WritePath -Confirm:\$false" -
When automating, pair
-WritePathwith-Confirm:$falseand consider-SkipCondaRefreshif your build agents provision Conda separately.
Uninstall
The installer drops C:\bin\shims\Uninstall-Pyshim.ps1. Run it from an elevated PowerShell prompt when you want to undo everything:
powershell.exe -ExecutionPolicy Bypass -File C:\bin\shims\Uninstall-Pyshim.ps1
The uninstaller:
- Verifies the shim directory only contains pyshim files (pass
-Forceto override). - Removes
C:\bin\shimsfrom your user PATH. - Deletes the shim directory (including any persisted specs like
python.env).
If you added Import-Module 'C:\bin\shims\pyshim.psm1' -DisableNameChecking -ErrorAction SilentlyContinue -WarningAction SilentlyContinue to your PowerShell profile, remove that line manually. After the script runs, restart your shells to pick up the cleaned PATH.
Update
To pick up the newest release without hunting through GitHub, run the module helper:
Update-Pyshim
By default it grabs the latest release asset and reruns Install-Pyshim.ps1 for you. Add -WritePath if you also want to ensure C:\bin\shims stays on your PATH, or -Tag 'v0.1.1-alpha' to pin a specific release. Supply a GITHUB_TOKEN environment variable (or pass -Token) if your network sits behind aggressive rate limiting.
Auto-load in PowerShell
- Run
Enable-PyshimProfileafter importing the module to append a guarded auto-import block to yourCurrentUserprofiles. Re-run it anytime; the sentinel comments prevent duplicates. - Pass
-Scope AllUsersAllHosts(and run elevated) to cover background agents or shared build accounts. Add-IncludeWindowsPowerShellif you still launch legacypowershell.exeshells that need the shim. - The cmdlet creates
.pyshim.bakbackups the first time it touches each profile unless you pass-NoBackup. Opening profiles with-NoProfileskips the block by definition. - The inserted code simply checks for
C:\bin\shims\pyshim.psm1and imports it withWrite-Verboselogging when anything goes sideways, so your existing profile customizations stay in control.
Conda Environment Helpers
- Once the module is imported you can run
Install-CondaPythons,Remove-CondaPythons, orRefresh-CondaPythonsto manage the sharedpy310..py314Conda environments. Each cmdlet accepts-CondaPathwhen auto-detection fails and honors-WhatIf/-Confirm. - The scripts
Install-CondaPythons.ps1,Remove-CondaPythons.ps1, andRefresh-CondaPythons.ps1live inC:\bin\shims(mirrored undertools/for repository workflows) and simply forward to those cmdlets. Install-Pyshim.ps1now runsRefresh-CondaPythons -IgnoreMissingby default; pass-SkipCondaRefreshto opt out during installation.
Usage
Global Interpreter
Set the global default interpreter for all sessions:
Use-Python -Spec 'py:3.12' -Persist
This writes to C:\bin\shims\python.env:
py:3.12
Now, any call to python—including from apps and services—will use that version.
Session-Only Interpreter
Use-Python -Spec 'conda:tools'
This sets the interpreter for the current shell session only. Background apps will still use the global default.
Disable Global Persistence
To temporarily ignore the persisted version:
Disable-PythonPersistence
This creates C:\bin\shims\python.nopersist, which causes the shim to skip python.env.
To re-enable:
Enable-PythonPersistence
Per-App Overrides
You can pin specific apps to specific interpreters:
Set-AppPython -App 'MyService' -Spec 'conda:svc'
This creates C:\bin\shims\python@MyService.env containing:
conda:svc
When that app launches with PYSHIM_TARGET=MyService, it uses the pinned interpreter.
Example:
@echo off
set PYSHIM_TARGET=MyService
python -V
Per-Project Versions
Drop a .python-version file in your project root:
py:3.11
or
conda:myenv
When you run python inside that folder, pyshim automatically respects the project’s version.
One-Shot Command Execution
You can also run a single command with a specific interpreter, without persistence:
Run-WithPython -Spec 'py:3.11' -- -m pip --version
Package Strategy
To keep your system clean and predictable:
- Global CLI tools → Install with
pipx. Each tool gets its own isolated virtual environment. - Per-project dependencies → Use a
.venvcreated by the interpreter chosen by pyshim. - Cache for speed → Set
PIP_CACHE_DIR=%LOCALAPPDATA%\pip\cache. - Conda users → Continue managing environments normally (
conda:envnamespecs work seamlessly).
This hybrid model ensures:
- Background apps remain stable.
- Projects stay isolated.
- Installations reuse cached wheels for efficiency.
Quick Test
Once installed, open PowerShell and run:
Use-Python -Spec 'py:3.12' -Persist
python -V
pip --version
Run-WithPython -Spec 'py:3.11' -- -c "print('hello from 3.11')"
Example Directory Layout
C:\
\-- bin\
\-- shims\
|-- python.bat
|-- pip.bat
|-- pythonw.bat
|-- pyshim.psm1
\-- Uninstall-Pyshim.ps1
The installer only drops the files above. Runtime metadata such as python.env, python.nopersist, or any python@*.env files show up later when you use the module; they aren’t part of the shipped tree.
Naming Conventions
python.env— global persistent interpreter spec..python-version— project-local interpreter spec.python@AppName.env— per-application interpreter spec.python.nopersist— disables persistence globally.Uninstall-Pyshim.ps1— local uninstaller dropped by the installer.
Supported Spec Formats
| Format | Description |
|---|---|
py:3.12 |
Use Python 3.12 via Windows launcher. |
conda:myenv |
Use Conda environment myenv. |
C:\Path\to\python.exe |
Use this exact interpreter binary. |
Example Workflows
Developer Switching Between Projects
cd ~/dev/project-a
python -V # => Python 3.12 (from .python-version)
cd ~/dev/project-b
python -V # => Python 3.10 (different .python-version)
Background Service Isolation
Set-AppPython -App 'DataIndexer' -Spec 'conda:data'
set PYSHIM_TARGET=DataIndexer
python -m indexer.main
Temporary Testing
Run-WithPython -Spec 'py:3.9' -- -c "import sys; print(sys.version)"
Maintainers: Building the Installers
-
Run both payload generators whenever
bin/shimschanges so the script and GUI installers stay in sync:pwsh ./tools/New-PyshimInstaller.ps1 -Force pwsh ./tools/New-PyshimSetupPayload.ps1 -Force - Publish two installer artifacts per release:
dist/Install-Pyshim.ps1— the unattended PowerShell installer that automation still uses.installer/Pyshim.Setup/bin/Release/net8.0-windows/win-x64/publish/Pyshim.Setup.exe— the WinForms GUI that end users run.
-
Build the GUI installer with
dotnet publish installer/Pyshim.Setup/Pyshim.Setup.csproj -c Release -r win-x64 -p:PublishSingleFile=true --self-contained false. The output folder already contains the manifest that forces elevation. - GitHub Actions workflow
.github/workflows/build-installer.ymlnow runs both payload generators, publishes the GUI installer, signs both artifacts, and uploads them to releases automatically. - Store the code-signing certificate as repository secrets so the workflow can sign unattended:
WINDOWS_CODESIGN_PFX— base64-encoded.pfxcontaining the code-signing certificate + private key.WINDOWS_CODESIGN_PASSWORD— password protecting the PFX. The workflow uses the local composite action.github/actions/sign-installersto wrapsigntool+Set-AuthenticodeSignaturewith those inputs.
Keep contributor-only notes down here so users don’t confuse the installer generators with the installers themselves.
License
MIT License Copyright (c) 2025 ShruggieTech
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction…
(full license text included in LICENSE)
Links
Credits
Designed and maintained by h8rt3rmin8r for ShruggieTech LLC. Originally conceived as part of the internal “dev-handbook” initiative for consistent Python environments across projects.
¯\_(ツ)_/¯