Monday, December 8, 2014

PowerShell domain disk statistics & block size report

We've been battling with disk block size issues on our SAN since we got it.  We're to a point where we're converting all our exiting VMs over to 32k block size disks.  Our SAN kind of requires it to function smoothly.

The below PowerShell script builds off my last domain disk script and adds the block size of the NTFS partition.  It lists the disk alongside the VMFS datastore it is stored on.  Using it you can easily see if the disk is on the proper data store.gging.

The script emails an HTML formatted report that shows each disk, its size, how much is used, it's block size, the datastore it's on, and how much space is left.  It also writes an HTML file to C:\Scripts so you can manually load up the output for testing.

I run this as needed with domain admin rights.  Change $Debug to $True to get console output while it runs.

Below is an example of the output as seen in email:


clear-host 

$ErrorActionPreference = "silentlycontinue"
$Debug = $False
$user = "domain\user"
$pass = "password"
$Global:ReportBody = ""
$vCenter = "vcenter"
$Color = "blue"

If ($Debug){
   $Global:eMailRecipient = "user@company.com"  #--[ Destination email address during debug.
}Else{
   $Global:eMailRecipient = "group@$company.com"    #--[ Destination email address.
}
                                                        
Function Cleanup-Variables {
   Get-Variable | Where-Object { $startupVariables -notcontains $_.Name } | % { Remove-Variable -Name "$($_.Name)" -Force -Scope "global" }
}

Function SendEmail {
   $email = New-Object System.Net.Mail.MailMessage
   $email.From = "DailyReports@company.com"
   $email.IsBodyHtml = $True
   $email.To.Add($Global:eMailRecipient)
   $email.Subject = "PowerShell Disk Block Size Report"
   $email.Body = $Global:ReportBody
   $smtp = new-object Net.Mail.SmtpClient('email-server-ip')
   $smtp.Send($email)
   If ($Debug){Write-Host "`n`rEmail sent..."}
}

Function Ping {
   $timeout = 120;
   trap { continue; }
   $ping = new-object System.Net.NetworkInformation.Ping
   $reply = new-object System.Net.NetworkInformation.PingReply
   $reply = $ping.Send($Global:Target, $timeout);
   if( $reply.Status -eq "Success" ){return $true;}
   return $false;
}

Function Get-VMDriveReport ($Color) {
   If ($Debug){Write-Host "`r`n--[ Processing $Global:Target ]--".PadRight((95-$Global:Target.length),"-") -ForegroundColor $Color}
   If (Test-Connection -ComputerName $Global:Target -count 1 -BufferSize 16 -ErrorAction SilentlyContinue ) {
      $HtmlData += '<tr>'
      $VM = Get-VM $Global:Target
      #$Datacenter = $VM | Get-Datacenter
      #$Cluster = $VM | Get-Cluster
      #$Datastore = $VM | Get-Datastore
      #$VMView = Get-View -ViewType VirtualMachine -Filter @{"Name"=$Global:Target}
      #$VMHardDisks = Get-HardDisk -VM $Global:Target
      #$VirtualSCSIControllers = $VMView.Config.Hardware.Device | where {$_.DeviceInfo.Label -match "SCSI Controller"}
      #$VirtualDiskDevices = $VMView.Config.Hardware.Device #| where {$_.ControllerKey -eq $VirtualSCSIControllers.Key}
      $DiskDrives = Get-WmiObject Win32_DiskDrive -ComputerName $Global:Target
      $datastores = Get-Datastore -Name ($Global:Target | Get-HardDisk -DiskType flat | %{$_.Filename.Split(']')[0].TrimStart('[')} | Sort-Object -Unique) | Sort-Object -Property Name
      $DiskCount = 1
      foreach($DiskDrive in $DiskDrives){
         $DiskNumber = ($DiskDrive.Deviceid.Substring(4)).trimstart("PHYSICALDRIVE")
         If ($Debug){Write-Host "Physical Disk # =" $DiskNumber}
         $vmdisk = Get-VM $Global:Target | Get-harddisk
         $vmdiskDatastore = $vmdisk.Filename.Split(']')[0].trim('[')
         If ($Debug){Write-Host "Datastore       =" $vmdiskDatastore}
           
         $Partitions = Get-WmiObject  -ComputerName $Global:Target -Query "ASSOCIATORS OF {Win32_DiskDrive.DeviceID=`"$($DiskDrive.DeviceID.Replace('\','\\'))`"} WHERE AssocClass = Win32_DiskDriveToDiskPartition"
         If ($Partitions) {
            $VolCount = 1
            foreach($Part in $Partitions){
               If ($Debug){Write-Host "---------------"}
                Write-Host "   Partiton #      =" $Part.DeviceID
               $Vols = Get-WmiObject -ComputerName $Global:Target -Query "ASSOCIATORS OF {Win32_DiskPartition.DeviceID=`"$($Part.DeviceID)`"} WHERE AssocClass = Win32_LogicalDiskToPartition"
               ForEach($Vol in $Vols){
                $Block = Get-WmiObject -ComputerName $Global:Target -Class win32_volume | where-object { ($_.Name -eq ($Vol.name + "\")) -and ($_.FileSystem -eq "NTFS") } | Select-Object Blocksize
                If ($Debug){Write-Host "   Partition ID    =" $Vol.name}
                                                                                                      
                $HtmlData += '<td><font color=' + $Color + '>' + $Global:Target + '</td>' #--[ Target name ]--
                $HtmlData += '<td><font color=' + $Color + '>' + ($DiskDrive.Deviceid.Substring(4)) + '</td>'  #--[ Physical disk # ]--
                $HtmlData += '<td><font color=' + $Color + '>' + $VM.VMHost.Name.split(".")[0].ToUpper() + '</td>'   #--[ ESX Host ]--   
                $HtmlData += '<td><font color=' + $Color + '>' + $vmdiskDatastore + '</td>'    #--[ ESX Datastore ]--
                $HtmlData += '<td><font color=' + $Color + '>' + $Block.Blocksize + '</td>'   #--[ Blocksize ]--
                 $DiskSizeGB = ([System.Math]::Round(($DiskDrive.Size/1GB),0))
                 $HtmlData += '<td><font color=' + $Color + '>' + $DiskSizeGB + '</td>'     #--[ Disk size in GB ]--
                 $HtmlData += '<td><font color=' + $Color + '>' + $VolCount + "</td>"     #--[ Volume or Partition # ]--
                 $HtmlData += '<td><font color=' + $Color + '>' + $DiskDrive.Partitions + '</td>'       #--[ Total # of Vols or Partitions ]--
                 $HtmlData += '<td><font color=' + $Color + '>' + $Vol.Name + '</td>'      #--[ Drive letter in Windows ]--
                 If([string]::IsNullOrEmpty($Vol.VolumeName)){
                    $HtmlData += '<td><font color=' + $Color + '>N/A</td>'     #--[ Volume label if one exists ]--
                 }Else{
                    $HtmlData += '<td><font color=' + $Color + '>' + $Vol.VolumeName + '</td>'  #--[ Volume label if one exists ]--
                 }
                 $PartSizeGB = ([System.Math]::Round(($Vol.Size/1GB),0))           #--[ Volume size in bytes ]--
                 $HtmlData += '<td><font color=' + $Color + '>' + $PartSizeGB + '</td>'         #--[ Partition size in GB ]--
                 $PartFreeGB = ([System.Math]::Round(($Vol.FreeSpace/1GB),0))     
                 $HtmlData += '<td><font color=' + $Color + '>' + $PartFreeGB + '</td>'         #--[ Partition freespace in GB ]--                                
                 $HtmlData += '</tr>'#>
                 $VolCount++
                }
             }
         }
        $DiskCount++
      }
      $Global:ReportBody += $HtmlData
   }Else{
      If (Ping){
         $Global:ReportBody += '<tr><td><font color=' + $Color + '>' + $Global:Target + '</td><td colspan=11><center><font color=darkcyan>Ping succeeded but failed to successfully connect to target system.</center></td></tr>'
         If ($Debug){Write-Host " ...Failed to sucessfully ping target system...`n" -ForegroundColor Cyan}
      }Else
         $Global:ReportBody += '<tr><td><font color=' + $Color + '>' + $Global:Target + '</td><td colspan=11><center><font color=darkcyan>Failed to successfully ping or connect to target system.</center></td></tr>'
      }
   }  #--[ End - test-connection ]--
sleep -Milliseconds 50
}

#--[ Add header to html log file ]--
$Global:ReportBody = @() 
$Global:ReportBody += '
<style type="text/css">
    table.myTable { border:5px solid black;border-collapse:collapse; }
    table.myTable td { border:2px solid black;padding:5px;background: #E6E6E6 }
    table.myTable th { border:2px solid black;padding:5px;background: #B4B4AB }
    table.bottomBorder { border-collapse:collapse; }
    table.bottomBorder td, table.bottomBorder th { border-bottom:1px dotted black;padding:5px; }
</style>
<center><h1>-= SAN Disk Block Size Report =-</h1></center>
The following report displays disks configured on every server and PC in the domain.
<ul>
    <li>Only powered-on Vms are shown since the script relies on WMI to get the block size.</li>
    <li>"Vol", or "Volume" refers to a Windows disk partition.</li>
    <li>The hidden system partition, which is typically Vol 0 on Disk 0, is not shown.</li>
    <li>Disk numbers are assigned beginning with 0 (zero).</li>
    <li>Partition numbers are assigned beginning with 1 (not 0).</li>
</ul>'

ForEach ($VC in $vCenter){
   Connect-VIServer -Server $VC -User $user -Password $pass | Out-Null
   $GuestVMs = get-vm | where {$_.PowerState -eq "PoweredOn" -and $_.Guest.OSFullName -match "Microsoft Windows*" } | sort name

   $Global:ReportBody += '<table class="myTable"><tr><th>Target VM</th><th>Disk ID</th><th>ESX Host</th><th>DataStore</th><th>Block Size</th><th>Disk Size</th><th>Vol #</th><th>Total Vols</th><th>Drive</th><th>Vol Label</th><th>Vol Size</th><th>Vol Free</th></tr>'
   ForEach($Global:Target in $GuestVMs ){
      Get-VMDriveReport $Color
      If ($Color -eq "green"){$Color = "blue"}Else{$Color = "green"}
   }
$Global:ReportBody += '</table><br><br>'
}     

If (Test-Path "c:\Scripts\temp.html"){Remove-Item "c:\Scripts\temp.html" -Confirm:$false -Force:$true }
If ($Debug){$Global:ReportBody | Out-File c:\Scripts\temp.html}

SendEmail
 
If ($Debug){Write-Host "`r`n--- COMPLETED ---" -ForegroundColor Red}