Power shell send email in loop and write output to file - powershell

Send-MailMessage -From "test#xxx.com" -To "aaa#xxx.com","bbb#xxx.com","ccc#xxx.com","ddd#xxx.com" -SmtpServer "xx.xx.xx.xx" -Subject "Test email subject" -Body "Test email body"
I want to write a script using above command in loop say 100 times and run every 2 seconds and write an output to a log file which later can be used for analysis.
Issue: My severer gives intermittent issue while sending emails. I want to see logs to see how many emails go through out of 100 and how many failed and with what error message.

Running multiple times would require some sort of loop. A for loop seems most appropriate for each loop of 100, and a while loop seems appropriate for looping over each set.
Since you are running commands very quickly you will want some sort of parallelism. Jobs, Runspaces, and workflows are the three most common forms. Jobs are the easiest to work with but the least efficient.
Then the Start-Sleep command can be used for the delay between trials.
There are many ways to write out to a file, Out-File -Append is one way. Try\catch can be used to catch the errors and output those to the log as well.
$Iterations = 100
$Delay = 2
$StopTime = (Get-Date).AddMinutes(5)
$Command = {Send-MailMessage -From "test#xxx.com" -To "aaa#xxx.com","bbb#xxx.com","ccc#xxx.com","ddd#xxx.com" -SmtpServer "xx.xx.xx.xx" -Subject "Test email subject" -Body "Test email body"}
while ((Get-Date -le $StopTime)) {
try {
Remove-Job -State Completed
for ($i = 0; $i -lt $Iterations; $i++) {
Start-Job -ScriptBlock $command
}
"Jobs created at (Get-Date)" | Out-File "C:\example\log.txt" -Append
Start-Sleep $delay
}
catch {
$error[0] | Out-File "C:\example\log.txt" -Append
}
}

Related

Powershell send-mailmessage to read 17k emails from csv & send to 100 users in each email

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.

Powershell script for monitoring a list of files in parallel

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
}
}

IF statement + send-mailmessage confused

Quick question;
I want to make a log of bad stuff happening to the servers, but that's not the question.
The log should be deleted upon send, and if there is no log at the time the script is being run it should send a different message, without an attachment.
Here's the script so far:
<#
Set-ExecutionPolicy unrestricted -Force
iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))
cinst BetterCredentials
#>
Import-Module BetterCredentials
$Mailcred = Get-Credential -user user#gmail.com -pass 12345password
Try{
$File = C:/path/errorfile.log
}
Catch [System.Management.Automation.PSArgumentException]
{
"invalid object"
}
Catch [system.exception]
{
"caught a system exception"
}
Finally
{
If ($File -eq ){
then (send-mailmessage -ErrorAction SilentlyContinue -from "Server <Server#company.com>" -to "IT Dept. <Itdept#company.com>" -subject "Log of the day." -body "Good Morning,
Fortuneatly, nothing bad to report today!" -priority High -dno onSuccess, onFailure -smtpServer smtp.company.com -credential ($Mailcred) -usessl)
else (send-mailmessage -ErrorAction SilentlyContinue -from "Servers <Server#company.com>" -to "IT Dept. <ITDept#company.com>" -subject "Log of the day." -body "Good Morning,
Here is your daily report." -Attachments $File -priority High -dno onSuccess, onFailure -smtpServer smtp.company.com -credential ($Mailcred) -usessl)
}
}
What AM I doing wrong here?
Some parts of your code do not make any sense. Let's review those.
$File = C:/path/errorfile.log
...
If ($File -eq ){
The first part is about assignment. In order to find out wether errorfile.log exists, use the Test-Path cmdlet like so,
$File = test-path 'C:/path/errorfile.log'
Always use quotes around file name. If you got spaces within path, unquoted path doesn't work.
The second part is the actual test.
If ($File){
... # file was on disk, send alert and delete it
} else {
... # no file found, send ok
}

Send outlook email from powershell script

When I write the following script into the powershell command line one by one, it successfully sends the email, but when I run the script, it returns a bunch of errors. I'm guessing something syntactically needs to be changed in order to run it as a script? Any ideas?
Start-Process Outlook
$o = New-Object -com Outlook.Application
$mail = $o.CreateItem(0)
#2 = high importance email header
$mail.importance = 2
$mail.subject = “Auto Build Test“
$mail.body = “This is a test“
#for multiple email, use semi-colon ; to separate
$mail.To = “myemail#company.com"
$mail.Send()
# $o.Quit()
Param(
[parameter(Mandatory=$true)]
[alias("e")]
[string]$RecipientEmailAddress
)
if($RecipientEmailAddress -notmatch "\b[A-Za-z0-9._%+-]+#BLAHH.com")
{
Write-Output "the email address for the receipient of log reports is not a valid email address, hence will not send the report via email. They can still be accessed at " |Out-String ;
}else
{
$returnVal= New-Object PSObject ;
$returnVal |Add-Member -Name is_Success -MemberType NoteProperty -Value $null;
$returnVal |Add-Member -Name Explanation -MemberType NoteProperty -Value $null;
try{
$Attachments =Get-ChildItem -Path "C:\FOLDERWHEREYOURAATACHMENTS ARESTORED";
if($Attachments.count -eq 0)
{
$returnVal.Explanation="Error sending log report email to the user: $RecipientEmailAddress. Please check if the C:\FOLDERWHEREYOURAATACHMENTS is accessible and there are indeed log files present";
#Write-Output "Error sending log report email to the user: $RecipientEmailAddress" |Out-String ;
#Write-Output "Please check if the C:\FOLDERWHEREYOURAATACHMENTS is accessible and there are indeed log files present "|Out-String;
$returnVal.is_Success= $false;
return $returnVal;
}
$TestedAttachmentsList = new-Object System.Collections.ArrayList;
for($i=0;$i -lt $Attachments.count;$i++)
{
$TestedAttachmentsList.add($Attachments[$i].FullName);
}
Send-MailMessage -From "<FROM#BLAHH.COM>" -To "<$RecipientEmailAddress>" -SmtpServer "mail.BLAHH.com" -Attachments $TestedAttachmentsList -Subject "BLAHH SUBJECT" -Body "BLAHH BLAHH";
$returnVal.is_Success=$true;
$returnVal.Explanation="An email has been sent to the $RecipientEmailAddress containing the log of the setup and configuration."
return $returnVal ;
}Catch [System.Exception]
{
#Write-Output "Error sending log report email to the user: $RecipientEmailAddress" |Out-String ;
#Write-Output "Please check communication between your host machine and mail.BLAHH.com on port 25 is possible"|Out-String;
$returnVal.is_Success= $false;
$returnVal.Explanation="Error sending log report email to the user: $RecipientEmailAddress Please check communication between your host machine and mail.BLAHH.com on port 25 is possible";
return $returnVal ;
}
}
The syntax doesn't change between the command line and a script file. What changes is how fast the commands are executed. If you're typing them in then there is plenty of delay between each command. But if they run from a script they get presented to Outlook much more quickly.
A simple way to fix this is add Sleep 1 (or similar) before the command that fails. Without seeing your error output I would guess that you want to sleep after CreateItem and maybe before Send. But if you look at the error messages closely you'll see they identify which line of the script failed. Put Sleep before the first line that failed. Retry the script. If a new line fails, then put a delay before it as well. If the first line still fails you can try Sleep 2. You can also make the Sleep shorter. For 1/2 second: Sleep -milliseconds 500.
IF adding Sleeps fixes the problems - in other words the problem is a synchronization issue, there may be something in the Outlook object model you could use that wouldn't be as hackish as using Sleeps.
I wasn't able to repro this on my Outlook 2010 installation. However I did look up an alternate method for sending email from PS (below). Maybe this method will work.
$i=$o.Session.folders.item(2).folders.item("Outbox").items.add(0)
$i.to="me#whereiwork.com"
$i.Subject="a wittle testy"
$i.Body="some body"
$i.send()

Powershell Function runs for each item in array (I don't want this to happen)

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
}