I also wanted to do this, and I also wanted to be able to carefully control which python virtual environment is used when whatever script I call actually runs. The solution below, although rather extensive, or ridiculous, it may be, it should work with any number of arguments or flags passed to the python script, handling things appropriately and returning an appropriate execution status. The python script should run inside a virtual python environment it will work in and leave the current environment unscathed once completed. Also, when the project directory is on your path it will also still execute in your current working directory seamlessly, just like any other command.
If you need or want to see what the power shell script is really doing, modify the setting $VerbosePreference to your needs. This also happens to turn on debugging in the activate.ps1 script as well, which is rather handy.
I did have to change the windows execution policy on my system to get this to work, however you have to do that anyway if you want to run the .\venv\scripts\activate.ps1 script.
The least intrusive, and least permanent way I've found so far to do this, is to just set the current power shell you are using with Set-ExecutionPolicy Unrestricted -Scope Process. Which can go in a specific terminal initialization file and probably needs some admin rights for that. There are also forms of this command that allow scripts to run in a more sweeping, and more permanent ways as well. I would recommend running a form of this command over editing the registry directly. For more info on that see: 'virtualenv' won't activate on Windows.
I definitely recommend using a virtual environment, but that means the script should use the correct one when it runs, particularly if it needs packages installed that other virtual environments do not. I use python 3.x and here is an example of is how I always build all of mine:
mkdir xuxu ;cd xuxu
python -m venv venv # creates venv environment named venv in current directory .\venv\Scripts\activate.ps1 # activates local venv for this project
python -m pip install --upgrade pip # Important - Upgrade your pip first!
pip install [modules for project]
The solution depends on this being true and also that all of your venv environments will be named 'venv' and live in a subdirectory of the location of your xuxu.ps & xuxu.py. It does not support Conda or other python environment managers.
The thing about these venv environment scripts is they change the environment of the current shell and child shells. We don't want to have two environments conflicting with each other and we don't want the script to leave the current environment polluted with its runtime environment. Therefore, the only clean way I could think of, and there may be a better one, is to have two modes the script can work with a virtual environment.
This is done using one of two modes determined by setting the variable $venvEnvironmentPreference to either useExisting or isolated. In useExisting mode, if there is an active venv, it will use it and won't deactivate it after it's finished. If you don't have a current venv, it will look for a venv in the subdirectory under the directory the script is in, which needs to be on the path if it's not the current directory.
In isolated mode, if you are in a different venv than the xuxu venv, it will deactivate that, then activate the xuxu venv, run xuxu.py with its own specific venv, and then deactivate it and reactivate the venv you were originally in.
In either mode, if you are not in a venv when you execute it, it will use the xuxu venv and deactivate it when completed, leaving your power shell environment untainted.
With this solution, you'll need a xuxu.ps1 like this for each command xuxu.py you have, or you won't be able to xuxu --flags arg1..argN. and everyone wants to xuxu without being bothered, including me. The other reason is you may want to have specific differences in these scripts as they may be different depending on what you are doing with them at the time, or other specific requirements.
Also, if the xuxu directory is not on your path and that is where you are when you run it, then you will need to add .\ on your path or you will still have to issue: .\xuxu ..args.. and nobody wants that. A more secure and less sweeping option is to add it to the path temporarily for the current shell and all children with something nonexplosive either when you first get started, or in your shell initialization script like this:
$currentDir = $PWD.Path if (($env:PATH -split ';') -notcontains $currentDir) { $env:PATH = "$env:PATH:$currentDir" }
# xuxu.ps1 - PowerShell execution wrapper for xuxu.py
# --- Configuration ---
#$venvEnvironmentPreference = "useExisting"
$venvEnvironmentPreference = "isolated"
# $VerbosePreference = "SilentlyContinue"
$VerbosePreference = "Continue" # Enable verbose output for debugging
# --- Script Start ---
$ScriptDir = $PSScriptRoot
$PythonScriptPath = Join-Path -Path $ScriptDir -ChildPath "xuxu.py"
$VenvDir = Join-Path -Path $ScriptDir -ChildPath "venv"
$VenvActivateScript = Join-Path -Path $VenvDir -ChildPath "Scripts\Activate.ps1"
$invokedLocalVenv = $false
$originalVenvPath = $null
# Check if the Python script exists first
if (-not (Test-Path $PythonScriptPath -PathType Leaf)) {
Write-Error "Error: Python script not found at '$PythonScriptPath'."
exit 1
}
# --- Virtual Environment Handling ---
Write-Verbose "Virtual environment preference set to: '$venvEnvironmentPreference'"
# Check if the local venv activation script exists (needed for both modes eventually)
if (-not (Test-Path $VenvActivateScript -PathType Leaf)) {
Write-Error "Error: Local virtual environment activation script not found at '$VenvActivateScript'."
Write-Error "Please ensure a virtual environment named 'venv' exists in the script directory: $ScriptDir"
exit 1
}
# Handle based on preference
if ($venvEnvironmentPreference -eq "isolated") {
Write-Verbose "Mode: Isolated. Preparing local venv."
$originalVenvPath = $env:VIRTUAL_ENV
if ($originalVenvPath) {
Write-Verbose "External virtual environment detected: $originalVenvPath. Attempting deactivation."
if (Get-Command deactivate -ErrorAction SilentlyContinue) {
try {
deactivate
Write-Verbose "Deactivated external venv successfully."
if ($env:VIRTUAL_ENV) { Remove-Item Env:\VIRTUAL_ENV }
} catch {
Write-Warning "Warning: Failed to deactivate the external virtual environment. Attempting to proceed. Details: $($_.Exception.Message)"
}
} else {
Write-Warning "Warning: 'deactivate' command not found. Cannot deactivate external venv '$originalVenvPath' automatically. Attempting to proceed."
}
} else {
Write-Verbose "No external virtual environment detected."
}
Write-Verbose "Attempting to activate local venv: $VenvActivateScript"
try {
. $VenvActivateScript
$invokedLocalVenv = $true
Write-Verbose "Local venv activated successfully for isolated mode."
} catch {
Write-Error "Error activating the local virtual environment for isolated mode. Details: $($_.Exception.Message)"
exit 1
}
} elseif ($venvEnvironmentPreference -eq "useExisting") {
Write-Verbose "Mode: useExisting. Checking for active venv."
if ($env:VIRTUAL_ENV) {
Write-Verbose "External virtual environment detected: $env:VIRTUAL_ENV. Using existing venv."
$currentVenvPathNormalized = $null
$localVenvPathNormalized = $null
try { $currentVenvPathNormalized = (Resolve-Path $env:VIRTUAL_ENV).Path } catch {}
try { $localVenvPathNormalized = (Resolve-Path $VenvDir).Path } catch {}
if ($currentVenvPathNormalized -and $localVenvPathNormalized -and ($currentVenvPathNormalized -eq $localVenvPathNormalized)) {
Write-Verbose "The active virtual environment appears to be the local one."
}
} else {
Write-Verbose "No external virtual environment detected. Attempting to activate local venv."
try {
. $VenvActivateScript
$invokedLocalVenv = $true
Write-Verbose "Local venv activated successfully for useExisting mode."
} catch {
Write-Error "Error activating the local virtual environment. Details: $($_.Exception.Message)"
exit 1
}
}
} else {
Write-Warning "Warning: Invalid value '$venvEnvironmentPreference' for \$venvEnvironmentPreference. Defaulting to 'useExisting'."
if ($env:VIRTUAL_ENV) {
Write-Verbose "External virtual environment detected: $env:VIRTUAL_ENV. Using existing venv."
} else {
Write-Verbose "No external virtual environment detected. Attempting to activate local venv."
try {
. $VenvActivateScript
$invokedLocalVenv = $true
Write-Verbose "Local venv activated successfully (defaulted from invalid preference)."
} catch {
Write-Error "Error activating the local virtual environment. Details: $($_.Exception.Message)"
exit 1
}
}
}
# --- Execute Python Script ---
$pythonExitCode = 1
try {
Write-Verbose "Executing: python ""$PythonScriptPath"" $args"
$command = "python ""$PythonScriptPath"" $args"
Invoke-Expression $command
$pythonExitCode = $LASTEXITCODE
Write-Verbose "Python script finished with exit code: $pythonExitCode"
} catch {
$ErrorMessage = if ($_.Exception) { $_.Exception.Message } else { $_ }
Write-Error "Error executing Python script '$PythonScriptPath'. Details: $ErrorMessage"
}
# --- Cleanup ---
$reactivationNeeded = $false
$originalVenvActivateScript = $null
if ($venvEnvironmentPreference -eq "isolated" -and $originalVenvPath) {
$reactivationNeeded = $true
$originalVenvActivateScript = Join-Path -Path $originalVenvPath -ChildPath "Scripts\Activate.ps1"
Write-Verbose "Original venv path stored: $originalVenvPath. Reactivation will be attempted using: $originalVenvActivateScript"
}
if ($invokedLocalVenv) {
Write-Verbose "Attempting to deactivate locally activated virtual environment."
if (Get-Command deactivate -ErrorAction SilentlyContinue) {
try {
deactivate
Write-Verbose "Local venv deactivated."
if ($env:VIRTUAL_ENV) { Remove-Item Env:\VIRTUAL_ENV }
} catch {
Write-Warning "Warning: 'deactivate' command failed for the local venv. The environment might be in an inconsistent state. Details: $($_.Exception.Message)"
$reactivationNeeded = $false
}
} else {
Write-Warning "Warning: 'deactivate' command not found after script execution. Cannot deactivate local venv automatically."
$reactivationNeeded = $false
}
} else {
Write-Verbose "Skipping deactivation (script did not activate the local venv)."
}
if ($reactivationNeeded) {
Write-Verbose "Attempting to reactivate original virtual environment: $originalVenvPath"
if ($originalVenvActivateScript -and (Test-Path $originalVenvActivateScript -PathType Leaf)) {
try {
. $originalVenvActivateScript
Write-Verbose "Original virtual environment reactivated successfully."
if ($env:VIRTUAL_ENV -ne $originalVenvPath) {
Write-Verbose "Warning: VIRTUAL_ENV variable might not be correctly restored after reactivation."
}
} catch {
Write-Warning "Error reactivating the original virtual environment '$originalVenvPath' using '$originalVenvActivateScript'. You may need to reactivate it manually. Details: $($_.Exception.Message)"
}
} else {
Write-Warning "Cannot find the assumed activation script '$originalVenvActivateScript' for the original virtual environment '$originalVenvPath'. You may need to reactivate it manually."
}
} else {
Write-Verbose "Reactivation of an original venv was not required or not possible."
}
Write-Verbose "Script finished. Exiting with Python script's exit code: $pythonExitCode"
exit $pythonExitCode