You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 07:43:01