Created
June 14, 2025 00:08
-
-
Save dlenski/234e2fcf5eaa16687b7ce2c04ed01ad4 to your computer and use it in GitHub Desktop.
Access Windows environment variables from Python under WSL
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
r''' | |
Access Windows environment variables from Python under WSL | |
The :py:data:`winenv` and py:data:`winenv_wslpath` objects are | |
immutable mappings, lazy-loaded upon initial usage. | |
py:data:`winenv` contains the unmodified Windows environment variables, | |
while py:data:`winenv_wslpath` attempts to convert all ``PATH``-like | |
variables to WSL paths. | |
Example: | |
from wslenv import winenv, winenv_wslpath | |
print("Windows username is", winenv['USERNAME']) | |
print("WSL-ified Windows PATH is", winenv_wslpath['PATH']) | |
This requires WSL and PowerShell 3.0 installed in the standard location of | |
``C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe``. | |
It grew out of https://stackoverflow.com/a/79662726 | |
''' | |
import subprocess as sp | |
import json | |
from typing import Union, Container, Mapping | |
from pathlib import PureWindowsPath, PurePosixPath | |
import shlex | |
import sys | |
from warnings import warn | |
__all__ = ['winenv', 'winenv_wslpath'] | |
def _load_winenv() -> dict: | |
env2json = '[System.Environment]::GetEnvironmentVariables() | ConvertTo-Json' | |
# Robustness fixes based on https://github.com/wslutilities/wslu/blob/1bb1b53c0532394a1433b2eaa041b1c1e6ce0b69/src/wslu-header#L290, | |
# with the addition of ErrorActionPreference from https://stackoverflow.com/a/9949909/20789. | |
cli = [ | |
'/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe', | |
'-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', | |
'$ErrorActionPreference = "Stop"; [Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ' + env2json | |
] | |
try: | |
return json.loads(sp.check_output(cli, stdin=sp.DEVNULL)) | |
except sp.CalledProcessError as exc: | |
raise NotImplementedError("Could not get environment variables from PowerShell in JSON format; PowerShell 3.0+ is required") from exc | |
except FileNotFoundError as exc: | |
raise NotImplementedError(f"Could not invoke {cli[0]}; may not be running under WSL") from exc | |
def _convert_paths(winenv: Mapping, paths: Union[Container, bool] = True) -> Mapping: | |
if not paths: | |
return winenv | |
j = {} | |
for var, vals in winenv.items(): | |
convert = vals.startswith('\\') or vals[1:2] == ':' if paths == True else var in paths | |
if convert: | |
winvals = map(PureWindowsPath, vals.split(';')) | |
outvals = [] | |
for val in winvals: | |
if val.anchor.startswith('\\\\'): | |
warn(f'Windows environment variable {var!r} contains UNC path {shlex.quote(str(val))}, not converting') | |
elif val.anchor == '\\': | |
anchor = winenv.get('SystemDrive', 'C:') | |
warn(f'Windows environment variable {var!r} contains driveless path {shlex.quote(str(val))}, assuming {anchor!r}') | |
val = PurePosixPath(f'/mnt/{anchor.rstrip("\\").removesuffix(":").lower()}') / val.relative_to(val.anchor) | |
elif not val.is_absolute(): | |
warn(f'Windows environment variable {var!r} contains relative path {shlex.quote(str(val))}, not converting') | |
else: | |
val = PurePosixPath(f'/mnt/{val.anchor.rstrip("\\").removesuffix(":").lower()}') / val.relative_to(val.anchor) | |
outvals.append(str(val)) | |
else: | |
j[var] = ':'.join(outvals) | |
else: | |
j[var] = vals | |
return j | |
if __name__ != '__main__': | |
from types import MappingProxyType | |
winenv = MappingProxyType(_load_winenv()) | |
winenv_wslpath = MappingProxyType(_convert_paths(winenv)) | |
else: | |
import argparse | |
p = argparse.ArgumentParser( | |
description='''Capture Windows environment variables in a format that can be used under Windows Subsystem for Linux. | |
Requires PowerShell 3.0+ (Windows 8+). | |
Under bash or other POSIX Bourne shells, use: eval "$(%(prog)s)"''') | |
g = p.add_argument_group('Path conversion') | |
x = g.add_mutually_exclusive_group() | |
x.add_argument('-w', '--wslpath', metavar='COMMA_SEPARATED_LIST', default=True, | |
help="Only try to convert these specific Windows environment variables to WSL paths (default is to try to convert all)") | |
x.add_argument('-W', '--no-wslpath', dest='wslpath', action='store_false', | |
help='Do not try to convert any Windows environment variables containing paths to WSL paths (default is to try to convert all)') | |
g = p.add_argument_group('Output format') | |
x = g.add_mutually_exclusive_group() | |
x.add_argument('-A', '--associative', metavar='ARRAY_NAME', | |
help='Output environment variables in named associative array (Bash extension)') | |
x.add_argument('-b', '--sh', '--bash', dest='format', action='store_const', const='sh', default='sh', | |
help='Output environment variables as POSIX Bourne shell "export" commands (the default)') | |
x.add_argument('-c', '--csh', dest='format', action='store_const', const='csh', | |
help='Output environment variables as C Shell "setenv" commands') | |
x.add_argument('-j', '--json', dest='format', action='store_const', const='json', | |
help='Output environment variables as JSON') | |
p.add_argument('-p', '--prefix', default='WIN_', help='Prefix for variable names in sh/csh output (default %(default)r)') | |
args = p.parse_args() | |
try: | |
winenv = _load_winenv() | |
except Exception as exc: | |
p.error(f'Error loading Windows environment variables: {exc.args}') | |
winenv = _convert_paths(winenv, paths=args.wslpath) | |
if args.associative: | |
print(f'declare -A {args.associative}') | |
for var, val in winenv.items(): | |
print(f'{args.associative}[{shlex.quote(var)}]={shlex.quote(val)}') | |
elif args.format == 'json': | |
json.dump(winenv, sys.stdout) | |
else: | |
kw = 'setenv' if args.format == 'csh' else 'export' | |
for var, val in winenv.items(): | |
if not var.isidentifier(): | |
warn(f'Windows environment variable {var!r} is not a valid POSIX shell identifier, commenting out.') | |
print(("" if var.isidentifier() else "# ") + f'{kw} {args.prefix}{var}={shlex.quote(val)}') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment