PowerShell脚本发送带附件邮件报错:文件被其他进程占用
解决Send-MailMessage附件文件被占用的问题
这个错误的核心原因很明确:你要发送的.err文件还在被其他进程(比如生成这些文件的程序)占用,还没完成写入或者释放文件句柄,导致Send-MailMessage无法读取这些文件作为附件。下面给你几个可行的解决方案,按推荐程度排序:
方案1:复制文件到临时目录再发送(最稳妥)
把目标文件复制到本地临时文件夹,发送复制后的文件,这样即使原文件被占用,复制后的副本是独立的,不会有句柄冲突问题。修改后的代码如下:
$Email = "myemail" $Internal = "cc" $Subject = "Form" $errFolder = "\\ip\ftp$\c\1\files\Backorder" # 创建唯一的临时目录,避免冲突 $tempDir = Join-Path $env:TEMP "Err_Attachments_$(Get-Date -Format 'yyyyMMddHHmmss')" New-Item -ItemType Directory -Path $tempDir | Out-Null $copiedAttachments = @() Get-ChildItem $errFolder *.err | ForEach-Object { $destPath = Join-Path $tempDir $_.Name try { # 尝试复制文件,-Force覆盖已存在的临时文件 Copy-Item -Path $_.FullName -Destination $destPath -Force $copiedAttachments += $destPath Write-Host "成功复制文件:$($_.Name)" } catch { Write-Warning "无法复制文件 $($_.FullName),可能被其他进程占用:$_" } } if ($copiedAttachments.Count -gt 0) { $Msg = @{ to = $Email cc = $Internal from = "address" Body = "some text" subject = $Subject smtpserver = "server" BodyAsHtml = $True Attachments = $copiedAttachments } Send-MailMessage @Msg Write-Host "邮件已发送,共包含 $($copiedAttachments.Count) 个附件" # 发送完成后清理临时文件 Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue } else { Write-Host "没有可用的.err文件可以发送" } Start-Sleep -Seconds 1800
方案2:先检查文件是否可访问,跳过被占用的文件
在收集附件前,先验证每个文件是否能被读取(无占用),只收集可用的文件。这种方法适合不需要等待文件释放,只发送当前可访问的文件的场景:
$Email = "myemail" $Internal = "cc" $Subject = "Form" # 定义检查文件是否被占用的函数 function Test-FileIsAccessible { param([string]$FilePath) try { # 尝试以独占读取模式打开文件,失败则说明被占用 $stream = [System.IO.File]::Open($FilePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::None) $stream.Close() return $true } catch { return $false } } # 筛选出可访问的.err文件 $attachments = Get-ChildItem "\\ip\ftp$\c\1\files\Backorder" *.err | Where-Object { if (Test-FileIsAccessible $_.FullName) { $true } else { Write-Warning "文件 $($_.FullName) 被占用,跳过" $false } } if ($attachments.Count -gt 0) { $Msg = @{ to = $Email cc = $Internal from = "address" Body = "some text" subject = $Subject smtpserver = "server" BodyAsHtml = $True Attachments = $attachments.FullName } Send-MailMessage @Msg } Start-Sleep -Seconds 1800
方案3:等待文件释放后再发送
如果必须等待所有目标文件都被释放后再发送,可以加入循环等待逻辑,给文件一定的时间释放句柄:
$Email = "myemail" $Internal = "cc" $Subject = "Form" $maxWaitSeconds = 30 # 最大等待时间,可根据需求调整 $checkInterval = 2 # 每隔2秒检查一次 function Test-FileIsAccessible { param([string]$FilePath) try { $stream = [System.IO.File]::Open($FilePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::None) $stream.Close() return $false # 返回false表示未被占用 } catch { return $true # 返回true表示被占用 } } $allAttachments = Get-ChildItem "\\ip\ftp$\c\1\files\Backorder" *.err $readyAttachments = @() foreach ($file in $allAttachments) { $elapsedTime = 0 while (Test-FileIsAccessible $file.FullName -and $elapsedTime -lt $maxWaitSeconds) { Write-Host "等待文件 $($file.Name) 释放...已等待 $elapsedTime 秒" Start-Sleep -Seconds $checkInterval $elapsedTime += $checkInterval } if (-not (Test-FileIsAccessible $file.FullName)) { $readyAttachments += $file Write-Host "文件 $($file.Name) 已准备好" } else { Write-Warning "等待 $maxWaitSeconds 秒后,文件 $($file.Name) 仍被占用,跳过" } } if ($readyAttachments.Count -gt 0) { $Msg = @{ to = $Email cc = $Internal from = "address" Body = "some text" subject = $Subject smtpserver = "server" BodyAsHtml = $True Attachments = $readyAttachments.FullName } Send-MailMessage @Msg } Start-Sleep -Seconds 1800
额外建议
- 如果你能控制生成
.err文件的程序,最好让它写完文件后立即释放句柄,这是从根源解决问题的方法。 - 可以在脚本中加入日志记录,方便排查哪些文件被占用、邮件是否发送成功等问题。
内容的提问来源于stack exchange,提问作者user12821291
相关产品推荐
相关产品推荐

