Dwarfsoft [GPA]

PowerCLI – Virtual Machine Disk Usage Report

by on Oct.25, 2012, under Scripting, Work


Print This Post Print This Post

So, this is one of my largest PowerCLI/PowerShell scripts. This maps for each Virtual Machine, every Disk that is attached and trace back to what the LUN ID is that the disk is stored on, be it a VMDK, Thick/Thin provisioned disk, Physical/Virtual mode RDM, etc.

For the moment I will just paste the script as it stands, but I hope to return and explain some of the components.

Param ($OutFile = ".\VMDiskUsageReport.csv", [switch]$Debug)
 
  Connect-VIServer VIServerName
  $vms = get-vm 
  $count = 0
 
  $VMSummaries = @()
 
  if ($Debug)
  {
    #Limit the Number of VMs if we are running in Debug mode
    $vms = ($vms | ?{ ($_.Name -like '*MG*') -or ($_.Name -like '*EV*') })
  }
  Foreach ($vm in $vms)
  {
    $count++
    $percentcomplete = ($count/$vms.count)*100
    Write-Progress -activity "Mapping VM Disks to LUNs" -status "Getting View" -PercentComplete $percentComplete
    $vmview = $vm | Get-View
 
    $vdd = $vm |Get-HardDisk
 
    $vdd | %{
      $vdisk = $_
      Write-Progress -activity "Mapping VM Disks to LUNs" -status "Mapping Hard-Disk" -PercentComplete $percentComplete
      $VMSummary = “” | Select VM, HostName, VMHostName,VMHostCluster, Site, PowerState, DiskType, DiskFile, DiskName, LogicalDisks, LogicalDisksGuess,  DataStore, DiskSize, DiskSizeInGB, SCSIController, SCSITarget, LUNId, SANLunId
      $VMSummary.VM = $VM.Name
      $VMSummary.HostName = $VMView.Guest.HostName
      $VMSummary.VMHostName = $VM.VMHost.Name
      $VMSummary.VMHostCluster = $VM.VMHost.Parent.Name
      $VMSummary.Site = $VMSummary.VMHostCluster.Substring(3,3)
      if (($VMSUmmary.HostName -eq "") -or ($VMSummary.HostName -eq $null)) { $VMSummary.HostName = $VM.Name }
      $VMSummary.PowerState = $VM.PowerState
      $VMSummary.DiskName = $_.Name
 
      $VMSummary.DiskFile = $_.ExtensionData.Backing.FileName #$_.FileName
      $VMSummary.DataStore = $VMSummary.DiskFile.Split("[]")[1]
      $VMSummary.DiskSize = $_.CapacityKB * 1KB
      $VMSummary.DiskSizeInGB = $VMSummary.DiskSize/1GB
 
      $ControllerKey = $vdisk.ExtensionData.ControllerKey
      $Controller = $vmview.Config.Hardware.Device | ?{ $_.Key -eq $ControllerKey }
      $BusNumber = $Controller.BusNumber
 
      $VMSummary.SCSIController = $_.ExtensionData.ControllerKey-1000  # $scsi.BusNumber
      $VMSummary.SCSITarget = $_.ExtensionData.UnitNumber #$_.UnitNumber
 
      $VMSummary.DiskType = $_.DiskType
 
      switch -wildcard ($_.DiskType)
      {
        "Flat" {
          #On DS
          #$ds = Get-Datastore $VMSummary.DataStore
          Write-Progress -activity "Mapping VM Disks to LUNs" -status "Getting LUN from Flat File" -PercentComplete $percentComplete
          $ds = Get-Datastore -VM $vm | ?{ $_.Name -eq $VMSummary.DataStore }
          $dsview = ($ds | Get-View)
 
          $ESXView = Get-View -id $vm.Host.Id
          $StorageSystemView = Get-View -Id $ESXView.ConfigManager.StorageSystem
 
          $volume = ($StorageSystemView.FileSystemVolumeInfo.MountInfo | ?{$_.Volume.Name -eq $VMSummary.DataStore }).Volume
          $diskname = ($volume.Extent | Select DiskName -f 1).DiskName
          $VMSummary.LUNId = $diskname
        }
        "Raw*" {
          Write-Progress -activity "Mapping VM Disks to LUNs" -status "Getting LUN from RDM" -PercentComplete $percentComplete
          $VMSummary.LUNId = ([string]$vdisk.ScsiCanonicalName)
          Write-Host "$($VMSummary.HostName) RDM $($VMSummary.LUNId)" -F Green
        }
      }
 
      $DiskFileName = $VMSummary.DiskFile.Split("[]/.")[-2]
      $FolderName = $VMSummary.DiskFile.Split("[]/.")[-3]
 
      if ($vm.PowerState -eq "PoweredOn")
      {
        try {
          #On VM
          Write-Progress -activity "Mapping VM Disks to LUNs" -status "Extracting Guest Logical Disks/Volumes" -PercentComplete $percentComplete
          Write-Host "SCSI $($VMSummary.HostName)/$($VMSummary.SCSIController):$($VMSummary.SCSITarget) " -F Blue
          $PhysicalDisks = gwmi -ComputerName $VMSummary.HostName Win32_DiskDrive -ErrorAction "silentlyContinue" | ?{($_.SCSIBus -eq $VMSummary.SCSIController) -and ($_.SCSITargetId -eq $VMSummary.SCSITarget) }
          $PartName = ($PhysicalDisks.PSBase.GetRelated('Win32_DiskPartition') | Select Name -f 1).Name
          $DiskParts = (gwmi -ComputerName $VMSummary.HostName Win32_DiskPartition -ErrorAction "silentlyContinue" | ?{ $_.Name -eq $PartName } )
          $LogicalDisks = @($DiskParts | %{ $_.PSBase.GetRelated('Win32_LogicalDisk') })
          #Ldi = logical disk info
          $ldi = ""
          $LogicalDisks | %{ $ldi += "    $($_.DeviceId) [$($_.VolumeName)] ($( "{0:P}" -F ($_.FreeSpace/$_.Size) ) Free);".Trim() }
 
          $VMSummary.LogicalDisks = $ldi
        } catch {
        }
 
      } else
      {
        Write-Host "SCSI $($VMSummary.HostName)/$($VMSummary.SCSIController):$($VMSummary.SCSITarget) " -F Gray
      }
 
      $x = ($VMSummary.LUNId+"    ").Substring(4).Trim()
      if (($x.Length -gt 0)) {
        $VMSummary.SANLunId = [String]::Join(":",(0..(($x.Length)-1) | ?{ ($_/2) -eq [int]($_/2) } | %{ $x.Substring($_,2) }))
      }
      $VMSummary.SANLunId = $VMSummary.SANLunId.ToUpper()
 
      if ($VMSummary.LogicalDisks.Length -le 0)
      {
        Write-Host " Logical Disks guessing " -F Red
        Write-Progress -activity "Mapping VM Disks to LUNs" -status "Could not Map Logical Disks, so Guessing them Instead" -PercentComplete $percentComplete
        # A factor that we use to weed out Volumes that are too small for the drive
        $differential = 0.99
        if ($vm.Guest.Disks.Count -eq $vm.HardDisks.Count)
        {
          # a 1 for 1 match, this may not be entirely correct, but assume that it is
          #$vdisk.CapacityKb*1KB
          Write-Host " a 1 for 1 match, this may not be entirely correct, but assume that it is" -F Red
          $VMSummary.LogicalDisksGuess = ($vm.Guest.Disks | sort -Property Path |?{ (($_.Capacity / $VMSummary.DiskSize) -lt 1) -and (($_.Capacity / $VMSummary.DiskSize) -gt $differential) } | %{ "$($_.Path.Substring(0,2)) {$("{0,5:N2} GB" -F ($_.Capacity/1GB))} ($("{0,5:N2} GB Free" -F ($_.FreeSpace/1GB)))" }) -Join "; or "
        } elseif ($vm.Guest.Disks.Count -lt $vm.HardDisks.Count)
        {
          # Obviously one disk is not partitioned
          Write-Host "Obviously one disk is not partitioned" -F Red
          $VMSummary.LogicalDisksGuess = ($vm.Guest.Disks | sort -Property Path |?{ (($_.Capacity / $VMSummary.DiskSize) -lt 1) -and (($_.Capacity / $VMSummary.DiskSize) -gt $differential) } | %{ "$($_.Path.Substring(0,2)) {$("{0,5:N2} GB" -F ($_.Capacity/1GB))} ($("{0,5:N2} GB Free" -F ($_.FreeSpace/1GB)))" }) -Join "; or "
        } else
        {
          #More Volumes than assigned disks. There must be multiple partitions
          Write-Host "More Volumes than assigned disks. There must be multiple partitions" -F Red
          try{ 
          $VMSummary.LogicalDisksGuess = ($vm.Guest.Disks | sort -Property Path |?{ (($_.Capacity / $VMSummary.DiskSize) -lt 1) -and (($_.Capacity / $VMSummary.DiskSize) -gt 0) } | %{ "$($_.Path.Substring(0,2)) {$("{0,5:N2} GB" -F ($_.Capacity/1GB))} ($("{0,5:N2} GB Free" -F ($_.FreeSpace/1GB)))" }) -Join "; or "
          } catch
          {
          }
        }
        $VMSUmmary.LogicalDisksGuess
      }
      Write-Progress -activity "Mapping VM Disks to LUNs" -status "Adding Summary to List" -PercentComplete $percentComplete      
      $VMSummaries += $VMSummary    
    }
  }
  $VMSummaries | Export-Csv -Path $OutFile

One of the strengths of this script is that it tries to map each VMDK/RDM/etc through the OS to get a drive letter, and if it cannot do this directly (through VMGuest or gwmi to the Servers FQDN) then it tries to guess what the Drive letter COULD be based upon the size of the disk that is presented and the size of the disks that are visible by the OS.

Cheers, Chris.

:, , , , , ,

Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!