If you work as a consultant for small-to-midsized businesses (SMBs), you know that assessing client networks is a perennial task. In some cases, you might have sources such as Active Directory (AD) for identifying some of the computers on a LAN, but the tried-and-true method for discovering everything that's out there is frequently just testing the addresses on the entire local IP network. This method won't find machines that are turned off or currently away from the network, but it's a handy foundation for live data collection—and, as a bonus, also discovers network appliances that you might not know about. But it's not acceptable to have someone manually enter all the valid addresses for a typical network—typing all those numbers is laborious and error-prone. This is where knowing a few easy tricks with Windows PowerShell will help you out.

PowerShell's range operator (..), combined with pipeline processing, lets you run a task hundreds of times just as easily as running it once. This feature is handy for network administration tasks involving many computers. To show you what I mean, I'm going to demonstrate how to quickly generate an entire family of IP addresses for computers, letting you easily perform data-collection tasks that would otherwise take a lot of effort.

Find the Range
Let's look at a standard Class C private network using the 192.168.1.0 subnet with subnet mask 255.255.255.0. If you prefer Classless Inter-Domain Routing (CIDR) notation, this is 192.168.1.0/24. Legitimate computer addresses on this network are in the range 192.168.1.1 to 192.168.1.254, making a total of 254 valid addresses. Our first step is to generate all of the addresses. PowerShell has special syntax that lets you easily generate a range of numbers. The command

5..10

returns the numbers 5, 6, 7, 8, 9, and 10. We can use this range operator to generate the unique portion of the possible IP addresses, the numbers 1 through 254, like this:

1..254

To get the entire set of possible addresses into a variable called $nodes, we can use the ForEach-Object cmdlet:

$nodes = 1..254 |
  ForEach-Object\{ "192.168.1.$_"\}

You could instead use an alias for ForEach-Object, either ForEach or %:

$nodes = 1..254 |
  %\{ "192.168.1.$_"\}

If you're not familiar with PowerShell's $_ variable, it just means "the current object in the pipeline." ForEach-Object runs the command in braces (\{\}) once for each value in $nodes, substituting the value in place of $_. After running the command, the variable $nodes will contain all IP addresses from 192.168.1.1 through 192.168.1.254. To verify this, enter

$nodes

Hit Complicated Targets
You can create some fairly complex ranges using the range operator. For example, if servers and DHCP clients have network IDs in the range of 1 to 127 and network-attached appliances use numbers from 240 through 254, you can create the entire range of addresses with

$nodes = 1..127 + 240..254 |
  %\{ "192.168.1.$_"\}

This command works because 1..127 and 240..254 both evaluate as arrays, and PowerShell interprets the plus sign (+) as a concatenation operator on arrays.

The range operator can be used in all sorts of other ways that you might not expect. For example, you can use the range operator to select a subset of an array. To show the first 10 elements of the array $nodes, enter the following command:

$nodes\[0..9\]

You can create a range using any symbols that PowerShell can translate into integers. For example, you can generate the numbers from 240 through 255 using hexadecimal values:

0xf0..0xff

You can generate the numbers from 10 through 100 with the scientific notation (e) operator:

1e1..1e2

You can use the PowerShell binary kilo (kb)operator to generate the numbers from 1 through 1,024 by entering

1..1kb

(For information about this operator, see the sidebar "A Common Misconception About PowerShell's kb, mb, and gb Operators.") You can even generate a range of character codes. To generate the character codes for everything from uppercase letter A to lowercase letter z, you'd use

\[char\]'A'..\[char\]'z'

The range values are all numerals, but you can convert them into a character sequence by using ForEach-Object:

\[char\]'A'..\[char\]'z' | %\{\[char\]$_\}

Alternatively, you could cast the output to a character array directly:

\[char\[\]\](\[char\]'A'..\[char\]'z')

You can even generate a range of numbers in decreasing order if you set the second value to a value less than the first one. The statement

3..-1

for instance, generates the sequence 3, 2, 1, 0, -1.

This procedure also gives you a way to reverse an array. You can reverse the array $nodes that you generated earlier by specifying a range starting with the index of the last element (which would be $nodes.count-1) and ending with the index of the first element (0), like this:

$nodes\[($nodes.count-1)..0\]

You can nest ranges to produce some complex collections that otherwise might take a great deal of scripting work. For example, suppose you use several Class C private networks on one location, with network addresses 192.168.2.0 through 192.168.25.0. You can generate the entire set of 6,096 valid IP addresses for all of these networks by using the following statement:

2..25 | %\{$net = $_; 1..254 |
  %\{"192.168.$net.$_"\}\}

2 Restrictions and a Caveat
There are only two basic restrictions on range use. The first restriction is that the numbers in the range must be between -2,147,483,648 and 2,147,483,647, inclusive; this limit is because PowerShell's range operator changes the values into 32-bit .NET integers, which can only take on these values. The second limit is that an individual range can contain no more than 50,001 items. PowerShell documentation says that 50,000 is the item limit, but it's incorrect. You can confirm this by generating the range from 0 through 50,000 and checking the length with

(0..50000).Count

which should return the value 50,001. If you try to generate the range

0..50001

however, you'll get the error message Bad range expression; 50001 is larger than the maximum size of a range (=50000 elements). This limit is hard-coded as a performance measure. When a range is evaluated, every single element is created immediately. Without limits, it would be easy to bog down a machine by telling it to give you a list of 1 million numbers:

1..1e6

However, 50,001 is the item limit only on an individual range; you can generate greater lists of numbers. For example, by using two range statements, you can get all numbers from 0 through 65,535:

0..40000 + 40001..65535

One word of caution about using the range operator: It's a syntactic sweetener designed to speed up your use of PowerShell and make it easier to understand PowerShell code in some circumstances. Syntactic sweeteners can cause confusing behavior in certain situations. A good example of this is what happens when you combine ranges with syntactic sweeteners designed for accessing array elements.

Arrays in PowerShell are 0-based—meaning that the index of the first element is 0, the index of the second element is 1, and so on. An array element can never have a negative index. Since negative numbers aren't used otherwise, PowerShell lets you use them to count from the end of the array. When used as if it were an index, the numeral -1 refers to the last element of an array, -2 refers to the next-to-last element of an array, and so on. This is easiest to show with an example. PowerShell treats strings as arrays of characters. Suppose you have a variable, $s, containing the string C:\Windows\. You want to inspect the last character and trim it from the string if it's a backslash (\). You can enter this command to look at the final character:

$s\[-1\]

Because this character is a backslash, we need to extract all characters except the last one. You can do this with

$s\[0..($s.length-2)\]

However, if you're thinking about the fact that you can use a negative number for an array element, it seems instinctive to try this instead:

$s\[0..-2\]

This procedure won't work. PowerShell examines the range first, and 0..-2 means the numbers from 0 to -2, inclusive. So you get back $s\[0\] (the letter C), followed by $s\[-1\] (the final \), and $s\[-2\] (the next-to-last character in $s, the letter s).

Hit the Bull's-eye
You probably won't need to worry about some of these more complicated cases very often. But if you learn the tricks to using PowerShell's range operator, you'll find it an invaluable tool for generating complex collections of values that can help you automate difficult network administration tasks. Procedures such as reversing arrays, combining ranges, and nesting ranges, combined with PowerShell's pipelining feature, will have you hitting the bull's-eye every time.