PowerShell捕获错误后重启进程失败:计划任务中进程未重启
进程重启失败问题排查与解决
环境配置
- Windows计划任务运行PowerShell脚本
- PowerShell脚本调用bat文件
- bat文件运行.NET可执行文件
原脚本代码
$pinfo = New-Object System.Diagnostics.ProcessStartInfo $pinfo.FileName = "Console.bat" $pinfo.RedirectStandardError = $true $pinfo.RedirectStandardOutput = $true $pinfo.UseShellExecute = $false $pinfo.Arguments = @("-s SomeArgument") $p = New-Object System.Diagnostics.Process $p.StartInfo = $pinfo $p.Start() | Out-Null $stdout="" do { $stdout += $p.StandardOutput.ReadLine() } while (!$p.HasExited) if($stdout.Contains("Error while processing data")) { Start-Sleep -Seconds 30; $p.Start() | Out-Null }
问题描述
通过计划任务运行脚本时,能看到cmd.exe、conhost.exe及.NET可执行文件进程。强制触发错误测试进程重启功能时,进程并未重启,仅conhost.exe停留30秒后关闭。
问题原因与解决方案
1. 无法复用已退出的Process对象
$p对应的进程退出后,该Process对象内部状态已标记为已退出,无法直接通过Start()方法重启。必须重新创建Process实例,或把启动逻辑封装为函数重复调用。
2. 标准输出读取可能导致死锁
仅同步读取StandardOutput但未处理StandardError,当进程往stderr输出内容且缓冲区满时,会导致进程阻塞无法正常退出,进而影响后续重启逻辑。
3. 计划任务工作目录问题
计划任务默认工作目录通常不是脚本所在目录,直接用相对路径调用Console.bat可能导致找不到文件,需指定绝对路径。
修正后的脚本
# 封装进程启动逻辑,避免重复代码 function Start-TargetProcess { $pinfo = New-Object System.Diagnostics.ProcessStartInfo # 替换为Console.bat的绝对路径 $pinfo.FileName = "C:\Full\Path\To\Console.bat" $pinfo.RedirectStandardError = $true $pinfo.RedirectStandardOutput = $true $pinfo.UseShellExecute = $false $pinfo.Arguments = "-s SomeArgument" # 设置工作目录为bat文件所在目录,避免路径依赖问题 $pinfo.WorkingDirectory = Split-Path $pinfo.FileName -Parent $p = New-Object System.Diagnostics.Process $p.StartInfo = $pinfo # 异步读取输出,避免死锁 $stdoutBuilder = New-Object System.Text.StringBuilder $stderrBuilder = New-Object System.Text.StringBuilder $stdoutEvent = Register-ObjectEvent -InputObject $p -EventName OutputDataReceived -Action { $Event.MessageData.AppendLine($EventArgs.Data) } -MessageData $stdoutBuilder $stderrEvent = Register-ObjectEvent -InputObject $p -EventName ErrorDataReceived -Action { $Event.MessageData.AppendLine($EventArgs.Data) } -MessageData $stderrBuilder $p.Start() | Out-Null $p.BeginOutputReadLine() $p.BeginErrorReadLine() # 等待进程完全退出 $p.WaitForExit() # 清理事件注册 Unregister-Event $stdoutEvent.Id Unregister-Event $stderrEvent.Id # 返回输出内容,方便后续检查 return @{ StdOut = $stdoutBuilder.ToString() StdErr = $stderrBuilder.ToString() } } # 第一次启动进程 $runResult = Start-TargetProcess # 同时检查标准输出和错误输出中的错误信息 if ($runResult.StdOut.Contains("Error while processing data") -or $runResult.StdErr.Contains("Error while processing data")) { Start-Sleep -Seconds 30 # 重新启动进程 Start-TargetProcess | Out-Null }
额外注意事项
- 确保计划任务的执行账户有足够权限启动目标进程,必要时勾选"不管用户是否登录都要运行",并配置正确的权限。
- 可添加日志记录,将输出内容写入文件方便排查,比如在函数末尾加入
$runResult.StdOut | Out-File "C:\Logs\process_log.log" -Append。
内容的提问来源于stack exchange,提问作者Luci C
相关产品推荐
相关产品推荐

