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.
Related
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
}
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 am having trouble getting the PID from a Start-Process within a Start-Job in Powershell (latest version). The Start-Process cmdlet runs and returns the PID as expected when run outside of the Start-Job Scriptblock. When the statement is added to the Start-Job Scriptblock, no PID is returned. Can someone point this newbie in the right direction?
$myJob = Start-Job -Name newJob -ScriptBlock {
$procID = (Start-Process myprogram.exe -PassThru -ArgumentList "myArgs" -WorkingDirectory "myWorkingDir").Id
}
Perhaps you think that assigning to a variable in a script block being executed in a background job makes that variable visible to the caller, which is not the case.
Background jobs run in an entirely separate session, and, in order to communicate data to the caller, the success output stream must be used, whose content the caller must collect via the Receive-Job cmdlet:
$myJob = Start-Job -Name newJob -ScriptBlock {
# Implicitly output the PID of the asynchronously launched process,
# by using -PassThru and returning the output object's .Id property value.
# Note: By default, Start-Process runs the process
# *asynchronously*, in a *new window*.
(
Start-Process myprogram.exe -PassThru -ArgumentList "myArgs" -WorkingDirectory "myWorkingDir"
).Id
}
# Use Receive-Job to collect the background job's output, once available,
# then remove the job automatically.
# Note: The process launched *asynchronously* from the background job
# with Start-Process lives on.
$procId = Receive-Job -Wait -AutoRemoveJob
Taking a step back: Start-Process is itself asynchronous, so there's no need to use a background job: just start myprogram.exe directly in the caller's context:
# Launch myprogram.exe asynchronously, in a new window.
# $proc receives a value once the program has *launched* and
# your script continues right after.
$proc = Start-Process myprogram.exe -PassThru -ArgumentList "myArgs" -WorkingDirectory "myWorkingDir"
# $proc.Id retrieves the PID (process ID)
# $proc.HasExited tells you whether the process has exited.
# $proc.WaitForExit() allows you to wait synchronously for the process to exit.
# * If you intend to wait *right away*, i.e run in a new window, but
# in a blocking fashion, you can omit -PassThru and pass -Wait instead.
However, if myprogram.exe is a console (terminal) application that you want to run asynchronously and whose output you want to capture, do use Start-Job, but don't use Start-Process to launch myprogram.exe: invoke it directly from the background job:
$myJob = Start-Job -Name newJob -ScriptBlock {
Set-Location "myWorkingDir"
# Launch console app myprogram.exe *synchronously*, *invisibly*,
# and relay its (stdout) output as the job's output.
myprogram.exe
}
# ... do other things
# Wait for myprogram.exe to finish, at which point the
# job exits, relaying its (stdout) output.
$myJob | Receive-Job -Wait -AutoRemoveJob
I know how to already run an exe file, easy part.
I also figured out how to run a command when powershell closes:
Register-EngineEvent PowerShell.Exiting –Action { 'Code' }
But I can't figure out how I would run a program with Powershell and then have it execute a command in Powershell when it closes, and then close Powershell itself.
$job = Start-Job { net start wuauserv }
Wait-Job $job
Receive-Job $job
Start-Process -FilePath "C:\Program Files (x86)\CpuCoreParking\CpuCoreParking3.exe" -Wait
Register-EngineEvent PowerShell.Exiting –Action {
$job = Start-Job { net stop wuauserv }
Wait-Job $job
Receive-Job $job
Start-Sleep -Seconds 2
}
This is my code so far, but it doesn't seem to stop the service on the end. Nothing happens, it just closes itself after I close the program. It does do something but ... not sure what.
Found out the problems:
First of all
Register-EngineEvent PowerShell.Exiting –Action {}
Makes the Powershell itself wait to be closed. So if it closes by the means of it finishing the execution of all commands, it will not run the command.
Second
Invoke-Expression -Command "cmd.exe /c net stop wuauserv"
This needs administrator privileges. So it's best you turn this into an exe files with admin privileges. That's the one method that comes to mind. Here is the finished code that functions as it should:
Invoke-Expression -Command "cmd.exe /c net start wuauserv"
Start-Sleep -Seconds 2
Register-EngineEvent PowerShell.Exiting –Action {
Invoke-Expression -Command "cmd.exe /c net stop wuauserv"
}
Start-Process -FilePath "C:\Program Files (x86)\CpuCoreParking\CpuCoreParking3.exe" -Wait
Invoke-Expression -Command "cmd.exe /c net stop wuauserv"
Start-Sleep -Seconds 2
Or you can replace the Invoke-Expression commands with
$job = Start-Job { net start/stop wuauserv }
Wait-Job $job
Receive-Job $job
Which I believe doesn't require admin privileges.
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' "