Interrupting a loop - powershell

I just do my first steps with powershell and have a questions about a loop I'm using in a script:
for ($i=1; $i -le 100; $i++){
$res = Test-Connection $IP -count 1 -Quiet
...do something more
start-sleep -seconds 30
}
This script does not allow to close the windows form (it's started from a GUI) or interrupt the loop. Is there a way to do so? Sometimes I want to stop the loop manually.
Thanks a lot for your help.

I think you might want to use powershell flow control.
With powershell flow control you are able to manually control loops in powershell.
Let me give you an example:
for ($i=1; $i -le 100; $i++){
$res = Test-Connection $IP -count 1 -Quiet
...do something more
if ($res -eq $anyresultyouwouldexpect) {
break ##with **break** you interupt the loop completely- You script
## would continue after the loop.
}
}
There are also flow control statements like continue to jump into the next iteration loop. It is depending on what you need in your case.

Related

Date based foreach loop [duplicate]

I'm working on my first PowerShell script and can't figure the loop out.
I have the following, which will repeat $ActiveCampaigns number of times:
Write-Host "Creating $PQCampaign1 Pre-Qualified Report"
Invoke-Item "$PQCampaignPath1\PQ REPORT $PQCampaign1.qvw"
Write-Host "Waiting 1 minute for QlikView to update"
sleep -seconds 60 # Wait 1 minute for QlikView to Reload, create Report and Save.
DO{
Write-Host "Daily Qlikview Reports"
Write-Host "Wating for QlikView to create the $PQCampaign1 PQ Report"
Get-Date
Write-Host "Checking...."
sleep -seconds 1
Write-Host ""
Write-Host "Not Done Yet"
Write-Host "Will try again in 5 seconds."
Write-Host ""
sleep -seconds 5
}
Until (Test-Path "$PQCampaignPath1\$PQCampaign1 $PQReportName $ReportDate.xlsx" -pathType leaf)
Get-Date
Write-Host "Done with $PQCampaign1 PQ Report. Wait 10 seconds."
sleep -seconds 10
These parameters need to increase with one for each loop:
$PQCampaign1 (should become $PQCampaign2, then 3, etc.)
$PQCampaignPath1 (should become $PQCampaignPath2, then 3, etc.)
So if $ActiveCampaigns is set to 8 on a certain day, then this needs to repeat 8 times and the last time it must open $PQCampaign3 which lies in $PQCampaignPath8.
How can I fix this?
Use:
1..10 | % { write "loop $_" }
Output:
PS D:\temp> 1..10 | % { write "loop $_" }
loop 1
loop 2
loop 3
loop 4
loop 5
loop 6
loop 7
loop 8
loop 9
loop 10
This may be what you are looking for:
for ($i=1; $i -le $ActiveCampaigns; $i++)
{
$PQCampaign = Get-Variable -Name "PQCampaign$i" -ValueOnly
$PQCampaignPath = Get-Variable -Name "PQCampaignPath$i" -ValueOnly
# Do stuff with $PQCampaign and $PQCampaignPath
}
Here is a simple way to loop any number of times in PowerShell.
It is the same as the for loop above, but much easier to understand for newer programmers and scripters. It uses a range and foreach. A range is defined as:
range = lower..upper
or
$range = 1..10
A range can be used directly in a for loop as well, although not the most optimal approach, any performance loss or additional instruction to process would be unnoticeable. The solution is below:
foreach($i in 1..10){
Write-Host $i
}
Or in your case:
$ActiveCampaigns = 10
foreach($i in 1..$ActiveCampaigns)
{
Write-Host $i
If($i==$ActiveCampaigns){
// Do your stuff on the last iteration here
}
}
See this link. It shows you how to dynamically create variables in PowerShell.
Here is the basic idea:
Use New-Variable and Get-Variable,
for ($i=1; $i -le 5; $i++)
{
New-Variable -Name "var$i" -Value $i
Get-Variable -Name "var$i" -ValueOnly
}
(It is taken from the link provided, and I don't take credit for the code.)

Test-Path timeout for PowerShell

I'm trying to routinely check the presence of particular strings in text files on hundreds of computers on our domain.
foreach ($computer in $computers) {
$hostname = $computer.DNSHostName
if (Test-Connection $hostname -Count 2 -Quiet) {
$FilePath = "\\" + $hostname + "c$\SomeDirectory\SomeFile.txt"
if (Test-Path -Path $FilePath) {
# Check for string
}
}
}
For the most part, the pattern of Test-Connection and then Test-Path is effective and fast. There are certain computers, however, that ping successfully but Test-Path takes around 60 seconds to resolve to FALSE. I'm not sure why, but it may be a domain trust issue.
For situations like this, I would like to have a timeout for Test-Path that defaults to FALSE if it takes more than 2 seconds.
Unfortunately the solution in a related thread (How can I wrap this Powershell cmdlet into a timeout function?) does not apply to my situation. The proposed do-while loop gets hung up in the code block.
I've been experimenting with Jobs but it appears even this won't force quit the Test-Path command:
Start-Job -ScriptBlock {param($Path) Test-Path $Path} -ArgumentList $Path | Wait-Job -Timeout 2 | Remove-Job -Force
The job continues to hang in the background. Is this the cleanest way I can achieve my requirements above? Is there a better way to timeout Test-Path so the script doesn't hang besides spawning asynchronous activities? Many thanks.
Wrap your code in a [powershell] object and call BeginInvoke() to execute it asynchronously, then use the associated WaitHandle to wait for it to complete only for a set amount of time.
$sleepDuration = Get-Random 2,3
$ps = [powershell]::Create().AddScript("Start-Sleep -Seconds $sleepDuration; 'Done!'")
# execute it asynchronously
$handle = $ps.BeginInvoke()
# Wait 2500 milliseconds for it to finish
if(-not $handle.AsyncWaitHandle.WaitOne(2500)){
throw "timed out"
return
}
# WaitOne() returned $true, let's fetch the result
$result = $ps.EndInvoke($handle)
return $result
In the example above, we randomly sleep for either 2 or 3 seconds, but set a 2 and a half second timeout - try running it a couple of times to see the effect :)

Pause a Powershell script and resume

I have a script which currently pulls the file location from a CSV and uploads the files to a database, using a ForEach-Object loop.
What I'd like it to do is upload 1000 files, then be able to pause the loop and resume it later from file 1001.
I don't want to use the Start-Sleep command, as I do not want the script to automatically resume after a set amount of time.
This is a one time deal, so I'd rather not convert it to a workflow.
What command or cmdlet can be used to accomplish this?
The Read-Host command would be perfect if there were a way to break the script and then resume from the same line later.
Here's how I'd do it:
$i = 0;
foreach ($file in (Get-ChildItem $path_to_directory)) {
# Code to upload the file referenced by $file
if (++$i -eq 1000) {
Write-Host -NoNewLine '1000 files have been uploaded. Press capital "C" to continue uploading the remaining files...'
do {
} until (($Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyUp').Character) -ceq 'C')
}
}
Using pause, as already suggested in Bluecakes' answer, is a perfectly good solution. The advantage of this method is that it gives you more control. pause always requires the Enter key and always gives you the same prompt, Press Enter to continue...:, whereas this way you can define both to your liking.
More significantly, and the reason I personally prefer to do it this way in my own scripts, is that you can protect against accidental key presses. In my example I made the required keystroke a capital C, so that it's very unlikely that you'd continue by accident.
However, if you don't care about any of that and just want a quick and simple say to do it, then pause is really all you need.
Use pause:
For ($i=1; $i -lt 2000; $i++) {
if ($i -eq 1001)
{
pause
}
Write-Host $i
}
Something along these lines could work for you...
For ($i=1; $i -lt 50; $i++) {
if ($i -eq 10)
{
$input = Read-Host "Should I continue Y/N"
if ($input -eq "N")
{
break
}
}
Write-Host $i
}

PowerShell -- Looking for equivalent of Perl's redo

I have the following PowerShell loop:
Set-Variable -Name YELLOW -Option ReadOnly -Value 0
Set-Variable -Name ORANGE -Option ReadOnly -Value 1
Set-Variable -Name RED -Option ReadOnly -Value 2
While ($true) {
for ($color = $YELLOW; $color -le $RED; $color++) {
$LastWriteTime = ((Get-Item $FilePath).LastWriteTime)
$waitTime = (Get-Date).AddMinutes(-($WarningTimeArray[$color]))
$SleepTime = ($LastWriteTime - $waitTime).TotalSeconds
If ($SleepTime -gt 0) {
$WarningFlagArray[$color] = $false;
SendMail ("Start-Sleep -Seconds" + $SleepTime)
Start-Sleep -Seconds $SleepTime
Continue #Actually want a "redo"
}
ElseIf (-not $WarningFlagArray[$color]) {
SendMail $WarningMessageArray[$color]
$WarningFlagArray[$color] = $true
}
}
}
When PowerShell hits the "Continue", it continues back to the top of the for loop, and goes to the next color.
In Perl, you have last (equivalent to PowerShell's break) and you have next (equivalent to PowerShell's continue). You also have the redo which goes back to the top of the loop, but doesn't increment the for loop counter.
Is there an equivalent Powershell command to Perl's redo function for the for loop?
I can easily convert the statement into a while loop to get around this particular issue, but I was wondering if there is something like redo anyway.
I'm pretty sure PowerShell doesn't support what you're looking for. You can have labelled break and continue statements but neither will take you to the top of the inside of the associated loop/switch construct. If PowerShell had a goto statment you'd be set but I'm glad it doesn't have that keyword. :-) For more info on labelled break/continue see the about_break help topic.
An equivalent of Perl's redo would be nice, and I hope they introduce it in a future version, but mainly because it would be useful in Foreach-Object, foreach, and while loops.
However, in a for loop, at least a typical one, you can easily reproduce the functionality by simply reversing the incrementation immediately before the continue statement.
In this case, since the increment statement (aka <repeat> in the PowerShell documentation) is $color++, just stick $color-- immediately before continue. For clarity/elegance, I'd suggest putting them on one line to make it clearer that it's essentially a single loop control contsruct:
$color--; continue
The only reasons I can think of why that would ever not work:
You're otherwise manipulating the loop variable inside the loop. But I think that's terrible programming style and anyone who's doing that deserves to have their script break. ;)
You have a condition that depends on some other variable that can change within the loop, e.g. for ($i = 0; $i -lt $j; $i++) where $j can change within the loop.

Detect number of processes running with the same name

Any ideas of how to write a function that returns the number of instances of a process is running?
Perhaps something like this?
function numInstances([string]$process)
{
$i = 0
while(<we can get a new process with name $process>)
{
$i++
}
return $i
}
Edit: Started to write a function... It works for a single instance but it goes into an infinite loop if more than one instance is running:
function numInstances([string]$process)
{
$i = 0
$ids = #()
while(((get-process $process) | where {$ids -notcontains $_.ID}) -ne $null)
{
$ids += (get-process $process).ID
$i++
}
return $i
}
function numInstances([string]$process)
{
#(get-process -ea silentlycontinue $process).count
}
EDIT: added the silently continue and array cast to work with zero and one processes.
This works for me:
function numInstances([string]$process)
{
#(Get-Process $process -ErrorAction 0).Count
}
# 0
numInstances notepad
# 1
Start-Process notepad
numInstances notepad
# many
Start-Process notepad
numInstances notepad
Output:
0
1
2
Though it is simple there are two important points in this solution: 1) use -ErrorAction 0 (0 is the same as SilentlyContinue), so that it works well when there are no specified processes; 2) use the array operator #() so that it works when there is a single process instance.
Its much easier to use the built-in cmdlet group-object:
get-process | Group-Object -Property ProcessName
There is a nice one-liner : (ps).count
(Get-Process | Where-Object {$_.Name -eq 'Chrome'}).count
This will return you the number of processes with same name running. you can add filters to further format your data.