Showing posts with label xml. Show all posts
Showing posts with label xml. 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

02 June, 2013

Edit scheduled task remotely - OS

The other day, I had a couple of 100 servers where I needed to edit a particular scheduled task. Fortunately or unfortunately all of them were Windows 2008 servers. Why am I saying that?
On Windows 2003, scheduled tasks are simple, there's one command or action, start time, run as credentials and some little other bits. So if you want to "edit" a task, you can just delete and recreate it with whatever change you wanted to make.
Unfortunately, on Windows 2008, it's not that simple. There are multiple triggers you can define, multiple actions a task can run...etc.:

Scheduled Task on Windows 2008

However, fortunately, you can export a scheduled task into an xml file, which is quite cool as the xml format is easy to handle in PowerShell.
You can just edit the xml and import it back (basically recreate the whole task with all the settings defined in the xml).

Easy to get xml representation of a Scheduled task in PowerShell

A couple of key things in the script below. Connect to the remote machine and get task details:
$schedSvc = New-object -ComObject Schedule.Service
$schedSvc.connect($srv
$schFolder = $schedSvc.GetFolder("\"
$schFolder = $schFolder.GetFolder($taskfolder
$schedTask = $schFolder.GetTask($taskName
$schedTask.xml | Out-File "$env:TEMP\$srv-$taskname.xml" -Force

Edit the xml and create a new file:
gc "$env:TEMP\$srv-$taskname.xml" | %{
....
....
} | Out-File "$env:TEMP\$srv-$taskname-new.xml" -Force 


Recreate the scheduled task remotely (for this I use schtasks.exe for the sake of simplicity, although the COM object mentioned above could be used as well):
$res = schtasks.exe /create /S $srv /RU SYSTEM /TN "$taskfolder\$taskName" /XML "$env:TEMP\$srv-$taskname-new.xml" /F

The script has Object type output which looks like this:
ComputerName oldValue Result
------------ -------- ------
R2D2              120 OK
JARJAR            120 OK
C3PO             3456 OK

The script (without log and error handling):
   
        
        
 param(     [string] $hosts = "",  
           [string] $taskName = "",  
           [string] $taskFolder = "",  
           [string] $newvalue = "")  
   
 #### Collate the host list.  
 $hostlist = @($Input)  
 if ($hosts) {  
      if($hosts -imatch " "){  
           $hostsArr = @($hosts.split(" "))  
           $hostlist += $hostsArr  
      }  
      else{  
           $hostlist += $hosts  
      }  
 }  
   
 foreach($srv in $hostlist){  
      $script:sObject = "" | select ComputerName,oldValue,Result  
      $script:sObject.ComputerName = $srv  
        
      # connect to remote host and export the scheduled task to an xml  
      $schedSvc = New-object -ComObject Schedule.Service  
      $schedSvc.connect($srv)  
      $schFolder = $schedSvc.GetFolder("\")  
      $schFolder = $schFolder.GetFolder($taskfolder)  
      $schedTask = $schFolder.GetTask($taskName)  
      $schedTask.xml | Out-File "$env:TEMP\$srv-$taskname.xml" -Force  
        
      # edit the given text in the xml  
      if(Test-Path "$env:TEMP\$srv_$taskname.xml"){  
           gc "$env:TEMP\$srv-$taskname.xml" | %{  
             
                if($_ -imatch "\\myapplication.exe\."){  
                     $script:sObject.oldValue = ([regex]::Match($_, "-parameter=\d")).Value -ireplace "-parameter=",""  
   
                     $newline = $_ -ireplace "parameter=\d+", "parameter=$newvalue"  
                       
                }  
                else{  
                     $newline = $_  
                }  
                $newline  
           } | Out-File "$env:TEMP\$srv-$taskname-new.xml" -Force  
             
             
           # import the new task xml  
           $res = schtasks.exe /create /S $srv /RU SYSTEM /TN "$taskfolder\$taskName" /XML "$env:TEMP\$srv-$taskname-new.xml" /F  
             
           if($res -imatch "Success"){  
                $script:sObject.Result = "OK"  
           }  
           else{  
                $script:sObject.Result = "Could not import task $taskname"  
           }  
      }  
      else{  
           $script:sObject.Result = "Could not export task $taskname"  
      }  
      $objColl += $script:sObject  
 }  
   
 $objColl  
   


t