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?
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.
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:
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
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:
$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.