这是发生了什么。我有这个方便的PowerShell脚本,我已经编写了使用模拟连接到EWS,读取包含电子邮件消息的纯文本文件,并在循环中一次发送一个。最后,有一个小的收件箱和已发送邮件文件夹清理。 (感谢stackoverflow给我的朋友们,因为我写这篇文章时遇到的很多问题都是通过在这里查找答案来解决的。)
除了ToRecipients.Add之外,一切都很好用,它有点_too_well _。
$EmailMessage.ToRecipients.Add($DName)
当循环迭代读取下一个文件时,$ EmailMessage.ToRecipients数组在执行后保留它的信息
$EmailMessage.SendAndSaveCopy($SentItems.Id).
我曾以某种方式期望SendAndSaveCopy丢弃$ EmailMessage中的所有信息,但它没有(发送,保存,然后丢弃,对吧?)。
因此,在发送一个文件时,没有任何问题。但是,当发送多个文件时,每个后续文件只会将其电子邮件地址添加到$ EmailMessage.ToRecipients数组的末尾。因此,第二个文件将发送到第一个文件的收件人和第二个文件的收件人。第三个文件转到第一个,第二个和第三个文件收件人。文件越多,混乱就越大。
我在循环结束时尝试了各种各样的东西,在它迭代之前,清除ToRecipients,但没有任何运气。 Powershell拒绝任何直接分配。我希望有一个ToRecipients.Clear或.Empty或.Remove或类似的东西,但我没有找到任何有用的东西。这些方法失败了:
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 = ""
我可以遍历并读取每个值,计算出有多少ToRecipients,打印出来以及各种其他漂亮的东西,但它似乎是只读的,我没有找到任何方法在迭代之前清除它们。
我想过几个“创可贴”的方法,我还没有尝试过: 1.将它放在具有局部变量的函数中,并且$ EmailMessage将在函数出口处填充(运气好的话)。 2.只处理一个文件,并在退出之前在脚本的末尾,检查是否还有任何文件要处理,然后使用Invoke-Expression再次调用脚本。
真的,这看起来有点矫枉过正,并且解决原始问题而不是直接解决问题。我应该能够在循环迭代之前清除电子邮件地址,对吗?
以下是完整代码:
<#
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
##############################################################################
以下是一些示例电子邮件输入文件,其中行以:开头:,subject:,并且消息正文的第一行以body开头:
示例文件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
示例文件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
使用这两个示例文件,第一个将按预期发送到sam.lemon@lemonade.org。但是第二个将转到sam.lemon@lemonade.org和bill.hickock@wild.west.org,它不应该,因为第二个文件仅适用于bill.hickock@wild.west.org。
提前致谢! Zistrosk
@BenH - 这是我在Powershell ISE控制台上编码的内容和白色内容。没有错误或警告,只是这些信息消息,以及电子邮件收件人未被清除或删除,问题的相同症状仍然存在。我的编码看起来不错吗?
$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
注意:由于@BenH,上面添加了以下行,它修复了问题并在循环迭代和读取下一个文件之前清除了ToRecipients数组,防止了不必要的TO地址累积:
$EmailMessage.ToRecipients.Clear() #@BenH - clears ToRecipients before next file is processed so they don't accumulate