Count number of scripts running and wait for them to finish - powershell

I'm looking for the best way to count the number of PowerShell scripts that are currently running.
I run .ps1 scripts from windows batch files. The script I am working on now is launched when a particular email is received from a client - but I want this script to first of all check that no other scripts are busy running at the moment, and if they are it must wait for them to finish before it continues.
I'm sure there are a few ways to go about this, but what would be the safest? I am still learning.

If it is possible to move away from batch files to launch PowerShell then I would suggest using Start-Process to launch your scripts. This will allow you to wait for your processes to exit using where-object and Measure-Object to filter the scripts that have not yet completed.
So your script might look something like this:
# create a loop
foreach ($item in $reasontoloop) {
$arguments = "define script names and arguments"
# Start the powershell script
$procs += Start-Process powershell -PassThru -argumentlist $arguments
}
Write-Host -message "Waiting for Processes to complete"
while( $procs | Where-Object { $_.hasExited -eq $false } )
{
# Display progress
$measureInfo = $procs | Where-Object { $_.hasExited -eq $true } | Measure-Object
write-host "$($measureInfo.count) of $($procs.Length) still running"
Start-Sleep 1
}
Write-Host -message "Processes complete"
If you are simply interested in the number of PowerShell instances executing then the following one liner using Get-Process will help.
#(Get-Process | where-object {$_.ProcessName -like 'powershell'}).count

Related

How to capture external command progress in PowerShell?

I'm using a PowerShell script to synchronize files between network directories. Robocopy is running in the background.
To capture the output and give statistics to the user, currently I'm doing something like:
$out = (robocopy $src $dst $options)
Once that is done, a custom windows form is presented with a multi-line text box containing the output string.
However, doing this way halts the script execution until file copy is done. Since all the other input screen are presented to the user as graphical dialogues, I would like to give user progress output in a graphical way.
Is there a way to capture the stdout from robocopy, on the fly ?
Then the next question would be:
How to pipe that output into a form with a text box?
You can run the robocopy job in the background and keep checking on the progress.
Start-Job will start the process in the background
Receive-Job will give you all the data that has been printed so far.
$job = Start-Job -scriptBlock { robocopy $using:src $using:dst $using:options }
$out = ""
while( ($job | Get-Job).HasMoreData -or ($job | Get-Job).State -eq "Running") {
$out += (Receive-job $job)
Write-output $out
Start-Sleep 1
}
Remove-Job $job

Script to kill started application

I am looking to:
delete the contents of a folder
run an application
then put in a Kill command (Stop-Process) if it runs longer than a specified period of time.
But I'm not sure where to start. Any suggestions?
If you are starting the application from a PowerShell script, and waiting in that script... then you just need a way to keep track of time in your script before calling Stop-Process. You'll also need to be sure you know what process you are going to kill.
A simple implementation could use Start-Sleep in a loop:
$pid = PID OF THE RUNNING APPLICATION
$iter = 0
while((Get-Process -Id $pid)){
$iter++
Start-Sleep -Seconds 1
if($iter -gt 60) {
Stop-Process -Id $pid
break
}
}
If your process was started elsewhere you might need to pull the process start time. This doesn't seem to be returned by Get-Process so you might want to try a WMI query:
Get-WmiObject -Class Win32_Process -Filter "Name = 'APP.exe'"-Property Name,CreationDate
You can then look for instances of APP.exe that started before a certain time.
Before you try to delete an item (folder/file) check it exists:
$folder = "C:\folder"
if (Test-Path $folder) { Remove-Item $folder -Recurse }
Stop process if it's been running for 30mins:
Get-Process firefox | Where StartTime -lt (Get-Date).AddMinutes(-30) | Stop-Process -Force

How can I start a process, pause for 2 hours and then kill a process in Powershell

How can I start a process, pause for 2 hours and then kill a process in Powershell. I can get it to launch the process and kill the process but the Start-Sleep command doesn't seem to be working in my script. I thought this would be simple. Not sure if I'm missing something or if this is even possible to sleep for 2 hours.
if((Get-Process -Name test -ErrorAction SilentlyContinue) -eq $null){
."C:\Program Files (x86)\test.exe" Start-Sleep -s 7200 Stop-Process -name test}
just to add something to Jeff's answer - you can use Start-Process and -PassThru to make sure you're ending the correct process that you launched.
if ((Get-Process 'test' -EA SilentlyContinue) -eq $null){
$Process = Start-Process "C:\Program Files (x86)\test.exe" -PassThru
Start-Sleep -Seconds (2*60*60)
$Process | Stop-Process
}
this will mean that if the process dies for another reason and is relaunched manually or by another copy of the script etc, that this script won't just kill it after two hours, but will kill the correct process.
When you are placing multiple PowerShell commands in a single-line script block, you must separate the commands with semicolons:
if((Get-Process -Name test -ErrorAction SilentlyContinue) -eq $null){ ."C:\Program Files (x86)\test.exe" ; Start-Sleep -s 7200 ; Stop-Process -name test}

Wait for an Active Directory query to finish before executing next line

Trying to work out how I can make the below code:
Wait for line 1 to complete before continuing.
Wait for line 4 to complete before running line 5
.
$invokevar = Get-ADComputer -Filter * -SearchBase $searchbase | select -Expand dnshostname
New-Variable -name "invoke$dom" -value $invokevar -Force
$fullvar = Get-Variable -Name "invoke$dom" -ValueOnly
$results = Invoke-Command -ComputerName $fullvar -ScriptBlock $sbmain
$badhosts = Compare-Object $($invokevar | Sort-Object) $($results | select -expand pscomputername | Sort-Object) | select -expand InputObject
Having a mental block, any help would be appreciated.
In powershell, the script executes line by line
Unless or until the execution of line 1 finishes, the script wont go for line 2.
So ideally you shouldn't be worrying about the problem stated above.
For internal commands PowerShell does wait before starting the next command. One exception to this rule is external Windows subsystem based EXE applications, you can apply out-null
PowerShell will wait until the exe process has been exited before continuing.
You can also use Start-Process with the -Wait parameter:
Start-Process <path to exe> -NoNewWindow -Wait
If you are using the PowerShell Community Extensions version it is:
$proc = Start-Process <path to exe> -NoWindow
$proc.WaitForExit()
Another option in PowerShell 2.0 is to use a background job:
$job = Start-Job { invoke command here }
Wait-Job $job
Receive-Job $job
In your case it will wait for the execution to get completed. Else you can check the status using a do-While loop and keep on adding a start-sleep of 1 sec
Hope this approach helps you.
Those answers are wrong. Get-ADUser absolutely may return data in the middle of the script down the line..
Some get-aduser command
echo "some string"
I have seen output line 2 first and then the results from line 1.
The only way around this is to assign a variable to the query and process the variable.
$string = get-aduser....
process $string
echo "some string"
This will process in order 1,2,3 without failure.

PowerShell to monitor a text file

I have a PowerShell script running on a remote machine. I have it writing data to a text file as it completes its work. Once it's done it writes a specific line.
I have this in the local PowerShell script to monitor that file on the remote machine:
Get-Content -Path $Path -Tail 0 -Wait
It is working great, but how do I tell it to stop monitoring once that specific line is reached?
I tired putting it into a do while loop, but it never releases to complete the do while.
Here is a link to a simpler version of what I am asking:
How to monitor a text file in realtime
The first answer is good, but I don't want to just look for a certain line. I want to write them all VIA Write-Host till that phrase then break from Get-Content and continue with the remaining parts of the script.
Here is what I finally ended up with. It is not pretty due to the way I exited the ForEach-Object.
Get-Content -Path $path -Tail 0 -wait | ForEach-Object{if($_ -match $word){write-host "- $_" ;cjklnsrvf } else {write-host "- $_"} }
I used a Try and Catch for the cjklnsrvf in the if statement above. This is done because ForEach-Object cannot use the break or continue statements. It seems that when piping a ForEach loop it is turned into (alias) the ForEach-Object cmdlet. The ForEach-Object cmdlet doesn't use the break and continue commands like a foreach loop.
If you use a break in a ForEach-Object it will immediately exit the whole script. There was one guy on one site that brought up loop death by garbage, and it indeed does work here as well.
UPDATE: Here is what I finally ended up with. It is not pretty due to the way I exited the foreach-object.
Get-Content -Path $path -Tail 0 -wait | ForEach-Object{if($_ -match $word){write-host "- $_" ;cjklnsrvf } else {write-host "- $_"} }
I used a Try and Catch for the cjklnsrvf in the if statement above. This is done because ForEach-Object cannot use the break or continue statements. It seems that when piping a ForEach loop it is turned into(alias) the ForEach-Object cmdlet. The ForEach-Object cmdlet doesn't use the break and continue commands like a foreach loop. If you use a break in a ForEach-Object it will immediately exit the whole script. There was one guy on one site that brought up loop death by garbage and it indeed does work here as well.
Get-Content $path -Tail 0 -Wait | foreach { if ($_ -eq "Specific Line") { Write-Output $_ ; break } }
Or just the break, obviously, if you have no use of the output.
Do {
$content = get-content $path -tail 0 -ea 00 | where {$_ -like "*string found*"}
Sleep -milliseconds 1000
} until($content)
Invoke-Command -ComputerName xxx -Credential $cred -ScriptBlock {if(test-path 'c:\1.txt'){cat 'c:\1.txt'}else{'file not exist'}}