I apologize for this in advance because I see plenty of questions regarding it throughout the web but for some reason I'm still having issues.
I have a script that creates an array that has information dynamically added to it. When the script completes, I need it to email that information to me. The problem is that each line is combined to that I get 1 line. For example:
$Body = #()
$Body += "1"
$Body += "2"
$Body += "3"
$Body += "4"
Here's my send command:
Send-MailMessage -To $Recipients -From $Sender -Smtp $SMTP $Subject "Test" -Body ($Body | Out-String)
What I get in the email body is: 1234
I've tried this for loop to append `n to the beginning of each line (except the 1st) like so:
for ($i = 0; $i -lt Body.Count; $i++){
if($i -eq 0){
Write-Host $i
}else{
$Body[$i] = "`n" + $Body[$i]
Write-Host $Body[$i]
}
}
The results of that are better but I get an extra line:
1
2
3
4
Ultimately I just want this:
1
2
3
4
In the past, I've gotten the format I want by creating the variable like this:
$Body = #"
This is the email I want to send. It formats great if:
1) I want to make all the content static.
Is it possible to create the `$Body variable like this but add line by line dynamically and maintain a serpate line. (without extra lines)?
"#
What am I missing? It HAS to be something simple... Thanks for your help!
WOW so it's such a finicky thing... I found that using this would get everything to look right (as suggested by Ryan, above):
-Body ($Body -Join "`r`n")
But in the context of my actual script it wasn't working. Well, it appears that it all had to do with the way the date was going in there. Here's a reduction to show:
DOES NOT WORK
$Body = #()
$Body += "Beginning processing on: " + (Get-Date)
$Body += "No advertisements found, exiting"
ALSO DOES NOT WORK
$Body = #()
$DateTime = Get-Date
$Body += "Beginning processing on: $DateTime"
$Body += "No advertisements found, exiting"
When I removed anything that had to do with the Date, formatting was correct. Ultimately it just took a simple "." to make it work right.
WORKS - NOTE THE 3RD LINE
$Body = #()
$DateTime = Get-Date
$Body += "Beginning processing on: $DateTime." #Note the period at the end of the line.
$Body += "No advertisements found, exiting"
Presumably I could just concatenate and it would work but oh well, I'm keeping it as it. I didn't think that it would be that finicky about a period but now I know.
Related
I have written a piece of powershell code to send emails to different users based on an extraction from a table (and some conditions).
#extraction skipped
foreach($row in $result) {
Clear-Variable template* -Scope Global
$template = (Get-Content "C:\template.html")
#user definition skipped
$message = "Hello..."
$html = $template |
ForEach-Object { if($_.Trim() -eq "<p>$[CONTENT]</p>") {
-join(($_).ToString().Replace("$[CONTENT]", $message),$html)
} else { -join($_, $html) }
}
Send-MailMessage -From $fromAddr -To $mail -Bcc $maintainer -Subject $sub -Body ($html | Out-String) -BodyAsHtml -SmtpServer $smtpserver -Encoding UTF8
}
If you need anything else feel free to ask, I felt like this was the relevant part of the code but I'm not 100% sure.
The Problem: Basically if i get only one user the email text looks fine. If there is more than one $row the second email is full of System.Object[].
I'm not sure what I've been doing wrong. My guess would be ForEach-Object, but why work for the first and not the following?
Thanks in advance for any help.
The problem in your code on the second iteration is that it's basically doing this:
$html = $template | ... -join($_, $html) ...
where $html still contains the value from the first iteration, so you're joining the new $message with the entirety of the first email text and getting into a bit of a pickle.
You can avoid this if you add:
$html = $null
$html = $template | ... -join($_, $html)
so each iteration gets a clean start.
Having said that, you can just do as #Mathias R. Jessen suggested and replace your entire templating logic with this:
$body = -join #((Get-Content .\template.html) -replace '\$\[CONTENT\]', $message)
or more succinctly, this:
$body = (Get-Content .\template.html -Raw).Replace("`$[CONTENT]", $message)
I have a Powershell script that automates a process and emails the report of what happened.
Send-MailMessage -To $toAddress -From "no-reply#domain.org" -subject "Automation status" -body $bodystr -SmtpServer SERVER1 -EA Stop
So $bodystr is essentially an appended string throughout the script to report what happened and has multiple lines. Things like:
$bodystr = $bodystr + "Line found: 305`n"
$bodystr = $bodystr + "Moving line 305 to 574`n"
The Send-MailMessage command is at the bottom of the script outside any function. But most other code is in various different functions.
The issue is $bodystr does not seem accessible inside functions, and so the email is lacking a lot of information.
I believe I could use Set-Variable or passing arguments, but there are so many arguments it seems farther away from best practice to add a new argument for each function just to keep the string updated.
What's the best practice to handle this?
As a general rule, don't write data back to variables outside the scope of your function.
If you are compiling an email by gathering data from multiple sources, abstract it away in multiple functions that does one thing each and have them return a multiline string with the relevant output.
At the end of your script, collect the different message body parts and join them to a single string before sending.
In this example, we have a script that takes a path to a log file, defines a function to extract errors from a log file, and send an email with the errors in the body:
param(
[ValidateScript({Test-Path $_ -PathType Leaf })]
[string]$LogPath = 'C:\Path\To\File.log',
[string]$From = 'noreply#company.example',
[string]$To = #('ceo#company.example','finance#company.example'),
[string]$Subject = 'Super Important Weekly Report',
[string]$SmtpServer = $PSEmailServer,
[string]$Credential
)
# Define functions with a straight forward purpose
# e.g. Searching a logfile for errors
function Parse-Logfile {
param($LogPath)
[string[]]$LogErrors = #()
Get-Content $LogPath |ForEach-Object{
if($_ -contains $Error){
$LogErrors += $_
}
}
# Create and return a custom object has the error details as properties
New-Object psobject -Property #{
ErrorCount = $LogErrors.Count
Errors = $LogErrors
}
}
# Create a email template that's easy to maintain
# You could store this in a file and add a $TemplateFile parameter to the script ;-)
$EmailTemplate = #'
Hi there!
Found {0} errors in log file: {1}
{2}
Regards
Zeno
'#
# Use your function(s) to create and gather the details you need
$ErrorReport = Parse-Logfile -LogPath $LogPath
# If necessary, concatenate strings with -join
$ErrorString = $ErrorReport.Errors -join "`n"
# Use the format operator to the final Body string
$Body = $EmailTemplate -f $ErrorReport.ErrorCount, $LogPath, $ErrorString
# Set up a splatting table (Get-Help about_Splatting)
$MailParams = #{
To = $To
From = $From
Subject = $Subject
Body = $Body
SmtpServer = $SmtpServer
}
if($PSBoundParameters.ContainsKey('Credential')){
$MailParams['Credential'] = $Credential
}
# Send mail
Send-MailMessage #MailParams
I previously posted this script and had some help but later deleted it to avoid confusion. I have it almost working with the one exception. The content of the email sent to each manager only includes the data from the last direct report in the array. Do I need to restructure the script? I feel like I am chasing my tail if I had one :)
EDIT: I added the recommended changes, but now I get:
Email 1: Manager and direct reports
Email 2: Content of Email 1 and the next Manager and direct reports.
Email 3: Content of Email 1 & 2, the next Manager and direct reports.....
RESOLVED: Thanks TheMadTechnician. Modifying the += and moving the $body to the proper location in the loops was the key.
My script Updated script:
Import-Module -Name ActiveDirectory
$today = (Get-Date).ToString()
# Html
$a = "<style>"
$a = $a + "BODY{background-color:Lavender ;}"
$a = $a + "TABLE{border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}"
$a = $a + "TH{border-width: 1px;padding: 5px;border-style: solid;border-color: black;background-color:thistle}"
$a = $a + "TD{border-width: 1px;padding: 5px;border-style: solid;border-color: black;background-color:PaleGoldenrod}"
$a = $a + "</style>"
# Email Variables
$smtp = "192.168.1.1"
$to = "User#Company.com"
$from = "User#company.com"
$subject = "Managers - Direct Report's Group Membership"
$managers = Get-ADUser -Filter * -Properties name, directreports, EmailAddress | where {$_.directreports -ne $Null}
foreach ($i in $managers)
{
$mgrname = $i.Name
$mgremail = $i.EmailAddress
$dreports = $i.directreports
$body = "Report Date $today ."
$body = "`n"
$body = "<H3>The direct reports for $mgrname<H3>"
foreach ($d in $dreports)
{
$user = get-aduser $d -properties *
$mems = $user.memberof -replace '^CN=(.+?),(?:OU|CN)=.+','$1' | %{New-Object PSObject -Property #{'Group Membership'=$_}} | convertTo-html -Head $a -Body "<H2>Group Membership.</H2>"
$dreport = $d -replace '^CN=(.+?),(?:OU|CN)=.+','$1'
$body += "`n"
$body += "<H3>Direct Report: $dreport</H3>"
$body += "`n"
$body += $mems
$body += "`n"
}
Send-MailMessage -SmtpServer $smtp -To $to -From $from -Subject $subject -Body $body -BodyAsHtml
}
Ok, resolution for this issue was to move the bulk of the email generation outside of the ForEach($d in $dreports) loop. The header section ($Body='Report for $today', and the following two lines) was moved up, and the line to actually send the mail was moved down outside of the loop. Updated code is already in the OP I think, so I won't bother to repost it again.
This line:
$body = "Report Date $today ."
Is resetting the $body variable for each direct report. So by the time you get to Send-MailMessage all you have is the last direct report.
Try moving that line above the foreach loop that iterates through the direct reports.
Assuming you want to send all reports as individual emails to all managers, move the Send-MailMessage call inside the report foreach loop.
Or if you want to send one email containing all reports to each manager, declare $body outside of you report loop and change the first assigment within the loop to `$body += "Report Date $today ."
Every iteration of foreach ($d in $dreports) resets $body. Change the line
$body = "Report Date $today ."
to
$body += "Report Date $today ."
I have an array of names that I'm trying to join using a new line character. I have the following code
$body = $invalid_hosts -join "`r`n"
$body = "The following files in $Path were found to be invalid and renamed `n`n" + $body
Finally, I send the contents via email.
$From = "myaddress#domain.com"
$To = "myaddress#domain.com
$subject = "Invalid language files"
Send-MailMessage -SmtpServer "smtp.domain.com" -From $From -To $To -Subject $subject -Body $body
When I receive the message, the line The following files in <filepath> were found to be invalid and renamed has the expected double space, but the contents of $invalid_hosts are all on one line. I've also tried doing
$body = $invalid_hosts -join "`n"
and
$body = [string]::join("`n", $invalid_hosts)
Neither way is working. What do I need to do to make this work?
Pipe the array to the Out-String cmdlet to convert them from a collection of string objects to a single string:
PS> $body = $invalid_hosts -join "`r`n" | Out-String
It is sufficient just pipe to Out-String (see https://stackoverflow.com/a/21322311/52277)
$result = 'This', 'Is', 'a', 'cat'
$strResult = $result | Out-String
Write-Host $strResult
This
Is
a
cat
I'm unsure about how to answer everything else, but for guaranteed newlines in Powershell, use:
[Environment]::NewLine in place of your "`n"
Had to solve this today; thought I'd share my answer since the question and other answers helped me find the solution. Instead of
$body = $invalid_hosts -join "`r`n"
$body = "The following files in $Path were found to be invalid and renamed `n`n" + $body
use
$MessageStr = "The following files in " + $Path + " were found to be invalid and renamed"
$BodyArray = $MessageStr + $Invalid_hosts
$Body = $BodyArray -join "`r`n"
I went about it differently and just replaced the newline
$result -replace("`r`n"," ")
I am certainly no expert in PowerShell, but I found a much easier way to do it. Simply pipe to Write-Host like this:
$array = 'This', 'Is', 'a', 'cat'
$array | Write-Host
Output:
This
Is
a
cat
This is a slightly different use case than the OP question. It does not join the array with newlines, but it does give newlines when writing the output.
List item
Here is a weird one. $body is buildt with
a loop adding 30 - 100 lines:
$body += "+ $stuff `n"
The problem is if I use in $body a plus (+), it takes each line in the body and splits it to half the line size and continues it on the next line:
Example,
The line is actually:
moon|glowing so nice today|buy gold now and get really rich soon
It gets changed to (stackoverflow changed my (+) to a bullet. Just note that there is a plus in front of the next two lines).
moon|glowing so nice today|buy gold now
moon|and get really rich soon
If I change the plus (+) to the word Add, I get in the email body:
Add moon|glowing so nice today|buy gold now and get really rich soon
I would like to use the plus (+) as that is the same format another program uses with +/- of lines in the body of the email. Any ideas how to get this working?
function mailalert($body) {
$SMTPserver = "mailhost.domain.com"
$from = "replies-disabled#domain.com"
$to = "joe#domain.com"
$subject = "Report for Stuff"
$mailer = new-object Net.Mail.SMTPclient($SMTPserver)
$msg = new-object Net.Mail.MailMessage($from, $to, $subject, $body)
$mailer.send($msg)
}
Okay, I abandoned Net.Mail.SMTPclient and decided to use html as body of text to get around this:
function mailalert($diff) {
$SMTPserver = "mailhost.domain.com"
$smtp = New-Object system.Net.Mail.SmtpClient($SMTPserver)
$mail = New-Object System.Net.Mail.MailMessage
$mail.From = "replies-disabled#domain.com"
$mail.To.Add("joe#domain.com")
$mail.Subject = "Report for stuff"
$mail.Body = $diff
$mail.IsBodyHtml = $true
$smtp.Send($mail)
}
I changed all use of `n to and tada it works. It still is a bit perplexing why the other way did not work. I see another issue where email addresses with pluses in them was being addressed, so I wonder if fixing that broke this. :)
Try:
$body = "$body $stuff `n"
in your loop.
Escape any troublesome characters using a " ` ".
$body = "$body `+ $stuff `n"