PowerShell: break nested loops - loops

There should be a break command in PowerShell that can exit nested loops by assigning a label. Just it doesn't work. Here's my code:
$timestampServers = #(
"http://timestamp.verisign.com/scripts/timstamp.dll",
"http://timestamp.comodoca.com/authenticode",
"http://timestamp.globalsign.com/scripts/timstamp.dll",
"http://www.startssl.com/timestamp"
)
:outer for ($retry = 2; $retry -gt 0; $retry--)
{
Write-Host retry $retry
foreach ($timestampServer in $timestampServers)
{
Write-Host timestampServer $timestampServer
& $signtoolBin sign /f $keyFile /p "$password" /t $timestampServer $file
if ($?)
{
Write-Host OK
break :outer
}
}
}
if ($retry -eq 0)
{
WaitError "Digitally signing failed"
exit 1
}
It prints the following:
retry 2
timestampServer http://timestamp.verisign.com/scripts/timstamp.dll
Done Adding Additional Store
Successfully signed and timestamped: C:\myfile.dll
OK
retry 1
timestampServer http://timestamp.verisign.com/scripts/timstamp.dll
Done Adding Additional Store
Successfully signed and timestamped: C:\myfile.dll
OK
ERROR: Digitally signing failed
What have I done wrong?
Can I have goto and labels, please?
Using Windows 7 and I guess PS 2.0. This script is supposed to run on PS 2 at least.

You do not add the colon when using break with a loop label. This line:
break :outer
should be written like this:
break outer
For a further demonstration, consider this simple script:
:loop while ($true)
{
while ($true)
{
break :loop
}
}
When executed, it will run forever without breaking. This script however:
:loop while ($true)
{
while ($true)
{
break loop
}
}
exits as it should because I changed break :loop to break loop.

So, I changed the code a bit to make it clear
$timestampServers = #(
"http://timestamp.verisign.com/scripts/timstamp.dll",
"http://timestamp.comodoca.com/authenticode",
"http://timestamp.globalsign.com/scripts/timstamp.dll",
"http://www.startssl.com/timestamp"
)
:outer for ($retry = 2; $retry -gt 0; $retry--)
{
Write-Host retry $retry
foreach ($timestampServer in $timestampServers)
{
Write-Host timestampServer $timestampServer
#& $signtoolBin sign /f $keyFile /p "$password" /t $timestampServer $file
if ($true)
{
break :outer
Write-Host OK
}
}
}
if ($retry -eq 0)
{
Write-Error "Digitally signing failed" ## you have a typo there
exit 1
}
This produces the following:
retry 2
timestampServer http://timestamp.verisign.com/scripts/timstamp.dll
retry 1
timestampServer http://timestamp.verisign.com/scripts/timstamp.dll
C:\temp\t.ps1 : Digitally signing failed
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,t.ps1
So, skips Write-Host OK, but also seems to continue to loop. In other words, it acts like 'Continue' statement.
Changed it like the folks mentioned to remove ':', although PowerShell documentation does not exclude it:
if ($true)
{
break outer
Write-Host OK
}
I get the correct behavior.
retry 2
timestampServer http://timestamp.verisign.com/scripts/timstamp.dll
Long story short... do not use ':'

Related

How to continue a loop using PowerShell?

I want to continue my looping. But it does not continue once the first process not found.
This is my code.
function Capture
{
##some process##
Write-Host "Capture"
Start-Sleep -s 1
}
function error
{
##some process##
write-host "error function"
write-host "error function2"
write-host "error function3"
Capture
Exit
}
String = #('The Student Name is','Data Member')
try
{
foreach ($str in $String )
{
$Bio = Get-Content ".\DUMP1.log" -ErrorAction Stop | Where-Object {$_ -like "*$str*"}
if ($null -eq $Bio)
{
Write-Host "not found"
error
continue
}
return $Bio
}
}
catch
{
$msg = "Bio Not Found`n" + $_
$ExitCode = "133"
Exit $ExitCode
}
If the first array not exist in the DUMP.log file, then it stop the process, it does not continue to check the second array.
My expectation is, once the first array can not found, then check the second array, after all the array checking not found, then the process will continue to error function. But The process will not go to error function as long as the process still not checking all the array.
Anyone can help please. Thank you

If-else inside a foreach doesn't return what I think it should

I have an array of servers that I need to loop through, and have an attribute assigned only to specific servers (this happens later in the script).
Here's the array:
$test = #('test_dc','test_fp','test_ts','test_ap')
In the array, I have a domain controller, file/print, terminal server, and application servers (in that order).
The only servers that should get the attribute are the fp, ts, and ap servers.
Here's what I've tried thus far:
foreach ($item in $test) {
Write-Host $item "`n"
Write-Host "Start IF here `n"
if ($item.Name -like '*fp*') {
Write-Host "Found $item"
} else {
Write-Host "ELSE `n"
Write-Host '-=-=-=-=-'
}
}
Here's the output from that:
PS C:\Users\me\Desktop> .\scratch.ps1
test_dc
Start IF here
ELSE
-=-=-=-=-
test_fp
Start IF here
ELSE
-=-=-=-=-
test_ts
Start IF here
ELSE
-=-=-=-=-
test_ap
Start IF here
ELSE
-=-=-=-=-
PS C:\Users\me\Desktop>
According to the way I think it should work, I should see this:
...
test_fp
Found test_fp
...
I've also tried this:
if ($test -contains '*fp') {
Write-Host "Found $_"
} else {
Write-Host 'end'
}
and I just get an empty line.
You're seeing extra info being written to host as you have indefinite writes for each item, regardless if it matches. Since you're including the else statement as well, you're going to see extra stuff written for the non-matched items. Your foreach loop also specifies the name attribute for the object, while your $test array only contains strings.
Here's what I updated it to to limit to only write the host name in your loop if it matches *fp*, otherwise writing your divider:
$test = #('test_dc','test_fp','test_ts','test_ap')
foreach ($item in $test) {
if ($item -like '*fp*') {
Write-Host "Found $item"
} else {
Write-Host '-=-=-=-=-'
}
}
Running that will output this:
-=-=-=-=-
Found test_fp
-=-=-=-=-
-=-=-=-=-

Do..Until on a never ending loop

I have written a Do..Until statement to check whether a file exists. If the file is not there, it waits a couple seconds and checks again.
It is supposed to end when the file appears. I have tested by running the script without the file and then adding it in the folder as the script is running.
Instead of ending, it continues to loop endlessly. Can anyone see what I have missed?
$path = test-path "C:\Temp\test.txt"
do {
if (!($path)) {
Write-Host "Not here yet..."
Start-Sleep -s 3
}
} until($path)
Write-Host "Files here now"
Write-Host "Press any key to continue ..."
$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
The Test-Path statement is evaluated in the line
$path = Test-Path "C:\Temp\test.txt"
After that the variable $path contains the boolean result of the evaluation. Put the actual check in your conditions:
$path = "C:\Temp\test.txt"
do {
if (-not (Test-Path $path)) {
Write-Host "Not here yet..."
Start-Sleep -s 3
}
} until (Test-Path $path)
or define it as a function that you call in your conditions:
function Test-File {
Test-Path "C:\Temp\test.txt"
}
do {
if (-not (Test-File)) {
Write-Host "Not here yet..."
Start-Sleep -s 3
}
} until (Test-File)
OK, figured it out 3 minutes after posting this (and an hour before that of frustration!).
I needed to put the variable INSIDE the Do..Until statement. Like so:
do{
$path = test-path "C:\Temp\test.txt"
if (!($path))
{Write-Host "Not here yet..."
start-sleep -s 3}
}
until($path)
Write-Host "Files here now"
Write-Host "Press any key to continue ..."
$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

Powershell override command

I'm really new to powershell (I've used javascript a bit before), and I was wondering if there's a way to have a powershell script continue running though a loop until a user inputs a specific command to override the loop and break out of it? I've been able to pause the program to wait for a response, but I need the loop to continue running until someone enters a string such as "end." For example, in the following bit of code I wanted to break the while loop and complete a specific command if the user typed "quit," "charger on," or "charger off" at any point.
$overrideProgram = "null"
$overrideProgram = Read-Host
while ($overrideProgram -ne "quit",$overrideProgram -ne "charger on",$overrideProgram -ne "charger off") {
while ($true) {
$chargeLevel = (Get-WmiObject win32_battery).estimatedChargeRemaining #defining the variable "chargeLevel" as the charge percentage
if ($chargeLevel -le 40) {chargerOn}
if ($chargeLevel -ge 80) {chargerOff}
Start-Sleep -s 30 # 30 second delay before next check
}
}
Thanks so much.
Best way I can think to do this is to start a background job and use a script to control the termination of that job:
$job = start-job -scriptblock {
while ($true) {
$chargeLevel = (Get-WmiObject win32_battery).estimatedChargeRemaining #defining the variable "chargeLevel" as the charge percentage
if ($chargeLevel -le 40) {chargerOff}
if ($chargeLevel -ge 80) {chargerOn}
Start-Sleep -s 30 # 30 second delay before next check
}
}
Write-Host "Background job started with id: $($job.id)"
$run = $true
while ($run) {
$op = Read-Host -Prompt "What ya wanna do?"
switch($op) {
"quit" { $run = $false }
"charger on" { $run = $false }
"charger off" { $run = $false }
default { continue }
}
}
stop-job $job.id
receive-job $job.id # If you want to see the output
remove-job $job.id
In the above, a job is started and then your script will just sit in a loop waiting for the correct input. Once it has that, it will exit the loop and proceed to stop and remove the job that was started to begin with.
You will need to extend the contents of the -scriptblock argument of Start-Job to include the chargerOn and chargerOff definitions. If the script is going to be considerably complex, probably best to save it to a file and use the -FilePath argument of Start-Job

Loop section of script

mode con: cols=52 lines=16
while ($script -ne "Q") {
$tag = ""
while (-not ($tag)) {
$tag = Read-Host 'Enter tag # or Q to quit'
$date = get-date -f MM-dd-yyyy_HH_mm_ss
$username = [Environment]::Username
if(!(Test-Path -path "C:\Users\$username\Desktop\PSTools\Screenshot Dump\")) {
New-Item "C:\Users\$username\Desktop\PSTools\Screenshot Dump\" -type directory
}
}
if ($tag -eq "Q"){break}
cls
#$ErrorActionPreference = 'silentlycontinue'
cd "C:\Users\$username\Desktop\PSTools"
set-alias psexec "C:\Users\$username\Desktop\PSTools\PsExec.exe"
set-alias nircmd "C:\Users\$username\Desktop\PSTools\nircmd.exe"
------>LOOP START HERE
psexec \\$tag -i -c -s nircmd savescreenshotfull "C:\$tag-$date.png"
move "\\$tag\c$\$tag-$date.png" "C:\Users\$username\Desktop\PSTools\Screenshot Dump\$tag-$date.png"
explorer "C:\Users\$username\Desktop\PSTools\Screenshot Dump\$tag-$date.png"
------>LOOP END HERE
"`n"
}
Basically I'm trying to loop where I have stated above, but I'm use to doing it in command prompt and it's not the same. I want to make it loop X amount of times or until I CTRL+C to quit it.
You could also use a Foreach loop.
Let's say you want to loop 5 times :
$NumberOfLoops = 5
Foreach ($loop in (1..$NumberOfLoops)) { Do Loopy Stuff }
You can use a basic FOR statement to loop. Although, PowerShell has some great looping techniques. It's worth a look through...
http://social.technet.microsoft.com/wiki/contents/articles/4542.powershell-loops.aspx
For an example of a basic FOR loop, just do as so (will loop 10 times ($x=0-9)):
FOR ($x=0; $x -lt 10; $x++) { DO LOOPY STUFF };

Resources