Sending emails using multiple attachments using powershell script - powershell

I have the following variable stored
PS C:>$PathArray
jagbir.singh1990#gmail.com
mr.singh#gmail.com
PS C:>$PathArray2
805775-1.zip
805775-2.zip
$Arrayresult = for ( $i = 0; $i -lt $max; $i++)
{
Write-Verbose "$($PathArray[$i]),$($PathArray2[$i])"
[PSCustomObject]#{
PathArray = $PathArray
PathArray2 = $PathArray2
}
PS C:>$Arrayresult
PathArray PathArray2
--------- ----------
{jagbir.singh1990#gmail.com, mr.singh#gmail.com...} {805775-1.zip, 805775-2.zip...}
{jagbir.singh1990#gmail.com, mr.singh#gmail.com...} {805775-1.zip, 805775-2.zip...}
I want to send email1 with body text containing zip file1 name and email2 with body text containing zip file2 name
Ex:
From : jagbir.singh1990#gmail.com
Body : 805775-1.zip file transfer successful.
No Attachments required
code:
foreach($X in $Arrayresult){
Send-MailMessage -From $X.Patharray -To 'jagbir.singh1990#gmail.com' -Subject 'File Transfer completed successfully' -body 'File $X.PathArray2 transfer success' -smtpServer 'smtp.gmail.com' -Port 465
}
Write-Host "Email sent.."
How can I seperate each email for each zip file
email1 --> file1
email2 --> file2

I'm not sure why you would want to keep the email addresses and filenames in separate arrays and then combine them in an array of PSObjects for this.
Anyway, the first mistake is that you did not specify a value for variable $max, which should be the count for the smallest of the two arrays (I'm using more descriptive variable names for these arrays):
$max = [math]::Min($email.Count, $files.Count)
The second one is that you are adding the complete arrays in the properties of each PSCustomObject you create instead of one element of the input arrays.
Finally, I would suggest using a simpler loop (not creating a new PSObject array), use Splatting for the parameters of Send-MailMessage and add some error checking while sending:
Something like:
# array of email addresses to send TO
$email = 'jagbir.singh1990#gmail.com', 'mr.singh#gmail.com'
# array of filenames to use in the mail body
$files = '805775-1.zip', '805775-2.zip'
# determine the max number of iterations
$max = [math]::Min($email.Count, $files.Count)
# set up a hashtable for splatting the parameters to the Send-MailMessage cmdlet
$mailParams = #{
From = 'me#gmail.com'
Subject = 'File Transfer completed successfully'
SmtpServer = 'smtp.gmail.com'
Port = 465
# other parameters go here
}
for ($i = 0; $i -lt $max; $i++) {
# inside the loop we add/update the parameters 'To' and 'Body'
$mailParams['To'] = $email[$i]
$mailParams['Body'] = "File $($files[$i]) transfer success"
try {
Send-MailMessage #mailParams -ErrorAction Stop
Write-Host "Email sent.."
}
catch {
Write-Error "Email NOT sent.`r`n$($_.Exception.Message)"
}
}

Related

email to check size / if it's complete

Running the following script:
$FileToCheck = Get-Item -Path $folder/test.zip -ErrorAction SilentlyContinue
$EmailSplat = #{
To = 'business#email.com'
CC = 'admin#email.com'
#SmtpServer = 'smtp.server.net'
From = 'my#email.com'
Priority = 'High'
}
$folder = "C:\test\"
# first condition: 'If the file does not exist, or was not created today, an e-mail should be sent that states "File not created" or similar.'
if ((-not $FileToCheck) -or ($FileToCheck.CreationTime -le (Get-Date).AddDays(-1))) {
$EmailSplat.Subject = 'File not Found or not created today'
$EmailSplat.building = 'This is the email building'
Send-MailMessage #EmailSplat
# second condition 'If the file exists and was created today, but has no content, no e-mail should be sent.'
} elseif (($FileToCheck) -and ($FileToCheck.Length -le 2)) {
#third condition and the default condition if it does not match the other conditions
} else {
$EmailSplat.Subject = 'Active Directory Accounts To Check'
$EmailSplat.building = Get-Content -Path/test.zip //maybe add the file??
Send-MailMessage #EmailSplat
}
Goal: Check if file .zip is complete, once is complete it sends an email to let business that file is good to go. I'm running the script, getting no errors but no alert emails either.
Build on: Add maybe a time that the email could be sent. For example, script will run every morning, at 6:00 the email gets sent to users to notify that the file is complete.
The $Folder variable needs to be on the line before the "$FileToCheck = Get-Item..." line since it uses that variable.
There is no such parameter "building" in the Send-MailMessage cmdlet. I think you are after Body, since you are trying to Get-Content...?
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/send-mailmessage?view=powershell-6
Another note is that Get-Content won't be able to read the contents of a zip file. You would need to unzip the file then read the file or add the file as an attachment.
This is an example of using Get-Content on an zip file that has only a text file in it:
PK
ï¸ÌN_S³ test.txtsdafasdfPK
ï¸ÌN_S³ test.txtPK 6 .
Add $ErrorActionPreference = "Stop" at the top of the script, so errors are shown.
Use the Attachments parameter to add files, building is not a valid parameter of Send-MailMessage
Get-Content is not needed, just add the path to the attachment:
$EmailSplat.Attachments = "Path/test.zip"
So something like this:
$ErrorActionPreference = "Stop"
Set-StrictMode -Version Latest
$folder = "C:\test"
$fileToCheck = Get-Item -Path (Join-Path $folder test.zip) -ErrorAction SilentlyContinue
$emailOptions = #{
"To" = "business#email.com"
"CC" = "admin#email.com"
"SmtpServer" = "smtp.server.net"
"From" = "my#email.com"
"Priority" = "High"
}
# first condition: If the file does not exist, or was not created today, an e-mail should be sent that states "File not created" or similar.
if ((-not $fileToCheck) -or ($fileToCheck.CreationTime -le (Get-Date).AddDays(-1)))
{
$emailOptions.Subject = "File not Found or not created today"
Send-MailMessage #emailOptions
}
elseif ($fileToCheck -and ($fileToCheck.Length -le 2))
{
# second condition: If the file exists and was created today, but has no content, no e-mail should be sent.
}
else
{
# third condition and the default condition if it does not match the other conditions
$emailOptions.Subject = "Active Directory Accounts To Check"
$emailOptions.Attachments = $fileToCheck.FullName
Send-MailMessage #emailOptions
}

unable to set up send-email function as desired

I'm attempting to create a Send-Email function in a script which can change the email address each time the script is used. However, I'm currently suck being forced to use a static email:
function Send-Email (
$recipientEmail = "EMAIL",
$subject = "Ticket" + $type,
$body
) {
$outlook = New-Object -ComObject Outlook.Application
$mail = $outlook.CreateItem(0)
$mail.Recipients.Add("EMAIL")
$mail.Subject = ("Ticket - " + $type)
$mail.Body = $body # For HTML encoded emails
$mail.Send()
Write-Host "Email sent!"
}
I'd like to set it up so that I can define $recipientEmail and then later use this input with $mail.Recipients.Add("EMAIL") - I've beend doing $mail.Recipients.Add("$receipientemail") and the like without any luck and wondering if I'm approaching this completely wrong?
I'd like it to be set up in a way that it could use
[void] $listBox.Items.Add("Email1")
[void] $listBox.Items.Add("Email2")
[void] $listBox.Items.Add("Email3")
And accept that as the email to send to instead of only working with one email.
This whole thing rang a bell with me, and I went "code dumpster diving". I wrote this same function (more or less) a year ago. I'm not using $mail.recipients.add(); I'm using $Mail.To = $RecipientEmail; if I want multiple recipients, I simply -join ";" them and assign to $mail.To. The members $mail.CC and $mail.BCC work the same way:
Function Send-Email {
<#
.Synopsis
Sends Email using Microsoft Outlook
.DESCRIPTION
This cmdlet sends Email using the Outlook component of a locally-installed
Microsoft Office. It is not expected to work with the "Click-to-run" versions.
This version requires that Outlook be running before using the cmdlet.
.PARAMETER To
Must be a quoted string or string array, may not be omitted.
Specifies the address to send the message to. If an array, it will
be converted to a semicolon-delimited string. May contain contact
groups.
.PARAMETER Subject
Must be a quoted string, may not be omitted.
Specifies the subject of the message to be sent.
.PARAMETER Body
Must be a string or a string array, may not be omitted.
Contains the text of the message to be sent. If supplied as a string in double
quotes, any PowerShell variables will be expanded unless the leading $ is
escaped. If supplied in a script via a variable containing a here-doc, will
reproduce the here-doc. If supplied as an array, either by variable or by the
Get-Content cmdlet, the array elements will be joined with `r`n as "separators".
A body supplied via Get-Content will not have expanded variables; arrays created
in scripts using double quotes or via here-docs willl have expanded variables.
.PARAMETER CC
Must be a quoted string or string array, may be omitted.
Specifies an address to send a copy of the message to. If an array, it will be
converted to a semicolon-delimited string. All recipients can see that a copy
of the message was sent to the addresses here.
.PARAMETER BCC
Must be a quoted string or string array, may be omitted.
Specifies an address to send a copy of the message to. If an array, it will be
converted to a semicolon-delimited string. The recipients named here are not
displayed as a recipient of the message, even though they do receive the message.
.PARAMETER Attachments
Must be a quoted string or string array, may be omitted.
Specifies one or more files to be included with the message as attachments.
.INPUTS
This version of the cmdlet does not accept input from the pipeline
.OUTPUTS
No output to the pipeline is generated. The cmdlet does return an error code.
.EXAMPLE
Send-Email -To "boss#example.com" -Subject "Personnel Issue - John M. Ployee" -Body "I need a raise."
Sends an email with the indicated subject and body to the stated address.
.EXAMPLE
$messagebody = #"
Roses are red
Violets are blue
I need a raise
And so do you
"#
$others = "boss-of-boss#example.com","hr#example.com"
Send-Email -To "boss#example.com" -Subject "Personnel Issue - John M. Ployee" -cc $others -Body $messagebody
Sends an email with the indicated subject to the stated address. The body
will be composed of the lines in the variable $messagebody as shown; the
line breaks are preserved. A copy of the message is sent to the two addresses
listed in $others.
.EXAMPLE
Send-Email -To "boss#example.com" -Subject "Personnel Issue - John M. Ployee" -Body (Get-Content -Path "C:\Request-for-raise.txt")
Sends an email with the indicated subject and body to the stated address.
The body is drawn from the indicated file, and line breaks are preserved.
.EXAMPLE
Send-Email -To "boss#example.com" -Subject "Personnel Issue - John M. Ployee" -Body "Please see attached for rationale for raise" -Attachments (Get-Content -Path "C:\Request-for-raise.txt")
Sends an email with the indicated subject and body to the stated address.
The indicated file is included as an attachment.
.NOTES
Planned Future Enhancements:
1. Allow the cmdlet to accept input (message body text) from the pipe.
2. Allow the cmdlet to accept the body from a file (parameter -BodyFromFile)
3. Allow the cmdlet to start up Outlook if it is not already running.
This includes shutting it down afterward.
4. Allow the body to be formatted as HTML or RTF, and properly handle this.
Initially, switches (e.g., -BodyAsHTML or -BodyAsRTF); better would be to
use file name (e.g., -BodyFromFile "C:\Test.html" would still set the message
format to HTML even in the absence of -BodyAsHTML); best would be to inspect
content of file.
Based on a script from http://www.computerperformance.co.uk/powershell/powershell_function_send_email.htm
#>
[cmdletbinding()]
Param (
[Parameter(Mandatory=$True)]
[String[]]$To,
[String[]]$CC,
[String[]]$BCC,
[Parameter(Mandatory=$True)]
[String]$Subject,
[Parameter(Mandatory=$True)]
[String[]]$Body,
[String[]]$Attachments
)
Begin {
# Future Enhancement: Start Outlook if it's not already running
} # End of initialization
Process {
# Create an instance Microsoft Outlook
$Outlook = New-Object -ComObject Outlook.Application
# Create a new mail message
$Mail = $Outlook.CreateItem(0)
# Set up the email
$Mail.To = $To -join ";"
if ($CC -ne $null) {
$Mail.CC = $CC -join ";"
}
if ($bcc -ne $null) {
$Mail.BCC = $BCC -join ";"
}
$Mail.Subject = "$Subject"
$Mail.Body = $Body -join "`r`n"
if ($attachments -ne $null) {
foreach ($Attachment in $Attachments) {
$silencer = $Mail.Attachments.Add($Attachment)
}
}
# I need to investigate scripting Outlook more.
# The next three comments may be all that's needed
# to handle HTML and/or attachments.
# $Mail.HTMLBody = "When is swimming?"
# $File = "D:\CP\timetable.pdf"
# $Mail.Attachments.Add($File)
# Place message in outbox.
$Mail.Send()
} # End of Process section
End {
# Clean up after ourselves. This prevents Outlook from reporting
# an error on exit.
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($Outlook)
$Outlook = $null
# If Outlook settings are to Send/Receive only on command rather
# than as-needed, force the send at this time.
# NOTE TO SELF: CHECK THIS!
# $Outlook.GetNameSpace("MAPI").SendAndReceive(1)
# If we launched Outlook at the beginning, close it here.
# $Outlook.Quit()
} # End of End section!
} # End of function
<#
Note 2: Look in Outlook's outbox for a newly-created message.
Note 3: If you really want to send the email then append this command:
$Outlook.GetNameSpace("MAPI").SendAndReceive(1)
#>

In EWS/Powershell How Do I Remove Email Addresses From $EmailMessage.ToRecicpients?

Here's what is happening. I have this handy powershell script I've written up to connect to EWS using Impersonation, read plain-text files containing email messages, and send them one at a time in a loop. At the end there is a little Inbox and Sent Items folder clean up. (Kudos to my friends at stackoverflow, as many of the issues I already encountered in writing this have been solved by looking up the answers right here.)
Everything works great except ToRecipients.Add, which works a little _too_well_.
$EmailMessage.ToRecipients.Add($DName)
When the loop iterates to read the next file, the $EmailMessage.ToRecipients array retains it's information after executing
$EmailMessage.SendAndSaveCopy($SentItems.Id).
I had somehow expected a SendAndSaveCopy to discard all information in $EmailMessage, but it does not (Send, Save, and then Discard, right?).
So when sending one file, there is no issue whatsoever. But when sending multiple files, each subsequent file just adds its email addresses on to the end of the $EmailMessage.ToRecipients array. So the second file is sent to the first file's recipients AND the second file's recipients. The third file goes to first, second, and third file recipients. The more files, the bigger the mess.
I've tried various things at the end of the loop, before it iterates, to clear out the ToRecipients, but have not had any luck. Powershell rejects any direct assignment of any kind. I expected there to be a ToRecipients.Clear or .Empty or .Remove or something like that, but I haven't found anything that works. These approaches fail:
Remove-Variable $EmailMessage
Remove-Variable $EmailMessage.ToRecipients
Remove-Variable $EmailMessage.ToRecipients[x] #rejects all counter values
Clear-Variable $EmailMessage
Clear-Variable $EmailMessage.ToRecipients
Clear-Variable $EmailMessage.ToRecipients[x] #rejects all counter values
$EmailMessage.ToRecipients = NULL
$EmailMessage.ToRecipients = ""
I can loop through and read every value, get a count of how many ToRecipients there are, print them out, and all sorts of other nifty things, but it seems to be read-only and I haven't found any way to clear them out before iterating.
I have thought of a couple "band-aid" approaches, which I haven't tried yet:
1. Put it in a function with local variables, and $EmailMessage will poof on the function exit (with any luck).
2. Just process one file, and at the end of the script right before exiting, check to see if any files remain to process, and call the script again using Invoke-Expression.
Really, this seems like overkill, and a work around to the original problem rather than a direct fix. I should just be able to clear out the email addresses before the loop iterates, right?
Here is the full code:
<#
Script Overview
1. Opens EWS services for shared mailbox using an appid
2. Reads files from c:\outbox\ containing email to send, one per file
3. Sends the mail message in each file found
4. Renames each file found
5. Moves sent items into c:\sentemail\
6. Deletes any emails in the Inbox or Sent Items folders that are older than 14 days ($purgebeforedate))
#>
#Variables
## Define UPN of the Account that has impersonation rights
$AccountWithImpersonationRights = "myappid"
$appidpasswd = "mysupersecretpassword"
##Define the SMTP Address of the mailbox to impersonate
$MailboxToImpersonate = "autoemail#mysite.mydomain"
##Define CAS URL (Client Access Server) - can be found in system registry if necessary
$CASURL = "https://my.site.and.domain/ews/exchange.asmx"
$ewsApiDownload = "http://www.microsoft.com/en-us/download/details.aspx?id=35371"
## Define Exchange web services DLL path (requires 2.0 for our environment)
$dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll"
##Define folders for incoming and archived email files
$GetFolder = "c:\outbox\" #folder where input email message files are in plaintext
$SaveFolder = "c:\sentemail\" #folder to move sent email files to after processed
$PurgeBeforeDate = (Get-Date (Get-Date).AddDays(-14) -format G) #format m/d/yyyy hh:mm:ss ap- ::Date is 14 days ago [AddDays(-14)]
##Define location of error log on the local windows server where this script runs
$errorLog = "c:\sentemail\MyAutoSentEmail.log"
# Post a note in the error log that the script has started
$date = Get-Date -format G #Default date and time output used in log file.
Add-Content $errorLog $date": Email Service script started."
Add-Content $errorLog $date": Old sent email delete cutoff - $PurgeBeforeDate"
## Load Exchange web services DLL
Import-Module $dllpath
#Exit script if importing EWS API fails.
if ($? -eq $false)
{
$date = Get-Date -format s #Default date and time output used in log file. (was G before)
Add-Content $errorLog $date": Faied to load EWS, ensure it is installed:"$ewsApiDownload
Add-Content $errorLog $date": EWS API expected location:" $dllpath
Add-Content $errorLog $date": Failed to import the EWS API. Script terminated."
exit
}
## Set Exchange Version (our site requires Exchange2010_SP2)
$ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2
## Create Exchange Service Object
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)
#Get valid Credentials using UPN for the ID that is used to impersonate mailbox
$service.Credentials = New-Object System.Net.NetworkCredential($AccountWithImpersonationRights, $appidpasswd);
## Set the URL of the CAS (Client Access Server)
$service.Url = New-Object Uri($CASURL)
##Login to Mailbox with Impersonation
#Write-Host 'Using ' $AccountWithImpersonationRights ' to Impersonate ' $MailboxToImpersonate
$service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress,$MailboxToImpersonate );
#Connect to the Inbox and display basic statistics
$SentFolder = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::SentItems,$ImpersonatedMailboxName)
$SentItems = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$SentFolder)
if ($? -eq $false) #Exit script if binding to folder fails.
{
Add-Content $errorLog $date": Failed to bind to the specified mailbox folder. Script terminated."
exit
}
#For info only, can uncomment to get a list of how many items are in the Inbox and how many are unread
#Write-Host 'Total Item count for Inbox:' $Inbox.TotalCount
#Write-Host 'Total Items Unread:' $Inbox.UnreadCount
#get the filenames in the outgoing-email directory into $files so they may be read one at a time
$files = Get-ChildItem $GetFolder
if ($files.Count -gt 0)
{ #if there are files to process
#Create new email message to send out
$EmailMessage = New-Object Microsoft.Exchange.WebServices.Data.EmailMessage -ArgumentList $service
$EmailMessage.Body = New-Object Microsoft.Exchange.WebServices.Data.MessageBody
$EmailMessage.Body.BodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::HTML
$EmailMessage.From = $MailboxToImpersonate
for ($i=0; $i -lt $files.Count; $i++) { #start for $i loop for each filename found
$emailfile = Get-Content $files[$i].FullName #read file into $emailfile
$To = "empty" #init $To to empty
$Subject = "empty" #init $Subject to empty
$Body = "empty" #init $Body to empty
$Bodytext = "" #init $Bodytext - place to accumulate body lines before adding to email message, gets html return on end of each line
$Bodyhtml = "" #init $Bodyhtml - body lines are placed here in parallel, no html carriage return added
foreach ($Data in $emailfile) { #put each line of the file into $Data and process it
if ( ($Data.StartsWith("to:")) -and ($Subject -eq "empty") )
{ #add recipients to the email message, all to: lines must be before the subject: line
$To = "found" #mark $to line(s) as having been found
Write-Host "TO: line found - " $Data
$Data = $Data.Substring(3) #get all chars after subject:
if ($Data.Contains(";"))
{ #if the email address passed in contains multiples separated by semicolons, split and add them
$DataNames = $Data -split ';' #split $Data into an array of semicolon separated substrings
foreach ($DName in $DataNames) { #validate each semicolon separated email address and add it
$EmailMessage.ToRecipients.Add($DName)
Write-Host $date": TO: address added: "$DName
} #end foreach $DName in $DataNames
} else { #else if no semicolon, just add what you have
$EmailMessage.ToRecipients.Add($Data)
Write-Host $date": TO: address added: "$DName
} #end if email address passed doesn't contain a semicolon
} #end add recipients to $To
if ( ($Data.StartsWith("subject:")) -and ($Body -eq "empty") )
{ #add subject line to the email message, must come before any body: line(s)
$Data = $Data.Substring(8) #get all chars after subject:
$EmailMessage.Subject = $Data
$Subject = "found" #mark $subject line as having been found
Write-Host "SUBJECT: line found - " $Data
} #end add recipients to $To
if ($Data.StartsWith("body:"))
{ #first line of body found
$Data = $Data.Substring(5) #get all chars after subject:
$Bodytext = $Data
$Bodyhtml = $Data
$Body = "found" #mark $body line as having been found
Write-Host "BODY: line found - " $Data
} #end add recipients to $To
elseif ($Body -eq "found")
{ #accumulate the remaining lines into the $bodytext string with '<br />' as carriage returns between lines
$Bodytext = -join($Bodytext, '<br />', $Data); #text lines get an html carriage return
$Bodyhtml = -join($Bodyhtml, $Data); #html lines don't get an html carriage return added, shouldnt need it
Write-Host "body data found - " $Data
} #end accumulate the remaining lines into the $bodytext string with '<br />' as carriage returns between lines
} #end foreach - done processing this text file
$CurrentDateAndTime = $(get-date -f yyyy-MM-dd-HH-mm-ss) #date format to add to the front of the filename when moving/renaming
$OldFileName = $files[$i].FullName
$NewFileName = [io.path]::GetFileName($OldFileName) #just the filename without the path for rename later
Write-Host "OldFileName - " $OldFileName
if ( ($To -eq "found") -and ($Subject -eq "found") -and ($Body -eq "found") )
{ #if all parts found, add body text to the new email message and send it off
if ( ( $Bodyhtml -Match "<" ) -and ( $Bodyhtml -Match ">" ) ) {
$EmailMessage.Body.Text = $Bodyhtml } else {
$EmailMessage.Body.Text = $Bodytext
} #end if html set body.text to html, otherwise set to bodytext with '<br />' crlf's added
$EmailMessage.SendAndSaveCopy($SentItems.Id)
Add-Content $files[$i].FullName "EMAIL-SENT-AT: $CurrentDateAndTime"
Add-Content $errorLog $date": Email File Sent Successfully:"$OldFileName
$NewFileName = -join($CurrentDateAndTime, "-$i-", $NewFileName)
} Else { #add error messages to output file, and select a name that includes NOTSENT so it is obvious
Add-Content $errorLog $date": NOTSENT-ERROR in file: "$OldFileName
if ( $To -eq "empty") {
Add-Content $files[$i].FullName "NOTSENT-ERROR: NO TO: LINE FOUND"
Add-Content $errorLog $date": NOTSENT-ERROR: NO TO: LINE FOUND"
}
if ( $Subject -eq "empty") {
Add-Content $files[$i].FullName "NOTSENT-ERROR: NO SUBJECT: LINE FOUND"
Add-Content $errorLog $date": NOTSENT-ERROR: NO SUBJECT: LINE FOUND"
}
if ( $Body -eq "empty") {
Add-Content $files[$i].FullName "NOTSENT-ERROR: NO BODY: LINE FOUND"
Add-Content $errorLog $date": NOTSENT-ERROR: NO BODY: LINE FOUND"
}
$EmailMessage.Delete #delete new composition if it is not set up properly
$NewFileName = -join($CurrentDateAndTime, "-$i-NOTSENT-", $NewFileName) #note in filename if email was not sent
} #end send-or-not-to-send if
Write-Host "NewFileName - " $NewFileName
#rename and move the file with the date and time it was sent (or tried to be sent)
$NewFileName = -join($SaveFolder, $NewFileName)
Move-Item $OldFileName $NewFileName
$EmailMessage.ToRecipients.Clear() ##BenH - clears ToRecipients before next file is processed so they don't accumulate
} #end for $i loop for each filename found
} #end if there are files to process
if ($? -eq $false)
{
Add-Content error.log $date": error sending message. Script terminated."
exit
}
#Delete items from the Sent Items folder that are too old
#Get the ID of the folder to move to, by searching up from the mailbox root folder
$RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service, [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
$fvFolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(1)
$fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep
$SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,"Sent Items")
$findFolderResults = $RootFolder.FindFolders($SfSearchFilter,$fvFolderView) #search from mailbox root folder for a folder named Sent Items
if ($? -eq $false)
{
#Write-Host 'Unable to locate destination folder:' $findFolderResults
Add-Content error.log $date": Unable to locate the Sent Items folder for cleanup. Script terminated."
exit
}
$sentitemsfolder = $findFolderResults.Folders[0] #save reference to the Sent Items folder in $sentitemsfolder
$SfSearchFilterI = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,"Inbox")
$findFolderResults = $RootFolder.FindFolders($SfSearchFilterI,$fvFolderView) #search from mailbox root folder for a folder named Inbox
if ($? -eq $false)
{
#Write-Host 'Unable to locate destination folder:' $findFolderResults
Add-Content error.log $date": Unable to locate the Inbox folder for cleanup. Script terminated."
exit
}
$inboxfolder = $findFolderResults.Folders[0] #save reference to the Inbox folder in $inboxfolder
#search through the Sent Items and Inbox folders for items older than $purgebeforedate and soft delete them
$puItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(500, 0, [Microsoft.Exchange.WebServices.Data.OffsetBasePoint]::Beginning)
$puItemView.PropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly, [Microsoft.Exchange.WebServices.Data.ItemSchema]::Subject, [Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived)
$puItemView.Traversal = [Microsoft.Exchange.WebServices.Data.ItemTraversal]::Shallow
#Sent Items scan for old items to delete
$puItems = $null #init FindItems results to null before executing find
do
{ #start do
$puItems = $service.FindItems($sentitemsfolder.Id,$puItemView) #find all items in Sent Items
if ($puItems.Items.Count -gt 0)
{ #if Sent Items folder not empty, inspect all items found
foreach($Item in $puItems.Items)
{
if ($item.datetimereceived -le $PurgeBeforeDate) #compare email date to purge cutoff date
{
#Write-Host "Delete Item with date time received" $item.DateTimeReceived
[void]$item.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::SoftDelete)
#Note: exchange email internal date is in format of : 5/31/2016 8:02:19 AM (get-date -format G)
} #end compare email date to purge cutoff date
} #end foreach $Item in $puItems.items
} #end if Sent Items folder not empty
$puItemView.Offset += $puItems.Items.Count
} #end do
while($puItems.MoreAvailable -eq $true)
#Inbox scan for old items to delete
$puItems = $null #init FindItems results to null before executing find
do
{ #start do
$puItems = $service.FindItems($inboxfolder.Id,$puItemView) #find all items in Sent Items
if ($puItems.Items.Count -gt 0)
{ #if Sent Items folder not empty, inspect all items found
foreach($Item in $puItems.Items)
{
if ($item.datetimereceived -le $PurgeBeforeDate) #compare email date to purge cutoff date
{
#Write-Host "Delete Item with date time received" $item.DateTimeReceived
[void]$item.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::SoftDelete)
#Note: exchange email internal date is in format of : 5/31/2016 8:02:19 AM (get-date -format G)
} #end compare email date to purge cutoff date
} #end foreach $Item in $puItems.items
} #end if Sent Items folder not empty
$puItemView.Offset += $puItems.Items.Count
} #end do
while($puItems.MoreAvailable -eq $true)
#script done
Add-Content $errorLog $date": Email Service script finished."
exit
##############################################################################
And here are some sample email input files, where lines start with to:, subject:, and the first line of the message body starts with body:
Sample File 1:
to:sam.lemon#lemonade.org
subject:ews mail test
body:testing line 1
testing line 2
testing line 3
testing line 4
done testing
Sample File 2:
to:bill.hickock#wild.west.org
subject:ews mail test
body:testing line 1
testing line 2
testing line 3
testing line 4
done testing
With these two sample files, the first one would be sent to sam.lemon#lemonade.org as expected. But the second one would go to both sam.lemon#lemonade.org and bill.hickock#wild.west.org, which it should not, since the second file is only for bill.hickock#wild.west.org.
Thanks in advance!
Zistrosk
#BenH - Here's what I coded and what came out in white on the Powershell ISE console. No errors or warnings, just these info messages, and the email addressees were not cleared or removed, same symptoms of the problem persisted. Does my coding look right?
$EmailMessage.ToRecipients.Clear
MemberType : Method
OverloadDefinitions : {void Clear()}
TypeNameOfValue : System.Management.Automation.PSMethod
Value : void Clear()
Name : Clear
IsInstance : True
$EmailMessage.ToRecipients.Remove
MemberType : Method
OverloadDefinitions : {bool
Remove(Microsoft.Exchange.WebServices.Data.EmailAddress
emailAddress)}
TypeNameOfValue : System.Management.Automation.PSMethod
Value : bool
Remove(Microsoft.Exchange.WebServices.Data.EmailAddress
emailAddress)
Name : Remove
IsInstance : True
Note: added the following line above, thanks to #BenH, which fixes the issue and clears the ToRecipients array before the loop iterates and the next file is read, preventing an unwanted accumulation of TO addresses:
$EmailMessage.ToRecipients.Clear() ##BenH - clears ToRecipients before next file is processed so they don't accumulate

send-mailmessage to send different attachments to different recipients

I have a requirement to send email to thousands of different recipients with a different attachment to each of them.
I have the list of recipients in a text file and the list of attachment paths in another text file.
For the first recipient in the text file, first attachment path should be used.
For the second recipient in the text file, second attachment path should be used.
The code below sends all attachments to all recipients separately.
But I would like this to work as I described above. Only one email should be sent to each recipient with the corresponding attachment.
Please let me know if this is achievable. I can also copy the recipients and paths to attachments in two different columns of an Excel spreadsheet if it is possible with Excel.
I can achieve this by constructing thousand different send-mailmessage lines with Excel but that looks like an ugly way of doing. That's why I would like to know if there is a better way of doing this.
$attachments = get-content C:\attach.txt
$recipients = get-content C:\recip.txt
foreach ($attachment in $attachments)
{
foreach ($recipient in $recipients)
{
Send-MailMessage -From "recipient#target.com" -To $recipient -subject "Test" -smtpServer smtp.server.com -attachments $attachment
}
}
One possibility:
$MailParams =
#{
From = "recipient#target.com"
Subject = "Test"
SmtpServer = 'smtp.server.com'
}
$recipients = get-content C:\recip.txt
get-content C:\attach.txt |
foreach { $i=0 } { $_ | Send-MailMessage #MailParams -To $recipients[$i++] }
Use a for loop to iterate through both files in tandem:
for ($i = 0; $i -lt $recipients.Count; $i++) {
Send-MailMessage -From "recipient#target.com" -To $recipients[$i] -Subject "Test" -SmtpServer smtp.server.com -Attachments $attachments[$i]
}
Presumably the number of lines in both files should be the same, so $recipients.Count is the number of lines in both recip.txt and attach.txt; you could use $attach.Count in the for loop just as well.
You could also go the Excel route, but then save the file as a CSV to make things simpler. With a CSV file, you could use this code, where Recipient and Attachment are the column names:
Import-Csv <path_to_csvfile> | %{Send-MailMessage -From "recipient#target.com" -To $_.Recipient -Subject "Test" -SmtpServer smtp.server.com -Attachments $_.Attachment}
That's probably a better idea, because you're less likely to have mistakes matching the attachments to the right recipients if they're in the same file. With separate lists, one error could result in all recipients after that point receiving the wrong attachments.

powershell - get file, parse it and if "value=" < X then function sendMail

i have a text file that is automatically downloaded and updated with Task Scheduler.
It looks like this:
C:\PrintLog\GetUsageReportFromPrinterWebService\10.22.17.102:58:<input type="hidden" name="AVAILABELBLACKTONER" value="60">
C:\PrintLog\GetUsageReportFromPrinterWebService\192.167.10.140:58:<input type="hidden" name="AVAILABELBLACKTONER" value="80">
C:\PrintLog\GetUsageReportFromPrinterWebService\192.167.14.128:58:<input type="hidden" name="AVAILABELBLACKTONER" value="80">
I would like to:
delete "C:\PrintLog\GetUsageReportFromPrinterWebService\" and "input type="hidden" name="AVAILIBLETONER""
replace that IP adress with printer name (example 10.51.17.122:58 equals HP-01, 192.168.10.150:58 equals HP-02 etc)
check if "value=" is smaller than 20 and if so than send an email with function sendMail
It doesn't matter what is in that email (after that I will check it manually with webservice anyway).
I just need this as an remainder/alerter that some printer is getting low on toner, so I am not forced to manually check that txt file everyday(I most certainly would forget that :) ). These printers are offsite so I need to know in advance that the printer is low.
Note1: There are empty lines and spaces at the beginning of that txt
Note2: And no, there is no send report via email when low on toner to be configured on those printers (I double checked).
Note3: using C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
I guess point one and two are optional. The third one is important for me, I googled for something similar but just got lost as everyone wanted something little bit different.
Thanks
Here's another possibility (I think it requires Powershell 4 because of Send-Mailmessage, but I could be wrong):
#requires -version 4.0
$logs = Get-Content "F:\scripts\ParseLog\file.log"
$warning = $false
$lowPrinters = ""
# Mail Settings
$Subject = "Low Toner Warning"
$To = "printeradmin#contoso.com"
$From = "admin#contoso.com"
$SMTPServer = "smtp.contoso.com"
$Priority = "High"
$printerList = #{
"10.51.17.122:58" = "HP-01";
"192.168.10.150:58" = "HP-02";
"10.22.17.102:58" = "HP-03";
"192.167.10.140:58" = "HP-04";
}
foreach ( $log in $logs ) {
if ( $log -match '^C:\\PrintLog\\GetUsageReportFromPrinterWebService\\([^:]+:[^:]+):.*value="(.*)">$' ) {
$printer = $Matches[1]
$toner = [int]$Matches[2]
}
if( $toner -lt 20 ) {
$warning = $true
if( $printerList.ContainsKey( $printer ) ) {
$printerName = $printerList[ $printer ]
} else {
$printerName = $printer
}
$lowPrinters += "Printer {0} has a low toner level of {1}.`n" -f $printerName, $toner
}
}
if( $warning ) {
Send-MailMessage -From $From -To $To -Subject $Subject -body $lowPrinters -SmtpServer $SMTPServer
}
Along about line 8 we setup some stuff for sending email. Starting at line 15, we build a hash table mapping printer IPs/Ports with Printer Names (since printer queues aren't always listed in DNS, I decided to use a hash table instead). On line 23, we use a regular expression to grab the ip and port, and the toner value by using the -match operator. Stuff grabbed by the regex is stored in an array called $Matches.
As an example, you could do something like this:
$filePath = "C:\updatedFile.txt"
$prefix = "C:\PrintLog\GetUsageReportFromPrinterWebService\"
$lines = Get-Content $filePath |Where-Object {$_.Trim() -like "$($prefix)*"}
foreach($line in $lines)
{
$content = $line.Trim().Substring($prefix.Length)
$parts = $content -split ":"
$inputML = [xml]"$($parts[2])</input>"
$inputValue = [int]$inputML.input.value
if($inputValue -lt 20)
{
$printer = [System.Net.DNS]::GetHostByAddress($parts[0]).HostName
<#
Your sendMail call in here
#>
}
}
You remove the "C:\PrintLog\GetUsageReportFromPrinterWebService\" part with Substring(), split it into 3 pieces, and then parse to last part as an xml element, giving much easier access to the value attribute
The IP to Printer name part will only work if you have reverse DNS in place already