Disable close button of MSBuild child process - powershell

I have a PowerShell script that launches an MSBuild child process. I would like to disable the "close" button on the child process window, so that the user cannot interrupt the process. However, I have not been able to find any information indicating whether this is possible.
If someone could either confirm (and tell me how I would go about doing this) or deny whether this is possible I would greatly appreciate it.

MSBuild.exe is a console application, and as such by default it will run in a console window. You can't really "disable" the close button anymore than you could stop someone (with the right privileges) from just terminating the msbuild.exe process...
What you could do to mitigate some risk would be to use the the jobs feature that was introduced in PowerShell 2.0:
$job = Start-Job -ScriptBlock {
& msbuild app.csproj
if ($LASTEXITCODE -ne 0) { throw "MSBuild failed. Exit code: $LASTEXITCODE" }
}
This will schedule the script block to be run on a background thread of your PowerShell session and it will not show a window for msbuild. All of the output will be captured and held until you decide to retrieve the job. You can check the status of all background jobs with the Get-Job cmdlet, and receive the results with Receive-Job
Wait-Job $job # this line pauses PowerShell/your script until the job returns
$output = $job | Receive-Job
You can do whatever you want with the output - it is worth noting that the exception thrown if the msbuild exit status code is non-zero will be held until you receive the job, at which point it will be raised to your code like any other exception would be. You may want to consider wrapping your call to Receive-Job in a try/catch block to deal with a failed build.

Another option if you don't want a separate window to appear:
$buildArgs = "MySolution.sln", "/t:Build", "/p:Configuration=Debug"
Start-Process -FilePath "msbuild" -ArgumentList $buildArgs -NoNewWindow -Wait
Start-Process has other flags to control redirecting output as well.

Related

Powershell script waiting for user input and wont exit

I am trying to run a script silently, its runs fine but then after its run it displays
Succeeded : 0
Press 'Enter' to continue
How can i check if succeeded and then send the enter key..
Note I am running this via the start process command as below but as it is waiting for the user to press enter it never exits:
Start-Process -Wait -FilePath "C:\windows\temp\abc.exe" -ArgumentList '/S','/v','/qn' -passthru
Your best bet is to check if your executable supports a command-line parameter (option) that skips the prompt at the end.
If there is none, you can try the following approach, but note that, as with all attempts to control an application via simulated user interaction, the solution is awkward and brittle:
# Comment this out to hide the verbose messages.
$VerbosePreference = 'Continue'
# Load helper assemblies.
Add-Type -ErrorAction Stop -AssemblyName Microsoft.VisualBasic, System.Windows.Forms
# Launch the external program.
# In this simple example, cmd.exe is invoked with its internal pause
# command, which waits for a keystroke to continue.
Write-Verbose 'Launching the external program asynchronously...'
# IMPORTANT: Do NOT use -Wait, as that will block execution
# indefinitely, and prevent you from sending the Enter keystroke.
$process = Start-Process -PassThru cmd '/c pause'
Write-Verbose 'Sleeping for as long as execution is expected to last at a minimum...'
Start-Sleep 5 # Adjust this as needed.
Write-Verbose 'Sending ENTER keystrokes until the window closes...'
while (-not $process.HasExited) {
# To be safe, activate the external program's window. If that fails, it must be closed already.
try { [Microsoft.VisualBasic.Interaction]::AppActivate($process.Id) } catch { break }
# Send the keystroke.
[System.Windows.Forms.SendKeys]::SendWait('{Enter}')
Start-Sleep -Milliseconds 200 # Sleep a little between attempts.
}
Write-Verbose 'The external program''s window is now closed.'

How do I get a Powershell process that was opened by another Powershell process?

I am running multiple PowerShell scripts at once. I would like to be able to wait on certain ones to finish before opening new scripts. Basically, I was thinking if I could find the command line option that ran it something like "powershell.exe -Path "<script dir>" that would do it.
I tried doing a Get-Process | gm to find any parameters that I could call to get that information and I didn't see any (doesn't mean they aren't there) I tried looking through Task Manager to see if I could view something through the gui that I could link to but that didn't help either.
I hope I can get something like
Start-Process -FilePath ".\<script>.ps1" -ArgumentList "<args>"
do
{
sleep 10
}
until ((Get-Process -ProcessName "PowerShell" | where "<paramater>" -EQ ".\<script>")
I need to wait until that process is done but I don't want to put a wait at the end of the Start-Process because after that Start-Process kicks off I need some other items to go to while my .\ is running. I just need it to wait before another section of script kicks off.
Have a look at the "Job" cmdlets https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_jobs?view=powershell-6
And the $PID automatic variable, this will give the process ID of the current PowerShell session.

Calling print verb in windows powershell without using Start-Sleep

I have an electron app (nodejs) and have a requirement to print pictures from it. I am using the powershell command:
$path = "C:\person.jpg";
Start-Process -FilePath $path -Verb Print | Out-Null;
Start-Sleep -s 150;
This works but as soon as I remove Start-Sleep, the print window opens for half a second and closes by itself. Then, if I don't do this and the window opens, even after the user presses the close button on the title bar, the powershell process still remains open until the timeout is completed. Is there a way to have this window open and to clean up it's memory when the user closes it?
Many Thans
Add the -Wait parameter to you Start-Process call. From Start-Process:
-Wait
Indicates that this cmdlet waits for the specified process and its descendants to complete before accepting more input. This parameter suppresses the command prompt or retains the window until the processes finish.
Otherwise, PowerShell fires up the process and moves on the script execution. That's the reason why you need a Start-Sleep. So, change your code to:
$path = "C:\person.jpg";
Start-Process -FilePath $path -Verb Print -Wait | Out-Null;
if you are using PowerShell 5.x you can also try `Out-PrinterĀ“ see this link for additional docu.
Hope that helps.

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.

PowerShell Start-Process not setting $lastexitcode

I have a set of test DLL's that I'm running from a powershell script that calls OpenCover.Console.exe via the Start-Process command.
I have the -returntargetcode flag set
After execution I check $lastexitcode and $?. They return 1 and True respectively all the time. Even when tests are failing.
Shouldn't $lastexitcode be 0 when all tests pass and 1 when they fail?
By default, Start-Process is asynchronous, so it doesn't wait for your process to exit. If you want your command-line tool to run synchronously, drop the Start-Process and invoke the command directly. That's the only way it will set $LASTEXITCODE. For example, causing CMD.exe to exit with a 2:
cmd /c exit 2
$LASTEXITCODE
You can make Start-Process synchronous by adding the -Wait flag, but it still wont' set $LASTEXITCODE. To get the ExitCode from Start-Process you add -PassThru to your Start-Process, which then outputs a [System.Diagnostics.Process] object which you can use to monitor the process, and (eventually) get access to its ExitCode property. Here's an example that should help:
$p = Start-Process "cmd" -ArgumentList "/c exit 2" -PassThru -Wait
$p.ExitCode
Of course the advantage of this approach is you don't need to wait for the process, but later when it exits you have the information about it's run in $p.
When executing a GUI application, dropping the Start-Process does not help, as PowerShell does not wait for GUI application to complete, when executing them directly this way. So $LASTEXITCODE is not set.
Piping the (non existing) GUI application output helps, as it makes PowerShell to wait for the application to complete.
notepad.exe | Out-Null
echo $LASTEXITCODE
Note that "GUI application" does not necessarily mean that the application has windows. Whether an application is GUI or console is a flag in .exe file header.
Start-Process -PassThru -Wait as suggested in the answer by #Burt_Harris works too in this case, it's just a bit more complicated.