El cmdlet Powershell Start-Process está actuando de manera extraña:

Cuando inicio otro proceso de consola y especifico -NoNewWindow, la propiedad ExitCode (¡int!) Es nula.

Aquí hay una prueba: lo mismo con otra cosa además de cmd. Esta prueba fue en un Win10 con PS5, también es lo mismo con Win7 y PS5:

PS C:\Users\Martin> cmd.exe /Cver

Microsoft Windows [Version 10.0.15063]
PS C:\Users\Martin> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.15063.296
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.15063.296
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1


PS C:\Users\Martin> $pNewWindow = Start-Process -FilePath "cmd.exe" -ArgumentList '/C"exit 42"' -PassThru
PS C:\Users\Martin> $pNewWindow.WaitForExit()
PS C:\Users\Martin> $pNoNewWindow.HasExited
True
PS C:\Users\Martin> $pNewWindow.ExitCode
42
PS C:\Users\Martin> $pNoNewWindow = Start-Process -FilePath "cmd.exe" -ArgumentList '/C"exit 42"' -PassThru -NoNewWindow

PS C:\Users\Martin> $pNoNewWindow.WaitForExit()
PS C:\Users\Martin> $pNoNewWindow.HasExited
True
PS C:\Users\Martin> $pNoNewWindow.ExitCode
PS C:\Users\Martin> $pNoNewWindow.ExitCode -eq $null
True
PS C:\Users\Martin> $pNoNewWindow | Get-Member | ? {$_.Name -imatch "exit"}


   TypeName: System.Diagnostics.Process

Name        MemberType Definition
----        ---------- ----------
Exited      Event      System.EventHandler Exited(System.Object, System.EventArgs)
WaitForExit Method     bool WaitForExit(int milliseconds), void WaitForExit()
ExitCode    Property   int ExitCode {get;}
ExitTime    Property   datetime ExitTime {get;}
HasExited   Property   bool HasExited {get;}


PS C:\Users\Martin>

... Entonces, ¿la Propiedad está ahí, pero es null, aunque sea un int?

2
Martin Ba 19 may. 2017 a las 00:03

2 respuestas

La mejor respuesta

Respuesta de pregunta vinculada respuesta de / comentario:

tuvo que hacer fue almacenar en caché el identificador de proceso. Tan pronto como hice eso, $ process.ExitCode funcionó correctamente. Si no almacené en caché el identificador de proceso, $ process.ExitCode fue nulo.

Y, de hecho, para un proceso que no termina inmediatamente (a diferencia del cmd.exe en el ejemplo), la solución funciona:

$proc = Start-Process -NoNewWindow -PassThru ...
$handle = $proc.Handle # cache proc.Handle https://stackoverflow.com/a/23797762/1479211
$proc.WaitForExit()
$proc.ExitCode ... will be set

Un usuario agregó una explicación en los comentarios:

Esta es una peculiaridad de la implementación del objeto .NET Process. La implementación de la propiedad ExitCode primero verifica si el proceso ha salido. Por alguna razón, el código que realiza esa verificación no solo examina la propiedad HasExited sino que también verifica que el identificador de proceso esté presente en el objeto de proceso y lanza una excepción si no es. PowerShell intercepta esa excepción y devuelve nulo.

Al acceder a la propiedad Handle, el objeto de proceso recupera el identificador de proceso y almacenarlo internamente. Una vez que el identificador se almacena en el objeto de proceso, la propiedad ExitCode funciona como se esperaba.

Desde Start-Process no puede iniciar un proceso suspendido, y el identificador solo puede obtenerse mientras el proceso se esté ejecutando (parece), el uso es un poco frágil para los procesos de ejecución corta.

2
Community 20 jun. 2020 a las 09:12

Encontramos un problema similar en nuestro entorno. Mientras ejecuta el siguiente comando:

Start-Process -FilePath C:\Windows\system32\reg.exe -PassThru -Wait -NoNewWindow

O

Start-Process -FilePath C:\Windows\system32\reg.exe -PassThru -Wait -WindowStyle Hidden

Como dijo Martin Ba, parece que el proceso iniciado (reg.exe) se ha detenido antes de que Start-Process pudiera obtener un identificador del proceso.

Usar -Wait o .WaitForExit() no hace la diferencia porque ambos métodos verifican si el proceso ha salido. Si salió, PowerShell no podrá obtener el controlador necesario para el Proceso. El controlador es necesario porque contiene el código de salida del proceso iniciado.

Se produce el siguiente error, si el proceso ya ha salido:

System.Management.Automation.CmdletInvocationException: Cannot process request because the process (<ProcessIdHere>) has exited. ---> 
System.InvalidOperationException: Cannot process request because the process (<ProcessIdHere>) has exited.
  at System.Diagnostics.Process.GetProcessHandle(Int32 access, Boolean throwIfExited) 
  at System.Diagnostics.Process.OpenProcessHandle(Int32 access) 
  at System.Diagnostics.Process.get_Handle()

El uso de -WindowStyle Hidden en lugar de -NoNewWindow puede reducir la frecuencia con la que se produce el error. Esto se debe a que -WindowStyle Hidden está creando un nuevo Shell (-> más Overhead), mientras que -NoNewWindow usa el Shell actual.

Creamos una entrada UserVoice para este problema. Tal vez el equipo de PowerShell pueda solucionar este comportamiento: https://windowsserver.uservoice.com/forums/301869-powershell/suggestions/35737357--bug-start-process-might-not-return-handle-exitco

2
Roman 29 oct. 2018 a las 08:35