Saturday, September 24, 2011

Powershell Script to Restart an IP Address Range using WMI

Background

These script snippets and the final script help solve the problem of not having a centralized way of rebooting a set of systems. This require WMI (default TCP port 135) to function between the system that is initiating the reboot and the system that is being rebooted. Additionally, the account that is running the script (or scheduled task) requires permissions to reboot the system. This requires the "Force shutdown from a remote system" right that is given to the Administrators group by default (and also to the domain Server Operators group on domain controllers).

Expansion of an IP Range

I created a function called ExpandRange:
function ExpandRange([string] $network, [int] $start, [int] $end) {
     $retval = @()
     
     while ($start -le $end) {
         $temp = $network + $start
         $retval += $temp
         $start++
     }
     
     return $retval
} 
This function does little more than take a string argument, a start, and an end, and iterates through and returns an array of the string and each number in the range appended. This is especially straightforward for Class C network addresses:
#Returns array holding 192.168.0.1 to 192.168.0.254 
$hosts += ExpandRange -network "192.168.0." -start 1 -end 254  
Expanding class A, B, and C networks follows from running the ExpandRange function multiple times building from the first octet to the last octet. Classless ranges require performing a little bit of math to figure out the start and end IP addresses, but are also very simple using the start and end parameters.

Performing the Reboot

The Win32_OperatingSystem WMI class has two methods of interest with regard to reboots and shutdown: The reboot() method and the Win32Shutdown(int) method. Another method also exists for organizations that need to specify reasons for reboots, Win32ShutDownTracker. The reboot() method will not force a reboot (if applications block the system from shutting down), but the Win32Shutdown method can force a reboot. The integer parameter that is currently used breaks down as follows:

Bit position (value)
Meaning
0 (0)
Log off
1 (1)
Shut down
2 (2)
Reboot
3 (4)
Force action
4 (8)
Power off

It follows from this table that a forced reboot would be 2 + 4 = 6. In PowerShell, the method can be invoked using the Get-WMIObject cmdlet. Using the -ComputerName option allows the WMI class instance to be retrieved from multiple systems. To perform the reboot:
Get-WmiObject -computername $i Win32_OperatingSystem -ErrorAction SilentlyContinue | ForEach-Object {$_.Win32Shutdown(6)}  

Make it Run Faster With Concurrency

Unfortunately, just doing a Foreach-Object of the array that is built from the ExpandRange function above is a O(n) operation. This can be slow waiting for unreachable IP addresses to time out and a sparsely populated IP range can cause a script to run for a very long time. Using jobs in powershell allows this to become a number of smaller O(n) operations running concurrently.
while ($hosts.count -gt 0) {
     #split list into smaller lists
     $dispatch = @()
     
     #Full blocks of $job_size
     if ($hosts.count -gt $job_size) {
         $dispatch = $hosts[0 .. ($job_size - 1)]
         $hosts = $hosts[$job_size .. ($hosts.count-1)]
     }
     #Partial blocks (there should either be 0 or 1 of these)
     else {
         $dispatch = $hosts
         $hosts = @()
     }
     
     #reboot the smaller block of hosts in a separate job and continue kicking off reboots of other hosts
     Start-Job -InputObject $dispatch -ScriptBlock {
          foreach ($i in $Input.'<>4__this'.Read()) {
            #Perform the Reboot
            Get-WmiObject -computername $i Win32_OperatingSystem -ErrorAction SilentlyContinue | ForEach-Object {$_.Win32Shutdown(6)}
         }
     }
}
Wait-Job * 
Receive-Job * 
Remove-Job * 

No comments:

Post a Comment