Loops are a good example that iterations do not have to be boring. They repeat particular PowerShell statements with the pipeline being one of the areas where you can benefit from loops. Most PowerShell commands wrap their results in arrays, and you'll need a loop when you want to examine single elements in an array more closely.
Topics Covered:
ForEach-Object
The PowerShell pipeline works like an assembly line. Each command is tied to the next and hands over its result to the following command, pretty much like assembly line robots. So, the results from the initial command will be processed by all other commands in real time. If you'd like to look more closely at these objects, you'll need the ForEach-Object cmdlet. It executes the code that you specify after it for every object that is guided through the pipeline. This is one of the most important ways to acquire native PowerShell objects. At the same time, it's the simplest form of a loop.
Evaluating Pipeline Objects Separately
If you use Get-WmiObject to retrieve all information about all running services, Get-WmiObject will acquire the services as objects and direct them through the pipeline. Normally, PowerShell converts these objects into text when they reach the end of the pipeline; at most, you could format the output by using the formatting cmdlets described in Chapter 5:
Get-WmiObject Win32_Service |
Format-Table Name, StartMode, PathName
Name StartMode PathName
---- --------- --------
AeLookupSvc Auto C:\Windows\system32\
svchost.ex...
AgereModemAudio Auto C:\Windows\system32\
agrsmsvc.exe
ALG Manual C:\Windows\System32\
alg.exe
Appinfo Manual C:\Windows\system32\
svchost.ex...
AppMgmt Manual C:\Windows\system32\
svchost.ex...
Ati External Event Utility Auto C:\Windows\system32\
Ati2evxx.exe
AudioEndpointBuilder Auto C:\Windows\System32\
svchost.ex...
Audiosrv Auto C:\Windows\System32\
svchost.ex...
Automatic LiveUpdate... Auto "C:\Program Files\
Symantec\Liv...
(...)
ForEach-Object gives you more options. It enables you to access all the properties and methods of each object. The ForEach-Object cmdlet executes a block of statements for every single object in a pipeline. Automatic variable $_ contains the current pipeline object.
Get-WmiObject Win32_Service |
ForEach-Object { "{0} ({1}): Path: {2}" `
-f $_.Name, $_.StartMode, $_.PathName }
AeLookupSvc (Auto): Path: C:\Windows\system32\svchost.exe -k netsvcs
AgereModemAudio (Auto): Path: C:\Windows\system32\agrsmsvc.exe
ALG (Manual): Path: C:\Windows\System32\alg.exe
Appinfo (Manual): Path: C:\Windows\system32\svchost.exe -k netsvcs
AppMgmt (Manual): Path: C:\Windows\system32\svchost.exe -k netsvcs
(...)
Integrating Conditions
In the script block after ForEach-Object, all PowerShell commands and statements are permitted, so you could output only running services along with their descriptions:
Get-WmiObject Win32_Service |
ForEach-Object {
if ($_.Started) {
"{0}({1}) = {2}" -f $_.Caption, $_.Name, $_.Description
}
}
Windows Audio Endpoint Builder = Manages audio devices
for the Windows Audio service. If this service is stopped,
audio devices and effects will not function properly. If
this service is disabled, any services that explicitly
depend on it will no longer start.
Windows-Audio(Audiosrv) = Manages audio devices for
Windows-based programs. If this service is stopped, audio
devices and effects will not function properly. If this
service is disabled, any services that explicitly depend
on it will fail to start.
Base Filtering Engine (BFE) = The Base Filtering Engine
is a service that manages firewall and Internet Protocol
security (Ipsec) policies and implements user mode
filtering. Stopping or disabling the BFE service will
significantly reduce the security of the system. It will
also result in unpredictable behavior in IPsec management
and firewall applications.
Remember the building-block principle of the pipeline and keep it simple and modular! Although it is permitted to specify conditions and complex instructions in the script block after ForEach-Object, the pipeline will be easier to read and more flexible if you sub-divide each task into separate steps and use the Where-Object cmdlet described in Chapter 7 as a condition:
Get-WmiObject Win32_Service |
Where-Object { $_.Started -eq $true } |
ForEach-Object {"{0}({1}) = {2}" -f `
$_.Caption, $_.Name, $_.Description}
Don't forget the conditions covered in Chapter 7: they must result in $true or $false—that's the only requirement. If a variable already contains $true or $false, its result can be used immediately. So, it doesn't matter at all whether you formulate $_.Started -eq $true as a condition or the shorter $_.Started, because in either case, the result will be either $true or $false.
Because the Where-Object and ForEach-Object building blocks are often used in practice, you can use aliases: "?" stands for Where-Object and "%" stands for ForEach-Object. This won't make the lines more readable, but they'll be shorter and easier to enter:
Get-WmiObject Win32_Service | ? { $_.Started } | % {
"{0}({1}) = {2}"-f $_.Caption, $_.Name, $_.Description }
Invoking Methods
Because ForEach-Object gives you access to each object in a pipeline, you can invoke the methods of these objects. In Chapter 7, you already learned how to take advantage of this to close all instances of the Notepad.
Get-Process notepad | ForEach-Object { $_.Kill() }
However, this instruction closes all processes called notepad, even the Notepads that you had opened much earlier. Because PowerShell always works with objects, and because you have access to all object properties and methods within the scope of the ForEach-Object cmdlet, you could select just some of them. For example, you could stop only those Notepad processes that haven't been running for longer than three minutes. How can you find out how long a process has already been running?
Notepad
$process = @(Get-Process notepad)[0]
$process.StartTime
Sunday, March 8, 2009 08:17:27
The time difference between the current and the start time is calculated by the New-TimeSpan cmdlet:
New-TimeSpan $process.StartTime (Get-Date)
Days : 0
Hours : 0
Minutes : 3
Seconds : 7
Milliseconds : 766
Ticks : 1877660000
TotalDays : 0,00217321759259259
TotalHours : 0,0521572222222222
TotalMinutes : 3,12943333333333
TotalSeconds : 187,766
TotalMilliseconds : 187766
And that's how the command line could look that ends all processes called Notepad that have not been running for longer than three minutes:
Get-Process notepad | ForEach-Object {
$time = (New-TimeSpan $_.StartTime (Get-Date)).TotalSeconds;
if ($time -lt 180) {
"Stop process $($_.id) after $time seconds...";
$_.Kill()
}
else {
"Process $($_.id) has been running for " +
"$time seconds and have not be stopped."
}
}
These lines function extremely well, but are somewhat unclear. The ForEach-Object loop contains a condition. That's actually where Where-Object can come in:
Get-Process notepad |
Where-Object {
$time = (New-TimeSpan $_.StartTime (Get-Date)).TotalSeconds;
($time -lt 180)
} |
ForEach-Object {
"Stop process $($_.id) after $time seconds...";
$_.Kill()
}
This works, too. Now, while you have separated condition and loop, you have been confronted with a disadvantage of Where-Object: this cmdlet allows only those objects to pass that match your condition. All the others will quietly vanish. That's why this approach doesn't have any option to output a notification about processes that have already been running for a longer period of time and have not been stopped. Perhaps you still remember from Chapter 7 that Switch combines the features of a loop and a condition. If you need both, Switch can be a useful solution:
Switch (Get-Process notepad) {
{
$time = (New-TimeSpan $_.StartTime (Get-Date)).TotalSeconds;
$time -le 180
}
{
"Stop process $($_.id) after $time seconds...";
$_.Kill()
}
default {"Process $($_.id) has been running for some time and will not be stopped."}
}
Foreach
Aside from ForEach-Object, PowerShell also comes with the Foreach statement. At first glance, both appear to work nearly identically. While ForEach-Object obtains its entries from the pipeline, the Foreach statement iterates over a collection of objects:
Dir C:\ | ForEach-Object { $_.name }
Foreach ($element in Dir C:\) { $element.name }
And here is precisely the basic difference between them. ForEach-Object works best in a pipeline, where each result is returned by the preceding command in real time. Foreach can only process objects that are already completely available. Foreach blocks PowerShell until all results are available; for complex commands that can take a very long time. Foreach processes the objects only after Dir has retrieved them:
Foreach ($element in Dir C:\ -recurse) { $element.name }
Now you won't see anything at all for a long time—at most a few strange error messages. The reason is that you have assigned Dir the task of recursively retrieving the directory listing for the entire C:\ drive, and that can take some time. The error messages that may appear come from directories for which you have no read rights. The Foreach loop cannot go into action until the Dir result is completely available.
The PowerShell pipeline does a better job. In it, Dir gets its results one at a time so that ForEach-Object can already work through them while Dir is still performing its task. This means that there's no delay, and everything proceeds in real time. In other words, ForEach-Object processes the results of Dir while Dir returns them:
Dir C:\ -recurse | ForEach-Object { $_.name }
What are the special strengths of Foreach? It is the better choice whenever the results that you want to evaluate are already completely available, such as in a variable, because it is considerably quicker.
Let's read all elements of an array using a Foreach loop.
$array = 3,6,"Hello",12
Foreach ($element in $array) {"Current element: $element"}
Current element: 3
Current element: 6
Current element: Hello
Current element: 12
ForEach-Object and the pipeline could also iterate through an array:
$array = 3,6,"Hello",12
$array | ForEach-Object { "Current element: $_" }
But Foreach is significantly quicker. You can find out how dramatic the time advantage is by using Measure-Command cmdlet:
(Measure-Command {
$array | ForEach-Object { "Current element: $_" }
}).totalmilliseconds
2.8
(Measure-Command {
Foreach ($element in $array) {"Current element: $element"}
}).totalmilliseconds
0.2
If the objects are already in a variable, it's more than 10 times faster to use Foreach to evaluate them directly than to drive them through the pipeline.
The following rules can be deduced:
- ForEach-Object: If you have to acquire the results first, and if this acquisition lasts longer than a few milliseconds, then use ForEach-Object and the pipeline so that you won't have to wait for long periods of time and the results are processed immediately where they are available.
- Foreach: If you have the results already available in a variable or if their acquisition is very fast, then use Foreach because of its speed advantage.
Foreach functions in principle with any kind of collection. For example, you could use Dir to obtain a directory listing and then use Foreach to further process each file and directory:
Foreach ($entry in dir c:\) {
"File $($entry.name) is $($entry.length) bytes large"
"File {0} is {1} bytes large" -f $entry.name, $entry.length
}
File autoexec.bat is 24 bytes large
File autoexec.bat is 24 bytes large
File BOOTSECT.BAK is 8192 bytes large
File BOOTSECT.BAK is 8192 bytes large
(...)
The Foreach loop can also handle empty collections and even objects that aren't even collections. If Dir doesn't retrieve any files at all, the loop won't run a single time. If Dir returns just one file, then Foreach will automatically recognize that this is one single object, and it will run the loop exactly one time.
You could just as well have used the Get-WmiObject cmdlet to look for instances of a WMI class and had it retrieve all running services on your system. Foreach would then examine each of the services and generate a list with the general service names, as well as the localized service names:
$services = Get-WmiObject Win32_Service
Foreach ($service in $services) { $service.Name +
" = " + $service.Caption }
AeLookupSvc = Application Experience Lookup
AgereModemAudio = Agere Modem Call Progress Audio
ALG = Application Layer Gateway Service
Appinfo = Application Information
(...)
That, however, pushes things to the limit because Get-WmiObject may require several seconds in some circumstances. It would probably be better for you to use ForEach-Object.
In principle, Foreach requires only a collection of objects. Such collections, when you look closely, are widely distributed. The Resolve-Path cmdlet uses wildcards to change a path specification to an array with all actual paths matching the wildcard characters. The next example lists all the text files in your user profile:
Resolve-Path -Path "$home\*.txt"
Path
----
C:\Users\Tobias Weltner\output.txt
C:\Users\Tobias Weltner\cmdlet.txt
C:\Users\Tobias Weltner\error.txt
C:\Users\Tobias Weltner\list.txt
C:\Users\Tobias Weltner\snapshot.txt
The Foreach loop could now go through the result of Resolve-Path and open every single file it found in the Notepad:
function open-editor ([string]$path="$home\*.txt") {
$list = Resolve-Path -Path $path
Foreach ($file in $list) {
"Open File $file..."
notepad $file
}
}
This line would then open all log files in your Windows subdirectory in the Notepad:
open-editor $env:windir\*.log
Now and then, commands like Dir (or Get-Childitem) retrieve several different object types, FileInfo objects for files and DirectoryInfo objects for directories. That doesn't matter to Foreach: every time a loop cycle is completed, Foreach will get an object until all objects are processed. However, it should matter a little to you, and so you could use a condition to test whether the retrieved object matches the desired type. The following loop gets different objects depending on whether it is a directory or a file:
Foreach ($entry in dir c:\) {
if ($entry -is [System.IO.FileInfo]) {
"File {0} is {1} bytes large" -f $entry.name, $entry.length
}
elseif ($entry -is [System.IO.DirectoryInfo]) {
"Subdirectory {0} was created on {1:}" -f $entry.name,
$entry.CreationTime
}
}
Documents and Settings subdirectory was created on
08.28.2006 19:15:14
Program Files subdirectory was created on 11.02.2006 12:18:33
Programs subdirectory was created on 08.28.2006 19:15:47
Users subdirectory was created on 11.02.2006 12:18:33
Windows subdirectory was created on 11.02.2006 12:18:34
autoexec.bat file is 24 bytes large
BOOTSECT.BAK file is 8192 bytes large
config.sys file is 10 bytes large
Do and While
Do and While generate endless loops. Endless loops are practical if you don't know exactly how long the loop should iterate. To prevent an endless loop to really run endlessly, you must set additional abort conditions. The loop will end when the conditions are met.
Continuation and Abort Conditions
A typical example of an endless loop is a user query that you want to iterate until the user gives a valid answer. How long that lasts and how often the query will iterate depends on the user and his ability to grasp what you want.
Do {
$input = Read-Host "Your homepage"
} While (!($input -like "www.*.*"))
This loop asks the user for his home page Web address. At the end of the loop after While is the criteria that has to be met so that the loop can be iterated once again. In the example, -like is used to verify whether the input matches the www.*.* pattern. While that's only an approximate verification, usually it suffices. To refine your verification you could also use regular expressions. Both procedures will be explained in detail in Chapter 13.
This loop is supposed to iterate only if the input is false. That's why "!" is used to simply invert the result of the condition. The loop will then be iterated until the input does not match a Web address.
In this type of endless loop, verification of the loop criteria doesn't take place until the end. The loop will go through its iteration at least once, because before you can check the criteria, you have to query the user at least once.
However, there are also cases in which the criteria is supposed to be verified at the beginning and not at the end of the loop, namely whenever there are certain conditions when the loop must not go through any iteration. An example could be a text file that you want to read one line at a time. The file could be empty and the loop should check before its first iteration whether there's anything at all to read. To accomplish this, just put the While statement and its criteria at the beginning of the loop (and leave out Do, which is no longer of any use):
$file = [system.io.file]::OpenText("C:\autoexec.bat")
While (!($file.EndOfStream)) {
$file.ReadLine()
}
$file.close
Using Variables as Continuation Criteria
The fact is that the continuation criteria after While works like a simple switch. If the expression is $true, then the loop will be iterated; if it is $false, then it won't. Conditions are therefore not obligatory, but just simply provide the required $true or $false. You could just as well have presented the loop with a variable as criteria as long as the variable contained $true or $false.
In such a way, you could have verified the criteria in the loop as well and stored the result in a variable. Then you could have used the verification result in the loop and output an explanatory text when the user gave false input so that he would know why he was being queried a second time:
Do {
$input = Read-Host "Your Homepage"
if ($input -like "www.*.*") {
$furtherquery = $false
} else {
Write-Host -Fore "Red" "Please give a valid web address."
$furtherquery = $true
}
} While ($furtherquery)
Your Homepage: hjkh
Please give a valid web address.
Your Homepage: www.powershell.com
Endless Loops without Continuation Criteria
In extreme cases, you should not use any continuation criteria at all but simply type the fixed value $true after While. The loop will then become a genuinely endless loop, which from then on will no longer stop on its own. Of course, that makes sense only if you exit the loop in some other way. The break statement makes that possible:
While ($true) {
$input = Read-Host "Your homepage"
if ($input -like "www.*.*") {
break
} else {
Write-Host -Fore "Red" "Please give a valid web address."
}
}
Your homepage: hjkh
Please give a valid web address.
Your homepage: www.powershell.com
For
If you know exactly how often you want to iterate a particular code segment, then use the For loop. For loops are counting loops, and when the loop is iterated often enough, it will end its iterations automatically. To define the number of iterations, specify the number at which the loop begins and at which number it will end, as well as which increments will be used for counting. The following loop will retrieve exactly seven lottery numbers for you. It begins counting at 0, counts until the value is less than seven, and increases the value by one with every new iteration.
$random = New-Object system.random
For ($i=0; $i -lt 7; $i++) {
$random.next(1,49)
}
32
29
44
43
6
38
9
For Loops: Just Special Types of the While Loop
If you take a closer look at the For loop, you'll quickly notice that it is actually only a specialized form of the While loop. The For loop, in contrast to the While loop, evaluates not only one but three expressions:
- Initialization: The first expression is evaluated when the loop begins.
- Continuation criteria: The second expression is evaluated before every iteration. It basically corresponds to the continuation criteria of the While loop. If this expression is $true, the loop will iterate.
- Increment: The third expression is likewise re-evaluated with every looping, but it is not responsible for iterating. Be careful: this expression cannot generate output.
These three expressions are used to initialize a control variable, to verify whether a final value is achieved, and to change a control variable with a particular increment at every iteration of the loop. Of course, it is entirely up to you whether you want to use the For loop solely for this purpose.
A For loop can become a While loop if you ignore the first and the third expression and only use the second expression, the continuation criteria:
$i = 0
While ($i -lt 5) {
$i++
$i
}
1
2
3
4
5
$i = 0
For (;$i -lt 5;) {
$i++
$i
}
1
2
3
4
5
Unusual Uses for the For Loop
Of course, it might have been preferable in this case to use the While loop right from the beginning. It certainly makes more sense not to ignore the other two expressions of the For loop, but to use them for other purposes. The first expression of the For loop can be used in general for initialization tasks. The third expression could set the increment of a control variable as well as perform different tasks in the loop. You could also use it, in fact, in the user query example we just had:
For ($input=""; !($input -like "www.*.*");
$input = Read-Host "Your homepage") {
Write-Host -fore "Red" " Please give a valid web address."
}
In the first expression, the $input variable is set to an empty string. The second expression checks whether a valid Web address is in $input, and if it is, it uses "!" to invert the result so that it is $true if an invalid Web address is in $input. In this case, the loop is iterated. In the third expression, the user is queried for a Web address. Really nothing more needs to be in the loop. In the example, an explanatory text is output.
In addition, the line-by-line reading of a text file can be implemented by a For loop with less code:
For ($file = [system.io.file]::OpenText("C:\autoexec.bat");
!($file.EndOfStream); $line = $file.ReadLine())
{
$line
}
$file.close()
REM Dummy file for NTVDM
In this example, the first expression of the loop opened the file so it could be read. In the second expression, a check is made whether the end of the file has been reached. The "!" operator inverts the result again so that it returns $true if the end of the file hasn't been reached yet so that the loop will iterate in this case. The third expression reads a line from the file. The read line is then output in the loop.
The third expression of the For loop is executed before every loop cycle. In the example, the current line from the text file is read. This third expression is always executed invisibly; that means you can't use it to output any text. So, the contents of the line are output within the loop.
Switch
Do you still remember the Switch statement discussed in Chapter 7? Switch is not only a condition but also functions like a loop. That makes Switch one of the most powerful statements in PowerShell. Switch works almost exactly like the Foreach loop. Moreover, it can evaluate conditions. For a demonstration, take a look at the following simple Foreach loop:
$array = 1..5
Foreach ($element in $array)
{
"Current element: $element"
}
Current element: 1
Current element: 2
Current element: 3
Current element: 4
Current element: 5
If you used Switch, this loop would look like this:
$array = 1..5
Switch ($array)
{
Default { "Current element: $_" }
}
Current element: 1
Current element: 2
Current element: 3
Current element: 4
Current element: 5
The control variable that returns the current element of the array for every loop cycle cannot be named for Switch, as it can for Foreach, but is always called $_. The external part of the loop functions in exactly the same way. Inside the loop, there's an additional difference: while Foreach always executes the same code every time the loop cycles, Switch can utilize conditions to execute optionally different code for every loop. In the simplest case, the Switch loop contains only the default statement. The code that is to be executed follows it in braces.
That means Foreach is the right choice if you want to execute exactly the same statements for every loop cycle anyway. On the other hand, if you'd like to process each element of an array according to its contents, it would be preferable to use Switch:
$array = 1..5
Switch ($array)
{
1 { "The number 1" }
{$_ -lt 3} { "$_ is less than 3" }
{$_ % 2} { "$_ is odd" }
Default { "$_ is even" }
}
The number 1
1 is less than 3
1 is odd
2 is less than 3
3 is odd
4 is even
5 is odd
If you're wondering why Switch returned this result, take a look at Chapter 7 where you'll find an explanation of how Switch evaluates conditions. What's important here is the other, loop-like aspect of Switch.
Processing File Contents Line by Line
If you need conditions in your loop, Switch is a clever alternative to Foreach. The same thing is true when you want to process text files, because if you wish Switch will treat a text file like an array and the lines it contains like elements in the array. This means that you don't have to worry about how to open files for reading; you can just leave that up to Switch.
For example, an interesting text file is windowsupdate.log in the Windows subdirectory because it records all updates of the operating system. Because the system often has exclusive access to this file, the following code copies the file and then uses Switch to output its contents line by line. Afterwards, the copy is deleted:
Copy-Item $env:windir\windowsupdate.log example.log
Switch -file example.log
{
Default { "read: $_" }
}
Remove-Item example.log
Switch is really too sophisticated a tool for just opening a text file and outputting its contents line by line. If all you were interested in was the entire text content of the file, you could have output it more easily:
Get-Content $env:windir\windowsupdate.log
The strength of Switch lies in its ability to evaluate single lines of a text file and then output only particular data. Because this is really a case for regular expressions, you'll find numerous examples in Chapter 13.
Exiting Loops Early
You can exit all loops by using the Break statement, which gives you the additional option of defining additional stop criteria in the loop. The following is a little example that asks for a password and then uses Break to exit the loop as soon as the password "secret" is entered.
While ($true)
{
$password = Read-Host "Enter password"
If ($password -eq "secret") {break}
}
The Break statement is actually unnecessary in this loop because you could have also stopped the loop by using the usual continuation criteria. You just have to consider here whether the iteration criteria should be verified at the beginning (and then you'd use While) or at the end (and then Do...While) of the loop:
Do
{
$password = Read-Host "Enter password"
} While ($password -ne "secret")
It would make more sense to use Break in For loops, because in For loops you can optimally combine the unscheduled Break with the scheduled iteration criteria of the loop. Perhaps you'd like to give users just three tries at entering a correct password. The following loop asks for a password a maximum three times, but can, thanks to Break, be exited earlier when the correct password is entered:
For ($i=0; $i -lt 3; $i++)
{
$password = Read-Host "Enter password ($i. try)"
If ($password -eq "secret") {break}
}
But the For loop would not only give up after a maximum three tries, but would also grant you access even without the right password. To prevent that from happening, after the third unsuccessful try, you should trigger an error:
For ($i=1; $i -lt 4; $i++)
{
$password = Read-Host "Enter password ($i. try)"
If ($password -eq "secret") {break}
If ($i -ge 3) { Throw "The entered password was incorrect." }
}
What you see here is only a very simple password query showing the password in plain text. Secure password queries that have encrypted input will be covered in Chapter 13 in connection with the feature called SecureStrings.
Continue: Skipping Loop Cycles
The Continue statement operates somewhat more mildly than Break, because Continue won't force you to exit the entire loop right away but will only skip the current loop cycle. Let's look at the Foreach loop that cycles through all elements of a collection. In this case, Dir will supply the collection and the collection will hold the contents of a directory. These contents can consist of files and subdirectories; and, as you should know by now, files are represented by a FileInfo and sub-directories by a DirectoryInfo object.
So, when you want to process just files and not directories in the Foreach loop, then you should initially verify the type of the respective object. If the type doesn't match the FileInfo object, provide the Continue statement: the loop will then stop its current cycle immediately and continue with the next element:
Foreach ($entry in Dir $env:windir)
{
If (!($entry -is [System.IO.FileInfo])) { Continue }
"File {0} is {1} bytes large." -f $entry.name, $entry.length
}
Of course, you could have also achieved the same thing if you had used a condition to sub-divide the entire contents of the loop, though usually that is substantially less clear:
Foreach ($entry in Dir $env:windir)
{
If ($entry -is [System.IO.FileInfo]) {
"File {0} is {1} bytes large." -f $entry.name, $entry.length
}
}
Nested Loops and Labels
Loops may be nested within each other. However, if you do nest loops, the question arises of how their Break and Continue statements will behave. Of course, they will behave for the time being the way you expect them to and will always have an effect on the current loop in which they were invoked.
The next example nests two Foreach loops. The first (outer) loop cycles through a field with three WMI class names. The second (inner) loop runs through all instances of the respective WMI class. In this way, you could output all instances of all three WMI classes. The inner loop checks whether the name of the current instance begins with "a"; if not, the inner loop invokes Continue and so skips all instances not beginning with "a." The result is a list of all services, user accounts, and running processes that begin with "a":
Foreach ($wmiclass in "Win32_Service","Win32_UserAccount","Win32_Process")
{
Foreach ($instance in Get-WmiObject $wmiclass) {
If (!(($instance.name.toLower()).StartsWith("a"))) {continue}
"{0}: {1}" -f $instance.__CLASS, $instance.name
}
}
Win32_Service: AeLookupSvc
Win32_Service: AgereModemAudio
Win32_Service: ALG
Win32_Service: Appinfo
Win32_Service: AppMgmt
Win32_Service: Ati External Event Utility
Win32_Service: AudioEndpointBuilder
Win32_Service: Audiosrv
Win32_Service: Automatic LiveUpdate - Scheduler
Win32_UserAccount: Administrator
Win32_Process: Ati2evxx.exe
Win32_Process: audiodg.exe
Win32_Process: Ati2evxx.exe
Win32_Process: AppSvc32.exe
Win32_Process: agrsmsvc.exe
Win32_Process: ATSwpNav.exe
As expected, the Continue statement in the inner loop had an effect on the inner loop in which the statement was contained. But how should you proceed if you'd like to see only the first respective element of all services, user accounts, and processes that begins with "a"? Actually, nearly the exact same way, only in this case Continue would have to have an effect on the outer loop. As soon as an element is found that begins with "a," the outer loop should jump to the next WMI class.
So that statements like Continue or Break know which loop they are supposed to relate to, you should give loops unambiguous names and then specify these names after Continue or Break:
:WMIClasses Foreach ($wmiclass in
"Win32_Service","Win32_UserAccount","Win32_Process") {
:ExamineClasses Foreach ($instance in
Get-WmiObject $wmiclass) {
If (($instance.name.toLower()).StartsWith("a")) {
"{0}: {1}" -f $instance.__CLASS, $instance.name
continue WMIClasses
}
}
}
Win32_Service: AeLookupSvc
Win32_UserAccount: Administrator
Win32_Process: Ati2evxx.exe
Summary
The cmdlet ForEach-Object gives you the option of processing single objects of the PowerShell pipeline, such as to output the data contained in object properties as text or to invoke methods of the object. Foreach is a similar type of loop whose contents do not come from the pipeline, but from an array or a collection.
In addition, there are endless loops that iterate a code block until a particular condition is met. The simplest type of such loops is While, in which continuation criteria are checked at the beginning of the loop. If you want to do the checking at the end of the loop, choose Do...While. The For loop is an extended While loop, because it can count loop cycles and automatically terminate the loop after a designated number of iterations.
This means that For is suited mainly for loops in which counts are to be made or which must complete a set number of iterations. Do...While and While, on the other hand, are suited for loops that have to be iterated as long as the respective situation and running time conditions require it.
Finally, Switch is a combined Foreach loop with integrated conditions so that you can immediately implement different actions independently of the read element. Moreover, Switch can step through the contents of text files line by line and evaluate even log files of substantial size.
All loops can exit ahead of schedule with the help of Break and skip the current loop cycle with the help of Continue. In the case of nested loops, you can assign an unambiguous name to the loops and then use this name to apply Break or Continue to nested loops.
Posted
Mar 08 2009, 06:02 PM
by
ps1