Home > Software design >  Powershell SetEnvironmentVariable not setting the variables as expected
Powershell SetEnvironmentVariable not setting the variables as expected

Time:10-18

I'm attempting to save some environment variables based on the output of the command "wsl --list" using Powershell, when I debug this code it seems to be flowing as expected however when I inspect my environment variables I'm unable to find the expected keys and values.

When I use the same SetEnvironmentVariable method with any other hardcoded value it seems to work. Write-Host on $distroName results in the expected string too so I'm honestly lost on this. Any help would be appreciated! Here is my code:

$wslListOutput = wsl --list
((Get-ChildItem env:*).Name | Select-String -Pattern "(SINDAGAL_INIT_DISTRO_([a-zA-Z=])*)|SINDAGAL_DEFAULT_DISTRO")
foreach ($line in $wslListOutput)
{
  $lineIsEmpty = ("" -eq $line) -or ([string]::IsNullOrWhiteSpace($line))
  $Introline = $line -eq "Windows Subsystem for Linux Distributions:"

  if($lineIsEmpty -or $Introline){ continue } 
  
  if($line -Match "(([a-zA-Z]*) ([(Default)])*)"){
     $distroName = ($line -split ' ',2)[0]    
     [System.Environment]::SetEnvironmentVariable("SINDAGAL_DEFAULT_DISTRO",$distroName)
  } else{
     $distroName = $line.ToUpper()
     $variablePath = "Env:SINDAGAL_INIT_DISTRO_${distroName}"
     [System.Environment]::SetEnvironmentVariable("SINDAGAL_INIT_DISTRO_${distroName}",$true)
  }
}

# Cannot see the variables which are supposed to be set in here at all
((Get-ChildItem env:*).Name)

my wsl --list output:

┖[~]> wsl --list
Windows Subsystem for Linux Distributions:
Debian (Default)
Alpine

CodePudding user response:

This is an encoding issue. Redirected output of wsl is UTF-16 LE encoded, which is not recognized by PowerShell (as of PS 7.1.5). It tries to interpret the output as UTF-8, so we end up with embedded \0 characters (for UTF-16 LE encoded ASCII characters, every 2nd byte is 0). These cause the string to be clipped, when calling SetEnvironmentVariable, because the API expects null-terminated strings.

Workarounds

As of PS 7.x it seems to be sufficient to set

[Console]::OutputEncoding = [Text.Encoding]::Unicode

before launching the wsl process. Afterwards the encoding should be restored. See this answer for more in-depth information and an Invoke-WithEncoding cmdlet, which automates the process of temporarily changing and restoring the encoding.

As another workaround we can use Start-Process to redirect the output to a file as-is, then use Get-Content to read the file with forced UTF-16 LE (aka "Unicode") encoding. We also use parameter -raw to read the file as a single string, because WSL writes strange line separators, which confuses PowerShell when it tries to split the input. Better split the string by ourselfs using -split operator.

As suggested by commenter, I've also added "machine" argument to SetEnvironmentVariable so the variable value will persist when the PowerShell session ends. Note this requires admin privileges when running the script.

# Redirect WSL output to temp file, which keeps the UTF-16 encoding intact
$tempFilePath = (New-TemporaryFile).FullName
Start-Process -FilePath wsl -ArgumentList '--list' -RedirectStandardOutput $tempFilePath -NoNewWindow -Wait

# Read temp file with forced UTF-16 LE encoding
$wslListOutput = Get-Content $tempFilePath -Raw -Encoding unicode

# Split on new lines ("\r*" because WSL writes duplicate "\r" chars) 
$wslLines = $wslListOutput -split "\r*\n"

foreach ($line in $wslLines)
{
  $lineIsEmpty = ("" -eq $line) -or ([string]::IsNullOrWhiteSpace($line))
  $Introline = $line -eq "Windows Subsystem for Linux Distributions:"

  if($lineIsEmpty -or $Introline){ continue } 
  
  if($line -Match "(([a-zA-Z]*) ([(Default)])*)"){
     $distroName = ($line -split ' ',2)[0]    
     [System.Environment]::SetEnvironmentVariable("SINDAGAL_DEFAULT_DISTRO",$distroName,"machine")
  } else{
     $distroName = $line.ToUpper()
     $variablePath = "Env:SINDAGAL_INIT_DISTRO_${distroName}"
     [System.Environment]::SetEnvironmentVariable("SINDAGAL_INIT_DISTRO_${distroName}",$true,"machine")
  }
}

Get-ChildItem env:SINDAGAL_*
  • Related