Saturday, 9 August 2014

PowerShell - Read File with Hostnames and Perform Task

Contents



How many times have I needed to do something to a list of servers in one go and always ended up writing a script on the fly? Over the last 15 years, I have probably done this hundreds of times. So I'll be using this blog as a reference point for me and for those who are getting to grips in scripting with PowerShell, I will break things down and hopefully you will be able edit according to your needs.

Ping a list of hostnames
Before we cover how to ping a bunch of hostnames its important to have a structure with your script. Later on you will see that no matter what I need a script to do I always output to a log and have a few variables defined.
So I will start by showing what variables I tend to use, how to spit things out to a file and then the blood & guts of the script left for last. I have broken the script down into "snippets" where the first is variable definition, second snippet is about logging and so on.
At the end you will have the entire script so you can copy & paste and edit to your hearts content.

Snippet #1
Lets put our variables on the table!!
$scriptfunction = "PingHostnames"
$hostslist = "D:\Scripts\ServerList.txt"
$logpath = "D:\Scripts\logs\"
$logfile = "$logpath $scriptfunction -$(get-date -format `"dd-MM-yyyy_hhmmtt`").txt"

Snippet #1 explained:
Line 1 - is just declaring a name that we can identify with what this script will do. I later refer to it for naming the log file.
Line 2 - Full path to file with list of machine names. Simple text file with 1 hostname per line will do.
Line 3 - Directory path to place log file. I always output to a log. Cannot stress how useful it can be.
Line 4 - Here we wrap the entire log structure and set it as $logfile which we will call later on. If this is unclear, just hang in there.

Snippet #2
Log it!!!
function log($logfunction, $colour)
    {
    if ($colour -eq $null) {$colour = "white"}
    write-host $logfunction -foregroundcolor $color
    $logfunction | out-file -Filepath $logfile -append
    }

Snippet #2 explained:
Line 1 - Here we are creating a function called "log" which later on we will be calling upon.
Line 2 - Just setting default text colour as white unless specified.
Line 3 - Write to display and use colour if specified.
Line 4 - Write to file at specified directory "Line 3 -@ snippet #1" and append if file exists or else create it.

Snippet #3
Read my file and one line at a time....
$hosts = Get-Content $hostslist

Snippet #3 explained:
Line 1 - of snippet #3 is going to read the file defined earlier "Line 2 - @ snippet #1" one line at a time using the Get-Content command and declaring that line as a variable "$hosts".

Make sure you have a file saved with a list of hostnames at the location defined earlier in "Line 2 - @ snippet #1" which in our little example is:
 "D:\Scripts\ServerList.txt" 

Snippet #4
Lets do something. At last!
$hosts | % {
           $testping =(ping -n 1 $_)
           log $testping yellow
           }

Snippet #4 explained:
Line 1 - A line from the file stored as variable "$hosts" is pipped "|" to "%" which is an alias for the "Foreach-Object" command.
Line 2 - Is a simple ping command sending only 1 echo request "ping -n 1" and "$_" which is a special variable meaning current pipeline object. In other words, "$_" represents the current line being read.
 Line 3 - Logs all our hard work to display and file.

Complete Script (Ping list of hostnames)
###snippet 1
$scriptfunction = "PingHostnames"
$hostslist = "D:\Scripts\ServerList.txt"
$logpath = "D:\Scripts\logs\"
$logfile = "$logpath $scriptfunction -$(get-date -format `"dd-MM-yyyy_hhmmtt`").txt"
###snippet 2
function log($logfunction, $colour)
    {
    if ($colour -eq $null) {$colour = "white"}
    write-host $logfunction -foregroundcolor $color
    $logfunction | out-file -Filepath $logfile -append
    }
###snippet 3
$hosts = Get-Content $hostslist
###snippet 4
$hosts | % {
           $testping =(ping -n 1 $_)
           log $testping yellow
           }
Screen Output




Log File
Notice how the time is appended to the file name with the code @ Line 4 - Snippet #1 (get-date -format `"dd-MM-yyyy_hhmmtt`"). You can play around with the format to your hearts content!!


Forget the ping, lets get registry values
Pinging a list of hostnames was just an example, although sometimes useful when looking for that dead box amongst a gazillions hostnames. Auditing a registry value across a range of machines can be done in a few minutes .
Make the following changes to the previous script:
  1. Replace snippet #4 with snippet #5 below. Edit "SOFTWARE\AVG\AVG2014\" to reflect a real registry path that you want to enumerate. Also edit "BuildNo" for a registry key who's value you want. And last but not least, adjust "4716" to the value you want to compare against. 
  2. Change the value @ Line 1 snippet #1 to something like "GetRegValue"

Snippet #5
$hosts | % {
           $baseKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine",$_)
           $key = $baseKey.OpenSubKey("SOFTWARE\AVG\AVG2014\")
           $subkeys = $key.GetValue("BuildNo")
           $key.Close()
           $baseKey.Close() 
           If ($subkeys | Where {$_ -lt "4716"}) 
              {
              log "$_ $subkeys is UPDATED " green
              }
           Else 
              {
              log "$_ $subkeys OLD VERSION DETECTED"  red
              }
           }

TIP: I work with environments containing several hundred, sometimes even thousands of servers, so I tend to manipulate the log in excel to get some statistics. You can add some characters to the "log" so that you have recognised delimiters in place. For example:
log "$_ $subkeys is UPDATED " green
log "$_ $subkeys OLD VERSION DETECTED" red
change to:
log "$_ , $subkeys , is UPDATED " green
log "$_ , $subkeys , OLD VERSION DETECTED"  red

Complete Script (Get registry value form list of servers)
###snippet 1
$scriptfunction = "GetRegistryValue"
$hostslist = "D:\Scripts\ServerList.txt"
$logpath = "D:\Scripts\logs\"
$logfile = "$logpath $scriptfunction -$(get-date -format `"dd-MM-yyyy_hhmmtt`").txt"
###snippet 2
function log($logfunction, $colour)
    {
    if ($colour -eq $null) {$colour = "white"}
    write-host $logfunction -foregroundcolor $color
    $logfunction | out-file -Filepath $logfile -append
    }
###snippet 3
$hosts = Get-Content $hostslist
###snippet 5
$hosts | % {
           $baseKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine",$_)
           $key = $baseKey.OpenSubKey("SOFTWARE\AVG\AVG2014\")
           $subkeys = $key.GetValue("BuildNo")
           $key.Close()
           $baseKey.Close() 
           If ($subkeys | Where {$_ -lt "4716"}) 
              {
              log "$_ $subkeys is UPDATED " green
              }
           Else 
              {
              log "$_ $subkeys OLD VERSION DETECTED"  red
              }
           }
Lets Speed Things Up 
With the script below I have added one extra check to speed things up. The script will ping the host with 1 echo first to check availability. If the host is up then it will read the registry. If the host does not respond then it will log it and move on speeding up the entire process.

Complete Script With a Ping Check (Get registry value form list of servers)
###snippet 1
$scriptfunction = "GetRegistryValue"
$hostslist = "D:\Scripts\ServerList.txt"
$logpath = "D:\Scripts\logs\"
$logfile = "$logpath $scriptfunction -$(get-date -format `"dd-MM-yyyy_hhmmtt`").txt"
###snippet 2
function log($logfunction, $colour)
    {
    if ($colour -eq $null) {$colour = "white"}
    write-host $logfunction -foregroundcolor $color
    $logfunction | out-file -Filepath $logfile -append
    }
###snippet 3
$hosts = Get-Content $hostslist
###snippet 5
$hosts | % {
           $PingTest = (Test-Connection $_ -Count 1 -ea 0 -Quiet)
           If ($PingTest -eq "True")
              {
              $baseKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine",$_)
              $key = $baseKey.OpenSubKey("SOFTWARE\AVG\AVG2014\")
              $subkeys = $key.GetValue("BuildNo")
              $key.Close()
              $baseKey.Close() 
              If ($subkeys | Where {$_ -lt "4716"}) 
                 {
                 log "$_ $subkeys is UPDATED " green
                 }
              Else 
                 {
                 log "$_ $subkeys OLD VERSION DETECTED"  red
                 }
              }
              Else
                 {
                 log "$_ - NOT RESPONDING" red
                 }
              }
Get system uptimes
Just like with the previous example, all you have to do is swap snippet #4 or snippet #5 with the code below (snippet #6).
Don“t forget change the value @ Line 1 snippet #1 to something meaningful like "SysUpTime" so your log gets named accordingly.

Snippet #6
$hosts | % {
           $PingTest = (Test-Connection $_ -Count 1 -ea 0 -Quiet)
           If ($PingTest -eq "True")
              {
              $lastboottime = (Get-WmiObject -Class Win32_OperatingSystem -computername $_).LastBootUpTime
              $sysuptime = (Get-Date) - [System.Management.ManagementDateTimeconverter]::ToDateTime($lastboottime)
              $d = $sysuptime.days
              $h = $sysuptime.hours
              $m = $sysuptime.minutes
              $s = $sysuptime.seconds
              log "$_, has been up for:, $d Days $h Hours $m Minutes $s Seconds, $d$h$m$s"  
              }
           else
              {
              log "$_ - NOT RESPONDING" red
              }
           }
Complete Script (Get system UpTime form list of servers)
###snippet 1
$scriptfunction = "SysUpTime"
$hostslist = "D:\Scripts\ServerList.txt"
$logpath = "D:\Scripts\logs\"
$logfile = "$logpath $scriptfunction -$(get-date -format `"dd-MM-yyyy_hhmmtt`").txt"
###snippet 2
function log($logfunction, $colour)
    {
    if ($colour -eq $null) {$colour = "white"}
    write-host $logfunction -foregroundcolor $color
    $logfunction | out-file -Filepath $logfile -append
    }
###snippet 3
$hosts = Get-Content $hostslist
$hosts | % {
           $PingTest = (Test-Connection $_ -Count 1 -ea 0 -Quiet)
           If ($PingTest -eq "True")
              {
              $lastboottime = (Get-WmiObject -Class Win32_OperatingSystem -computername $_).LastBootUpTime
              $sysuptime = (Get-Date) - [System.Management.ManagementDateTimeconverter]::ToDateTime($lastboottime)
              $d = $sysuptime.days
              $h = $sysuptime.hours
              $m = $sysuptime.minutes
              $s = $sysuptime.seconds
              log "$_, has been up for:, $d Days $h Hours $m Minutes $s Seconds, $d$h$m$s"  
              }
           else
              {
              log "$_ - NOT RESPONDING" red
              }
           }
Screen Output