1

问题:

发送电子邮件的 PowerShell 脚本在从交互式会话调用时有效,但在通过计划任务调用时失败。这似乎是由于脚本退出时 SMTP 会话在传输过程中被中断所致。自然,这不会在交互式会话期间发生,因为脚本结束后会话继续存在。但是,当通过计划任务调用时,会话在脚本结束时被终止。

脚本:

    $msg = New-Object Net.Mail.MailMessage
    foreach( $To in $Recipients ) {
      $msg.To.Add($To)
    }
    $msg.From = $From
    $msg.Subject = $Subject
    $msg.Body = $Body
    $ctype = New-Object Net.Mime.ContentType -Property @{
      MediaType = [Net.Mime.MediaTypeNames+Application]::Octet
      Name = $AttachmentName
    }
    $msg.Attachments.Add([Net.Mail.Attachment]::CreateAttachmentFromString($csv, $ctype))
    $client = new-object System.Net.Mail.SmtpClient($SMTPServer)
    $client.Send($msg)

问题

System.Net.Mail.SmtpClient::Send() 应该是同步的,但在实践中并不是这样。我知道我可以通过在脚本结束时让脚本休眠一小段时间来缓解这种情况。坦率地说,这是一些非常草率的工作,我拒绝那样做。最好的解决方法是什么?

注意:Send-MailMessage不合适,因为我宁愿避免创建文件以制作附件。我不确定它是否遇到同样的问题。

4

1 回答 1

2

解决方案:SendAsync()

不幸的Send()是,它不像文档声称的那样同步。我发现使用异步变体SendAsync()并使用SendCompleted事件来提供同步性是最有益的。请参阅我为解决这个问题而编写的以下通用函数(尽管它可用于各种异步调用):

<#
.SYNOPSIS
  Calls a script block and waits for the specified event.
.DESCRIPTION
  Provides a convenient way to call an asynchronous method with synchronous semantics.
  This is achieved by registering to receive an event before calling the block that is
  expected to signal its completion via a .NET event.  The given script block need not
  necessarily cause the event to be signaled, but that is the most obvious use case.
.PARAMETER EventSource
  The object expected to raise the event.
.PARAMETER EventName
  The name of the expected event.
.PARAMETER ScriptBlock
  This block will be executed after registering the event and before waiting for the event.
.PARAMETER Timeout
  Maximum duration in seconds to wait for the expected event.  Default value of -1 means
  to wait indefinitely.  No error is raised when this timeout is reached.
.NOTES
  Author:  Erik Elmore <erik@ironsavior.net>
#>
function Wait-AsyncCall {
  Param(
    [Parameter(Mandatory = $True, Position = 0)]
    $EventSource,
    [Parameter(Mandatory = $True, Position = 1)]
    [string]$EventName,
    [Parameter(Mandatory = $True, Position = 2)]
    [ScriptBlock]$ScriptBlock,
    [int]$Timeout = -1
  )
  $id = "Wait-AsyncCall:$($EventName):$([guid]::NewGuid().ToString())"
  Register-ObjectEvent $EventSource $EventName -SourceIdentifier $id -EA Stop
  try {
    $output = &$ScriptBlock
    Wait-Event -SourceIdentifier $id -Timeout $Timeout -EA SilentlyContinue |Remove-Event -EA SilentlyContinue
  }
  finally {
    Unregister-Event -SourceIdentifier $id -EA SilentlyContinue
  }
  $output
}

使用Wait-AsyncCall,我可以改用该AsyncSend()方法,但使用同步语义。原来的代码块就变成了:

$msg = New-Object Net.Mail.MailMessage
foreach( $To in $Recipients ) {
  $msg.To.Add($To)
}
$msg.From = $From
$msg.Subject = $Subject
$msg.Body = $Body
$ctype = New-Object Net.Mime.ContentType -Property @{
  MediaType = [Net.Mime.MediaTypeNames+Application]::Octet
  Name = $AttachmentName
}
$msg.Attachments.Add([Net.Mail.Attachment]::CreateAttachmentFromString($csv, $ctype))
$client = new-object System.Net.Mail.SmtpClient($SMTPServer)
Wait-AsyncCall $client "SendCompleted" { $client.SendAsync($msg, $Null) }
于 2013-03-21T21:44:08.267 回答