Showing posts with label wmi. Show all posts
Showing posts with label wmi. Show all posts

23 November, 2015

Which DNS records would be scavenged - AD

In connection with a previous post on listing DNS scavenging settings, I thought I'd post those couple of lines of codes which gave me confidence before turning on or modifying scavenging settings - before I turn on any automation which only exists to delete stuff from production environment I always have a second thought when I put my head on the pillow, "did I really set the right values or will the phone ring in 2 hours waking me up and telling me there's a DNS outage?"

To make it a bit more scientific than "close your eyes and click OK", here is a couple of lines of PS which can help you identify all records from a DNS zone which would be deleted based on your thresholds.
  • Set parameters, DNS server name, the DNS zone and the age threshold which specifies how many days older records should be deleted. Scavenging has a 7 + 7 days "No-refresh" + "Refresh" interval, so records older than 14 days will potentially be deleted when scavenging process runs:
    #set parameters
    $server = "c3podc1"
    $domain = "tatooine.com"
    $agetreshold = 14
  • Threshold in hours from Microsoft's beginning of time definition (1st Jan 1601):
    # calculate how many hours is the age which will be the threshold
    $minimumTimeStamp = [int] (New-TimeSpan -Start $(Get-Date ("01/01/1601 00:00")) -End $((Get-Date).AddDays(-$agetreshold))).TotalHours
  • Enumerate all records older than the time threshold
    # get all records from the zone whose age is more than our threshold $records = Get-WmiObject -ComputerName $dnsServer -Namespace "root\MicrosoftDNS" -Query "select * from MicrosoftDNS_AType where Containername='$domain' AND TimeStamp<$minimumTimeStamp AND TimeStamp<>0 "
  • List the records and the time stamps
    # list the name and the calculated last update time stamp
    $records | Select Ownername, @{n="timestamp";e={([datetime]"1.1.1601").AddHours($_.Timestamp)}}
The output should look like this:
DNS records with time stamps


The full script:
 #set parameters  
 $dnsServer = "c3podc1"  
 $domain = "tatooine.com"  
 $agetreshold = 14  
   
 # calculate how many hours is the age which will be the threshold  
 $minimumTimeStamp = [int] (New-TimeSpan -Start $(Get-Date ("01/01/1601 00:00")) -End $((Get-Date).AddDays(-$agetreshold))).TotalHours  
   
 # get all records from the zone whose age is more than our threshold   
 $records = Get-WmiObject -ComputerName $dnsServer -Namespace "root\MicrosoftDNS" -Query "select * from MicrosoftDNS_AType where Containername='$domain' AND TimeStamp<$minimumTimeStamp AND TimeStamp<>0 "  
   
 # list the name and the calculated last update time stamp  
 $records | Select Ownername, @{n="timestamp";e={([datetime]"1.1.1601").AddHours($_.Timestamp)}}  
   



t


13 January, 2015

Move page file remotely - OS

With many servers in the environment, there are inevitably a bunch of them which are old, they have small disk capacity but they are needed "just for another couple of months" until the new system is up... and a couple of years later you are still the chosen one to keep them alive.

There are many challenges with these old boxes, so let me pick one: there's not enough disk space on drive C: and there's not much more to delete anymore. Last resort: move the pagefile to another drive (if there's one in the box). Let's script it to be able to make this change on many servers remotely.

Need to check a couple of things:
  • the original page file's details
  • target drive exists
  • it has sufficient space to accommodate the page file (based on MaximumSize)
  • target drive is local and is not hosted on SAN

Create the output object and get data of the current page file:
$obj = "" | Select ComputerName,OldPageFile,OldInitSize,OldMaxSize,Result
$obj.ComputerName = $srv
$pagefiledata = gwmi -ComputerName $srv -Class Win32_PageFileSetting

Get details of the volume where we want to move the page file (check whether it exists, whether there's enough space on it - here the limit is the size of the current page file twice)
$targetvolume = gwmi -ComputerName $srv -Class Win32_LogicalDisk -Filter "name='$movetodrive'"

Create new page file:
$result = Set-WmiInstance -ComputerName $srv -Class Win32_PageFileSetting -Arguments @{name="$targetfilename";InitialSize=$obj.OldInitSize;MaximumSize=$obj.OldMaxSize;}

Sometimes - especially on Windows Server 2003 - the Set-WmiInstance can't create the page file if the InitialSize and MaximumSize are set in the same command, for that the workaround is to create the page file as system managed (without additional parameters) and set the size afterwards.
$result = Set-WmiInstance -ComputerName $srv -Class Win32_PageFileSetting -Arguments @{name="$targetfilename"}

if($result.name -eq $targetfilename){
  $newPGFile = gwmi -ComputerName $srv -Query "Select * from Win32_PageFileSetting where name '$($targetfilename.replace("\","\\"))'"
  $newPGFile.InitialSize = $obj.OldInitSize
  $newPGFile.MaximumSize = $obj.OldMaxtSize
  $FinalResult = $newPGFile.Put()
}


If we do this, the script should check all the parameters we set and the existence of the new page file as part of error handling:
$checkoutPGFile = gwmi -ComputerName $srv -Query "Select * from Win32_PageFileSetting where name '$($targetfilename.replace("\","\\"))'"
if(!($checkoutPGFile -and ($checkoutPGFile.Maximumsize -eq $obj.OldMaxSize) -and ($checkoutPGFile.Maximumsize -eq $obj.OldMaxSize))){...

If all is good with the new page file, we can get rid of the old one, of course this will require a reboot but I decided to leave it to the scheduled reboots of the servers.
$oldPageFileObject = gwmi -ComputerName $srv -Query "Select * from Win32_PageFileSetting where name = '$searchString'"
$oldPageFileObject.Delete()

The full script which can take a list of hosts from the pipe, does all the checks looks like this (obviously I stripped out all the logging and fancy stuff so the essence of it is easier to see):
   
 $hostlist = @($input)  
   
 $objColl = @()  
 $movetodrive = "D:"  
 $targetfilename = $movetodrive + "\pagefile.sys"  
   
 foreach($srv in $hostlist){  
    $obj = "" | Select ComputerName,OldPageFile,OldInitSize,OldMaxSize,Result  
    $obj.ComputerName = $srv  
   
    $pagefiledata = gwmi -ComputerName $srv -Class Win32_PageFileSetting  
      
    if($pagefiledata){  
       $obj.OldPageFile = $pagefiledata.name  
       $obj.OldInitSize = $pagefiledata.Initialsize  
       $obj.OldMaxSize = $pagefiledata.Maximumsize  
         
       $targetvolume = gwmi -ComputerName $srv -Class Win32_LogicalDisk -Filter "name='$movetodrive'"  
         
       if(!$targetvolume){  
          $obj.Result = "$movetodrive does not exist"  
          $objColl += $obj  
          Continue  
       }  
         
       if(($targetvolume.Freespace / 1MB) -lt ($obj.OldMaxSize * 2)){  
          $obj.Result = "Not enough space on $movetodrive"  
          $objColl += $obj  
          Continue  
       }  
         
       $result = Set-WmiInstance -ComputerName $srv -Class Win32_PageFileSetting -Arguments @{name="$targetfilename";InitialSize=$obj.OldInitSize;MaximumSize=$obj.OldMaxSize;}  
         
       # this is needed in case the WMI instance can't be created with all parameters in one go  
       if(!($result.name -eq $targetfilename)){  
          $result = Set-WmiInstance -ComputerName $srv -Class Win32_PageFileSetting -Arguments @{name="$targetfilename"}  
         
          if($result.name -eq $targetfilename){  
             $newPGFile = gwmi -ComputerName $srv -Query "Select * from Win32_PageFileSetting where name '$($targetfilename.replace("\","\\"))'"  
             $newPGFile.InitialSize = $obj.OldInitSize  
             $newPGFile.MaximumSize = $obj.OldMaxtSize  
             $FinalResult = $newPGFile.Put()  
          }  
            
          $checkoutPGFile = gwmi -ComputerName $srv -Query "Select * from Win32_PageFileSetting where name '$($targetfilename.replace("\","\\"))'"  
          if(!($checkoutPGFile -and ($checkoutPGFile.Maximumsize -eq $obj.OldMaxSize) -and ($checkoutPGFile.Maximumsize -eq $obj.OldMaxSize))){  
             $obj.Result = "Could not create new page file"  
             $objColl += $obj  
             Continue     
          }  
       }  
         
       if($result.name -eq $targetfilename){  
          $searchString = $obj.OldPageFile.replace("\","\\")  
          $oldPageFileObject = gwmi -ComputerName $srv -Query "Select * from Win32_PageFileSetting where name = '$searchString'"  
          $oldPageFileObject.Delete()  
       }  
         
       if((gwmi -ComputerName $srv -Class Win32_PageFileSetting).name -eq $targetfilename){  
          $obj.Result = "SUCCESS - please reboot the host"  
       }  
       else{  
          $obj.Result = "Could not move page file"  
       }  
    }  
      
    $objColl += $obj  
 }  
   
 $objColl  
   



t

08 January, 2015

Determine if a drive is SAN drive - OS

If you have servers connected to SAN you would think that you don't really have to worry about the physical representation of your volumes, in other words, you don't need to know how many physical drives (spindles) are behind your e.g. D: drive.
Life is not simple. There are times when you need to know if a volume is on a local hard disk or is on a SAN LUN. An example would be when you need to move the pagefile out of drive C:, you might not want to put that on the SAN disk (there can be several reasons, one is that the volume may be in a dynamic or clustered disk group and can fail over to another node or just simply because SAN disk space is expensive therefore using it for paging is not the best use of you dollars.)

Now we have a case: we have a drive letter and we want to decide if the volume is on a SAN disk or not. Of course we want to do this remotely and on 100+ servers.
The philosophical problem with this is that engineers spent so much effort in the last decades on creating storage systems and disk manager sub-systems to hide the complex details of a storage - including multipath fiber channels, SAN switches, disk arrays...etc.) from the OS and to make sure that the OS can see a volume and does not need to worry about how it's presented and what's behind it. So any API that could be used to track down SAN drives remotely are vendor specific (it's different for EMulex, Qlogic or whatever HBA driver). I need something universal.

I was poking around the WMI classes and noticed that bits of information are in several classes, so started connecting the dots - nothing scientific, just trial and error as usual. If you look long enough you can connect the drive letter to a PNPDeviceID which can tell you if the physical drive is local or SAN, here is an example:
  • Win32_LogicalDiskToPartition: D: -> Disk #0, Partition#0
  • Win32_DiskDriveToPartition: Disk #0, Partition#0 -> PHYSICALDRIVE2
  • Win32_DiskDrive: PHYSICALDRIVE2 -> MPIO\DISK&....
    If the PNPDeviceID starts with MPIO (which stands for MultiPath I/O) then it's a drive hosted on SAN Array which the host can see on multiple fiber channels.

Just need a bit of regex matching to walk through this in Powershell:









  1. Get the disk number where the volume is hosted (and the partition number as well):
    $DriveletterToDiskNumberQuery = gwmi -ComputerName $srv -Class Win32_LogicalDiskToPartition | ?{$_.Dependent -imatch "Win32_LogicalDisk\.DeviceID=`"$driveletter\:`""} | select -First 1 | %{$_.Antecedent}
  2. Need to parse the exact Disk # from the long text which is in the WMI instance:
    $DriveletterToDiskNumber = ([regex]::Match($DriveletterToDiskNumberQuery, "Disk #\d+, Partition #\d+")).Value
  3. Take the disk number and lookup the DeviceID:
    $DiskNumberToDevideIDQuery = gwmi -ComputerName $srv -Class Win32_DiskDriveToDiskPartition | ?{$_.Dependent -imatch $DriveletterToDiskNumber} | select -First 1 | %{$_.Antecedent}
  4. Parse the DeviceID from the long text:
    $DiskNumberToDevideID = ([regex]::Match($DiskNumberToDevideIDQuery, "PHYSICALDRIVE\d+")).Value
  5. Get the PNPDeviceID of the given device to see if it's MPIO or not:
    gwmi -ComputerName $srv -Class Win32_DiskDrive | ?{$_.DeviceID -imatch $DiskNumberToDevideID} | Select -First 1 | %{$_.PNPDeviceID}
A simple script which takes the list of hosts from the pipe and outputs and object with the hostname, the PNPDeviceID and the Drive Type would look like this (without handling errors e.g. when there's no drive D or the host is not accessible...etc.):
 $hostlist = @($input)  
 $driveletter = "D"  
   
 foreach($srv in $hostlist){  
    $obj = "" | Select ComputerName,DriveType,PNPDeviceID  
    $obj.ComputerName = $srv  
   
    # Get the list Disk # for the given volume  
    $DriveletterToDiskNumberQuery = gwmi -ComputerName $srv -Class Win32_LogicalDiskToPartition | ?{$_.Dependent -imatch "Win32_LogicalDisk\.DeviceID=`"$driveletter\:`""} | select -First 1 | %{$_.Antecedent}  
   
    # parse the Disk and partition # from the text  
    $DriveletterToDiskNumber = ([regex]::Match($DriveletterToDiskNumberQuery, "Disk #\d+, Partition #\d+")).Value  
   
    # Get the DeviceID of the given Disk #  
    $DiskNumberToDevideIDQuery = gwmi -ComputerName $srv -Class Win32_DiskDriveToDiskPartition | ?{$_.Dependent -imatch $DriveletterToDiskNumber} | select -First 1 | %{$_.Antecedent}  
      
    # parse the DeviceID from the text  
    $DiskNumberToDevideID = ([regex]::Match($DiskNumberToDevideIDQuery, "PHYSICALDRIVE\d+")).Value  
   
    # get the PNPDeviceID of the given Device  
    $obj.PNPDeviceID = gwmi -ComputerName $srv -Class Win32_DiskDrive | ?{$_.DeviceID -imatch $DiskNumberToDevideID} | Select -First 1 | %{$_.PNPDeviceID}  
   
    if($obj.PNPDeviceID -imatch "^mpio"){  
       $obj.DriveType = "SAN"  
    }  
    else{  
       $obj.DriveType = "Local"  
    }  
   
    $obj  
 }  


t

02 February, 2013

Verify ComputerName remotely - OS

Quick question for you, my fellow IT engineers, how much do you trust the content of your DNS zone? Good question, isn't it? If a DNS zone and its servers are setup correctly (according to the size and requirements of the given infrastructure) then the content of DNS is supposed to be up-to-date, so DNS scavenging is setup to delete old records...etc.

However, in a global IT infrastructure, there's always someone rebuilding a server with a new name. Imagine this: there's a very enthusiastic engineer who is testing a new multicast OS build method by installing OS on 200+ virtual machines (VM). And to make this test more comprehensive, he/she rebuilds these VMs with new names 4-5 times - but keeps the IP addresses because changing the MAC <-> IP assignment would be too much hassle... yeah, you got it right, cleaning up DNS is not that much hassle at all?! :)
So at the end of the test we end up with ~800 incorrect DNS records (which we initially don't know about).

I've got scripts to check out different aspects of list of servers remotely and if I hit a name which points to an IP of one of those VMs and it already has a new name, then all remote queries will fail (while ping will be successful). Why? Because Windows has a so called Strict Name checking, it does not allow incoming connections if the connection has a different target name. (unless you disable this behaviour).

Solution:
To make sure my checks are transparent, I need to see if a server's name is different to what it was on my list. Here is an example code which can do that. For the sake of the simple example, I want to query the last boot time of each host on a list:

   
# get the IP of the server from DNS

   $ip = [System.Net.Dns]::GetHostAddresses($srv)[0].IPAddressToString

      # creating an object to store results
      $obj = "" | Select Computername,Real_computername,LastBootTime
      $obj.Computername = $srv

     
# reading the name of the computer (contacted via its IP) from WMI

      $obj.Real_computername = (gwmi -class Win32_ComputerSystem -ComputerName $ip).name

      
# reading boot time of the remote host

      $obj.LastBootTime = (gwmi -class Win32_OperatingSystem -ComputerName $ip).lastbootuptime

To explain the essential line:
$ip = [System.Net.Dns]::GetHostAddresses($srv)
This line returns an array, but this time I only care about the first IP, so the [0] element of the array, because I have only 1 IP for each sever.
The rest of the script is straightforward and commented.

Example output:
Computername          Real_computername       LastBootTime
------------          -----------------       ------------
R2D2                  NEWR2D2                 20130129000920.125000+060



Entire example code:
 ## Usage: PS C:\> gc hostlist.txt | getBootTime.ps1
  
 function PingServer ([string]$srv){
      $ping = new-object System.Net.Networkinformation.Ping
      Trap {Continue} $pingresult = $ping.send($srv, 3000, [byte[]][char[]]"z"*16)
      if($pingresult.Status -ieq "Success"){$true} else {$false}
 }
  
 $hostlist = @($Input)
 foreach($srv in $hostlist){
  
      # get the IP of the server from DNS
      $ip = [System.Net.Dns]::GetHostAddresses($srv)[0].IPAddressToString
  
      if(PingServer $ip){
  
           # creating an object to store results
           $obj = "" | Select Computername,Real_computername,LastBootTime
           $obj.Computername = $srv
  
           # reading the name of the computer (contacted via its IP) from WMI
           $obj.Real_computername = (gwmi -class Win32_ComputerSystem -ComputerName $ip).name
           # reading boot time of the remote host
           $obj.LastBootTime = (gwmi -class Win32_OperatingSystem -ComputerName $ip).lastbootuptime
           $objcoll += $obj
      }
 }  


May the Force...
t