3

When I create a automation script with PowerShell 5.1, I got an issue – in a script block of a job, the code after Start-Process will not get chance to execute. Here’s a simple repro:

Step 1 >> Prepare a .cmd file for Start-Process, the code in callee.cmd is:

@echo off
echo "Callee is executing ..."
exit /B 0

Step 2 >> Prepare the PowerShell code,

$scriptBlock = {
    $res = Start-Process -FilePath "cmd.exe" -Wait -PassThru -NoNewWindow -ArgumentList "/c .\callee.cmd"
    throw "ERROR!"
}

$job = Start-Job -ScriptBlock $scriptBlock
Wait-Job $job
Receive-Job $job
Write-Host($job.State)

Step 3 >> Run the PowerShell script, the output on screen is:

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command
--     ----            -------------   -----         -----------     --------             -------
1      Job1            BackgroundJob   Completed     True            localhost            ...
Completed

The expected value should be “Failed”. Does my code have problem or I’m using jobs in a wrong way?

  • 1
    Possible duplicate of [How to capture the exception raised in the scriptblock of start-job?](https://stackoverflow.com/questions/8751187/how-to-capture-the-exception-raised-in-the-scriptblock-of-start-job) – Hackerman Aug 18 '17 at 20:13
  • You're passing your argument list wrong. Should be a `String[]` type. New `[String]` for each arg. Also, why are you throwing an error every run? – Maximilian Burszley Aug 18 '17 at 20:39
  • 1
    The "throw" is just used to prove that the code after Start-Process will not be executed. Thanks guys. – Timothy Liu Aug 18 '17 at 20:51
  • Try `$res = Start-Process -FilePath "cmd.exe" -Wait -PassThru -NoNewWindow -ArgumentList ".\callee.cmd","/c"`, and you'll see that it works. Or change `-NoNewWindow` to `-WindowStyle Hidden`. – TheMadTechnician Aug 19 '17 at 01:50

2 Answers2

3

Start-Job run job in separate PowerShell process in so-called server mode. In this mode PowerShell job process use standard input and output streams to exchange messages with the parent process.

-NoNewWindow parameter of Start-Process cmdlet instruct it to connect spawned console child process to the standard streams of its parent.

Thus, using Start-Process -NoNewWindow inside of PowerShell job, you connect spawned cmd.exe process to the same streams, which PowerShell job process use to exchange messages with its own parent process.

Now, when spawned cmd.exe write something into its standard output stream, it disturb normal message exchange between PowerShell job process and its parent, which leads to some unexpected behavior.

user4003407
  • 21,204
  • 4
  • 50
  • 60
  • And this is what causes the `Receive-Job : There is an error processing data from the background process. Error reported: Cannot process an element with node type "Text". Only Element and EndElement node types are supported.` error right? For which it seems you've found a workaround: https://windowsserver.uservoice.com/forums/301869-powershell/suggestions/14915283-job-cmdlets-fail-with-utf-8-codepage (reposted in SO here: https://stackoverflow.com/a/43350250/67824) – Ohad Schneider Mar 07 '19 at 15:36
  • @OhadSchneider As far as I understand, this issues are not actually related. Them have different underlying cause. – user4003407 Mar 07 '19 at 15:52
  • I'm confused, you linked to this question from https://stackoverflow.com/questions/24689505/start-process-nonewwindow-within-a-start-job which discusses the "only Element and EndElement node types are supported" error... – Ohad Schneider Mar 08 '19 at 02:19
  • @OhadSchneider Similarities in error messages not necessary means that you have the same error with the same root cause. – user4003407 Mar 08 '19 at 02:30
  • When the error message is so unique and specific, it usually does, but I concede it's not a guarantee. What's the root cause in the case I linked then? – Ohad Schneider Mar 08 '19 at 11:13
  • @OhadSchneider It is [here](https://referencesource.microsoft.com/#System/services/monitoring/system/diagnosticts/Process.cs,2155). By turning on `AutoFlush`, `Process` class immediately write encoding preamble to the new process input stream. – user4003407 Mar 08 '19 at 11:30
  • Very interesting, but I'm asking about the other thread, which is more similar to my case, where no `System.Diagnostics.Process` is involved: https://stackoverflow.com/questions/33936510/start-job-including-custom-cmdlet-terminates-with-strange-error – Ohad Schneider Mar 08 '19 at 11:47
  • 2
    I think I understand what you're saying. This error generally means that a job-created PS process can't communicate with the parent PS process in the fashion you explained above. One reason would be a process writing directly to the output stream (as is the case in this thread). Another would be the parent expecting a BOM and the child not writing BOM (or possibly the other way around). In my case, Azure DevOps creates a PS session that expects a BOM, but that's not preserved across `Start-Job` invocations. – Ohad Schneider Mar 08 '19 at 11:59
1

PetSerAl gave a great explanation of why it happens but it took me while to find a proper solution.

First thing - as some people mentioned don't use -NoNewWindow, use -WindowStyle Hidden instead.

Second, output results to file and handle them in your script block. There are two parameters, one for output and another for errors -RedirectStandardOutput and -RedirectStandardError. For some reasons I was sure that first one will handle all output and my code worked well if there were no errors, but was failing with no output in case of exceptions in script block.

I created a function that also handles process status result:

function RunProcesSafe($pathToExe, $ArgumentList)
{               
 Write-Host "starting $pathToExe $ArgumentList"
 $logFile = Join-Path $env:TEMP ([guid]::NewGuid())
 $errorLogFile = Join-Path $env:TEMP ([guid]::NewGuid())
 try
 {
    Write-Host "starting $pathToExe $ArgumentList"
    $proc = Start-Process "$pathToExe" -Wait -PassThru -RedirectStandardError $errorLogFile -RedirectStandardOutput $logFile -WindowStyle Hidden -ArgumentList $ArgumentList
    $handle = $proc.Handle          
    $proc.WaitForExit();
    if ($proc.ExitCode -ne 0) {
         Write-Host "FAILED with code:"+$proc.ExitCode
         Throw "$_ exited with status code $($proc.ExitCode)"
    }
 }
 Finally
 {
    Write-Host  (Get-Content -Path $logFile) 
    Write-Host  (Get-Content -Path $errorLogFile) 
    Remove-Item -Path $logFile -Force
    Remove-Item -Path $errorLogFile -Force
 }         
}
Victor Fialkin
  • 165
  • 4
  • 7