I have the following code (thanks to input from 'jon Z' and 'manojlds') that processes server lists, checks the error logs for each server against a search string, and then sends an email report listing the servers with bad error logs and good error logs.
What I would like to do now is do a count of the number of servers with bad error logs and good error logs and put that info in the email report as well.
I would have something like this in each heading of my email report:
The following n servers have bad error logs:
The following n servers are OK:
I've been trying to accomplish this by using the Count method, but have so far been unsuccessful. Is the Count method the correct approach? Is so, how (where would I apply it in my code)? If not, then what is the best approach?
Code:
$BadServerLogs = "<font style=`"font-family:verdana;font-size:9pt`"><p><b>The following servers have bad error logs:</b></p>"
$GoodServerLogs = "<font style=`"font-family:verdana;font-size:9pt`"><p><b>The following servers are OK:</b></p>"
# Use hash table to associate server list to search string array
$Groups = #{
$SERVER_LST_1=$SEARCH_STR_ARRAY_1;
$SERVER_LST_2=$SEARCH_STR_ARRAY_2;
$SERVER_LST_3=$SEARCH_STR_ARRAY_3;
$SERVER_LST_4=$SEARCH_STR_ARRAY_4;
}
$StartupErrors = #{}
$Groups.keys | %{
$key = $_
gc $key | %{
# Check StartupError.log files for errors
$StartupErrors[$_] = Get-ChildItem -Path \\$_\$LOG_PATH -Include StartupError.log -Recurse | Select-String -notmatch $Groups["$key"]
If ($StartupErrors[$_])
{
$Subject = "StartupError Logs Report: BAD ERROR LOGS!"
$BadServerLogs += "<li>$_</li>"
}
Else
{
$Subject = "StartupError Logs Report: All Logs are Fine"
$GoodServerLogs += "<li>$_</li>"
}
}
}
# Send email listing servers with bad/good StartupError log files
Send-MailMessage -Body "$BadServerLogs $GoodServerLogs" -BodyAsHtml -Subject $Subject -SmtpServer $SmtpServer -To $MailTo -From $MailFrom
Thanks in advance! -Keith
You can just create two variables:
$goodCount = 0
$badCount = 0
Then in the loop increment each depending on the case:
If ($StartupErrors[$_])
{
$Subject = "StartupError Logs Report: BAD ERROR LOGS!"
$BadServerLogs += "<li>$_</li>"
$badCount += 1
}
Else
{
$Subject = "StartupError Logs Report: All Logs are Fine"
$GoodServerLogs += "<li>$_</li>"
$goodCount += 1
}
Then create some HTML and send it:
$html = "<h1>The following $badCount servers have bad error logs:</h1>" + $BadServerLogs
$html += "<h1>The following $goodCount servers are OK:</h1>" + $GoodServerLogs
Send-MailMessage -Body $html -BodyAsHtml -Subject $Subject -SmtpServer $SmtpServer -To $MailTo -From $MailFrom
Related
I'm creating a PS script to automate email blast to 17k users. Our exchange security baseline is set to only accept 60 requests per minute. Because I'm looping through the email list (CSV) line by line (sleep 1 sec), it took hours for my script to complete. What I'm trying to achieve now is to send the email to 100 users per request. I'm figuring out how to store the emails in an array of 100 & send the mail before going for the next 100. Any suggestion?
$recipients = Get-Content "mailinglist.csv"
foreach($rcpt in $recipients)
{
Write-Host "Attempt sending email to $rcpt ..."
Send-MailMessage -ErrorAction SilentlyContinue -ErrorVariable SendError -From $From -to $rcpt -Subject $Subject -SmtpServer $SMTPServer -port $SMTPPort -UseSsl -Credential $Cred -BodyAsHtml ($Body -f $Subject, $Date, $Venue, $Description, $Image)
$ErrorMessage = $SendError.exception.message
If($ErrorMessage)
{
Write-Host "Failure - $ErrorMessage" -ForegroundColor Red
Start-Sleep -Seconds 60
Send-MailMessage -ErrorAction SilentlyContinue -ErrorVariable SendError -From $From -to $rcpt -Subject $Subject -SmtpServer $SMTPServer -port $SMTPPort -UseSsl -Credential $Cred -BodyAsHtml ($Body -f $Subject, $Date, $Venue, $Description, $Image)
}
ElseIf($SendError.exception.message -eq $null)
{
Write-Host "Email has been sent to $rcpt" -ForegroundColor Green
Start-Sleep -Seconds 1
$n++
}
}
Write-Host "Total sent = $n"
You could use a traditional for loop and access your array elements by index.
$recipients = Get-Content "mailinglist.csv"
$To = <SomeValidEmailAddress>
$LastIndex = $recipients.GetUpperBound(0)
for ($i = 0; $i -le $LastIndex; $i+=100) {
$upperRange = [Math]::Min(($i+99),$LastIndex)
$Params = #{
ErrorAction = 'SilentlyContinue'
ErrorVariable = 'SendError'
Bcc = $recipients[$i..$upperRange]
To = $To
From = $From
Subject = $Subject
SmtpServer = $SMTPServer
Port = $SMTPPort
Credential $Cred
Body = $Body -f $Subject, $Date, $Venue, $Description, $Image
BodyAsHTML = $true
UseSsl = $true
}
"Attempt sending email to $($recipients[$i..$upperRange]) ..." # You may want to alter this to be more readable
Send-MailMessage #Params
# Other code
}
Explanation:
I've opted to use Splatting here for readability and manageability with the $Params hash table. It is entirely optional.
The -bcc parameter of Send-MailMessage supports a string array (string[]). Using this over the -To parameter will preserve privacy of the recipients. You can then easily send an email to multiple recipients provided you pass it an array. However, -To is required for Send-Mailmessage to work. It is recommended to make the email address passed into -To something that can be spammed or has a way of handling these types of emails. I have set up the $To variable for you to provide that email address. If privacy is of no concern whatsoever, -Bcc can just be replaced with -To.
Since $recipients is an array, you can access its elements by index, which supports the range operator ... $recipients[0..99] would be the first 100 items in the list.
$LastIndex stores the last index of the list, which is the value returned by the Array.GetUpperBound(Int32) method with dimension 0. Since the array is one-dimensional, 0 is the only dimension.
$upperRange is the beginning index ($i) plus 99. Should $upperRange ever be larger than $LastIndex, it will be set to $LastIndex. Depending on your PowerShell version, the $i+99 and $LastIndex comparison may not be necessary. Accessing an upperbound range beyond the size of the array, will just return all of the remaining elements of the array without throwing an error. This is likely just for completeness.
I am working on a PowerShell script which sends emails multiple times with different subject and body each time.
I am trying to move Send-MailMessage into a function or something that I could use to reduce the code lines.
$Sender = 'jones#example.com'
$text = "<html><body>"
$text += "<p>Welcome</p>"
### A cmdlet that would give recipient email address
$Recipient = (Get-Details -user $user).email
$smtp = "server.example.com"
$subject = "welcome email"
Send-MailMessage -BodyAsHtml $text -from $Sender -SmtpServer $smtp -Priority high -to $Recipient -Subject $subject
Write-Output "executing commands to capture results"
Write-Output ""
### Few Commands executed in this step
Write-Output "Analyzing results"
### Few commands executed in this step
$newtext = "<html><body>"
$newtext += "Congrats, you are selected"
$newsubject = "results email"
Send-MailMessage -BodyAsHtml $newtext -from $Sender -SmtpServer $smtp -Priority high -to $Recipient -Subject $subject
You could create a function like this:
Function Send-Email($text,$subject,$recipient)
{
Send-MailMessage -BodyAsHtml $text -From "jones#example.com"
-SmtpServer "server.example.com" -Priority High -To $recipient -Subject $subject
}
You can call it like:
Send-Email -text "Hello" -subject "Test" -recipient "test#example.com"
You can add or remove arguments depending on what will change though. Assuming the smtp server won't change for example, this isn't needed as a parameter.
I am trying to move Send-MailMessage into a function or something that I could use to reduce the code lines.
Writing a function for one line can be useful if the options are many and never change. However you do have one that changes. There is another PowerShell feature that would work here just as well. Splatting!
$emailParameters = #{
From = $Sender
SmtpServer = $smtp
Priority = "high"
To = $Recipient
Subject = $subject
}
Send-MailMessage -BodyAsHtml $text #emailParameters
# ... other code and stuff
Send-MailMessage -BodyAsHtml $newtext #emailParameters
Now you still only have to make changes in one place and the code is arguably more terse.
Another point is that when you are making multi-line strings all at once, as supposed to building over the course of the script you can always use here strings. You only have two lines but if you code evolves over time it is a good tactic to start early instead of many $object += "string" lines
$text = #"
<html><body>
<p>Welcome</p>
"#
Note that indentation is preserved in the resulting here-string. The "# has to appear on its own line with no leading whitespace. Using double quotes means you can still expand variables as well in there.
We have about a half dozen servers that ftp a pair of text file with a status update every five minutes. We're trying to use a combination of Windows Task Scheduler and a Powershell script as the monitor and alert engine. The script is looking for files that have not been refreshed in the last 60 minutes and reading the files for any condition other than 'OK'. We'd like the scheduled task to run every five minutes.
When one of the while conditions are true for a file defined in $statusfiles (see script below) the script is getting hung up looping the same file while it waits for the condition to be false rather than processing the next file in the list and watching the problem files in the background. We want to know as soon as the script is run if there's a problem reported by any of the status files.
In my first version I used an If-ElseIf-Else approach. This gave me the immediate notification I was looking for but if I don't loop the alert conditions I don't know how to set a retry interval ($retryinterval) to give us a grace period to fix the underlying problem.
Is it possible to make the foreach method run in parallel against all the files defined in $statusfiles? Would this be system intensive? Or is there another approach I'm not seeing?
Thanks in advance for the help!
$erroractionpreference="SilentlyContinue"
$filepath = "\\Server\DirectoryA\DirectoryB\DirectoryC"
$statusfiles = "$filepath\opmessage21.txt","$filepath\pgmessage21.txt","$filepath\opmessage23.txt","$filepath\pgmessage23.txt","$filepath\opmessage24.txt","$filepath\pgmessage24.txt","$filepath\opmessage25.txt","$filepath\pgmessage25.txt","$filepath\opmessage26.txt","$filepath\pgmessage26.txt"
$agelimit = "60"
$retryinterval = "1800"
$to = "recipient1#email.com","recipient2#email.com","recipient3#email.com","recipient4#email.com"
$from = "alert#email.com"
$smtp = "smtp.server.com"
$port = "25"
function send-email
{
Send-MailMessage -Body "$body" -to $to -from $from -Subject "$subject" -smtp $smtp
}
foreach ($statusfile in $statusfiles) {
# Initialize the filestale and file error variables and strip the path and extension from $statusfile
$filestale = 0
$fileerror = 0
$messageid = $statusfile.split('\.')[-2]
# Get LastWriteTime of the file and alert the admin if LastWriteTime is older than X minutes
$lastupdated = (get-childitem $statusfile).LastWriteTime
while ($lastupdated -lt (get-date).AddMinutes(-$agelimit)) {
$filestale = 1
write-host "$messageid is older than $agelimit minutes. (Last updated: $lastupdated)"
$subject = "$messageid is older than $agelimit minutes"
$body = "Last updated: $lastupdated"
send-email
start-sleep -s $retryinterval
$lastupdated = (get-childitem $statusfile).LastWriteTime
}
# Alert the admin the file is no longer outdated
if ($filestale = 1) {
$filestale = 0
write-host "$messageid has been refreshed. (Last updated: $lastupdated)"
$subject = "$messageid has been refreshed"
$body = "Last updated: $lastupdated"
send-email
}
# Check the file for an error and alert the admin if the status is not OK
$messagecontents = Get-Content -Path "$statusfile" -Raw
while ($messagecontents -ne 'OK') {
$fileerror = 1
write-host "$messageid reported the following error: $messagecontents (Last updated: $lastupdated)"
$subject = "Error reported by $messageid"
$body = "$messagecontents (Last updated: $lastupdated)"
send-email
start-sleep -s $retryinterval
$messagecontents = Get-Content -Path "$statusfile" -Raw
$lastupdated = (get-childitem $statusfile).LastWriteTime
}
# Alert the admin the status is now OK
if ($fileerror = 1) {
$fileerror = 0
write-host "$messageid indicates status OK. (Last updated: $lastupdated)"
$subject = "$messageid indicates status OK"
$body = "Last updated: $lastupdated"
send-email
}
}
I have created a script to provision Lync users, important details (such as assigned LineURI) for new provisions need to be emailed. Also any errors need to be sent (fluffed up with some friendly error messages of course :)).
So I created a few CSVs with all relevant data..
Then I created a function:
Function Send-Email ($attachArray) {
# Get a list of to addresses
$toAddresses = "foo#corp.local","bar#corp.local"
# Process replacments
Replace-EmailMasks
# Send conditionaly
Switch ($attachArray) {
$null {
Send-MailMessage -SmtpServer "internalrelay.corp.local" `
-From "test#andylab.local" -To $toAddresses `
-Subject "There should really be something more informative here" `
-BodyAsHTML $SCRIPT:htmlBody
}
Default {
Send-MailMessage -SmtpServer "internalrelay.corp.local" `
-From "test#andylab.local" -To $toAddresses `
-Subject "There should really be something more informative here" `
-BodyAsHTML $SCRIPT:htmlBody
-Attachments $attachArray
}
}
}
Here's how I invoke it:
# Logic, then send
If (($npSuccess -gt 0) -AND ($errorsExist -gt 0)) {
# Attaching both
# Heres the summary paragraph
$SCRIPT:customSummary = '<p>Success and errors :|</p>'
# Now I'm sending it.
Send-Email "$($tempPlace.fullname)\NewProviSsion_Output.csv","$($tempPlace.fullname)\Errors_Output.csv"
} ElseIf ($npSuccess -gt 0) {..} # output-generating Success
ElseIf ($errorsExist -gt 0) {..} # Failed somewhere
Else {..} # no output-generating Success, no overall fails
Now this works; Email looks nice, goes to who it should, files attached etc..
Problem is:
For however many files I specify in $attachArray, that's how many emails get sent. The emails are all exactly the same, going to all the same people n many times.
It's as if i'm doing this:
ForEach ($item in $attachArray) {
Send-Email "$($tempPlace.fullname)\NewProviSsion_Output.csv","$($tempPlace.fullname)\Errors_Output.csv"
}
Except i'm not..
To clarify my objective, I want the email to be sent to all in $toAddresses only once.
Can anyone enlighten me as to what's going on here?
Maybe I've just had a bad Monday morning..
The switch statement fires for each element of the array. This behavior is documented (check Get-Help about_Switch):
If the test value is a collection, such as an array, each item in the collection is evaluated in the order in which it appears.
Use a regular conditional instead (since you have only 2 cases anyway):
if ($attachArray -eq $null) {
Send-MailMessage -SmtpServer "internalrelay.corp.local" `
-From "test#andylab.local" -To $toAddresses `
-Subject "There should really be something more informative here" `
-BodyAsHTML $SCRIPT:htmlBody
} else {
Send-MailMessage -SmtpServer "internalrelay.corp.local" `
-From "test#andylab.local" -To $toAddresses `
-Subject "There should really be something more informative here" `
-BodyAsHTML $SCRIPT:htmlBody
-Attachments $attachArray
}
Here is my code that checks when users passwords will expire and emails them if it will expire in less than 14 days. Since the link in the body of the email has spaces in it, the link does not encompass the whole file name.
#Add the Quest PowerShell snapin
Add-PsSnapIn Quest.ActiveRoles.ADManagement -ErrorAction SilentlyContinue
#Clear the placeholder log file
Clear-Content O:\logs\network\passwordchangeemails.txt
#Set Email Variables
$today = Get-Date
$logdate = Get-Date -format yyyyMMdd
$emailFrom = "my.name#mycompany.com"
$body = "Good Morning, `n`n"
$body += "As a courtesy reminder, your Company password will be expiring soon. `n`n"
$body += "If you allow your password to expire, you will be unable to access our network, mail or QAD until you recieve assistance from the Helpdesk. `n`n"
**$body += "To avoid this, please change your network password as soon as possible. (Remote users, please follow the password change procedure for remote users. Click Here -> " + "\\mydfsshare.net\share\helpdesk\guides\Passwords - Change procedure for remote users.pdf ) `n`n"**
$body += "Feel free to contact the Help Desk by phone at 555-555-5555 or by email at Helpdesk#mycompany.com `n"
$body += "Thanks!"
#Get Active Directory Information
Get-QADUser -SizeLimit 0 | Select-Object samAccountName,mail,PasswordStatus |
Where-Object {$_.PasswordStatus -ne "Password never expires" -and $_.PasswordStatus -ne "Expired" -and $_.PasswordStatus -ne "User must change password at next logon." -and $_.mail -ne $null} |
#For each user, get variables
ForEach-Object {
$samaccountname = $_.samAccountName
$mail = $_.mail
$passwordstatus = $_.PasswordStatus
$passwordexpiry = $passwordstatus.Replace("Expires at: ","")
$passwordexpirydate = Get-Date $passwordexpiry
$daystoexpiry = ($passwordexpirydate - $today).Days
#If days to expire is lessthan 14 days, send email to user
if ($daystoexpiry -lt 14) {
$emailTo = "$mail"
$subject = "Company IT Notification: Your Network password will expire in $daystoexpiry day(s). "
Send-MailMessage -To $emailTo -From $emailFrom -Subject $subject -Body $body -SmtpServer 192.168.1.191
Write-Host "Email was sent to $mail on $today"
Add-Content O:\logs\network\passwordchangeemails.txt "Email was sent to $mail on $today"
}
}
$recipients = "my.name#mycompany.com"
#Copy contents of log file to email and send to IT Department
$content = [IO.File]::ReadAllText("O:\logs\network\passwordchangeemails.txt")
Send-MailMessage -To $recipients -From "my.name#mycompany.com" -Subject "Password change log for $today" -Body "This is the log from $today, $content" -SmtpServer 111.111.111.111
Sorry I can't test this right now but if HTML is OK to use, try wrapping the link as follows...
"Text you want the users to see goes here<br>`n
Then set -BodyAsHtml on Send-Mailmessage:
Send-MailMessage -To $emailTo -From $emailFrom -Subject $subject -Body $body -SmtpServer 192.168.1.191 -BodyAsHtml