How to run a batch script without the window flashing? - powershell

using suggestions from a previous a question (Kill process after timeout when homonym processes are open - batch) I was able to kill homonym processes after a timeout:
popd
pushd mypath
for /f %%i in ('powershell "(Start-Process myapp.exe -passthru).ID"') do (
timeout /t 180
taskkill /PID %%i
)
However, I would like to start the process without a window displaying (and flashing).
I found many questions, such as: How to run a PowerShell script without displaying a window? but I was not able to combine them to my script. How can I do that?
Thank you for your help

Following Steven's comment I added -WindowStyle Hidden also inside the loop. Here the solution:
pushd mypath
for /f %%i in ('powershell -WindowStyle Hidden -"(Start-Process myapp.exe -passthru -WindowStyle Hidden).ID"') do (
timeout /t 180
taskkill /PID %%i
)

tl; dr
To hide only the console window that results from use of Start-Process with a console application (myapp.exe in your case), use -WindowStyle Hidden, as shown in your answer, but you can also streamline and shorten your code by performing all operations in a single powershell.exe call:
#echo off
powershell.exe -c "if (-not ($ps = Start-Process -WindowStyle Hidden myapp.exe -PassThru).WaitForExit(60000)) { Stop-Process $ps }"
Use cases:
What you wanted is to hide the console window that by default results from PowerShell's Start-Process launching a console application (which isn't normally done, because direct, synchronous execution is by far the most common use case - see this answer):
-WindowStyle Hidden hides this new window; note that you won't be able to see or capture any output, unless you request redirection to files via the
-RedirectStandardOutput and -RedirectStandardError parameters; also, in order to check the exit code you need to use -PassThru so as to obtain an object that represents the newly launched process.
If you want to see the program's output in the current console window - albeit without being able to capture it - use -NoNewWindow, which, however, only makes sense if you also use -Wait, otherwise the asynchronously running program's output will interfere with your display (and if you're waiting anyway, direct, synchronous invocation is the better option).
As an aside: on Unix-like platforms, -WindowStyle isn't supported, and -NoNewWindow is implied, which mostly makes Start-Process only useful for launching GUI programs on Unix (including launching URLs in order to open them in the default web browser; e.g. Start-Process http://example.org)
However, your question's title might lead people to think your intent is to run the batch file itself - or any console application - invisibly:
Windows currently offers no direct mechanism for that; launching a console application invariably creates a visible console for it. A solution therefore requires a GUI-subsystem helper file or mechanism that (a) itself doesn't create a console window and (b) allows hiding the automatically console window when launching the target console application (however, a potential future enhancement is being discussed, conditional allocation of a console, which would allow applications to decide whether or not to allocate a console based on runtime conditions, but note that only applications designed to use this feature would then offer situational no-console launching).
For now, there are several solutions:
Call via mshta.exe:; a simple example that runs powershell.exe itself invisibly and ultimately opens Notepad; you can run it from the Windows Run dialog (Win-R) to verify that no console window is created for PowerShell:
mshta.exe vbscript:(CreateObject("WScript.Shell").Run("powershell -c notepad.exe",0))(Window.Close)
Caveat: AV software may prevent this invocation mechanism.
Use an auxiliary WSH script (*.vbs or *.js) launched via the GUI-subsystem wscript.exe host - see this answer.
Use the third-party RunHiddenConsole project, which offers a generic GUI-subsystem helper executable that you name for your target executable with w appended.
Use Python:
See this answer.
In the context of PowerShell, specifically:
Use a GUI-subsystem wrapper executable for PowerShell scripts that the third-party PS2EXE-GUI script can create for you; see this answer for an example.
Note: The wrapper-generating script must be run in Windows PowerShell and the resulting *.exe too executes via the Windows PowerShell SDK.
The PowerShell (Core) CLI, pwsh.exe, may itself offer a solution in the future, as discussed in GitHub issue #3028, based on the previously linked planned system-level enhancement, conditional console allocation).

Related

PowerShell Start-Process with -ArgumentList doesn't work

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.

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.

-s when using Start-Job in powershell

I am trying to call a Start-Job in powershell. When I do, it spawns a background powershell with the following arguments:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -Version 5.0 -s -NoLogo -NoProfile -EncodedCommand [encoded command I want to run in base64]
However, the powershell command never seems to complete, whatever the command I send it.
I tried spawning a powershell instance like this:
powershell.exe -s
And this also seems to create an instance that seems frozen, not executing or doing anything. Looking online, I cannot seem to find any reference to the -s argument.
Does anybody know what it's for or how to get rid of it so that my start-jobs work properly?
Edit: It's possible that -s is the shorthand for -sta, but my command does not freeze using -sta, but it does using -s.
Edit2: I have since found out that -s is a shorthand for -ServerMode, apparently a Legacy Powershell 2.0 option. I have no idea why that is added when using Start-Job.
Edit3: The command I use is:
$deploymentsJobs += Start-Job -InitializationScript { SomeSmallFunction } (AnotherFunction) -ArgumentList $arg1, $arg2, $arg3}
tl;dr:
The -s option is an expected part of the command line used to launch a background job via a new PowerShell process - it puts the new process in server mode, which is required to communicate with the calling process for background job management.
It is not a legacy option, but it isn't documented either, because it is only meant to be used internally by PowerShell.
Given that everything you describe is as expected, the problem is likely with the specific commands you run via -InitializationScript and in the main script block (the implied -ScriptBlock argument).
As you've discovered, a Start-Job call spawns a powershell -s -NoLogo -NoProfile call behind the scenes (discoverable via Task Manager).
That is, a new PowerShell process is created to run the commands in the background.
An -EncodedCommand parameter with a Base64-encoded command string is only present if you invoked Start-Process with the -Initialization parameter - the main script block (the (implied) -ScriptBlock argument) is not passed via the command line (see below).
-s is used PowerShell-internally - always - to invoke background jobs, and -s, as you've also discovered, is an alias for the -servermode switch. (Given that only -STA is documented, one would expect -s to be short for -STA, but it is not).
-s / -servermode is an implementation detail, used only by PowerShell itself, which is why it isn't documented.
This location in the PowerShell Core source code on GitHub shows you how the command line for the background process is constructed.
Server mode is the mode the background process must be in in order to communicate with the calling process via its standard streams (stdin, stdout, stderr): That is, the command to execute in the background is sent to the background process through its stdin stream, and the background process reports its output via its stdout and stderr streams.[1]
Note that XML-based serialization / deserialization happens during this inter-process communication, using the same infrastructure as PowerShell remoting - see this answer for more information.
[1] Ohad Schneider points out that it is possible to accidentally disrupt this communication if the main script block contains commands such as Start-Process -NoNewWindow with a console program that directly write to the background process' stdout stream - see this answer.

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.

Windows batch file does not wait for commands to complete

I have a batch file, which exists as soon as start it (run as admin) and does not execute commands that are in it, but if I specify it at the command-line, it runs fine and executes all commands.
Here's what's in it:
start /wait msiexec /x SetupServices.msi /qn /l* "SetupServices.uninstall.log"
start /wait msiexec /i SetupServices.msi /qn /l* "SetupServices.install.log"
(Corrected answer)
First, if you start .exe files in a batch, it is safer, to prefix it with "call".
Sometimes this is necessary to assure, that the batch is waiting to complete.
Using "start" is another possibility, but for this simple usecase not necessary.
You write that the commands are not executed. So, obviously, you have another problem, instead of the "does not wait.. to complete" problem.
Taking a look of your newly provided example, this is the case. In admin mode, you have to provide a full path. With my small trick below ("%~dp0", including already backslash), you can still use the current directory in batchfiles.
Most times, if such a problem occurs with admin rights, this is a problem of the "current directory" path. A batch file with admin rights is not using it in the same way as we were used to, it does not start in it's own directory (but in System32 mostly). Not relying on the CD is an important matter of writing bullet-proof batch files.
A good example batch , combining other answers here, and solving a number of possible problems in your case is:
call msiexec /i "%~dp0MySetup.msi" /qb /L*v "%~dp0MySetup.log"
echo Returncode: %ERRORLEVEL%
pause
It uses the current directory correctly, and assumes an install commandline including a logfile (works only, if you have write access in the current directory, if not specify a path for the logfile with write access like "%TEMP%\MySetup.log".
Attention: Remember to really start the batch file with admin rights (right mouse menu or opening an admin command shell before:)
Coming back to this question I think the "correct way" to do it is via PowerShell
Start-Process -Wait -FilePath msiexec -ArgumentList /i, "setup.msi", /qn, /l*v, "install.log"
Or just prefix it with PowerShell; to invoke directly from CMD
PowerShell; Start-Process -Wait -FilePath msiexec -ArgumentList /i, "setup.msi", /qn, /l*v, "install.log"
No hacks and no tricks :-)
Try taking the start /wait out for the msiexec lines, if that doesn't work create two more bat files one called uninstall.bat the other install.bat and use call to execute them in series.
It goes a little beyond the question, but an extension to my answer concerning handling the current directory: Here is my recommended beginning for every batch file conserving it's own path. The specialty is that it also works for UNC paths. "Pushd" automatically creates a new drive letter, if necessary (assumed, you have one free of 26). Of course you can use "popd" also at the end of the batch file instead immediately, but stable commands do not rely on the current directory as I mentioned, so it is better to always provide full paths.
#echo off
cls
pushd %~dp0
popd
set MYDIR=%CD%
echo Directory of this batch fil: %MYDIR%
You can then add the msi lines from the other answer like this:
call msiexec /i "%MYDIR%\MySetup.msi" /qb /L*v "%MYDIR%\MySetup.log"
echo Returncode: %ERRORLEVEL%
pause
(Remark: For the logfile path of course you are free, it has not to be necessarily in the same directory. But good for testing/debugging. In every case you have to have write access to the directory/file you are giving MSI with.)
While for normal MSI files it is not always necessary to start the batch with admin rights from the beginning, this technique is by far more safe (to start the MSI like already with admin rights) than too rely on the MSI UAC coming later (maybe).
And it works with msiexec ... /qn too, which is important (silent installs).
Add pause statement to the end of the batch, this will prevent the console window from closing and you will be able to see error messages if any. Errors could be the reason why it exits without actually running anything. What kind of error it may be? SetupServices.msi is not found — that's what comes to my mind.
Needs the "Window Title" if you use Parameter
start /wait "Window Title" "MsiExec.exe" /i SetupServices.msi /qn /l* SetupServices.uninstall.log
start /wait "Window Title" "MsiExec.exe" /i SetupServices.msi /qn /l* SetupServices.install.log