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"
Related
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.
I have a powershell script in my release pipeline stage that runs a command and passes values of a secret variable to it. The issue is that the Logs show each and every command as they are run including each arguments passed, one of which is from a secret variable.
How do I make the powershell output not show the command it is running? output of the command is okay to show if it can't be hidden.
Secrets shouldn't be converted to plain text but kept as such and passed as a SecureString to your application. In other words, the solution lays in making sure that your concerned application accepts a hashed password, a SecureString or a PSCredential object also knowing that sending a plain text password to an application isn't secure by itself.
#iRon Say this to Microsoft. I am trying to call their schtasks
I just did: #16502: Set-ScheduledTask shouldn't accept a plain text Password
As a workaround, you might keep your password covered in a SecureString as long as possible:
$Credentials = Get-Credential
Set-ScheduledTask -User $Credential.UserName -Password $Credential.GetNetworkCredential().Password
This will prevent that the passwords are revealed by logging but as there is still a potentially security risk that the password could be read from memory, I recommend to do a garbage collection ([system.gc]::Collect()) right after this command.
⚠️ Important
A SecureString object should never be constructed from a String, because the sensitive data is already subject to the memory persistence consequences of the immutable String class. The best way to construct a SecureString object is from a character-at-a-time unmanaged source, such as the Console.ReadKey method.
To be completely safe, you might also consider to run Set-ScheduledTask (without -User and -Password) under the credentials of the targeted user Start-Process -Credential $Credential ...
Update 2022-02-24:
Sadly😭, I got zero response on my feedback hub "Set-ScheduledTask shouldn't accept a plain text Password" (security) issue. Therefore, I have also just created a new Microsoft Feedback Portal issue for this: Windows-PowerShell: Set-ScheduledTask shouldn't accept a plain text Password
Anyhow, the organization I work for, deals with the same general issue where the use-case is defined as: "how can we hide sensitive information as passwords used by invoked 3rd party applications in PowerShell scripts"
As suggested before: the problem in not due to any (PowerShell) scripting limitations but how the information (as plain text) is provided (input) to the script and how it is expected to be passed (output) to any other application.
To make this clear and to supply at least some (easy) solution, I have created an [HiddenString] class that might be used in a script to hide information as much as possible end-to-end inside the script itself.
class HiddenString {
hidden [SecureString]$SecureString = [SecureString]::new()
HiddenString([Object]$String) {
if ($String -is [SecureString]) { $This.SecureString = $String }
else {
foreach ($Character in [Char[]]$String) { $This.SecureString.AppendChar($Character) }
}
}
[String]Reveal(){
$Ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($This.SecureString)
$String = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($Ptr)
[System.Runtime.InteropServices.Marshal]::ZeroFreeCoTaskMemUnicode($Ptr)
Return $String
}
}
Note that I am using the SecureString type in the class not for better security but just for better hiding the concerned string.
Usage example:
function MyScript([String]$TaskName, [String]$UserName, [HiddenString]$Password) {
Start-Transcript -Path .\Transcript.txt
Write-Host "Scheduling $TaskName for $UserName/$Password" # Write-Log ...
Set-ScheduledTask -TaskName $TaskName -User $UserName -Password $Password.Reveal()
Stop-Transcript
}
Recommended invocation of MyScript:
$SecuredString = Read-Host 'Enter Password' -AsSecuredString
MyScript NotePad.Exe JohnDoe $SecuredString
Just hiding the sensitive information inside the MyScript:
$String = 'Sensitive Information'
MyScript NotePad.Exe JohnDoe $String
Transcript started, output file is .\Transcript.txt
Scheduling NotePad.Exe for JohnDoe/HiddenString
Transcript stopped, output file is .\Transcript.txt
Again, (I can't stress this enough):
warning: as a whole, this workaround is nothing more than security through obscurity
As Microsoft states themselves at SecureString shouldn't be used:
The general approach of dealing with credentials is to avoid them and instead rely on other means to authenticate, such as certificates or Windows authentication.
(which they should also do in their own cmdlets along with Set-ScheduledTask)
I have created an enhancement request for this idea:
#16921 Add [HiddenString] Class
You can register secrets with the agent to ensure they're scrubbed from the logs.
Write this to the output:
write-output "##vso[task.setsecret]THEVALUEYOUWANTHIDDEN"
This should register the secret with the agent. If you know your script will also popentially log the base64 value or another representation of the secret, make sure you register all permutations.
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.
I have the below PowerShell script (myscript.ps1) in which I ask for username and password. Depending on the username and password it copies a file to a certain destination.
$credentials = Get-Credential
if ($credentials.Username -eq "user1" -And $credentials.GetNetworkCredential().password -eq "pass1")
{ Copy-Item "test1.pdf" "\test\test1.pdf"; }
else
{ Copy-Item "test2.pdf" "\test\test2.pdf"; }
Requirement: I want to make this file protected so no one can edit it and see the username and password.
PS2EXE
I found a solution found here which converts the PowerShell script to an .exe file. When I originally run the script using PowerShell a dialog box appears allowing me to enter the username and password:
After the .exe is generated and when I run it the credentials dialog box no longer appears. Instead, the console appears saying "Credential:"
I don't know why? I want the credentials form to still appear when running the exe. Any thoughts please?
Q: Why does the EXE prompt with "Credential"?
This isn't an answer to the real question, and is based on guessing/supposition about PS2EXE, but I hope it is useful to clear up some confusion.
Having looked briefly at the PS2EXE page linked above, it seems that this utility encodes the script in Base64 and bundles it with a lightweight (?) custom PowerShell host. When run, I suppose the EXE starts the host, decodes the script and runs it.
The problem is that the Get-Credential cmdlet is running within a PS host that probably can't interact with the desktop. That is, it can't put up the GUI prompt for credentials. It therefore needs to prompt for the Credential property on the command line, explaining why you see that behaviour.
Workaround with Read-Host?
Instead of trying to use Get-Credential to prompt for username and password, you could embrace what PS2EXE seems to be doing and just use Read-Host:
$UserName = Read-Host "Enter username"
$Password = Read-Host "Enter password" -AsSecureString
$Credentials = New-Object System.Management.Automation.PSCredential $UserName,$Password
if ($credentials.Username -eq "user1" -And $credentials.GetNetworkCredential().password -eq "pass1")
{ ... }
Using -AsSecureString will hide the password on the screen. The $Password variable will be of type System.Security.SecureString, which can be used to create a PSCredential object as shown.
You'd need to test this, but it seems that you're able to read from the shell but not from a GUI prompt.
And just to be clear: none of this is anywhere near best-practice security. If you need authentication/authorization for these activities, step back and look at the problem again.
Workaround with two scripts?
It seems that PS2EXE doesn't support -AsSecureString in the same way that normal PowerShell does, i.e. it doesn't hide the characters. A possible workaround for this would be to collect the username and password from the user in one script and then pass them to a PS2EXE-converted script for processing.
Launch-MyScript.ps1:
$Credentials = Get-Credential
& MyScript.exe $Credentials.Username $Credentials.Password
MyScript.exe (coverted with PS2EXE):
param($Username,$Password)
$Credentials = New-Object System.Management.Automation.PSCredential $Username,$Password
if ($Credentials.Username -eq "user1" -and
$Credentials.GetNetworkCredential().password -eq "pass1")
{
...
}
The user runs Launch-MyScript.ps1 and completes the password prompt. Then the EXE is run automatically with the username and password passed in as arguments. Note that, as shown above, the password is a Secure String. Test this; I'm not using PS2EXE so it's a theoretical solution at the moment.
If you can't pass $Password along the pipeline as a Secure String object, you can convert it to text with ConvertFrom-SecureString in the first script, then conver it back with ConvertTo-SecureString in the second one.
According to this article http://windowsitpro.com/powershell/protect-your-powershell-scripts you should first set ur execution policy to AllSigned by Set-ExecutionPolicy AllSigned, then create a certificate using makecert cmdlet.
Then u can sign single script using Set-AuthenticodeSignature cmdlet or use .pfx File to Sign a Script which appears even safer.
Hope it helps a bit.