Keeping PowerShell in the loop
Loop-the-Loop
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 do 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. PS>
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. Etc. PS>
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. PS>
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 XENAPP0 This is a configuration file. It contains important parameters for an application. Its contents must be consistant from system to system. XENAPP1 This is a configuration file. It contains important parameters for an application. Its contents must be consistant from system to system. SERVER8 This is a configuration file. It contains important parameters for an application. Its contents must be consistant from system to system. PS>
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:
{ NET USER John /DELETE }
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.
- Always test your scripts on a few non-production systems.
- Never abuse your power with PowerShell scripts.
- Document, date, and version your scripts.
- Keep scripts in a separate scripts folder.
- Redirect output to a non-script folder.
- Remove or disable scripts that don't work.
- Reverse any changes made to test systems.
- Warn users before making sweeping changes.
- Work within an accepted change window.
Remember: With great PowerShell comes great responsibility.
Summary
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.