dcaz
dcaz

Reputation: 909

Start-Process, Invoke-Command or?

Using the program got your back or GYB. I run the following command

Start-Process -FilePath 'C:\Gyb\gyb.exe' -ArgumentList @("--email <Email Address>", "--action backup", "--local-folder $GYBfolder", "--service-account", "--batch-size 4") -Wait

The issue is that when the process is done my script does not complete.

$GYBfolder = $GYBfolder.Replace('"', "")
$output = [PSCustomObject]@{
    Name   = $SourceGYB
    Folder = $GYBfolder
}

$filename = "C:\reports\" + $SourceGYB.Split("@")[0] + "_Backup.csv"

$output | Export-Csv $filename -NoTypeInformation | Format-Table text-align=left -AutoSize
Return $filename

For some reason the script stops right before the return. I am curious to know if I should be using a different command to run GYB? Any thoughts on why the script does not process the return?

Upvotes: 2

Views: 6356

Answers (1)

mklement0
mklement0

Reputation: 439228

There's great information in the comments, but let me attempt a systematic overview:

  • To synchronously execute external console applications and capture their output, call them directly (C:\Gyb\gyb.exe ... or & 'C:\Gyb\gyb.exe' ...), do not use Start-Process - see this answer.

    • Only if gyb.exe were a GUI application would you need Start-Process -Wait in order to execute it synchronously.

      • A simple, but non-obvious shortcut is to pipe the invocation to another command, such as Out-Null, which also forces PowerShell to wait (e.g. gyb.exe | Out-Null) - see below.
    • When Start-Process is appropriate, the most robust way to pass all arguments is as a single string encoding all arguments, with appropriate embedded "..." quoting, as needed; this is unfortunate, but required as a workaround for a long-standing bug: see this answer.

  • Invoke-Command's primary purpose is to invoke commands remotely; while it can be used locally, there's rarely a good reason to do so, as &, the call operator is both more concise and more efficient - see this answer.

  • When you use an array to pass arguments to an external application, each element must contain just one argument, where parameter names and their values are considered distinct arguments; e.g., you must use @(--'action', 'backup', ...) rather than
    @('--action backup', ...)

Therefore, use the following to run your command synchronously:

  • If gyb.exe is a console application:
# Note: Enclosing @(...) is optional
$argList = '--email',  $emailAddress, '--action', 'backup', '--local-folder', $GYBfolder, '--service-account', '--batch-size',  4

# Note: Stdout and stderr output will print to the current console, unless captured.
& 'C:\Gyb\gyb.exe' $argList
  • If gyb.exe is a GUI application, which necessitates use of Start-Process -Wait (a here-string is used, because it makes embedded quoting easier):
# Note: A GUI application typically has no stdout or stderr output, and
#       Start-Process never returns the application's *output*, though
#       you can ask to have a *process object* returned with -PassThru.
Start-Process -Wait 'C:\Gyb\gyb.exe' @"
--email $emailAddress --action backup --local-folder "$GYBfolder" --service-account --batch-size 4
@"

The shortcut mentioned above - piping to another command in order to force waiting for a GUI application to exit - despite being obscure, has two advantages:

  • Normal argument-passing syntax can be used.
  • The automatic $LASTEXITCODE variable is set to the external program's process exit code, which does not happen with Start-Process. While GUI applications rarely report meaningful exit codes, some do, notably msiexec.
# Pipe to | Out-Null to force waiting (argument list shortened).
# $LASTEXITCODE will reflect gyb.exe's exit code.
# Note: In the rare event that the target GUI application explicitly
#       attaches to the caller's console and produces output there,
#       pipe to `Write-Output` instead, and possibly apply 2>&1 to 
#       the application call so as to also capture std*err* output.
& 'C:\Gyb\gyb.exe' --email $emailAddress --action backup | Out-Null

Note: If the above unexpectedly does not run synchronously, the implication is that gyb.exe itself launches another, asynchronous operation. There is no generic solution for that, and an application-specific one would require you to know the internals of the application and would be nontrivial.


A note re argument passing with direct / &-based invocation:

  • Passing an array as-is to an external program essentially performs splatting implicitly, without the need to use @argList[1]. That is, it passes each array element as its own argument.

  • By contrast, if you were to pass $argList to a PowerShell command, it would be passed as a single, array-valued argument, so @argList would indeed be necessary in order to pass the elements as separate, positional arguments. However, the more typical form of splatting used with PowerShell commands is to use a hashtable, which allows named arguments to be passed (parameter name-value pairs; e.g., to pass a value to a PowerShell command's
    -LiteralPath parameter:
    $argHash = @{ LiteralPath = $somePath; ... }; Set-Content @argHash


[1] $argList and @argList are largely identical in this context, but, strangely, @argList, honors use of --%, the stop-parsing symbol operator, even though it only makes sense in a literally specified argument list.

Upvotes: 10

Related Questions