Powershell - Combining Looped Results - powershell

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

Related

Send-MailMessage full of "System.Object[]"after the first email

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)

Powershell script returns True instead of the value of the variable

I have a script that extracts Excel reports (runs every 15 minutes) that get sent to a mailbox and copies them to a file share and emails different distribution groups based on the name of the report.
If more than one report is downloaded to the folder in the 15 minute period, the script work fine and emails correctly with the name of the report in the email.
The issue is if there is only one report downloaded during the 15 minute period the script still works and sends an email, but instead of the name of the report in the body of the email it says True.
$downloadTemp = 'C:\Test\'
# List the files in the Temporary download folder
$files = (Get-ChildItem $downloadTemp -name)
# Set the Creation date
$exists = (Get-ChildItem $downloadTemp | Measure-Object ).Count
If ($exists -gt 0) {Get-Item $downloadTemp | % {$_.CreationTime = (Get-Date)}
}
# Look for Specific reports
$DailyOpen = $files -Like "Daily Open*"
$DailyProduct = $files -Like "Daily Product*"
$DailyRaised = $files -Like "Daily Raised*"
#More variables
$compareDate = (Get-Date).AddMinutes(-15)
$diff = ((Get-Item $downloadTemp).CreationTime)
If ($diff -gt $compareDate) {
If ([bool]$DailyOpen -eq $True) {
$body = "<HTML><HEAD><META http-equiv=""Content-Type"" content=""text/html; charset=iso-8859-1"" /><TITLE></TITLE></HEAD>"
$body += "<BODY bgcolor=""#FFFFFF"" style=""font-size: Small; font-family: CALIBRI; color: #000000""><P>"
$body += "Hi All<br><br>"
$body += "This is an email to let you know that a new report " + $DailyOpen + " has arrived in<br><br>"
$body += "\\{fileshare}\"
Send-MailMessage -To "" -From "" -Subject "New Daily Open Complaints Report Notification" -bodyashtml -Body $body -SmtpServer ""
}
If ([bool]$DailyProduct -eq $True) {
$body = "<HTML><HEAD><META http-equiv=""Content-Type"" content=""text/html; charset=iso-8859-1"" /><TITLE></TITLE></HEAD>"
$body += "<BODY bgcolor=""#FFFFFF"" style=""font-size: Small; font-family: CALIBRI; color: #000000""><P>"
$body += "Hi All<br><br>"
$body += "This is an email to let you know that a new report " + $DailyProduct + " has arrived in<br><br>"
$body += "\\{fileshare}\"
Send-MailMessage -To "" -From "" -Subject "New Daily Product Conversions Report Notification" -bodyashtml -Body $body -SmtpServer ""
}
#More If statements here. 1 for every report variable used.
}
Change
$files = (Get-ChildItem $downloadTemp -name)
to
$files = #(Get-ChildItem $downloadTemp -name)
and all similar cases.
PS is giving you either a single thing, or an array. You need it to always be an array, even if there's one thing, so you need to use the #() array constructor.
That's because this
$DailyOpen = $files -Like "Daily Open*"
changes depending on whether $files is an array (-like acts as a filter and returns a filtered array) vs whether it's a single thing (-like acts as a boolean operator and returns true/false).
I am sure there is a more elegant way of doing this, ignore your $files variable and reuse the code inside it:
Replace
$DailyOpen = $files -Like "Daily Open*"
With
$DailyOpen = Get-ChildItem $downloadTemp -name "Daily Open*"
On my Test it worked with a single file and two files.
So in your example you would need to replace:
# Look for Specific reports
$DailyOpen = $files -Like "Daily Open*"
$DailyProduct = $files -Like "Daily Product*"
$DailyRaised = $files -Like "Daily Raised*"
with
# Look for Specific reports
$DailyOpen = Get-ChildItem $downloadTemp -name "Daily Open*"
$DailyProduct = Get-ChildItem $downloadTemp -name "Daily Product*"
$DailyRaised = Get-ChildItem $downloadTemp -name "Daily Raised*"
Note, you will also have to change:
If ([bool]$DailyOpen -eq $True)
If ([bool]$DailyProduct -eq $True)
to:
If ($DailyOpen)
If ($DailyProduct)
As now these variables will not be initialized if there is no content matching your pattern.
You can also do away with the $file variable now
:-)
enjoy

Powershell - Email does not have same format as text despite Out-string

Folks,
Googling shows me lots of folks have this problem, however the answers I'm getting do not seem to work for me. Either that, or I don't understand.
Situation: I have a script that polls and gives a file count. It works great and I pipe it to a text file
Foreach ($Directory in $Directories) {
Write-Output "You have $Results files in that folder" | Out-File "C:\Filecheck.txt" -Append
}
Filecheck looks great. It does the above loop 6 times (as I have 6 directories) and it does the carriage returns.
In email, its all jumbled up. On here, someone suggested I use the out-string, so Ive done this:
$body = GC "C:\Filecheck.txt" | Out-string
I've also seen
$body = GC "C:\Filecheck.txt" -Raw
I get the email fine, but again, its still all one line, with no carriage returns.
Anyone have any idea? I know Im so close.
You could try using the [Environment] newline. I tested with the code below and the e-mail looked good and with the correct line breaks:
$DirectoriesFiles = 2,3,4,5
$newline = [Environment]::NewLine
$body = "List of number of files" + $newline
Foreach ($numOfFiles in $DirectoriesFiles) {
$body += "You have $numOfFiles files in that folder" + $newline
}
$ol = New-Object -comObject Outlook.Application
$Mail = $ol.CreateItem(0)
$Mail.To = "someone"
$Mail.Subject = "some test e-mail"
$Mail.Body = $body
$Mail.save() #or send
For the example's sake I just assumed you have an array with the number of files in the folder, but I think you can understand how to adapt to your context from here. My resulting e-mail looked like this:
List of number of files
You have 2 files in that folder
You have 3 files in that folder
You have 4 files in that folder
You have 5 files in that folder
Thanks for your help. My company email didn't like the format, but I format in html (using ) and utilize IsBodyHTML tag, it works like a charm!
Del "D:\Filecheck.txt"
$Directories = GC "D:\Directory.txt"
Foreach ($Directory in $Directories) {
$Results = (Get-ChildItem $Directory).count
If ($Results -gt 0) {
Write-Output "...You have $Results files stuck in $Directory...<br><br> " | Out-File "D:\Filecheck.txt" -Append
} else {
Write-Output "Phew! We're good, <br><br>" | Out-File "D:\Filecheck.txt" -Append
}
$Results = $null
}
$body = GC "D:\Filecheck.txt"
Add-PSSnapin Microsoft.Exchange.Management.Powershell.Admin -erroraction silentlyContinue
$SmtpClient = new-object system.net.mail.smtpClient
$SmtpServer = "localhost"
$SmtpClient.host = "relay.me.local"
$msg = new-object Net.Mail.MailMessage
$msg.IsBodyHTML = $true
$smtp = new-object Net.Mail.SmtpClient($smtpServer)
$msg.From = "TelluRyesFileCheck#me.you"
$msg.To.Add("me#you.org")
$msg.Subject = "Checking if files exist on 9901/2"
$msg.Body = $body
$SmtpClient.Send($msg)

Cannot Get Send-MailMessage to Send Multiple Lines

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.

Powershell script for Soon-to-expire AD users

So basically, what I have here is a script that will scan a CSV that it imports, and for every entry in the spreadsheet, except for people in the RANDOM.DOMAIN, it will find the managers email address and send an automated email to the manager telling them user XYZ is about to expire soon, and they need to do something about it.
If the managers email is unavailable for some reason, then it defaults to sending the email to me.
This script works well.
The problem I am running into is, I want to make it so only one email is sent to each manager, despite multiple users (or entries) from the spreadsheet, list them as the manager.
I.e. if Joe Bloggs has a manager Aaron T and Jane Doe has the manager Aaron T, then Aaron T will get two emails, one email for each user.
MY QUESTION:
Is there an easy way to only get it to send one email per manager, even if that manager has multiple users reporting to them that are about to expire?
$datai = Import-Csv "Soon-to-expire User Accounts22.csv" | select 'Display Name',Manager,'Domain Name','Account Expiry Time'
Connect-QADService -Service another.DC | Out-Null
$expiringUsers = #{}
foreach ($i in $datai) {
$dn = $i.'Display Name'
$dn1 = $i.'Domain Name'
$man = $i.'Manager'
$aet = $i.'Account Expiry Time'
$subject = "Account about to expire: $dn"
$getmail = get-qaduser "$man" -LdapFilter '(mail=*)' | select mail
$emailAD = $getmail.mail
if ($man -eq "-" -or $man -like 'CN=*' -or $getmail -eq $null -or $man -eq "") {
$man = "Aaron T"
$getmail = get-qaduser "$man" -LdapFilter '(mail=*)' | select mail
$emailAD = $getmail.mail
}
if ($expiringUsers.Contains($emailAD)) {
$expiringUsers[$emailAD]["dn"] += $dn += "`n"
$expiringUsers[$emailAD]["aet"] += $aet += "`n"
$expiringUsers[$emailAD]["man"] += $man += "`n"
} else {
$expiringUsers[$emailAD] = #{
#"dn1" = $dn1
#"aet" = $aet
#"man" = $man
# "dn" = #( $dn )
}
}
}
$expiringUsers | fc #as suggested
foreach ($emailAD in $expiringUsers.Keys) {
$dn = $expiringUsers[$emailAD]["dn"]
$dn1 = $expiringUsers[$emailAD]["dn1"]
$man = $expiringUsers[$emailAD]["man"]
$aet = $expiringUsers[$emailAD]["aet"]
$subject = "Account/s About to Expire!"
$content = #"
Hi,
$dn `n
$dn1 `n
$man `n
$aet `n
$emailAD `n
Technology Services
"#
Send-MailMessage -from "aaron#website.com" `
-To $emailAD `
-Subject $subject `
-Body $content `
-Priority high `
-smtpServer "relay.server"
#using this as a test instead of sending mass emais all the time
Write-Host $content
}
UPDATED with the new script as requested.... still having issues.
Is there an easy way to only get it to send one email per manager, even if that manager has multiple users reporting to them that are about to expire?
For this you need to defer e-mail processing. Collect the users in a hashtable, e.g. by manager e-mail address:
...
$expiringUsers = #{}
foreach ($i in $datai) {
If ($i.'Domain Name' -notmatch "RANDOM.DOMAIN") {
...
if ($expiringUsers.Contains($emailAD)) {
$expiringUsers[$emailAD]["dn"] += $dn
} else {
$expiringUsers[$emailAD] = #{
"dn1" = $dn1
"aet" = $aet
"man" = $man
"dn" = #( $dn )
}
}
}
}
and move the actual e-mail processing outside the loop:
foreach ($emailAD in $expiringUsers.Keys) {
$dn1 = $expiringUsers[$emailAD]["dn1"]
$man = $expiringUsers[$emailAD]["man"]
$aet = $expiringUsers[$emailAD]["aet"]
$subject = "Account about to expire: $($expiringUsers[$emailAD]["dn"])"
$content = #"
Hi,
...
Technology Services
"#
Send-MailMessage -from "Test Script - Powershell <email#test.com>" `
-To "$emailAD" `
-Subject $subject `
-Body $content `
-Priority high `
-smtpServer servername
Write-Host "Mail Sent to $man"
}
Note that for simplicity reasons the above code only records the expiry date of the first user. If you want the expiry date of each user recorded separately, you'll have to take additonal steps, e.g.
$expiringUsers[$emailAD]["expiry"] += #{
"name" = $dn;
"date" = $aet;
}
instead of
$expiringUsers[$emailAD]["dn"] += $dn
So I finally decided to revisit this script, after many, many months.
I'm get a little better at PowerShell and while I'm sure this isn't the most effective way to do it, this is something that works for me.
I've also changed the input method; it pulls the information directly from AD, instead of using a CSV file that used to be generated from an application called 'AD Manager Plus' (Hate it).
Remember, using Quest CMDlets here because we don't have a 2008 environment. (so using Get-QADUser instead of Get-ADuser)
FYI, I have only posted the code here which sorts out the data into separate tables - you can decide how you want to utilize those results. For our environment, I have it build an nice HTML table and body, then send it to the appropriate manager to deal with.
#user data input
$data = get-qaduser -SizeLimit 0 -includedproperties accountexpires | where {$_.AccountExpires -ne $null -and $_.AccountExpires -le ((Get-Date).AddDays(45)) }
#get a list of managers, unique.
$uniqueMan = $data | select Manager -Unique
#foreach manager from $uniqueman
Foreach ($manager in $uniqueman) {
#create the array variable / clear it out for the next manager.
$myarray = #()
#foreach User found in in $data query
Foreach ($user in $data) {
#Search the $user's query for people with the same manager as that of the $uniqueman list.
If ($user.Manager -eq $manager.Manager) {
#do what with the result.
#add the person to an array
$myarray += New-Object psobject -Property #{
Name = $user.'Name'
UserName = $user.'SAMAccountName'
AccountExpires = $user.'AccountExpires'
Manager = $user.Manager
}
}
#for testing, to output the results to an HTML file.
#$myarray | ConvertTo-Html | Out-File ("C:\test\" + $manager.Manager + ".html")
}
}