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
Cause
This is an encoding issue. Redirected output of wsl is UTF-16 LE encoded, which is not automatically recognized by PowerShell (as of PS 7.1.5). Instead it always interprets the output using the encoding stored in [Console]::OutputEncoding, which on Windows defaults to the given system's legacy OEM code page (e.g. 437 on US-English systems).
As UTF-16 is a two-byte encoding we typically end up with embedded \0 characters (code points below 256, such as the ASCII character set). These cause the string to be clipped, when calling SetEnvironmentVariable, because the API expects null-terminated strings.
Solution
Set [Console]::OutputEncoding to match the output encoding of the process, before launching it and restore the original encoding afterwards.
$oldEncoding = [Console]::OutputEncoding
[Console]::OutputEncoding = [Text.Encoding]::Unicode # on Windows: UTF-16 LE
$wslListOutput = wsl --list
[Console]::OutputEncoding = $oldEncoding
Another common output encoding used by some programs is [Text.Encoding]::UTF8.
See this answer for more in-depth information and an Invoke-WithEncoding cmdlet, which automates the process of temporarily changing and restoring the encoding.
Related
I've found a couple of resources (including Convert base64 string to file, which is practically a duplicate here since it's one of the resources I used to build this) but I can't seem to get it working.
I've got the following code (roughly - obviously it's stripped down,) and I can verify most of the steps of the process as per the comments.
$pic = Get-Content 'testpic.png'
# $pic looks like a binary dump.
$picBytes = [System.Text.Encoding]::Unicode.GetBytes($pic)
$ $picBytes is an array of bytes. Quite spammy.
$picEncoded = [Convert]::ToBase64String($picBytes)
# $picEncoded is indeed a Base64 string. Halfway there!
$picDecoded = [Convert]::FromBase64String($picEncoded)
# Also an array of bytes. I'm assuming they're right for now...
$outFile = "pic.png"
[IO.File]::WriteAllBytes($outFile,$picDecoded)
# but I get no output file and no error?
What am I missing here? For what it's worth, I'm willing to look at other solutions - but the Base64 is somewhat important (since I'm storing the data in the script.)
To read a binary file as-is into memory in PowerShell, use Get-Content's -AsByteStream switch (PowerShell (Core) 7+) / -Encoding Byte (Windows PowerShell, versions up to v5.1), and add the -Raw switch for efficiency when you're reading all bytes into memory at once:
# Windows PowerShell (up to v5.1).
# Note: In PowerShell (Core) v7+, you must use -AsByteStream instead of
# -Encoding Byte
$picBytes = Get-Content testpic.png -Encoding Byte -Raw
Note: This change in syntax between the PowerShell editions is unfortunate, as discussed in GitHub issue #7986. If enough people show interest, it is conceivable that -Encoding Byte will be reintroduced for cross-edition consistency and compatibility.
$picBytes, as a [byte[]] array, can then be passed directly to [Convert]::ToBase64String()
To pass a file name/path to a .NET method, always pass a full path, never a relative path or mere file name:
This is necessary, because .NET's working directory usually differs from PowerShell's.
This discrepancy is unfortunate, but cannot be avoided, as explained in this answer.
In the simplest case - if your current location is a file-system location that is not based on a PowerShell-specific drive:
$outFile = "$PWD/pic.png" # Use *full path*
[IO.File]::WriteAllBytes($outFile, $picDecoded)
The fully robust approach requires more work:
$outFile = Join-Path (Get-Location -PSProvider FileSystem).ProviderPath pic.png
[IO.File]::WriteAllBytes($outFile, $picDecoded)
I have a script task in powershell inline script in which i use
$text2 = "isApproved=$isApproved"
then i use,
Out-File -FilePath "${bamboo.build.working.directory}\repovar.properties" -InputObject $text2 -Append -Encoding utf8
$isApproved is determined in the script and can have value 0/1.
the properties file is showing proper key-value pair (isApproved=0). However, when i run the inject bamboo variable task, it injects a '?' symbol in the variable name
10-Aug-2020 05:17:58 key: [inject.?isApproved] value: [0] type: RESULT
It's a peculiar problem as it sometimes inject properly but sometimes doesn't. All other variables are injected in proper format.
When i remove the -Encoding utf8 in the cmdlet to default (utf8 with NoBOM), it then writes like this
i s A p p r o v e d = 0 and bamboo injects like this
bamboo.inject._i_s_A_p_p_r_o_v_e_d
I have tried with batch script as well, i still see a '?'. Can anybody help me with an workaround?
If i switch to script file instead of inline script, can i still use the previous inject variables??
This is not well documented indeed - you need to replace dots with underscores, i.e. for a plan variable named your.plan.variable, which you would reference in a regular Bamboo task as ${bamboo.your.plan.variable}, the resp. PowerShell syntax for use within the Script task is $bamboo_your_plan_variable.
I found the answer from the Atlassian forum, here it is
Out-File -FilePath "${bamboo.build.working.directory}\repovar.properties" -InputObject $text2 -Append -Encoding ascii
Changing encoding to ASCII did the trick.
I have the following scenario, running on Powershell v5:
A Powershell script pulls several bits of information from an API call to a 3rd party system in a bulk for-each loop and assigns them to Variables. Amongst the information that is pulled is Passwords (this is being done to get rid of said 3rd party system and to migrate it to something that doesn't allow you to retrieve passwords in plain text):
$userset = Invoke-WebRequest -Method Post -Uri "https://$Url/path/to/api.asmx" -Headers $Headers -Body $usercall
$xmluserset = [xml] $userset.Content
$userset2 = $xmluserset.Envelope.Body.UserSettingsResult.settingValues.string
$userpasstemp = $userset2[1].trimstart("password")
$userpass = $userpasstemp.trimstart("=")
These passwords are then used elsewhere in the Script.
For example, they are passed to a different API and need to be in a URL compatible format and so I run this:
$urlescapeduserpass = [uri]::EscapeDataString($userpass)
which works for that section of the Script
The problem is that these passwords can contain any of the special characters:
!"#$%&'()*+,-./:;<=>?#[]^_`{|}~
And when I call another part of the script, the special characters in the password string cause a failure and the script to exit. This occurs when using either the call command:
& .\application.exe --option1 $option1 --user1 $user --password1 $userpass
or when using invoke-expression
$command = "$path\application.exe --option1 $option1 --user1 $user --password1 $userpass"
Invoke-Expression $command
I've tried using Regex, using the -replace cmdlet:
$escapedpass = $userpass -replace ' !"#$%&()*+,-./:;<=>?#[\]^_`{|}~', '`$&'
But no luck, I know similar to the [uri]escapedatastring, there's a similar one for Regex, but there doesn't appear to be one native for Powershell. I'm sure there is either a [contenttype] that will have a native function to escape the special characters or some way to achieve the end-result.
Because PowerShell's handling of embedded " characters in argument passed to external programs is broken (as of PowerShell 7) - see this answer - you need to manually \-escape " characters embedded in your string:
$escapedpass = $userpass -replace , '"', '\"'
In the context of your command:
& .\application.exe --option1 $option1 --user1 $user --password1 ($userpass -replace , '"', '\"')
Let's create a Minimal, Reproducible Example with a round trip based on the answer How to escape special characters in PowerShell? from #mklement0 for this:
(Also take the comment in account that I just made on spaces)
Unfortunately, PowerShell creates an extra handicap as it requires to single quote the string otherwise it will interpret other characters along with the dollar sign ($). I have placed comments in the code where you might leave this out.
Password.ps1
Param (
[String]$Password
)
Write-Host $Password
Password check:
$Input = #'
!"'#$%&()*+,-./:;<=>?#[\]^_`{|}~
'#
Write-Host 'Input:' $Input
$Escape = $Input.Replace('"', '\"')
$Escape = $Escape.Replace("'", "''") # Only required for PowerShell
Write-Host 'Command:' PowerShell .\Password.ps1 "'$Escape'"
# Omit the inner single quotes if it doesn't concern PowerShell
# But note that your .\application.exe might have its own syntax
# to support spaces and special characters from the command line,
# like double quotes: """$Escape"""
$Output = & PowerShell .\Password.ps1 "'$Escape'"
Write-Host 'Output:' $Output
Write-Host 'Input and output are equal?' ($Input -eq $Output)
Results:
Input: !"'#$%&()*+,-./:;<=>?#[\]^_`{|}~
Command: PowerShell .\Password.ps1 "' !\"''#$%&()*+,-./:;<=>?#[\]^_`{|}~'"
Output: !"'#$%&()*+,-./:;<=>?#[\]^_`{|}~
Input and output are equal? True
I use the following script to send two E-Mails to different people:
# Datum von nächstem Samstag ermitteln
$Date = Get-Date "18:00"
while ($Date.DayOfWeek -ne "Saturday") { $date = $date.AddDays(1) }
# UTF-8 Encoding
$utf8 = New-Object System.Text.utf8encoding
# E-Mail Benachrichtigung zusammenstellen
$EmailNotifications = #{
AlleMAEmail = #{
From = "xy"
To = "xy"
Subject = "Serverarbeiten Update Installation $($Date.DateTime)"
Body = "abc äöü"
}
ITAdminEmail = #{
From = "xy"
To = "xy"
Subject = "Bitte bei XY Updates genehmigen & Ablehnen"
Body = "abc äöü"
}
}
# E-Mails versenden.
$EmailNotifications.GetEnumerator() | ForEach-Object {
$splat = $_.Value
Send-MailMessage -SmtpServer "xy" -BodyAsHtml -Encoding $utf8 #splat
}
This works when I run the code in Visual Studio Code, however I need a scheduled task on a server to run this. When the scheduled task runs the script, it can't handle the umlauts in the mail body, e.g it sends ü as ü
How can I fix this? I already specified my encoding
This is how my task is set up:
Start a Program: PowerShell
Arguments: -Command "& '\\server\path\script.ps1'" -ExecutionPolicy Bypass
Edit: I noticed that the PowerShell that gets started is the "old" PowerShell which has a black background. Could this be the problem? How to start the new one?
PowerShell interprets the source code of your .ps1 file when it reads it, but not necessarily in the encoding you expect.
When you save a file as UTF-8, but PowerShell's default is Windows-1252, then ü becomes ü before your code even runs. Send-MailMessage then correctly encodes ü into UTF-8 and so these characters are retained in the email. When you run the program from within Visual Studio Code, different defaults apply and the outcome is different.
I don't think there is a command line switch that forces PowerShell to interpret script files in a certain encoding, but you can help the encoding auto-detection along by prefixing your file with a byte-order mark (BOM).
A BOM is mandatory for UTF-16 (that is what's commonly called "Unicode" encoding in various Microsoft tools), but optional in UTF-8. UTF-8 BOMs are wrong for many use cases, so VS Code defaults to "UTF-8 without BOM". When you explicitly save the file as "UTF-8 with BOM" then Powershell will infer the correct encoding when reading the script.
There is a way to configure VS Code to pick specific encodings per file type, you could set it to always save .ps1 files as UTF-8 with BOM.
The alternative would be to save the file as Windows-1252, which would match PowerShell's expectation on your machine, but might break on different computers (or when run from within VS Code).
I assembled a Powershell script that is designed to grab other scripts that are hosted on Azure blobs, and execute them.
The relevant code blocks:
Obtaining the script:
$resp = (Invoke-WebRequest -Uri $scriptUri -Method GET -ContentType "application/octet-stream;charset=utf-8")
$migrationScript = [system.Text.Encoding]::UTF8.GetString($resp.RawContentStream.ToArray());
$tempPath = Get-ScriptDirectory
$fileLocation = CreateTempFile $tempPath "migrationScript.ps1" $migrationScript
Creating the file:
$newFile = "$tempFolder"+"\"+"$fileName"
Write-Host "Creating temporary file $newFile"
[System.IO.File]::WriteAllText($newFile, $fileContents)
And then I invoke the downloaded file with
Invoke-Expression "& `"$fileLocation`" $migrationArgs"
This is working well, for what I need. However, the Invoke-Expression is not correctly reading the encoding of the file. It opens correctly in Notepad or Notepad++, but not in ISE (where I am executing the script right now).
Is there a way I can ensure the script is read correctly? It is necessary to support UTF8, as there is a possibility that the scripts will need to perform operations such as setting an AppSetting to a value that contains special characters.
EDIT: Behaviour is the same on "vanilla" non-ISE Powershell invocation.
As per #lit and #PetSerAI, the BOM is required for Powershell to work correctly.
My first attempt had not been successful, so I switched back to non-BOM, but, with the following steps, it worked:
Perform the Invoke-WebRequest with -ContentType "application/octet-stream;charset=utf-8"
Grab the Raw content (you will see it in Powershell as a series of numbers, which I assume are the ascii codes?) and convert its bytes with [system.Text.Encoding]::UTF8.GetString($resp.RawContentStream.ToArray()); to an array containing the characters you want.
When saving the file via .NET's WriteAllText, ensure you use UTF8,
[System.IO.File]::WriteAllText($newFile, $fileContents, [System.Text.Encoding]::UTF8). In this case, UTF8 is understood to be UTF8 with a byte order mark, and is what Powershell needs.