powershellprocessmonitoringfreeze

Restart the process if its output unchanged in PowerShell


I have this command

for ($i=0; $i -gt -1; $i++) {
    $path = "..."
    ffmpeg -f dshow -i audio="Microphone (USB Microphone)" -y -t 00:10:00 -b:a 128k $path
}

I need to get the current state of the last line of the command output stream, then if the line remains unchanged (staled/freezing/hanging) over 5 seconds log "warning: the program is freezing. trying to restart...", then stop the process and re-start the command.

But I wonder, is it even possible? Thanks for your help.


Solution

  • You can use a job to run a program in the background, which enables you to monitor its output and check for timeout conditions.

    Note:

    while ($true) { # Loop indefinitely, until the ffmpeg call completes.
    
      Write-Verbose -Verbose "Starting ffmpeg..."
    
      # Specify the input file path.
      # Output file will be the same, except with extension .mp4
      $path = './sample.mov'
      
      # Use a job to start ffmpeg in the background, which enables
      # monitoring its output here in the foreground thread.
      $jb = Start-ThreadJob { 
        # Note the need to refer to the caller's $path variable value with $using:path
        # and the 2>&1 redirection at the end.
        # Once the output file exists, you can simulate freezing by removing -y from the call:
        # ffmpeg will get stuck at an invisible confirmation prompt.
        ffmpeg -y -i $using:path ([IO.Path]::ChangeExtension($using:path, '.mp4')) 2>&1
        $LASTEXITCODE # Output ffmpeg's exit code.
      }
      
      # Start monitoring the job's output.
      $sw = [System.Diagnostics.Stopwatch]::StartNew()
      do {
        Start-Sleep -Milliseconds 500 # Sleep between attempts to check for output.
        # Check for new lines and pass them through.
        $linesReceived = $null
        $jb | Receive-Job -OutVariable linesReceived | 
          ForEach-Object { 
            if ($_ -is [int]) {  
              # The value of $LASTEXITCODE output by the job after ffmpeg exited.
              $exitCode = $_
            }
            elseif ($_ -is [string]) { 
              # Stdout output: relay as-is
              $_
            } 
            else {
              # Stderr output, relay directly to stderr (bypassing PowerShell's error stream).
              # If it looks like a *progress* message, print it *in place*
              # Note: If desired, the blinking cursor could be (temporarily) turned off with [Console]::CursorVisible = $false
              if (($line = $_.ToString()) -match '^\w+= ') { [Console]::Error.Write("`r$line") } 
              else                                         { [Console]::Error.WriteLine($line) } 
            } 
          }
        if ($linesReceived) { $sw.Restart() } # Reset the stopwatch, if output was received.
      } while (($stillRunning = $jb.State -eq 'Running') -and $sw.Elapsed.TotalSeconds -lt 5)
    
      # Clean up the job forcefully, which also aborts ffmpeg, if it's still running.
      $jb | Remove-Job -Force
    
      # Handle the case where ffmpeg exited.
      # This can mean successful completion or an error condition (such as a syntax error)
      if (-not $stillRunning) {
        # Report the exit code, which implies success (0) vs. failure (nonzero).
        Write-Verbose -Verbose "The program exited without freezing, with exit code $exitCode."
        break # Exit the loop.
      }
    
      # ffmpeg froze: issue a warning and restart (continue the loop).
      Write-Warning "The program is freezing. Restarting..."  
    
    }
    
    # Exit with the exit code relayed from ffmpeg.
    exit $exitCode