Ryan
Ryan

Reputation: 415

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?

Upvotes: 22

Views: 21254

Answers (7)

S. Jacob Powell
S. Jacob Powell

Reputation: 406

Sharing Environment Variables between Windows and WSL

I found that you can use the WSLENV interop environment variable (it allows sharing environment variables between Windows/Win32 and WSL).

So in Windows environment variables (I used the GUI but you can do this from CMD/Powershell with setx) I set: WSLENV to Path/ul

  • The /u says that it's meant for WSL (meaning, share it from Windows to WSL, I believe. I think "u" as in "Unix/Ubuntu")
  • The /l means it is a list of paths
  • See my attached references for more info on the 4 available flags

Then in my ~/.zshrc (since I use zsh; if you use bash then ~/.bashrc or equivalent), I added this:

export WINDOWS_PATH=$Path
path+=($WINDOWS_PATH)

Which makes a WSL env var called WINDOWS_PATH, then appends the value of that to my WSL PATH env var (in a kind of weird notation I came across at some point; you can also do PATH=$PATH:$WINDOWS_PATH).

Keep in mind that Windows is case insensitive and Linux is case sensitive; I exploit this by sharing Path. If I shared PATH, then my Linux (WSL) side gets its PATH variable overwritten, so luckily I could just use Path and then rename it in WSL to WINDOWS_PATH for readability.


References for WSLENV

https://learn.microsoft.com/en-us/windows/wsl/filesystems#interoperability-between-windows-and-linux-commands

https://devblogs.microsoft.com/commandline/share-environment-vars-between-wsl-and-windows/


Additional notes

I found out that my VSCode, when connected to WSL, basically shares one process/environment/rc file (presumably the VSCode Server process), or at least the WSLENV changes on the Windows environment variables did not show up til I completely restarted (all open windows of) VSCode. Once I did though, I saw my changes in the VSCode integrated terminal (and the WSLENV part that VSCode adds as well).

I now can see all of my .exe files in my WSL because of forwarding the Path environment variable as explained above, so I aliased my 1Password exe op=op.exe and since op.exe is on the PATH now, this works great. In fact, because the VSCode server behaves as it does as mentioned above, it seems to integrate with the op.exe just fine which is now on the PATH. This means that so far, the 1Password integration with the CLI (on the Windows application), seems to actually work now with WSL.

When I open a WSL terminal, I get a Windows (from 1Password) popup asking to approve/allow the application to login, and I have to enter my password. This is likely not extremely ideal, as I'm sure it's supposed to stay logged in, but I'm just happy that this seems to be working quite well. I tried the Linux 1password op binary, and I got it working but it wouldn't integrate with the Windows side at all.


Hope this helps!

Upvotes: 1

dimo414
dimo414

Reputation: 48794

I added this to my .bashrc so I can reference my Windows user directory in a similar way to $HOME:

WIN_HOME=$(wslpath -au "$(cmd.exe /c 'echo %UserProfile%')")

This assigns $WIN_HOME to a value like /mnt/c/Users/Ryan. This question discusses wslpath and other approaches.

You can optionally export the variable, but that's only needed if you're going to read this variable from subprocesses and not just directly in your shell.

Upvotes: 0

Ryan Feeley
Ryan Feeley

Reputation: 667

In newer versions of WSL2, which I believe have been backported to Win10 1903 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.

Upvotes: 7

Rich Turner
Rich Turner

Reputation: 10984

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 :)

Upvotes: 20

Andy P
Andy P

Reputation: 325

rather old, but I find this very useful.

this changes dirs to your windows home directory:

cd `cmd.exe /c echo %systemdrive%%homepath% 2> /dev/null | tr -d '\r' | xargs -0 wslpath`

Upvotes: 0

MensSana
MensSana

Reputation: 570

Reviving this old post with this simple bash function I decided to add to my /etc/bash.bashrc

winenv()
{
  if [[ "$#" -eq 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=$(2>/dev/null cmd.exe /c echo %${PATH_TO_TRANSLATE}% | tr -d '\r')
  local NEW=$(wslpath -u "${VAR}" 2>/dev/null || echo ${VAR})
  echo "${PATH_TO_TRANSLATE} = $(printf '%q' "${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

Upvotes: 2

Ryan Conrad
Ryan Conrad

Reputation: 6900

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.

Upvotes: 4

Related Questions