在EWS/Powershell中,如何从$EmailMessage.ToRecipients中删除电子邮件地址?
下面是正在发生的事情。我编写了这个方便的powershell脚本,可以使用模拟连接到EWS,读取包含电子邮件的纯文本文件,然后以循环方式一次发送一条。在最后有一个小收件箱和已发送邮件文件夹清理。(感谢我在stackoverflow的朋友们,因为我在写这篇文章时遇到的许多问题都通过在这里查找答案得到了解决。) 除了收件人之外,其他一切都很好。另外,这也有点太好了在EWS/Powershell中,如何从$EmailMessage.ToRecipients中删除电子邮件地址?,powershell,email,exchangewebservices,impersonation,Powershell,Email,Exchangewebservices,Impersonation,下面是正在发生的事情。我编写了这个方便的powershell脚本,可以使用模拟连接到EWS,读取包含电子邮件的纯文本文件,然后以循环方式一次发送一条。在最后有一个小收件箱和已发送邮件文件夹清理。(感谢我在stackoverflow的朋友们,因为我在写这篇文章时遇到的许多问题都通过在这里查找答案得到了解决。) 除了收件人之外,其他一切都很好。另外,这也有点太好了 $EmailMessage.ToRecipients.Add($DName) 当循环迭代读取下一个文件时,$EmailMessage.
$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表达式再次调用脚本
事实上,这似乎有些矫枉过正,只是解决了最初的问题,而不是直接解决问题。我应该能够在循环迭代之前清除电子邮件地址,对吗
以下是完整的代码:
<#
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
##############################################################################
示例文件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正如所料。但是第二个会给两个山姆。lemon@lemonade.org还有比尔。hickock@wild.west.org,它不应该这样做,因为第二个文件仅用于bill。hickock@wild.west.org.
提前谢谢!
齐斯特罗斯克
@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
我不能测试,但是一个既有@BenH,又有@BenH的测试-谢谢你让我注意到这些。当我尝试它们时,.Clear或.Remove似乎都没有任何效果,第二个文件的电子邮件与第一个和第二个文件的所有收件人一起到达。当我将它们放在powershell中时,我在Move Item命令和$I循环结束之间执行了该操作,因此它将在循环迭代之前清除它。当我这样做时,我从控制台得到了一些输出。也许我没有把它们编对?至少他们没有返回错误,只是这些白色的信息性消息。我编辑了上面的主题以显示我得到了什么。你忘记了
()
。如果您使用的方法没有这些,它将告诉您可以在“重载定义”中放入什么。因为有一个方法可以接受多个不同的输入,所以每个方法都称为重载。所以clear是空的,所以这就是.clear()
@BenH-这很有效!谢谢你的帮助。我已经更新了上面的代码,使其能够工作,并添加了一行注释。旁注-我还测试了如何将$EmailMessage的创建和FOR循环的所有内容放入一个本地函数中,该函数在powershell代码顶部定义,这也行得通。该函数使用$service和文件名,通过在函数中创建$EmailMessage new来处理该文件,解析并发送电子邮件,将文件移动到其新名称和位置,当它退出该函数时,powershell将清除$EmailMessage及其ToRecipients。我真的很喜欢ToRecipients.Clear()解决方案,不过更好,更干净!
$EmailMessage.ToRecipients.Clear() #@BenH - clears ToRecipients before next file is processed so they don't accumulate