'Is there a way to get access to Windows environment variables in wsl?
I was thinking of adding something like this to my setup.bash script.
ln -s /mnt/c/Users/Ryan/Downloads $HOME/Downloads
But obviously that isn't always an accurate path so I was hoping to be able to do to something like
ln -s /mnt/c/Users/%USERNAME%/Downloads $HOME/Downloads
or
ln -s %USERPROFILE%/Downloads $HOME/Downloads
I know that obviously Windows % vars wouldn't work in bash but it would be cool if wsl could/does export those vars as $Win32.HOME
or $Win32.USER
or something.
Any thoughts?
Is there any way to do this already?
Solution 1:[1]
Remembering that it's a shell's job to evaluate environment variables, and since you can invoke Windows exe's from within Linux on WSL, you can ask a Windows shell (Cmd or PowerShell) to expand out a Windows env-var. Here's how you'd do that with Cmd:
$ cmd.exe /c echo %username%
richturn
$
Alternatively, you can choose to project some of your Windows environment variables into WSL if you prefer :)
Solution 2:[2]
Here is what I did:
Since WSL now has interop between windows and WSL, I took advantage of that.
I have a powershell script in my ~/
folder called ~/.env.ps1
# Will return all the environment variables in KEY=VALUE format
function Get-EnvironmentVariables {
return (Get-ChildItem ENV: | foreach { "WIN_$(Get-LinuxSafeValue -Value ($_.Name -replace '\(|\)','').ToUpper())='$(Convert-ToWSLPath -Path $_.Value)'" })
}
# converts the C:\foo\bar path to the WSL counter part of /mnt/c/foo/bar
function Convert-ToWSLPath {
param (
[Parameter(Mandatory=$true)]
$Path
)
(Get-LinuxSafeValue -Value (($Path -split ';' | foreach {
if ($_ -ne $null -and $_ -ne '' -and $_.Length -gt 0) {
(( (Fix-Path -Path $_) -replace '(^[A-Za-z])\:(.*)', '/mnt/$1$2') -replace '\\','/')
}
} ) -join ':'));
}
function Fix-Path {
param (
[Parameter(Mandatory=$true)]
$Path
)
if ( $Path -match '^[A-Z]\:' ) {
return $Path.Substring(0,1).ToLower()+$Path.Substring(1);
} else {
return $Path
}
}
# Ouputs a string of exports that can be evaluated
function Import-EnvironmentVariables {
return (Get-EnvironmentVariables | foreach { "export $_;" }) | Out-String
}
# Just escapes special characters
function Get-LinuxSafeValue {
param (
[Parameter(Mandatory=$true)]
$Value
)
process {
return $Value -replace "(\s|'|`"|\$|\#|&|!|~|``|\*|\?|\(|\)|\|)",'\$1';
}
}
Now that I have that, in my .bashrc
I have something like the following:
function winenv() {
echo $(powershell.exe -Command "Import-Module .\.env.ps1; Import-EnvironmentVariables") | sed -e 's|\r|\n|g' -e 's|^[\s\t]*||g';
}
eval $(winenv)
A caveat to this, that I have found, is that I had to put the full path to .env.ps1
in that command. What I did for that was wrote a function that converts the wsl style path back to the windows path. Then used that to translate it.
CMD_DIR=$(wsldir "/mnt/c/Users/$USER/AppData/Local/lxss$HOME/\.env.ps1")
because this is the function that loads the environment variables, I have to hard code the full path, to some extent. I did end up actually setting an environment variable in bash called LXSS_ROOT=/mnt/c/Users/$USER/AppData/Local/lxss
and then used that.
Then, when I start a new shell, and I run env
I get the following:
WIN_ONEDRIVE=/mnt/d/users/rconr/onedrive
PATH=~/bin:/foo:/usr/bin
WIN_PATH=/mnt/c/windows:/mnt/c/windows/system32
and I can now add something like ln -s "$WIN_ONEDRIVE" "~/OneDrive"
to my .bashrc
For your example, you would do this:
ln -s $WIN_USERPROFILE/Downloads $HOME/Downloads
Additionally, I then created a script in my bin path called powershell
.
# gets the lxss path from windows
function lxssdir() {
if [ $# -eq 0 ]; then
if echo "$PWD" | grep "^/mnt/[a-zA-Z]/" > /dev/null 2>&1; then
echo "$PWD";
else
echo "$LXSS_ROOT$PWD";
fi
else
echo "$LXSS_ROOT$1";
fi
}
PS_WORKING_DIR=$(lxssdir)
if [ -f "$1" ] && "$1" ~= ".ps1$"; then
powershell.exe -NoLogo -ExecutionPolicy ByPass -Command "Set-Location '${PS_WORKING_DIR}'; Invoke-Command -ScriptBlock ([ScriptBlock]::Create((Get-Content $1))) ${*:2}"
elif [ -f "$1" ] && "$1" ~!= "\.ps1$"; then
powershell.exe -NoLogo -ExecutionPolicy ByPass -Command "Set-Location '${PS_WORKING_DIR}'; Invoke-Command -ScriptBlock ([ScriptBlock]::Create((Get-Content $1))) ${*:2}"
else
powershell.exe -NoLogo -ExecutionPolicy ByPass ${*:1}
fi
unset PS_WORKING_DIR
So I can then do something like this:
$ powershell ~/my-ps-script.ps1
$ powershell -Command "Write-Host 'Hello World'"
I am sure there are improvements that can be made to all of this. But, it is currently working for my scenario.
Here is a gist of the scripts I use.
Solution 3:[3]
Reviving this old post with this simple bash function I decided to add to my /etc/bash.bashrc
winenv()
{
if [ "$#" == "0" ] || [ "$1" == "--help" ]
then
echo $'\n'Usage:
echo $'\t'winenv [-d] WINDOWS_ENVIRONEMENT_VARIABLE_NAME
echo $'\t'-d: Defines environment variable in current shell
echo $'\t'" "Note that paths will be translated into un*x-like paths$'\n'
return
fi
local IFS='$\n'
local PATH_TO_TRANSLATE=$1
[ "$1" == "-d" ] && PATH_TO_TRANSLATE=$2
local VAR=$(cmd.exe /c echo %${PATH_TO_TRANSLATE}% | tr -d '\r')
local NEW=$(wslpath -u "${VAR}" 2>/dev/null || echo ${VAR})
echo "${PATH_TO_TRANSLATE} = ${VAR} -> ${NEW}"
[ "$1" == "-d" ] && export "${PATH_TO_TRANSLATE}=${NEW}"
}
for EnvVar in 'USERNAME' 'USERPROFILE' 'USERDOMAIN' 'USERDNSDOMAIN' 'WINDIR' 'SystemDrive' 'SystemRoot' 'TNS_ADMIN' 'ORACLE_HOME' 'CLIENT_NAME' 'HOMEDRIVE' 'HOMEPATH' 'TMP' 'TEMP'
do
winenv -d $EnvVar >/dev/null
done
Solution 4:[4]
In newer versions of WSL2, which I believe have been backported to Win10 1093 and 1909, along with 2004, Microsoft introduced an environment variable WSLENV
that is shared between Win32 and WSL to accomplish exactly what you're after.
You set it equal to a colon-seperated list of variables, together with some syntax to trigger path conversion, and whether the variable should be shared bi-directionally, or whether it should only be shared from Win32-->WSL2 or WSL2-->Win32.
For your specific use-case, on Win32 set WSLENV=USERPROFILE/p
. That will trigger the Win32 env variable %USERPROFILE%
to be passed to WSL (and back to Win32 if you call cmd
or similar once you're in WSL), and to perform path translation.
Now in your setup.bash
script, you can do
ln -s $USERPROFILE/Downloads $HOME/Downloads
For the various syntax of this functionality, see the Microsoft article Share Environment Vars between WSL and Windows.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Rich Turner |
Solution 2 | |
Solution 3 | |
Solution 4 |