Send-MailMessage in Scheduled Task has wrong encoding - powershell

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

Related

Send tree from a directory via SMTP through PowerShell

I'm trying to develop a small script that sends the output of a tree of a certain directory, and I'm having problems with the presentation of the mail; The script already sends the info, but not in the way that I would like. My code is as follows:
# from to info
$MailFrom = ""
$MailTo = ""
# Credentials
$Username = "user"
$Password = "password"
# Server Info
$SmtpServer = "server"
$SmtpPort = "port"
# Menssage
$MessageSubject = "test"
$Message = New-Object System.Net.Mail.MailMessage $MailFrom,$MailTo
$Message.IsBodyHTML = $false
$Message.Subject = $MessageSubject
$Message.Body = tree /F directoryroute
# SMTP Client object
$Smtp = New-Object Net.Mail.SmtpClient($SmtpServer,$SmtpPort)
$Smtp.EnableSsl = $true
$Smtp.Credentials = New-Object System.Net.NetworkCredential($Username,$Password)
$Smtp.Send($Message)
So, the thing is that the mail shows the info like this:
When in fact, I want to see the following:
I am using power shell ISE and the tree is also different there:
What am I missing?
It sounds like you need to set the .BodyEncoding property to a character encoding that can represent the box-drawing characters that the tree.com utility uses for visualization, preferably UTF-8:
$Message.BodyEncoding = [Text.UTF8Encoding]::new()
Additionally, since the .Body property is a single string ([string]), whereas the lines output by tree.com are captured in an array by PowerShell, you need to create a multi-line string representation yourself:[1]
$Message.Body = tree /F directoryroute | Out-String
If you neglect to do that, PowerShell implicitly stringifies the array, which means joining the array elements on a single line with spaces, which results in what you saw.
As for the PowerShell ISE:
It misinterprets output from external programs such as tree.com, because it uses the system's ANSI code page by default for decoding, whereas most external programs use the OEM code page.
The ISE has other limitations, summarized in the bottom section of this answer, is no longer actively developed and notably cannot run the modern, cross-platform PowerShell edition, PowerShell (Core) 7+.
Consider migrating to Visual Studio Code with its PowerShell extension, which is an actively developed, cross-platform editor that offers the best PowerShell development experience.
In case you do want to make tree.com work in the ISE, run the following:
[Console]::OutputEncoding =
Text.Encoding]::GetEncoding([cultureinfo]::CurrentCulture.TextInfo.OEMCodePage)
[1] While using Out-String is convenient, it always appends a trailing newline to its output - see GitHub issue #14444. If you need to avoid that, use (tree /F directoryroute) -join [Environment]::NewLine instead.

Powershell SetEnvironmentVariable not setting the variables as expected

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.

Atlassian Bamboo - Inject variable having '?' in string, possible encoding issue

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.

How can I use UTF-8 on the cmdlet New-Mailbox?

We are running a script to create "equipment" and "room" resources in ExchangeOnline and everything works as intended except that it will not accept UTF-8 for the cmdlet 'New-Mailbox'.
Office365 accepts our fancy Swedish characters ÅÄÖ on the resources if you manually create them via the UI but I cannot make it happen through the script.
I tried changing the default encoding for PS but that only applies to cmdlets that can accept the -Encoding argument which it doesn't.
$extraParams = #{ $Type = $true }
New-Mailbox -Name "$($Resource)" #extraParams
When using ÅÄÖ for the script we are greeted with: "String syntax failed validation"

Calling a powershell script from another powershell script and guaranteeing it is UTF8

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.