In Powershell, do I need to use Stop-Process AND CloseMainWindow()? - powershell

I'm new to this Powershell lark, and I understood that Stop-Process should be sufficient to end a task. However, it didn't close the newly created window and it left me uncertain if the process had stopped or not.
But, after doing some ID checking, I discovered that the process ID had been removed from the list. Is it, therefore, best practice to call Stop-Process and then CloseMainWindow() ... or is CloseMainWindow() sufficient on its own?
Cls
$notepadId = Start-Process notepad -passthru
Echo "" "notepadId = "
Echo $notepadId.Id
Echo "" "Current Notepad tasks:"
Get-Process notepad
Start-Sleep -s 2
Echo "" "Stopping Task:"
Stop-Process $notepadId.Id -Force
Echo "" "Closing Window:"
(Get-Process -Id $notepadId.Id).CloseMainWindow()
Start-Sleep -s 2
Echo "" "Tasks remaining:"
Get-Process notepad
Pause

I don't know why it's not working for you, but here's how I'd write it:
Cls
$notepadProcess = Start-Process notepad -passthru
Start-Sleep -s 2
Stop-Process $notepadProcess
The return-value from Start-Process is actually a Process object, not just the ID. If you pass that process object to Stop-Process, it knows what to do. However, a few experiments suggest that Stop-Process is not synchronous, so if you do this:
Cls
$notepadProcess = Start-Process notepad -passthru
Start-Sleep -s 2
Stop-Process $notepadProcess
get-process Notepad -ErrorAction Ignore
You will probably still see the process listed, however a few hundred milliseconds sleep before the Get-Process command should be sufficent to eliminate it.

Related

Restarting a PowerShell script after the last process is closed

I wrote a script, which opens 7 programs approximately 10 times (yes its a prankscript).
My question is, is there a way to observe, if the last process is closed and if so, restarting the whole script again?
while ($start -le 10){
Start-Process mspaint.exe
Start-Process notepad.exe
Start-Process write.exe
Start-Process cmd.exe
Start-Process explorer.exe
Start-Process control.exe
Start-Process calc.exe
$start =+ 1
}
My script now looks like following:
$start; $process
PowerShell.exe -windowstyle hidden { script.ps1 }
while ($start -le 10){
Start-Process mspaint.exe
Start-Process notepad.exe
Start-Process write.exe
Start-Process cmd.exe
Start-Process explorer.exe
Start-Process control.exe
Start-Process calc.exe
$start =+ 1
}
$process = Get-Process mspaint.exe
if ($process = $false){
Start-Process -FilePath c:/script.ps1
}
I did test this, but it starts all over again... I think that I use Get-Process wrong...
Is there another way to observe, if the process is closed or not?
If it's acceptable to handle the re-launching inside the same, indefinitely running script:
# Note: This runs indefinitely.
# If run in the foreground, you can use Ctrl-C to stop.
while ($true) {
1..10 | ForEach-Object {
# Launch all processes and pass information
# about them through (-PassThru)
'mspaint.exe',
'notepad.exe',
'write.exe',
'cmd.exe',
'explorer.exe',
'control.exe',
'calc.exe' | ForEach-Object {
Start-Process -PassThru $_
}
} | Wait-Process # Wait for all processes to terminate.
# Continue the loop, which launches the programs again.
}
You could then launch the script invisibly in the background, via Start-Process; e.g.:
Start-Process -WindowStyle Hidden powershell.exe '-File c:\script.ps1'
Caveat: To stop the operation, you'll have to locate the hidden PowerShell process and terminate it. If you add -PassThru, you'll get a process-information object representing the hidden process back.
More work is needed if you want to be able to call the script itself normally, and let it spawn a hidden background process that monitors the launched processes and then reinvokes the script (invisibly):
# Launch all processes 10 times and
# collect the new processes' IDs (PIDs)
$allPids = (
1..10 | ForEach-Object {
'mspaint.exe',
'notepad.exe',
'write.exe',
'cmd.exe',
'explorer.exe',
'control.exe',
'calc.exe' | ForEach-Object {
Start-Process -PassThru $_
}
}
).Id
# Launch a hidden PowerShell instance
# in the background that waits for all launched processes
# to terminate and then invisibly reinvokes this script:
Start-Process -WindowStyle Hidden powershell.exe #"
-Command Wait-Process -Id $($allPids -join ',');
Start-Process -WindowStyle Hidden powershell.exe '-File \"$PSCommandPath\"'
"#
Caveat: To stop the operation, you'll have to locate the hidden PowerShell process and terminate it.
Without seeing your actual script you can use something along the lines of
$validate = Get-Process -Name pwsh
if ($validate){
Start-Process -FilePath c:/script.ps1
}

Get Ctrl-C to work better on invoke-expression for powershell?

I'm doing an invoke-expression of an old console command written in C++ and I don't have control over the C++ source code... But, I think it's trapping the Ctrl-C to prevent the command line from interrupting it... Thus, when I invoke-expression from my powershell script there's no way to break the execution using ctrl-C and it locks my terminal and I need to keep killing and restarting my terminal window... which is super annoying...
Is there a way to make sure that powershell gets the ctrl-C instead of the C++ program when starting the C++ program using invoke-expression?
Like maybe, I can refuse to give stdin to the C++ program, and let powershell have it instead? or maybe there's some solution where stdin goes to a different powershell thread that waits for a ctrl-c and then kills the invoke-expression thread..
Example:
PS> $cmd = <path_to_misbehaving_cpp_program_that_doesnt_Allow_ctrl_c_To_break_it>
PS> invoke-expression $cmd
# now here if I ctrl-C I can't break out of the invoke-expression....
# But I need this capability to ctrl-C
# to break the script and terminate the
# invoke expression of the C++ program.
Starting and stopping (killing) a process from within powershell:
Use the Start-Process cmdlet to start the other program. If you use the -PassThru switch you get back the information witch process was started.
$Proc = Start-Process powershell.exe -ArgumentList '-command "sleep 60" ' -PassThru
This process can easily be killed with Stop-Process even if it is supposed to run another 60 seconds:
$Proc | Stop-Process
Edit:
Now with exit code (Thx #mklement0 for the Wait)
$Proc.WaitForExit()
$Proc.ExitCode
The trick to get it to respond to ctrl-c was to pipe $null into the command run via start-process powershell... see below... Also, note at the end the finally "kill -force" kills the remaining child processes from the initial powershell.exe process-start... my program created childprocesses that keep running after the ctrl-c kill powershell.exe so i need to kill again...to kill the entire process tree.
$script:Proc = $null
# Run Command as a separate process
function x1_process {
write-host "`nx1_process: $args"
$posh = (get-command powershell.exe).Source
$iproc = Start-Process $posh -ArgumentList "`$null `| $args" -PassThru -NoNewWindow
$script:Proc = $iproc
if ($iproc -eq $null) {
throw "null proc"
}
Wait-Process -InputObject $iproc
$code = $iproc.ExitCode
if ($code -ne 0) {
throw "return-code:$code"
}
write-host "ok`n"
return
}
try {
x1_process mycommand.exe arg1 arg2 ...
}
finally {
if ($script:Proc) {
if (-not $script:Proc.HasExited) {
write-host -ForegroundColor Red "=> Killing X1_PROCESS <="
Stop-Process -InputObject $script:Proc -Force -ErrorAction SilentlyContinue
}
}
}

Refresh System Variable in PowerShell Script

Thank you for all the help I get here!
I researched a lot on the Internet (and StackOverFlow) but I believe there is no way I can add a System Variable within a script and somehow "restart" or "refresh" so that $env:MyTools can be populated :( I'll have to exit and request the user to restart PS.
$AddCheck = Start-Process powershell.exe -verb runAs -ArgumentList '-Command', "`"[System.Environment]::SetEnvironmentVariable('MyTools','C:\SomeFolder',[System.EnvironmentVariableTarget]::Machine)`";" -PassThru -Wait
if($AddCheck.ExitCode -eq 0)
{
Write-Host "[+] Addition Successful. Please restart PowerShell for changes to take effect!" -foregroundcolor green
""
"Exiting.."
sleep -s 5
stop-process -Id $PID
}
else
{"Something went wrong."}
Any help/inputs would be appreciated!

Code in Job's Script Block after Start-Process Does not Execute

When I create a automation script with PowerShell 5.1, I got an issue – in a script block of a job, the code after Start-Process will not get chance to execute. Here’s a simple repro:
Step 1 >> Prepare a .cmd file for Start-Process, the code in callee.cmd is:
#echo off
echo "Callee is executing ..."
exit /B 0
Step 2 >> Prepare the PowerShell code,
$scriptBlock = {
$res = Start-Process -FilePath "cmd.exe" -Wait -PassThru -NoNewWindow -ArgumentList "/c .\callee.cmd"
throw "ERROR!"
}
$job = Start-Job -ScriptBlock $scriptBlock
Wait-Job $job
Receive-Job $job
Write-Host($job.State)
Step 3 >> Run the PowerShell script, the output on screen is:
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
1 Job1 BackgroundJob Completed True localhost ...
Completed
The expected value should be “Failed”.
Does my code have problem or I’m using jobs in a wrong way?
Start-Job run job in separate PowerShell process in so-called server mode. In this mode PowerShell job process use standard input and output streams to exchange messages with the parent process.
-NoNewWindow parameter of Start-Process cmdlet instruct it to connect spawned console child process to the standard streams of its parent.
Thus, using Start-Process -NoNewWindow inside of PowerShell job, you connect spawned cmd.exe process to the same streams, which PowerShell job process use to exchange messages with its own parent process.
Now, when spawned cmd.exe write something into its standard output stream, it disturb normal message exchange between PowerShell job process and its parent, which leads to some unexpected behavior.
PetSerAl gave a great explanation of why it happens but it took me while to find a proper solution.
First thing - as some people mentioned don't use -NoNewWindow, use -WindowStyle Hidden instead.
Second, output results to file and handle them in your script block. There are two parameters, one for output and another for errors -RedirectStandardOutput and -RedirectStandardError.
For some reasons I was sure that first one will handle all output and my code worked well if there were no errors, but was failing with no output in case of exceptions in script block.
I created a function that also handles process status result:
function RunProcesSafe($pathToExe, $ArgumentList)
{
Write-Host "starting $pathToExe $ArgumentList"
$logFile = Join-Path $env:TEMP ([guid]::NewGuid())
$errorLogFile = Join-Path $env:TEMP ([guid]::NewGuid())
try
{
Write-Host "starting $pathToExe $ArgumentList"
$proc = Start-Process "$pathToExe" -Wait -PassThru -RedirectStandardError $errorLogFile -RedirectStandardOutput $logFile -WindowStyle Hidden -ArgumentList $ArgumentList
$handle = $proc.Handle
$proc.WaitForExit();
if ($proc.ExitCode -ne 0) {
Write-Host "FAILED with code:"+$proc.ExitCode
Throw "$_ exited with status code $($proc.ExitCode)"
}
}
Finally
{
Write-Host (Get-Content -Path $logFile)
Write-Host (Get-Content -Path $errorLogFile)
Remove-Item -Path $logFile -Force
Remove-Item -Path $errorLogFile -Force
}
}

How do I run a Windows installer and get a succeed/fail value in PowerShell?

I would like to install a set of applications: .NET 4, IIS 7 PowerShell snap-ins, ASP.NET MVC 3, etc. How do I get the applications to install and return a value that determines if the installation was successful or not?
These answers all seem either overly complicated or not complete enough. Running an installer in the PowerShell console has a few problems. An MSI is run in the Windows subsystem, so you can't just invoke them (Invoke-Expression or &). Some people claim to get those commands to work by piping to Out-Null or Out-Host, but I have not observed that to work.
The method that works for me is Start-Process with the silent installation parameters to msiexec.
$list =
#(
"/I `"$msi`"", # Install this MSI
"/QN", # Quietly, without a UI
"/L*V `"$ENV:TEMP\$name.log`"" # Verbose output to this log
)
Start-Process -FilePath "msiexec" -ArgumentList $list -Wait
You can get the exit code from the Start-Process command and inspect it for pass/fail values. (and here is the exit code reference)
$p = Start-Process -FilePath "msiexec" -ArgumentList $list -Wait -PassThru
if($p.ExitCode -ne 0)
{
throw "Installation process returned error code: $($p.ExitCode)"
}
Depends. MSIs can be installed using WMI. For exes and other methods, you can use Start-Process and check the Process ExitCode.
msi's can also be installed using msiexec.exe, msu's can be installed using wusa.exe, both have a /quiet switch, /norestart and /forcerestart switches and a /log option for logging (specify the file name).
You can read more about the options if you call them with /?
Note: wusa fails silently when they fail, so you have to check the log file or eventlog to determine success.
I have implemented exactly what you are looking for at my current project. We need to automate deployment and instillation of n number of apps across multiple environments and datacenters. These scripts are slightly modified from the original version for simplicity sake since my complete code is reaching 1000 lines but the core functionality is intact. I hope this does what you are asking for.
This PS function pulls all the apps from the registry (what add/remove programs reads from) and then search's for the supplied app name and display version. In my code (PSM1) I run this function before I install to whether or not it is their and then afterword’s to verify that it got installed…. All this can be wrapped in one master function to manager flow control.
function Confirm-AppInstall{
param($AppName,$AppVersion)
$Apps = Get-ItemProperty Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*|?{$_.DisplayName -ne $Null}|?{$_.DisplayName -ne ""}
$Apps += Get-ItemProperty Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*|?{$_.DisplayName -ne $Null}|?{$_.DisplayName -ne ""}
$Installed = $Apps|?{$_.DisplayName -eq ""}|?{$_.DisplayVersion -eq ""}|select -First 1
if($Installed -ne $null){return $true}else{return $false}
}
This PS function will load a txt file that has the install commands prepopulated (one command per line). and run each line indivaduly and wait for the install to complete before moving on to the next.
function Install-Application{
param($InstallList = "C:\Install_Apps_CMDS.txt")
$list = gc -Path $InstallList
foreach ($Command in $list){
Write-Output ("[{0}]{1}" -f (Get-Date -Format G),$call)
#Make install process wait for exit before continuing.
$p = [diagnostics.process]::Start("powershell.exe","-NoProfile -NoLogo -Command $Command")
$p.WaitForExit()
Start-Sleep -Seconds 2
#Searches for the installer exe or msi that was directly opened by powershell and gets the process id.
$ProcessID = (gwmi -Query ("select ProcessId from Win32_Process WHERE ParentProcessID = {0} AND Name = '{1}'" -f $p.Id,$ParentProcessFile)|select ProcessId).ProcessId
#waits for the exe or msi to finish installing
while ( (Get-Process -Id $ProcessID -ea 0) -ne $null){
Start-Sleep -Seconds 2
$ElapsedTime = [int](New-TimeSpan -Start $P.StartTime -End (Get-Date)|select TotalSeconds).TotalSeconds
#install times out after 1000 seconds so it dosent just sit their forever this can be changed
if(2000 -lt $ElapsedTime){
Write-Output ('[{0}] The application "{1}" timed out during instilation and was forcfully exited after {2} seconds.' -f (Get-Date -Format G),$App.Name,(([int]$App.InstallTimeOut) * 60))
break
}
}
#clean up any old or hung install proccess that should not be running at this point.
Stop-Process -Name $ParentProcessName -ea 0 -Force
Stop-Process -Name msiexec -ea 0 -Force
}
}
The TXT file should be formatted as such... you will need to do you research on how to each app needs to be installed. a good resource is appdeploy.com
C:\Install.exe /q
C:\install.msi /qn TRANSFORMS='C:\transform.mst'
C:\install2.msi /qn /norestart
C:\install3.exe /quiet
Let me know if there are any errors I had to modify my existing code to remove the proprietary values and make this a little more simplistic. I am pulling my values from a custom XML answer sheet. But this code should work as I have supplied it.
If you would like for me to discuss more about my implementation let me know and i can make a more detailed explanation and also add more of the supporting functions that I have implemented.