PowerShell Start-Process with -ArgumentList doesn't work - powershell

A very easy line in a powershell script
Start-Process -FilePath displayswitch -ArgumentList "/extend"
It is supposed to extend my screen. Yet it doesn't work because there is something wrong with the ArgumentList. What is it?

In general, do not use Start-Process to invoke CLIs (command-line interfaces; i.e., programs that accept command-line arguments), unless you explicitly want to launch them asynchronously, in a new window - see this answer for background information.
In the case at hand - for reasons unknown to me[1] - Start-Process additionally seems to prevent the displayswitch.exe executable from functioning properly when arguments are passed (the syntax of your Start-Process command is correct).
However, that is a moot point if you invoke displayswitch.exe directly, which is generally the right choice for CLIs:[2]
displayswitch.exe /extend
[1] The problem may be related to the fact that displayswitch.exe is a GUI-subsystem rather than a console-subsystem application. That is, it doesn't allocate a console window for itself and operates asynchronously when called from an existing console window. Using Start-Process therefore makes the executable run without a console window. That said, running displayswitch.exe /extend from the Windows Run dialog (WinKey-R), where no console window is involved either, works correctly.
[2] Given that displayswitch.exe is a GUI-subsystem application, it will act asynchronously even in direct invocation, but I assume that's not a problem here. If your Start-Process command had worked, it would have been asynchronous too. Adding -Wait to a Start-Process call makes it synchronous, and you can emulate this behavior in direct invocation by piping to Out-Null. However, due to how displayswitch.exe is implemented, even displayswitch.exe /extend | Out-Null doesn't result in fully synchronous operation.

Related

is there a wait parameter in PowerShell to allow the script to wait until a command is completed

I trying to install a software using Start-Process in PowerShell, I would like for the script to wait until a command is completed before proceeding to the next one. I'm not experienced I tired the script below but it did not work.
Start-Process -Wait -FilePath "C:\Temp\Latitude_5X10_Precision_3550_1.15.0.exe" -ArgumentList "/S" -PassThru
Your Start-Process call is correct, but -Wait invariably only tracks the lifetime of the directly launched process (C:\Temp\Latitude_5X10_Precision_3550_1.15.0.exe in your case).
That is, you're out of luck if the target process itself spawns yet another process in order to perform its task and then returns before that child process has terminated.
Additional work is then needed, if even feasible:
If you know the name of the child process, you can try to find and track it via Get-Process.
Alternatively, if you know of an indirect sign that the task has completed, such as the existence of a directory or a registry entry, look for that.
As an aside: console(-subsystem) applications can be invoked directly for synchronous (blocking) execution (e.g., foo.exe bar baz or & $fooExePath bar baz), which is the preferred method, because it connects the application's output streams to PowerShell's streams.

Start multiple programs using Powershell without leaving the window open afterward

I want to load a series of programs and then have the PowerShell instance close afterward. This is what I have:
Start-Process -FilePath <path to chrome>
Start-Process -FilePath <path to firefox>
Start-Process -FilePath <path to vs code>
So basically, I want to run it like a batch file where it launches the listed programs and then goes away. But instead, it leaves the powershell window open and if I manually close it, VScode shuts too (the browsers stay open for some reason).
I see there's a -wait option for start-process, but I want the opposite: -nowait. Just open and go away. How can I do this? The other commands like invoke and startjob don't seem right.
Since these are GUI applications, you don't need to worry about usingStart-Process -Wait:$False; GUI applications don't block in PowerShell (or Command Prompt) by default. You can simply run those programs with the call-operator & (technically optional to use the call-operator but it covers the path-with-spaces case):
& "\path\to\guiprogram.exe"
If you need to do this with a non-GUI application, then you would need to useStart-Process -Wait:$False (or omit -Wait altogether since Start-Process doesn't block by default) to continue executing:
Start-Process -Wait:$False "\path\to\program.exe"
Start-Process "\path\to\program.exe"
-SwitchParameter:$Value syntax allows you to set the explicit value of [switch] parameters. Usually the switch parameter is implicitly managed. It is in an "on (true)" state if provided, and an "off (false)" state when omitted.
However, you can explicitly set the value of a switch parameter by suffixing the parameter with :$Value. The value's truthiness will be evaluated and the switch state will match "on" for $True and "off" for $False.

Powershell not running on cmd

I'm trying to run the following command but when I do it the CMD windows closes without the command being executed
the command is
cmd.exe /c powershell.exe (New-Object System.Net.WebClient).DownloadFile(https://site,'%temp%\file.txt');Start-Process '%temp%\file.txt'
You don't need cmd here at all¹. You can spawn a process without a shell just as well. Furthermore, it's usually a good idea to quote arguments to avoid surprises with argument parsing (cmd for example has its own meaning for parentheses, which may well interfere here).
powershell -Command "(New-Object System.Net.WebClient).DownloadFile('https://site',$Env:Temp + '\file.txt');Invoke-Item $Env:Temp\file.txt"
I've also added quotes around the URL you want to download, since that wouldn't otherwise work, either. And since cmd is no longer around, environment variables can be expanded by PowerShell as well, with a different syntax.
Start-Process also is for starting processes and Invoke-Item is closer to what you actually want, although I'm sure with ShellExecute behavior, Start-Process could launch Notepad with a text file as well if desired.
¹ If in doubt, it's always a good idea to reduce the number of parts, processes and different wrapped concepts needed. Same reason why you don't use Invoke-Expression in PowerShell to run other programs: Unnecessary, and complicates everything just further by introducing another layer of parsing and interpretation.

Powershell closes on executing exe

I am trying to run and .exe in C:programfiles(x86)
I can launch it directly from the filepath. If I run in powershell just closes. No feedback.
Running powershell 5.1.17134 Rev 590
Start-Process -FilePath "C:\Program Files (x86)\App\App.exe"
I tried running powershell -NoExit and then start-process but it returns without any feedback.
If I run it on same machine in Powershell 6.1.0 preview - it runs fine. No issue. How can I track down whats causing this to 1) not run 2)close powershell.
Thanks,
This answer makes a general point. It doesn't solve your problem, if using Start-Process truly crashes the calling PowerShell window.
If C:\Program Files (x86)\App\App.exe is a console application that you want to run in the current console window, do not use Start-Process to invoke it.
Instead, invoke it directly.
Given that its path contains spaces and special characters (( and )), you need to:
quote it
invoke it with &, PowerShell's call operator.
PS> & 'C:\Program Files (x86)\App\App.exe'
Note:
The use of & with a command specified in a quoted string and/or via a variable reference is a syntactic necessity, given that PowerShell has two distinct parsing modes - see about_Parsing.
Invoking a console application directly:
ensures its synchronous execution
That is, control isn't returned to the caller until the application has terminated.
connects its standard output streams - stdout and stderr - to the equivalent PowerShell streams.
That is, you can capture or redirect the application's success output and/or error output, as needed.
You have an interactive shell. You spawn this new process - then your shell closes?
Clearly it is terminating its parent process, and clearly pwsh is doing something different.
I don't think this is truly a powershell question, it's a windows internals one. The suite of tools to use is Sysinternals. The first thing I'd try - and I'd do this on cmd, powershell and pwsh to establish a basis for comparison - is run Process Monitor with a filter on your app's path. Something in its last actions may prove illuminating. Process Explorer may also be useful.
Are you in a corporate environment? I have agents on my machine that kill processes based on heuristics. That can do things like this.
There may be a workaround based on how you invoke the app;
try mklement0's suggestion
try invoking through WMI; this does not provide your powershell process as a parent process: Invoke-WmiMethod -Class win32_process -Name create -ArgumentList "PathToApp.exe"
try invoking via cmd.exe if you are constrained by what's on your target machines
I do think this is off-topic, though.

Powershell waits on cmd.exe differently depending on environment

Consider the powershell command:
cmd.exe "/c start notepad.exe"
Using powershell.exe (console) this command completes immediately after starting the cmd/notepad process and does not wait for notepad/cmd to exit. If I want it to wait for completion I have to pipe the output to Out-Null.
Using the powershell ISE this command blocks execution until notepad/cmd is closed.
Further, if I use create a new process (of powershell.exe) in a script running in powershell.exe using System.Diagnostics.Process and redirect standard output the script now blocks execution until notepad/cmd is closed. If I don't redirect output it does not block execution.
But if I use c# to create a new process with the same settings/start info, and run the same script with redirected output it doesn't block execution.
I'm at a loss here. Obviously it has something to do with the setup of the execution and output and maybe "cmd.exe". I'm hoping someone understands what's fundamentally happening behind the scenes differently in these cases. I know the ISE and cmd.exe isn't fully supported but the I don't know why the other 3 aren't consistent.
Why do the scripts not run with the same behavior (especially the powershell console ones) and how do I get them to?
Edit:
After some troubleshooting I was able to get all the powershell.exe versions to run the same (the ISE isn't of importance to me). The odd ball where cmd.exe "/c start notepad.exe" blocks execution is when a new process is created in powershell to run powershell.exe using System.Diagnostics.Process. If output is redirected (the reason I was using System.Diagnostics.Process in the first place, Start-Process doesn't support redirecting except to a file) then calling WaitForExit() on the process object causes the blocking to occur.
Simply substituting WaitForExit() with Wait-Process (and the process ID) causes the powershell script running in the new powershell.exe to execute as expected and exit after running the command. Hopefully this information combined with #mklement0 answer will be sufficient for anyone else having similar problems.
To get predictable behavior in both the regular console and in the ISE:
Start a GUI application asynchronously (return to the prompt right away / continue executing the script):
notepad.exe
Invoking Notepad directly makes it run asynchronously, because it is a GUI-subsystem application.
If you still want to track the process and later check whether it is still running and what its exit code was on termination, use -PassThru, which makes Start-Process return a [System.Diagnostic.Process] instance:
$process = Start-Process -PassThru notepad.exe
$process.HasExited later tells you whether the process is still running.
Once it has exited, $process.ExitCode tells you the exit code (which may not tell you much in the case of a GUI application).
To wait synchronously (at some point):
Use Wait-Process $process.ID to wait (indefinitely) for the process to terminate.
Add a -Timeout value in seconds to limit the waiting period; a non-terminating error is reported if the process doesn't terminate within the timeout period, causing $? to reflect $False.
Start a GUI application synchronously (block until the application terminates):
Start-Process -Wait notepad.exe
-Wait tells Start-Process to wait until the process created terminates; this is the equivalent of cmd /c 'start /wait notepad.exe'.
There's a non-obvious shortcut to Start-Process -Wait: you can repurpose the Out-Null cmdlet to take advantage of the fact that piping invocation of a program to it makes Out-Null to wait for the program's termination (a GUI application has no stdout or stderr output, so there's nothing for Out-Null to discard; the only effect is synchronous invocation):
notepad.exe | Out-Null
In fact, this approach has two advantages:
If arguments must be passed to the GUI application, they can be passed directly, as usual - rather than indirectly, via Start-Process's -ArgumentList parameter.
In the (rare) event that a GUI application reports a meaningful process exit code (e.g, msiexec.exe), the Out-Null approach (unlike Start-Process -Wait) causes it to be reflected in the automatic $LASTEXITCODE variable.
Note: In rare cases, a GUI application may explicitly attach to the caller's console and write information to it; in order to surface that, pipe to | Write-Output instead (you'll still be able to evaluate $LASTEXITCODE) - see this answer.
Note that for console-subsystem applications (e.g., findstr.exe), synchronous execution is the default; Start-Process is only needed for GUI applications (and for special situations, such as wanting to run an application in a new console window or with elevation (run as admin)).
To run a console application or shell command asynchronously (without opening a new console window), you have the following options:
[Preferred] Use Start-Job kick off the command, and Receive-Job to receive its output / success status later.
$j = Start-Job { sleep 2; 'hi' }
To synchronously wait for this job to finish (and remove it automatically), use
Receive-Job -Wait -AutoRemoveJob $j
In PowerShell (Core) 6+:
You can use the simpler ... & syntax (as in
POSIX-like Unix shells such as bash) in lieu of Start-Job; e.g.:
$j = & { sleep 2; 'hi!' } &
Better yet, you can use the lighter-weight, faster Start-ThreadJob cmdlet, which uses threads for concurrency, but otherwise seamlessly integrates with the other *-Job cmdlets (note that it has no short-hand syntax):
$j = Start-ThreadJob { sleep 2; 'hi' }
[Not advisable] You can use something like Start-Process -NoNewWindow powershell -Args ..., but it is ill-advised:
Passing a shell command to powershell via Start-Process requires intricate quoting based on obscure rules - see this answer of mine for background information.
Any stdout and stderr output produced by the application / shell command will by default arrive asynchronously in the current console, interspersed with what you're doing interactively.
While you can redirect these streams to files with RedirectStandardOutput and -RedirectStandardError (and stdin input via -RedirectStandardInput) and you can use -PassThru to obtain a process-information object to determine the status and exit code of the process, Start-Job and Receive-Job are a simpler, more convenient alternative.
P.S.: I don't have an explanation for why cmd.exe "/c start notepad.exe" is blocking in the ISE but not in the regular console. Given the solutions above, however, getting to the bottom of this discrepancy may not be needed.