Executive Summary:
Although you need Windows PowerShell installed on your computer to run these pair of PowerShell scripts, you don't need to know how to write any PowerShell code to use them. You provide all the information they need when you execute the scripts from the PowerShell command line. With these PowerShell scripts, you can reboot, ping, power off, or shut down any number of computers in an AD domain. You can even use them to log off users.

Sometimes it's necessary to reboot computers in an Active Directory (AD) domain or organizational unit (OU). For example, if you use a Group Policy Object (GPO) to deploy software to computers, Group Policy won't install the software until the computers reboot. Or, you might need to reboot some computers after installing a security patch or when you run a computer startup script. Whatever the reason, rebooting multiple computers is a common administrative task that a script can accomplish.

Because I often have to reboot multiple computers, I decided to create a scripting solution that would:

  1. Create a list of computers.
  2. Reboot each computer in the list.
  3. Report on the success or failure of each reboot.

I first investigated using Windows' built-in command-line tools in the scripting solution. The Dsquery Computer command can produce a list of computers, and the Shutdown command can reboot a remote computer. However, these commands have some limitations. First, each computer name in the Dsquery Computer command's output ends with the $ character and is enclosed in double quotes, so my script would have to perform extra string manipulation to extract just the computer names. Second, the Shutdown command wasn't designed with automation in mind, so it's difficult to get its results into a readable format.

I then thought of writing a Windows Script Host (WSH) script that would use ActiveX Data Objects (ADO) to find the computers and Windows Management Instrumentation (WMI) methods to reboot them. However, creating formatted output with a WSH script is largely a manual process.

Due to these limitations, I decided to write two PowerShell scripts:

  • Get-EnabledComputerCN.ps1, which creates a list of computers.
  • Set-ComputerState.ps1, which reboots each computer in the list and reports on the success or failure of each reboot. This script also lets you log off users and power off or shut down computers.

I wrote two scripts instead of one because they're independently useful. When you just need to get the names of all the computers in a domain or OU, you can run Get-EnabledComputerCN.ps1 by itself. When you just need to reboot, power off, or shut down a few computers or log off a few users, you can use Set-ComputerState.ps1 by itself. When your needs change and you need to reboot, power off, or shut down all the computers or log off all the users in an OU or AD domain, you can easily combine the scripts using a single PowerShell command. I'll show you how to do this after I describe how to run the scripts individually.

Using Get-EnabledComputerCN.ps1
Get-EnabledComputerCN.ps1 is easy to use. The command to run the script follows the syntax

  \[-searchscope \]

(Although this command syntax wraps here, you'd enter the command all on one line in the PowerShell console. The same holds true for the other sample commands that follow.)

You use the -basename parameter to specify one or more base distinguished names (DNs)—this is where the script will start searching for computers. If you specify a blank string ("" or '), the script uses the current domain's DN for the start of the search.

You use the -searchscope parameter to specify the search scope (Base, Onelevel, or Subtree). If you don't specify -searchscope, the default search scope is Subtree. If you specify Onelevel for the -searchscope parameter, the script searches for enabled computers in the named DNs, but it doesn't search in containers underneath the named DNs. You'll most likely never use a Base search. For more information about search scopes, see MSDN's "SearchScope Enumeration" web page.

Both the -basename and -searchscope parameters are positional, so you can omit the parameter names if you specify their values as the first and second parameters on the command line. For example, the command

get-enabledcomputercn ""

outputs a list of all enabled computers in the current domain. The command


outputs a list of enabled computers in the Sales and Mktg OUs (and any OUs underneath them) in the domain. Enclosing the DNs in double quotes causes PowerShell to interpret each DN as a distinct string. Without the quotes, PowerShell will interpret OU=Sales,DC=wascorp,DC=net as an array of three strings instead of a single string.

Using Set-ComputerState.ps1
The Set-ComputerState.ps1 script uses WMI to log off, shut down, reboot, or power off one or more computers, then outputs objects containing the results of each operation. The command to run the script uses the syntax

  \[-force\] \[-ping\]

You use the -computername parameter to specify a computer name (or a list of computer names). You indicate the action you want to perform on that computer by specifying the -action parameter followed by Logoff, Shutdown, Reboot, Poweroff, or Test. If you include the -force parameter, the script will force the specified action. Including the -ping parameter tells the script to first ping the computers.

Although the Logoff, Shutdown, Reboot, and Poweroff values for the -action parameter are self-explanatory, the Test value needs a bit of explanation. The Test value tests whether Set-ComputerState.ps1 can establish a WMI connection with each specified computer, but it doesn't perform an action. So, you specify this action when you want to simply test whether you can connect to the specified computers. You can also use the Test value in conjunction with the -ping parameter. For example, if you want to test whether Set-ComputerState.ps1 can successfully ping and connect to a computer named pc4, you'd run the command

set-computerstate pc4 Test -ping

If you use the -force parameter with the Test action, the -force parameter is ignored because -force is only meaningful with other actions.

Both the -computername and -action parameters are positional, so you can omit the parameter names if you specify their values as the first and second parameters on the command line. Table 1 shows some sample Set-ComputerState.ps1 commands.

Table 1: Sample Set-ComputerState.ps1 Commands
Command Result
set-computerstate -computername pc1 -action Reboot Reboots pc1.
set-computerstate pc1,pc2 Logoff -force Forces a logoff on computers pc1 and pc2.
set-computerstate pc3 Test Tests whether Set-ComputerState.ps1 can connect to pc3.

Figure 1 shows sample output from Set-ComputerState.ps1.

Figure 1: Sample output from Set-ComputerState.ps1 (click to enlarge)

As you can see, it outputs objects that contain three properties:

  • Computer. The Computer property contains the computer name.
  • Action. The Action property contains the action attempted on the computer (e.g., reboot, logoff, forced reboot, forced logoff). If Test was the specified action, the Action property will contain the word Connect. If the -ping parameter was included and a ping fails, the Action property will contain the word Ping.
  • Result. The Result property contains the result (either a hexadecimal number or a string) of the specified action. When the Result property contains 0x00000000, the action was successful. When the action failed, the Result property will contain a non-zero hexadecimal code or an error message.

To interpret an error code, you can use the Net Helpmsg command by following the syntax

net helpmsg (0x)

where is the last four hex digits in the error code. For example, if you get the error code 0x800706BA, you'd type the command

net helpmsg (0x06BA)

after the PowerShell prompt. In this case, the result is the error message The RPC server is unavailable.

Combining the Commands
As I mentioned previously, PowerShell makes it easy to run Get-EnabledComputerCN.ps1 and Set-ComputerState.ps1 together using a single command. For example, suppose you want to reboot all the computers in the Mktg OU in the domain. You can use either this command

  "OU=Mktg,DC=wascorp,DC=net" |
  \{ set-computerstate $_ reboot \}

or this one

  "OU=Mktg,DC=wascorp,DC=net") reboot

The first command executes Get-EnabledComputerCN.ps1, then pipes the script's output to the ForEach-Object cmdlet, which executes Set-ComputerState.ps1 on each computer listed in that output. The second (and shorter) command executes Set-ComputerState.ps1, using Get-EnabledComputerCN.ps1 as the -computername parameter. Now that you know how to run the scripts individually and together, let's look at how they work.

Understanding Get-EnabledComputerCN.ps1
Get-EnabledComputerCN.ps1 is a fairly straightforward script that uses the .NET DirectoryEntry and DirectorySearcher classes to search AD for enabled computers. It uses PowerShell's \[ADSI\] type accelerator to create a System.DirectoryServices.DirectoryEntry object. Get-EnabledComputerCN.ps1 connects (or binds) to the requested object in AD by specifying its name after the \[ADSI\] type accelerator, as shown in callout A in Listing 1. If you specify an empty string, the DirectoryEntry object binds to the current domain.

Listing 1: The main Function in Get-EnabledComputerCN.ps1 (click to enlarge)

Get-EnabledComputerCN.ps1 then creates a System.DirectoryServices.DirectorySearcher object and sets that object's SearchRoot and Filter properties, as callout B shows. The script sets the SearchRoot property to the DirectoryEntry object it created in the code at callout A. It uses a search filter to find enabled computer accounts, whether they be workstations, members servers, or domain controllers (DCs). If you're unfamiliar with Active Directory Service Interfaces (ADSI) search filters, see MSDN's "Search Filter Syntax" web page.

Next, Get-EnabledComputerCN.ps1 sets the DirectorySearcher object's PageSize property to 1,000. This enables AD to return 1,000 objects from a search at a time. Otherwise, it returns only the first 1,000 matches. The script then configures the SearchScope property (which, as discussed previously, is Base, Onelevel, or Subtree).

The final step in setting up the DirectorySearcher object is to specify which properties you want to retrieve for each object. To do this, Get-EnabledComputerCN.ps1 calls the Add method of the DirectorySearcher object's PropertiesToLoad property, as callout C shows. The DirectorySearcher object's Add method returns an index, but since the script doesn't use the index, it casts the expression to \[Void\] to prevent the index value from appearing in the output. We only want to return the cn (common name) property for each computer name, so that's the parameter it passes to the Add method.

After setting up the DirectorySearcher object, the script creates a System.DirectoryServices.SortOption object and sets its PropertyName property so the results are sorted in ascending order. To output those results, the script calls the DirectorySearcher object's FindAll method. The FindAll method outputs a list of System.DirectoryServices.SearchResult objects. The script pipes this list to the ForEach-Object cmdlet in order to output the cn property for each object.

Understanding Set-ComputerState.ps1
Set-ComputerState.ps1 uses WMI to perform the specified actions on computers. Specifically, it uses the Win32Shutdown method of WMI's Win32_OperatingSystem class. This method requires a parameter that tells it what to do. Table 2 shows the valid parameter values for the Win32Shutdown method. (Test isn't a valid action for the Win32Shutdown method, but Set-ComputerState.ps1 uses the value 16 to represent the Test action.)

Table 2: Valid Parameter Values for the Win32Shutdown Method
Value Meaning
0 Logoff
1 Shutdown
2 Reboot
4 Forced logoff
5 Forced shutdown
6 Forced reboot
8 Poweroff
12 Forced poweroff

Set-ComputerState.ps1 assigns the Win32Shutdown method's parameter values to a series of variables representing the various actions, as callout A in Listing 2 shows. It then uses a hash table to associate the variables with the first letter of each action (callout B). The script checks the first character of the specified action against the hash table's keys. If there isn't a match (i.e., the specified action isn't valid), the script throws an error.

Listing 2: Set-ComputerState.ps1 Code That Associates the -action Parameter Values with the Win32Shutdown Parameter Method Values

Set-ComputerState.ps1 also uses the hash table to obtain the numeric value for the Win32Shutdown method and stores it in the $flags variable. If the -force parameter was entered on the command line, the script uses the -bor operator to obtain the value for the forced version of the action (provided that the action wasn't Test).

Next, the script creates a ManagementObjectSearcher object using PowerShell's \[WMISearcher\] type accelerator in a query that selects all properties from the Win32_OperatingSystem class. It then configures the ManagementObjectSearcher object's options to enable all WMI privileges and set the WMI impersonation level. (This is why Set-ComputerState.ps1 uses the ManagementObjectSearcher object instead of the Get-WMIObject cmdlet; the Get-WMIObject cmdlet doesn't support enabling all privileges.)

Set-ComputerState.ps1 uses a foreach loop to iterate through the computers specified with the -computername parameter. For each computer, the script creates a custom output object and configures its name. If the -ping parameter is present, the script calls the testIPHost function. The testIPHost function uses WMI's Win32_PingStatus class to check whether the computer responds to a ping. If the ping fails (i.e., the testIPHost function returns a non-zero value), the script updates the output object's Action and Result properties, outputs the object, and continues to the next computer.

In the code in Listing 3, Set-ComputerState.ps1 uses the PowerShell trap statement to capture exceptions.

Listing 3: The trap Script Block in Set-ComputerState.ps1

If an exception occurs, the trap script block updates the $ok variable in the parent scope to $FALSE, then attempts to retrieve the exception's ErrorCode property. Not all exceptions have an ErrorCode property, so the trap script block uses a regular expression to check if the exception's message contains a hex error code. If the exception message contains a hex error code, the script block updates the output object's Result property with the hex error code; otherwise, it updates the output object's Result property with the error message. The trap script block then uses the continue statement to continue to the statement following the one that caused the exception.

Finally, Set-ComputerState.ps1 points ManagementObjectSearcher to the root\cimv2 WMI namespace on the requested computer, then calls ManagementObjectSearcher's Get method to execute the query as a part of a foreach loop. If the $ok variable contains $TRUE, an exception didn't occur and the script checks whether Test was the requested action. If so, it updates the output object with a zero code (indicating success); otherwise, it calls the Win32Shutdown method and updates the output object's properties with the action and the result. The script uses the decodeFlags function to return a string representation of the $flags variable. After this, the script outputs the custom object and continues to the next computer.

Because Set-ComputerState.ps1 outputs objects, not just text, you can use PowerShell's formatting cmdlets to customize the script's output. For example, if you want to omit the Action property from the output, you can use the Format-Table cmdlet to select only the Computer and Result properties.

Exploiting PowerShell's Capabilities
The Get-EnabledComputerCN.ps1 and Set-ComputerState.ps1 scripts demonstrate how PowerShell makes it relatively easy to combine separate scripts to accomplish a single goal. If you add them to your toolbox, you'll be able to easily reboot computers whenever needed. You can download these scripts by clicking the Download the Code Here button near the top of the page. You can execute these PowerShell scripts on any machine that has PowerShell installed, but the computers on which you're performing the actions don't have to have PowerShell installed. You don't need to customize the scripts before you use them.