计划任务执行PowerShell脚本无法捕获EXE程序异常如何解决
解决方案
根因说明
$ErrorActionPreference = 'Stop'仅对PowerShell原生cmdlet生效,对外部.NET可执行程序的退出码、异常输出没有强制捕获效果,交互环境(ISE)和计划任务的非交互环境的stdout/stderr缓冲机制不同,导致非交互环境下程序报错触发异常时,输出还没赋值给$output就进入了catch块- 原计划任务用cmd套娃调用PowerShell,外层的流重定向可能会抢占程序的输出流,导致脚本内捕获不到输出
- 计划任务默认未设置工作目录,执行身份可能缺少对应程序、数据库、目录的访问权限,导致程序直接启动失败无输出
修复步骤
1. 调整计划任务执行命令
去掉外层cmd封装,直接配置计划任务的操作参数:
- 程序或脚本:
powershell.exe - 添加参数:
-ExecutionPolicy Bypass -File C:\ScheduledJobs\MyScript.ps1 > C:\ScheduledJobs\job.log 2>&1 - 起始于(可选):
C:\ScheduledJobs
2. 调整脚本捕获逻辑
修改脚本为以下内容,优先捕获所有输出再判断程序执行状态:
$ErrorActionPreference = 'Stop' # 提前初始化output变量,避免未赋值的情况 $output = @() try { # 用&调用外部程序,强制捕获所有输出 $output = & C:\Utilities\SomeDotNetProgram.exe /s:"SOME_STORED_PROCEDURE" 2>&1 # 手动判断程序退出码,非0则抛出异常进入catch if ($LASTEXITCODE -ne 0) { throw "Program execution failed, exit code: $LASTEXITCODE" } } catch { Write-Host "+++ $($output.Count) ---" $From = "Database Administrator <xxx@xxx.com>" $To = "xxx@xxx.com" $Subject = "Scheduled task failed" # 拼接异常信息和程序输出,避免output为空时无有效内容 $Body = @" Task error details: Exception message: $($_.Exception.Message) Program output: $($output -join "`n") "@ if ($output.Count -eq 0) { $Body += "`nNo additional output returned from the .NET program" } $SMTPServer = "mail.xxx.net" $SMTPPort = "25" Send-MailMessage -From $From -to $To -Subject $Subject -Body $Body -SmtpServer $SMTPServer -port $SMTPPort }
3. 校验执行权限
确认计划任务使用的运行账户具备以下权限:
- 读取和执行
C:\Utilities\SomeDotNetProgram.exe的权限 - 程序调用的存储过程对应数据库的访问权限
C:\ScheduledJobs目录的写入权限(用于写入日志文件)
4. 兜底捕获方案(可选)
如果调整后还是捕获不到输出,改用Start-Process强制重定向输出到文件再读取,彻底规避流缓冲问题:
$stdoutPath = "C:\ScheduledJobs\program_stdout.log" $stderrPath = "C:\ScheduledJobs\program_stderr.log" # 清空历史日志 if (Test-Path $stdoutPath) { Remove-Item $stdoutPath -Force } if (Test-Path $stderrPath) { Remove-Item $stderrPath -Force } $process = Start-Process -FilePath "C:\Utilities\SomeDotNetProgram.exe" ` -ArgumentList '/s:"SOME_STORED_PROCEDURE"' ` -NoNewWindow -PassThru -Wait ` -RedirectStandardOutput $stdoutPath ` -RedirectStandardError $stderrPath $output = @(Get-Content $stdoutPath) + @(Get-Content $stderrPath) if ($process.ExitCode -ne 0) { throw "Program exited with code $($process.ExitCode)" }
内容的提问来源于stack exchange,提问作者Bob V
相关产品推荐
相关产品推荐

