Showing posts with label compare. Show all posts
Showing posts with label compare. Show all posts

06 July, 2013

Compare two roots - DFS

Imagine you have many DFS (Distributed File System) servers across the globe and they are in pairs, so each location has 2 DFS servers which hold the same set of 1000+ DFS pointers in a load balanced fashion. In this case you want to make sure both these servers have exactly the same DFS pointers, otherwise when a user hits the first box will see different folder structure when hitting the second box. Of course you get a call from a user saying sometimes folders disappear and then when opening up the namespace again they are there. Hm... very suspicious. First thing that comes to mind is that the 2 DFS servers have a difference in the number of DFS pointers. How could you figure out from the 1000+ pointer which are the ones that don't exist on both servers. Bingo... a bit of PowerShelling will help (You can do it with dfsutil /compare as well but that wouldn't make me write an article on a PS solution and also it doesn't have the flexibility to pipe the output to  another command ;) ).

The easiest way of doing this is using dfsutil.exe to export the namespace to an xml file on both servers, read them, compare and then make a readable output of the differences.

Export namespace to an xml:
$command = "dfsutil /root:$dfsrt /export:c:\temp\$filename /api"
writelog 0 "running command: $command" 
$err = invoke-expression $command

Pick up the content with PowerShell but only the part we need and store it in our custom object for later use (pointer name and target):
$tmpXMLcontent = ([xml] [string]::join("`n",(get-content c:\temp\$filename))).root.link | %{$_.name}
$script:sObject.DFS_Links = $tmpXMLcontent

When we have this from both DFS servers, we can compare them and store differences in separate variables:
$notOnFirstHost = compare $objcoll[0].DFS_links $objcoll[1].DFS_links -SyncWindow 100000 | ?{$_.SideIndicator -ieq "=>;"} | %{$_.InputObject}
$notOnSecondHost = compare $objcoll[0].DFS_links $objcoll[1].DFS_links -SyncWindow 100000 | ?{$_.SideIndicator -ieq "<="} | %{$_.InputObject}

Now go through the content of the 2 variables and create our custom object collection with the results, e.g.:
$notOnFirstHost | %
$obj = "" | select DFS_Link,$objColl[0].ComputerName,$objColl[1].ComputerName
$obj.DFS_Link = $_ 
$obj."$($objColl[0].ComputerName)" = "Doesn't exist" 
$obj."$($objColl[1].ComputerName)" = "Exists" 
$resColl += $obj 
}


If we want to make the data even more detailed, we can look up the target folder of the particular DFS link from the xml dump to see where it is supposed to point to:
function lookupDFSTarget($fdfslink, $fdfsdumpfile, $fxmlcontent){
$retvalArr = @() 
$targets = $fxmlcontent | ?{$_.name -ieq $fdfslink} | %{$_.target} 
$targets | %
   $tmpstr = ("\\" + $_.server + "\" + $_.folder) 
   $retvalArr += $tmpstr 
}
$retval = [string]::Join(";", $retvalArr
return $retval 
}
 



The output is something like this:



The full script (usage compare-dfslinks.ps1 -dfsroots \\c3podfs1\root,c3podfs2\root -lookup)

   
param (     [string] $dfsroots = "",  
           [string] $log = "",  
           [switch] $lookupTarget = $false)  
 
 $logfile = new-item -type file "C:\temp\compare-dfslinks.log" -force
 
 #### Function for creating log entries in a logfile and on standard output  
 function writeLog ([int]$type, [string]$message, [string]$modifier) { #usage: writeLog 0 "info or error message"  
      # $modifier: <nonew | extend>  
      # Value nonew: writes the output the the console and the logfile without carriage return  
      # Value raw: writes the message to the output without date and   
      # both values can be used e.g. for writting to the console and logfile and put a status message at the end of the line as a second step  
      $date = get-date -uformat "%Y/%m/%d %H:%M:%S"  
      if($modifier -eq "extend"){  
           switch ($type) {  
                "0"     {$color = "Green"}  
                "1"     {$color = "Yellow"}  
                "2"     {$color = "Red"}  
           }  
      }  
      else{  
           switch ($type) {  
                "0"     {$message = $date + ", INF, " + $message; $color = "Green"}  
                "1"     {$message = $date + ", WAR, " + $message; $color = "Yellow"}  
                "2"     {$message = $date + ", ERR, " + $message; $color = "Red"}  
           }  
      }  
        
      if($modifier -eq "nonew"){  
           write-host $message -ForegroundColor $color -NoNewLine  
           $bytes = [text.encoding]::ascii.GetBytes($message)  
           $bytes | add-content $logfile -enc byte  
      }  
      else{  
           write-host $message -ForegroundColor $color  
           Add-Content $logfile $message  
      }  
 }  
   
   
   
 #### Function to lookup DFS target of a DFS link from dump xml  
 function lookupDFSTarget($fdfslink, $fdfsdumpfile, $fxmlcontent){  
      $retvalArr = @()  
      $targets = $fxmlcontent | ?{$_.name -ieq $fdfslink} | %{$_.target}  
      $targets | %{  
           $tmpstr = ("\\" + $_.server + "\" + $_.folder)  
           $retvalArr += $tmpstr  
      }  
      $retval = [string]::Join(";", $retvalArr)  
      return $retval  
 }  
   
   
 $objColl = $dfscontent = $resColl = @()  
 $k = 1  
 $dfsrootlist = $null
   
 $dfsrootsArr = @($dfsroots.split(" "))  
 $dfsrootlist += $dfsrootsArr  
   
 $dfsrootlistlength = $dfsrootlist.length  
   
   
 if($dfsrootlistlength -gt 0){  
      if($dfsrootlistlength -eq 2){  
           foreach($dfsrt in $dfsrootlist){  
             
                Write-Progress -id 1 -activity "Performing DFS checks" -Status "Processing DFS Master $k of $dfsrootlistlength : $dfsrt " -PercentComplete ($k/$dfsrootlistlength * 100) -currentoperation "checking DFS..."  
                $filename = $dfsrt.replace("\\","")  
                $filename = $filename.replace("\","_") + ".xml"  
                $srvname = $dfsrt.split("\")[2]  
                  
                $script:sObject = new-Object -typename System.Object  
                $script:sObject | add-Member -memberType noteProperty -name ComputerName -Value $srvname  
                $script:sObject | add-Member -memberType noteProperty -name DFS_Links -Value ""  
                $script:sObject | add-Member -memberType noteProperty -name DFS_dump_file -Value ""  
                  
                if($filename -and (Test-Path "c:\temp\$filename")){  
                     Remove-Item c:\temp\$filename -Force  
                }  
                  
                $script:sObject.DFS_dump_file = "c:\temp\$filename"  
                  
                $command = "dfsutil /root:$dfsrt /export:c:\temp\$filename"  
                writelog 0 "running command: $command"  
                $err = invoke-expression $command  
                 
                $tmpXMLcontent = ([xml] [string]::join("`n",(get-content c:\temp\$filename))).root.link | %{$_.name}  
                $script:sObject.DFS_Links = $tmpXMLcontent  
                $objColl += $script:sObject  
                $k++  
           }  
      }  
 }  
 else{  
      writeLog 2 "No DFS server is specified."  
 }  
   
   
 writelog 0 "Running compare between the 2 xmls"  
 $notOnFirstHost = compare $objcoll[0].DFS_links $objcoll[1].DFS_links -SyncWindow 100000 | ?{$_.SideIndicator -ieq "=>"} | %{$_.InputObject}  
 $notOnSecondHost = compare $objcoll[0].DFS_links $objcoll[1].DFS_links -SyncWindow 100000 | ?{$_.SideIndicator -ieq "<="} | %{$_.InputObject}  
   
   
 if($notOnFirstHost -or $notOnSecondHost){  
      if($notOnFirstHost){  
           $xmlcontent = $null  
           if($lookupTarget)     {$xmlcontent = ([xml] [string]::join("`n",(get-content $($objColl[1].DFS_dump_file)))).root.link}  
           $notOnFirstHost | %{  
                  
                if($lookupTarget)     {$obj = "" | select DFS_Link,DFS_Target,$objColl[0].ComputerName,$objColl[1].ComputerName}  
                else                    {$obj = "" | select DFS_Link,$objColl[0].ComputerName,$objColl[1].ComputerName}  
                  
                $obj.DFS_Link = $_  
                $obj."$($objColl[0].ComputerName)" = "Doesn't exist"  
                $obj."$($objColl[1].ComputerName)" = "Exists"  
   
                if($lookupTarget){$obj.DFS_Target = lookupDFSTarget $_ ($($objColl[1].DFS_dump_file)) $xmlcontent }  
                $resColl += $obj  
           }  
        
      }  
        
      if($notOnSecondHost){  
           $xmlcontent = $null  
           if($lookupTarget)     {$xmlcontent = ([xml] [string]::join("`n",(get-content $($objColl[0].DFS_dump_file)))).root.link}  
           $notOnSecondHost | %{  
                if($lookupTarget)     {$obj = "" | select DFS_Link,DFS_Target,$objColl[0].ComputerName,$objColl[1].ComputerName}  
                else                    {$obj = "" | select DFS_Link,$objColl[0].ComputerName,$objColl[1].ComputerName}  
                  
                  
                $obj.DFS_Link = $_  
                $obj."$($objColl[1].ComputerName)" = "Doesn't exist"  
                $obj."$($objColl[0].ComputerName)" = "Exists"  
                  
                if($lookupTarget){$obj.DFS_Target = lookupDFSTarget $_ ($($objColl[0].DFS_dump_file)) $xmlcontent }  
                $resColl += $obj  
           }  
      }       
 }  
 else{  
      writelog 1 ("No differences between links on " + $srvArr[0] + " and " + $srvArr[1] )  
 }  
   
 $resColl  
 
   


sdfsdf

20 January, 2013

Compare hotfixes on two computers - OS

In an enterprise environment, inevitably, you will find high availability systems. A typical solution to reach a relatively high availability is to setup a cluster. A cluster usually consists of two or more identical computers (nodes), ideally in different physical locations and they are capable of running exactly the same service, some of them have shared disks to be able to run database engines...etc. If one of the nodes in a cluster goes down for whatever reason, another one can pick up the services and run them (a service can be an IP address, a network name, database engine or any custom service which supports clustering).


In the definition of these systems, there's is a very important word: identical nodes. The computers which are part of a cluster of some sort should be very similar. Ideally same hardware model, same operating system, same applications installed, same OS, network, disk...etc configuration. And not least, same hotfixes installed.

Because Microsoft hotfixes are supposed to be installed at least once a month, a quick tool is always helpful which can tell you the differences between the list of installed hotfixes on two computers. You can get the list of Microsoft patches installed to a host with one line:
$hotfixes = gwmi -query "select HotFixID from Win32_quickfixengineering where hotfixID like 'KB%'" -computer $fsrv | select hotfixid

If you want to compare two lists, PowerShell offers you and easy way with Compare-Object, however, to make the output a bit better and more readable, you can feed the data into an object collection and then make it as the output of the script (see compareHotfixes function in the script below).

Example output:







 

Some of the interesting parts of the script:
#### Function for enumerating hotfixes

function gethotfixes ([string]$fsrv){
   writelog 0 "$fsrv, reading hotfixes data from Win32_quickfixengineering......" "nonew"
   $hotfixes = gwmi -query "select HotFixID from Win32_quickfixengineering where hotfixid like 'KB%'" -computer $fsrv | select hotfixid
   $strHotfixes = $hotfixes | %{$_.hotfixid.tostring()}
   writelog 1 "[done]" "extend"
   return $strHotfixes
} 
This function reads the list of hotfixes from a remote host (has some logging as well) and returns the results in $strHotfixes variable.

#### Function for comparing hotfixes between 2 hosts
function compareHotfixes ($fobjColl, $fullreport){
   writelog 0 "Comparing hotfixes between the 2 hosts......" "nonew"

   
# compare the list of hotfixes from the 2 hosts

   if($fullreport) {$comparedHotfixes = compare-object $fobjColl[0].hotfixes $fobjColl[1].hotfixes -SyncWindow 500 -IncludeEqual} #we need the equals for the host details output
   else {$comparedHotfixes = compare-object $fobjColl[0].hotfixes $fobjColl[1].hotfixes -SyncWindow 500}

   # going through the output of compare-object's output and feed the data into an object collection

   foreach ($c in $comparedHotfixes) {
      $fsObj = new-Object -typename System.Object
      $hotfixId = $c.InputObject
      switch ($c.SideIndicator)
      {
         "=>" {

               $fsObj | add-Member -memberType noteProperty -name "Item" -Value $hotfixId
               $fsObj | add-Member -memberType noteProperty -name $($fobjColl[0].ComputerName) -Value "Missing"
               $fsObj | add-Member -memberType noteProperty -name $($fobjColl[1].ComputerName) -Value "OK"
         }
           
"<=" {

               $fsObj | add-Member -memberType noteProperty -name "Item" -Value $hotfixId
               $fsObj | add-Member -memberType noteProperty -name $($fobjColl[0].ComputerName) -Value "OK"
               $fsObj | add-Member -memberType noteProperty -name $($fobjColl[1].ComputerName) -Value "Missing"
         }
           
"==" {

               $fsObj | add-Member -memberType noteProperty -name "Item" -Value $hotfixId
               $fsObj | add-Member -memberType noteProperty -name $($fobjColl[0].ComputerName) -Value "OK"
               $fsObj | add-Member -memberType noteProperty -name $($fobjColl[1].ComputerName) -Value "OK"
         }
      }

     
if($fsObj.item){

         $script:ReturnObjColl += $fsObj
      }
   }
  
writelog 1 "[done]" "extend"

} 
This is the main part of the script where we take the list of hotfixes on each node (first and second element of the $fobjcoll object collection, which is basically an array of objects and we take the hotfixes property of them: $fobjColl[0].hotfixes, $fobjColl[1].hotfixes.) and then we use the compare-object command to compare the lists. Then there's a bit of data processing with a switch case.

if($hostlistlength -eq 2){
   foreach ($srv in $hostlist) {
      $sObjHotfixes = new-Object -typename System.Object
      $sObjHotfixes | add-Member -memberType noteProperty -name ComputerName -Value $srv
      $sObjHotfixes | add-Member -memberType noteProperty -name hotfixes -Value ""
      $sObjHotfixes.hotfixes = gethotfixes $srv
      $objColl += $sObjHotfixes
   }
}


The script checks how any nodes were specified, if 2 then we are good to go. Then we just go through the list and create and object for each which will contain the name of the computer and the hotfixes enumerate from it.

Run it like this:
PS C:\> "host1","host2" | compare-hotfixes.ps1

Clipboard friendly code:
 param (     [string] $log = "",
            [switch] $fullreport = $false)
  
 if (!$log){
      $date = get-date -uformat "%Y-%m-%d-%H-%M-%S"
      $log = "c:\temp\$scriptname-$date.log"
      write-host -foregroundcolor 'yellow' "No logfile is specified, $log will be used." 
 }     
 $logfile = New-Item -type file $log -force
  
 ##################################################### Functions ##################################################### 
 # ================== Logging and reporting functions ==================
 #### Function for creating log entries in a logfile and on standard output
 function writeLog ([int]$type, [string]$message, [string]$modifier) { #usage: writeLog 0 "info or error message"
      # $modifier: <nonew | extend>
      # Value nonew: writes the output the the console and the logfile without carriage return
      # Value extend: writes the message to the output without date and 
      # both values can be used e.g. for writting to the console and logfile and put a status message at the end of the line as a second step
  
      $date = get-date -uformat "%Y/%m/%d %H:%M:%S"
      if($modifier -eq "extend"){
           switch ($type) {
                "0"     {$color = "Green"}
                "1"     {$color = "Yellow"}
                "2"     {$color = "Red"}
           }
      }
      else{
           switch ($type) {
                "0"     {$message = $date + ", INF, " + $message; $color = "Green"}
                "1"     {$message = $date + ", WAR, " + $message; $color = "Yellow"}
                "2"     {$message = $date + ", ERR, " + $message; $color = "Red"}
           }
      }
      if($modifier -eq "nonew"){
           write-host $message -ForegroundColor $color -NoNewLine
           $bytes = [text.encoding]::ascii.GetBytes($message)
           $bytes | add-content $logfile -enc byte
      }
      else{
           write-host $message -ForegroundColor $color
           Add-Content $logfile $message
      }
 }
  
 #### Function for enumerating hotfixes
 function gethotfixes ([string]$fsrv){
      writelog 0 "$fsrv, reading hotfixes data from Win32_quickfixengineering......" "nonew"
      $hotfixes = gwmi -query "select HotFixID from Win32_quickfixengineering where hotfixid like 'KB%'" -computer $fsrv | select hotfixid
      $strHotfixes = $hotfixes | %{$_.hotfixid.tostring()}
      writelog 1 "[done]" "extend"
      return $strHotfixes
 }
  
 #### Function for comparing hotfixes between 2 hosts
 function compareHotfixes ($fobjColl, $fullreport){
      writelog 0 "Comparing hotfixes between the 2 hosts......" "nonew"
      # compare the list of hotfixes from the 2 hosts
      if($fullreport)     {$comparedHotfixes = compare-object $fobjColl[0].hotfixes $fobjColl[1].hotfixes -SyncWindow 500 -IncludeEqual} #we need the equals for the host details output
      else               {$comparedHotfixes = compare-object $fobjColl[0].hotfixes $fobjColl[1].hotfixes -SyncWindow 500}
  
      # going through the output of compare-object's output and feed the data into an object collection
      foreach ($c in $comparedHotfixes) { 
           $fsObj = new-Object -typename System.Object
           $hotfixId = $c.InputObject
 
           switch ($c.SideIndicator) 
           {
                "=>" {
                     $fsObj | add-Member -memberType noteProperty -name "Item" -Value $hotfixId
                     $fsObj | add-Member -memberType noteProperty -name $($fobjColl[0].ComputerName) -Value "Missing"
                     $fsObj | add-Member -memberType noteProperty -name $($fobjColl[1].ComputerName) -Value "OK"
                } 
                "<=" {
                     $fsObj | add-Member -memberType noteProperty -name "Item" -Value $hotfixId
                     $fsObj | add-Member -memberType noteProperty -name $($fobjColl[0].ComputerName) -Value "OK"
                     $fsObj | add-Member -memberType noteProperty -name $($fobjColl[1].ComputerName) -Value "Missing"
                } 
                "==" {
                     $fsObj | add-Member -memberType noteProperty -name "Item" -Value $hotfixId
                     $fsObj | add-Member -memberType noteProperty -name $($fobjColl[0].ComputerName) -Value "OK"
                     $fsObj | add-Member -memberType noteProperty -name $($fobjColl[1].ComputerName) -Value "OK"
                } 
           }

           if($fsObj.item){
                $script:ReturnObjColl += $fsObj
           }
      } 
      writelog 1 "[done]" "extend"
}
  
##################################################### Body #####################################################
  
 writelog 0 "Syntax: $($MyInvocation.MyCommand.Path)$($myinvocation.line.substring(($myInvocation.InvocationName).length ))"
 writelog 0 "Invoked by: $([Security.Principal.WindowsIdentity]::GetCurrent().Name)"
  
 $hostlist = @($Input)
 $objColl = $script:ReturnObjColl = @()
 $hostlistlength = $hostlist.length
  
 if($hostlistlength -eq 2){
      foreach ($srv in $hostlist) {
           $sObjHotfixes = new-Object -typename System.Object
           $sObjHotfixes | add-Member -memberType noteProperty -name ComputerName -Value $srv
           $sObjHotfixes | add-Member -memberType noteProperty -name hotfixes -Value ""
           $sObjHotfixes.hotfixes = gethotfixes $srv
           $objColl += $sObjHotfixes
      }
 }
  
 compareHotfixes $objColl $fullreport
 $script:ReturnObjColl
  



Have a look and let me know what you think.

May the Force...
t