Skip to content

Instantly share code, notes, and snippets.

@dlenski
Created June 14, 2025 00:08
Show Gist options
  • Save dlenski/234e2fcf5eaa16687b7ce2c04ed01ad4 to your computer and use it in GitHub Desktop.
Save dlenski/234e2fcf5eaa16687b7ce2c04ed01ad4 to your computer and use it in GitHub Desktop.
Access Windows environment variables from Python under WSL
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