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
}
Related
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
}
}
}
So I am running a script to download from web and install a binary. I do that in a separate command prompt. When its done, prompt is closed. How can I know when that happened?
Example (from Windows 10) - powershell Start-Process cmd "/C tasklist"
Can I assign it to a variable and listen to some fallback function call, etc.? Like this pseudo code:
$process = powershell Start-Process cmd "/C tasklist"
while (!$process.done) {
// do stuff
}
p.s. I am very new to PowerShell. So sorry if that doesnt make sence
You could use WaitForExit()
$process = Start-Process cmd "/C tasklist" -PassThru
$process.WaitForExit()
Since you are using PowerShell, you can use background jobs for scenario:
$job = Start-Job -ScriptBlock {cmd.exe /c tasklist}
while ($job.State -eq 'Running') {
# do other stuff while waiting for job to complete
}
# To get the job results
Receive-Job $job
Based on your comments, adding -Wait makes Start-Process a synchronous command. At that point, you don't need Start-Process. You may as well just execute cmd.exe /c tasklist.
I'm currently struggling with an issue and would appreciated any help or input.
I have a powershell script that, during it's execution, spawns other powershell scripts that run various commands. All of these generated powershell scripts exit automatically after their commands have been executed.
What I'm trying to do is that after all of these spawned powershell instances exited, run another command within the initial powershell script. Basically wait for all generated powershell instances to exit and then run a command.
Any ideas on how to implement that?
Thanks
Edit: The code that spawns the powershell scripts looks like this:
foreach ($var in $filters){
start-process powershell.exe -Verb Runas -argument "-nologo -noprofile -command .\$var"}
You can use Wait-Process. Use the -PassThru switch to return the started processes. This should work:
$processes = foreach ($var in $filters) {
Start-Process powershell.exe -PassThru -Verb RunAs -Arg "-nologo -noprofile -command .\$var"
}
# wait for processes to finish
$processes | Wait-Process
# now, run your other commands
# ...
You could also use background jobs like this (using the $PWD automatic variable):
# run all scripts as background jobs
$jobs = foreach ($var in $filters) {
Start-Job -FilePath "$PWD\$var"
}
# wait for all jobs to finish
$jobs | Wait-Job
# now, run your other commands
# ...
or in a one-liner:
$filters | foreach { Start-Job -FilePath "$PWD\$_" } | Wait-Job
You can try this using powershell jobs as #jeorenmostert said
$j=$filters|%{
start-job -filepath "$($_)"
}
$j|wait-job
here first iterate over the $filters using % alias of ForEach
then start each job ($_) is the pipeline variable
get them inside $j variable
pipe $j jobs to wait-job to wait until the jobs has finished (you can pipe receive-job too to get passthru like results)
I have this batch file which runs the powershell script.
I want to run it the background but if I run with "windowstyle hidden" still visible.
powershell -ExecutionPolicy ByPass -windowstyle hidden -File "C:\script.ps1"
You can run, e.g. long running scripts, as a jobs.
To start it you run
$job = Start-Job -ScriptBlock {Get-Process}
this will start the Get-Process cmdlet in the background. The script can be also some custom made script or a longer script, it doesn't need to be a one-liner.
You can check its status by running
$job | Get-Job
and to receive the output you run
$job | Receive-Job
just note that once the data is received, it's lost. You can only receive it once, after that it's up to you to save it in a variable or later processing.
Finally to remove the job from the queue you run
$job | Remove-Job
I use the following function:
function bg() {
Start-Process `
-WorkingDirectory (Get-Location) `
-NoNewWindow `
-FilePath "powershell" `
-ArgumentList "-NoProfile -command `"$args`" "
}
It starts a new powershell instance which is executed in background and allows the usage of cmdlets.
You call it like:
bg "Start-Sleep 2; get-location; write 'done' "
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
}
}