Send tree from a directory via SMTP through PowerShell - 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.

Related

Escaping all password special characters in Powershell for Variable

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

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

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"

SSAS Deployment Via Powershell Corrupting Format String

I am having a problem when deploying cubes via PowerShell that I don’t know how to fix. In my solution I have some measures with FormatString: \£ #,##0;-\£ #,##0. If I deploy them normally via Visual Studio, the XMLA I pull from the SSAS server after deployment will have <FormatString>\£ #,##0;-\£ #,##0</FormatString> which is all good and expected. However, if I deploy via PowerShell, the resulting XMLA from the server is <FormatString>\£ #,##0;-\£ #,##0</FormatString>.
I realise the issue is with £ being parsed, but I am not sure how to resolve it.
The bit of background here is that I am using Octopus Deploy, and I've extracted the PowerShell below where the issue occurs:
$modelpath = "C:\...\Project_Name.xmla"
$Path_To_Microsoft_AnalysisServices_Deployment = "C:\Program Files (x86)\Microsoft SQL Server\140\Tools\Binn\ManagementStudio\Microsoft.AnalysisServices.Deployment.exe"
$SSAS_Project_Name = "Project_Name"
$Path_To_SSAS_Bin_Folder = "F:\Octopus Deploy\CustomInstallation\Project_Name\Content"
$FilePath_asdatabase = "$Path_To_SSAS_Bin_Folder\$SSAS_Project_Name.asdatabase"
$FilePath_deploymenttargets = "$Path_To_SSAS_Bin_Folder\$SSAS_Project_Name.deploymenttargets"
$FilePath_configsettings = "$Path_To_SSAS_Bin_Folder\$SSAS_Project_Name.configsettings"
$FilePath_deploymentoptions = "$Path_To_SSAS_Bin_Folder\$SSAS_Project_Name.deploymentoptions"
Start-Process -FilePath $Path_To_Microsoft_AnalysisServices_Deployment -ArgumentList "`"$FilePath_asdatabase`"","/s:`"$FilePath_DeploymentLog`"","/o:`"$SSAS_Project_Name.xmla`"","/d" -Wait -NoNewWindow
# Load the Adomd Client
[System.Reflection.Assembly]::LoadWithPartialName(“Microsoft.AnalysisServices.AdomdClient”) | Out-Null;
# Connect to the server
Write-Host "Executing Deployment Script"
$serverName = “Data Source=.”
$conn = New-Object Microsoft.AnalysisServices.AdomdClient.AdomdConnection $serverName
$conn.Open()
# Read XMLA
$xmla = Get-Content $modelpath
# Execute XMLA
$cmd = New-Object Microsoft.AnalysisServices.AdomdClient.AdomdCommand $xmla, $conn
return $cmd.ExecuteNonQuery();
I have done some investigation and I’ve found the following:
The xmla file produced by PowerShell (not in the snippet) has the correct Format String \£ #,##0;-\£ #,##0
The .asdatabase file also has the correct format string
I suspect that something is happening either at:
Start-Process -FilePath $Path_To_Microsoft_AnalysisServices_Deployment -ArgumentList “`”$FilePath_asdatabase`””,”/s:`”$FilePath_DeploymentLog`””,”/o:`”$SSAS_Project_Name.xmla`””,”/d” -Wait -NoNewWindow
or further down at:
# Execute XMLA
$cmd = New-Object Microsoft.AnalysisServices.AdomdClient.AdomdCommand $xmla, $conn
return $cmd.ExecuteNonQuery();
Any help would be appreciated.
PS: I’ve tried playing with the Format String when setting it in the solution but haven’t been able to find a solution. Note that I can’t simple use “Currency” b/c the server is set to English (US).
The answer to this is to specify the encoding. From Jeroen in the comment in response to my question:
It looks like PowerShell is auto-detecting the encoding of the XMLA incorrectly as Windows-1252, when it's actually UTF-8 (without a BOM). Try $xmla = Get-Content $modelpath -Encoding UTF8. It may also be the case that the XMLA itself is actually incorrect -- make sure to inspect it in an editor capable of reporting the encoding, like Notepad++, not just have it echo back by PowerShell itself. – Jeroen Mostert 15 mins ago

How to execute an external console application with PowerShell and send the result to email?

I need to execute some periodic console applications that give me some results (on the console)... How can I execute it and have its return data sent to my email?
I tried to use [Diagnostics.Process]::Start() and it launches my application but i don't know how to get the return... I do not want the exitCode, I want the text that the application prints on screen.
Using PS V2 CTP3.
*** UPDATE
The solutions presented worked fine but i have a problem... this application that i need to execute is gfix from the firebird database and now i discovered a thing, I can't redirect the output of gfix to a file, if i execute on command prompt the line:
gfix.exe -v -f dabatase.gdb > c:\test.txt
it print the output on the screen and the file is empty.
Same thing if I try to assign it to a variable... I don't know what difference gfix has from the other console apps that I use, but looks like its output can't be redirected.
Has someone seeing this?
*** UPDATE 2
Even if I use Start-transcript /Stop-Transcript, although on the screen I see the gfix output, on the file there is only the commands :/
*** UPDATE 3
Found the solution here
http://edn.embarcadero.com/br/article/25605
Something like this could work:
# temporary file
$f = [io.path]::GetTempFileName()
# start process and redirect its output to the temp file
ping localhost > $f
# create and send email
$emailFrom = "user#yourdomain.com"
$emailTo = "user#yourdomain.com"
$subject = "results"
$body = (Get-Content $f) -join "`r`n"
$smtpServer = "your smtp server"
$smtp = new-object Net.Mail.SmtpClient($smtpServer)
$smtp.Send($emailFrom, $emailTo, $subject, $body)
# delete the file
remove-item $f
I think in this case [Diagnostics.Process]::Start() is not needed. Besides that there is a cmdlet Start-Process that does almost the same.
PowerShell v2 is now out, so you could consider upgrading.
Then, you can simply try:
PS > [string]$ipconfig=ipconfig
PS > send-mailmessage -to some_email -from from_email -subject PowerShell -body $ipconfig -bodyashtml -smtp my_smtp_server
Now, it depends on how complicated your command-line output is, because the above method will collapse multiple lines into a single on.