Nuts and Bolts More PowerShell Lead image: © Michael Mihin,
© Michael Mihin,

Keeping PowerShell in the loop


PowerShell's ability to use loops extends its reach to remote systems and lets you perform repetitive operations with ease. By Ken Hess

Gathering information from a local system or a single remote system is all well and good, but often you need information from several systems at once. PowerShell can help. All you need is a little patience and light scripting skills. PowerShell versions 1.0 and 2.0 have a somewhat limited ability to grab information from remote systems singly or in bulk. Therefore, you have to work around those limitations to create automation tools that "trick" PowerShell into performing actions on remote systems as if they were operating on a local host.

In the previous issue [1], I talked about starting PowerShell, running some basic Get commands, and using the Help system. Then, I showed you how to control services, processes, and commands on local and remote systems. In this article, you'll discover the power of loops to automate repetitive processes.

PowerShell [2] has a Get-Content cmdlet that reads a text file into the buffer one line at a time. This feature is especially handy for use in loops to extract the same information from several systems within a single script.

Consider the following comparatively simple Linux Bash script that reads in a text file (systems.txt) one line at a time, performs an operation (echoes the system name) on each line, then exits. The file is a list of systems.

while read system
echo $system
done < systems.txt

The equivalent PowerShell script is:

ForEach ($system in Get-Content "systems.txt")
Write-Host $system

Both scripts read the systems.txt file line-by-line from top to bottom and perform the specified function until there are no more entries in the text file. Loops allow you to automate repetitive processes that are too tedious even for the newest of newbie in your group. Automation gives you the opportunity to improve the efficiency of your environment and to streamline your daily tasks.

A good script might take a few hours to create and to test, but those hours spread over a year's time are well worth your effort. If you're like many other administrators, you don't love scripting; however, you can use public domain or community-donated scripts and customize them to fit your requirements. Chances are very good that someone else has already solved the problem facing you. Leverage their work for your needs.

Scripting Tips for PowerShell

Although PowerShell is easy to learn and use, you should always document your scripts – in the scripts themselves. Describe what a script does and why you wrote it. Date and version your scripts to keep track of their usage and revisions.

On your primary workstation, create a scripts folder and keep all of your scripts there. You can separate scripts into subfolders by function, if necessary. Save any redirected output to a different folder on your system (e.g., in a temporary directory), so you don't clutter your scripts folder with output. The same rule applies to input, such as systems lists.

You have to name your PowerShell scripts with a .ps1 extension for the PowerShell interpreter to recognize them as PowerShell. To run a PowerShell script, simply type its name at the PS> prompt preceded by .\. The command

PS> .\admin_script.ps1

means "execute script.ps1 here." If you just type the name, you'll receive a message that the script was not found but does exist in the current location. If you're a Unix or Linux user, you'll recognize the similarity in invoking a shell script on those platforms.

Understanding the Get-Content Cmdlet

To begin, create a text file with Notepad or some other text editor and enter a single line of text into it – for example, Hello, world, this is a text file. Save and name the file; here, I'll use hello.txt.

Use Get-Content to read the file:

PS> Get-Content hello.txt
Hello, world, this is a text file.

Now, that you're excited about that, open the hello.txt file in Notepad again, enter text on one or more additional lines, and issue the Get-Content cmdlet again:

PS> Get-Content hello.txt
Hello, world, this is a text file.
This is line two.
This is line three.
Are you getting the idea?
Line five.

I hope you can see the possibilities the Get-Content cmdlet brings to you. The ability to read one line of text at a time into the buffer has significant implications for automation scripting, and you might already have thought of some ways you can use Get-Content in your own network.

Although it's somewhat interesting to see how you can read a local file, it's not as interesting as being able to read files from a remote host. Get-Content has no remote system hooks, but it does have the advantage of working in a Windows environment, where you can use UNC connections to remote hosts.

For example, you can read a file that's stored on a remote host by using the UNC location of that file. The file's name and location are E:\TEMP\test.txt on XENAPP0.

PS> Get-Content \\XENAPP0\e$\TEMP\test.txt
This is a test file.
It is on a remote system.
Hello from XENAPP0.

Being a system administrator, you have access to those hidden ($DRIVE) shares on remote systems. If you have a consistently located configuration file on your systems, you now have a method of checking the contents of the file. By adding a looping function to a script, you can even check the contents of the file on multiple systems.

Assume that the configuration file config.txt is located in C:\TEMP on your systems. With the following script, you can easily retrieve the contents of that file:

ForEach ($system in Get-Content "systems.txt")
Write-Host $system
Get-Content \\$system\c$\TEMP\config.txt
Write-Host " "

This script reads a system name from systems.txt into the variable $system. It then carries that system name into the script's action section and writes the name to standard output (the screen), reads the remote system's configuration file, and places a blank line after the script content to allow for better visual separation between systems and their configuration files.

The output in Listing 1 is from the three systems in my systems.txt list. This script allows you to note any part of a file that require changes. If you redirect this information to a file, change the Write-Host cmdlet entries to Write-Output so that host information and blank space will also redirect to your file. Notice in Listing 1 that the word consistant should be consistent in the configuration file.

Listing 1: System Configuration Files

PS> .\get_configs.ps1
This is a configuration file.
It contains important parameters for an application.
Its contents must be consistant from system to system.
This is a configuration file.
It contains important parameters for an application.
Its contents must be consistant from system to system.
This is a configuration file.
It contains important parameters for an application.
Its contents must be consistant from system to system.

Manipulating File Content

You can batch correct this problem for all or a subset of your systems by specifying the systems that need correction in your systems.txt file. To accomplish this, consider the script in Listing 2. This script iterates through the systems.txt list, removes the content from the config.txt file (leaving the file intact), adds the correct content into the file from a correct local file, then displays the correct content for each system.

Listing 2: Iterating Through a File List

01 ForEach ($system in Get-Content "systems.txt")
02 {
03 Write-Host $system
04 Clear-Content -path \\$system\c$\TEMP\config.txt
05 Add-Content -value (Get-Content configuration.txt) -path \\$system\c$\TEMP\config.txt
06 Get-Content \\$system\c$\TEMP\config.txt
07 Write-Host " "
08 }

A simpler script that performs the same task uses the Set-Content cmdlet to replace the file's content without having to first use Clear-Content. The Add-Content cmdlet appends to the end of a file any text that you specify, but it will not overwrite any part of the existing text. A good practical example of its use is adding a list of entries into your server's hosts files.

These scripts are perfect for replacing or appending hosts files. You can simply copy your correct local hosts file to all systems in your domain – a task that would have taken hours or days without an automated method.

Exploring Other Uses for PowerShell Loops

If you remember last time, I shared the Invoke-Command cmdlet. This command performs a non-interactive task for you on a remote system and has the following basic syntax:

Invoke-Command { cmd } -ComputerName <NAME>

By adding this command to a loop and using a variable – $system, for example – for the computer name, you have empowered yourself to administer an unlimited number of remote systems. For example, you need to add a new user to your local systems. Ordinarily, you'd have to log on to each system, open Server Manager, add the user manually, and then go to the next system. This script performs the function from your desktop.

ForEach ($system in Get-Content "systems.txt")
Invoke-Command { NET USER John /fullname:"John Smith"/ADD } -ComputerName $system

Save this script to a file with a ps1 extension and run it from within PowerShell. If you need to add other switches or parameters to the NET USER command, you can do so here. There's no difference in running the command in the script or on the remote server's command line.

Likewise, remove user accounts by issuing this command between the Invoke-Command cmdlet brackets:


The response from these scripts is exactly what you see at the command line: The command completed successfully. If you want to add the remote computer's name to the list so you can see which systems failed to add the new user, enter

Write-Host $system

above the Invoke-Command line or use

Write-Output $system

if you redirect the output to a file.

It's safe to use the Write-Output cmdlet in any of your scripts because the output from it displays on your screen and can be redirected to a file.

Practicing PowerShell Scripting: Best Practices

When using PowerShell scripts, you should keep a few key points in mind, although some are just common sense, I feel strongly about them, and they bear repeating here.

Remember: With great PowerShell comes great responsibility.


PowerShell is a good scripting language that has many elements of standard, top-down scripting languages, with the addition of very powerful cmdlets to extend and simplify its capability for automated management. Automation in your network, regardless of its size or its complexity, is absolutely essential for well-maintained systems, and it is also important for preserving your sanity.

Managing hundreds or thousands of disparate systems isn't easy.

You need automation and you need to use PowerShell in your Windows environment. I recommend picking up a book on PowerShell or taking Microsoft's "Automating Administration with Windows PowerShell 2.0" class. Scripting is a basic system administrator skill: Learn it or get left behind with the "old school" administrators who sit around talking about the way they did it in the old days before PowerShell. PowerShell is available and is easy to learn, so use it.

To maintain a timely list of topics and flow of content, this is the last PowerShell entry for a while. Next time, look for Windows Server 8 management.