Home > datastore, PowerCLI, PowerShell, Storage Views, VMDK > Storage Views – Datastores

Storage Views – Datastores

November 14th, 2011 Leave a comment Go to comments

In the vCenter Client, since vSphere 4, you can find a Storage Views tab on several of the VI containers. The data in these Storage Views is collected and provided by the vCenter Storage Monitoring plug-in.

Have a look at David Davis‘s post, called Using VMware vSphere Storage Views, for more information on what you can do with the Storage Views.

Some time ago I got a question from Andrew how the Multipathing Status presented in the Storage Views could be detected and reported upon by a PowerCLI script. What looked rather simple at first, turned out to be a bit more difficult than I anticipated.

The primary reason I created this function, is that it will allow to schedule the creation of the Storage Views – Datastores values at regular intervals. A feature that is unfortunately missing from the vCenter Client plug-in.

With those saved values it is easy to produce reports that show who is consuming the storage over time.

The script

function Get-StorageViewDatastore{
<#
.SYNOPSIS  Retrieve the "Storage Views - Show all Datastores" values
.DESCRIPTION The function calculates and returns all the values
  that you get in the Storage Views - Show all Datastores panel.
.NOTES  Author:  Luc Dekens
.PARAMETER VMHost
  Specify the ESX(i) server for which you want to retrieve
  the Storage Views values.
.PARAMETER Cluster
  Specify the Cluster for which you want to retrieve
  the Storage Views values.
.PARAMETER Datacenter
  Specify the Datacenter for which you want to retrieve
  the Storage Views values.
.PARAMETER VM
  Specify the VM for which you want to retrieve
  the Storage Views values.
.PARAMETER ResourcePool
  Specify the ResourcePool for which you want to retrieve
  the Storage Views values.
.EXAMPLE
  PS> Get-StorageViewDatastore -VMHost MyCluster
.EXAMPLE
  PS> Get-Datacenter MyDC | Get-StorageViewDatastore
#>

  [CmdletBinding()]
  param(
  [parameter(ParameterSetName="VMHost",Mandatory = $true)]
  [PSObject]$VMHost,
  [parameter(ParameterSetName="Cluster",Mandatory = $true)]
  [PSObject]$Cluster,
  [parameter(ParameterSetName="Datacenter",Mandatory = $true)]
  [PSObject]$Datacenter,
  [parameter(ParameterSetName="VM",Mandatory = $true)]
  [PSObject]$VM,
  [parameter(ParameterSetName="ResourcePool",Mandatory = $true)]
  [PSObject]$ResourcePool
  )

  process{
    # Get the datastores and ESX(i) servers
    switch($PSCmdlet.ParameterSetName){
      "Datacenter" {
        if($Datacenter.GetType().Name -eq "String"){
          $Datacenter = Get-Datacenter -Name $Datacenter
        }
        $vms = Get-VM -Location $Datacenter
        $esx = Get-VMHost -Location $Datacenter
        $datastores = Get-Datastore -VMHost $esx
      }
      "Cluster" {
        if($Cluster.GetType().Name -eq "String"){
          $Cluster = Get-Cluster -Name $Cluster
        }
        $vms = Get-VM -Location $cluster
        $esx = Get-VMHost -Location $Cluster
        $datastores = Get-Datastore -VMHost $esx
      }
      "VMHost" {
        if($VMHost.GetType().Name -eq "String"){
          $VMHost = Get-VMHost -Name $VMHost
        }
        $vms = Get-VM -Location $VMHost
        $esx = $VMHost
        $datastores = Get-Datastore -VMHost $VMHost
      }
      "VM" {
        if($VM.GetType().Name -eq "String"){
          $VM = Get-VM -Name $VM
        }
        $vms = $vm
        $esx = $vm.VMHost
        $datastores = Get-Datastore -Name ($vms | Get-HardDisk -DiskType flat | %{$_.Filename.Split(']')[0].TrimStart('[')} | Sort-Object -Unique) | Sort-Object -Property Name
      }
      "ResourcePool" {
        if($ResourcePool.GetType().Name -eq "String"){
          $ResourcePool = Get-ResourcePool -Name $ResourcePool
        }
        $vms = Get-VM -Location $ResourcePool
        $esx = $vms  | %{$_.VMHost} | Sort-Object -Property Name -Unique
        $datastores = Get-Datastore -Name ($vms | Get-HardDisk -DiskType flat | %{$_.Filename.Split(']')[0].TrimStart('[')} | Sort-Object -Unique) | Sort-Object -Property Name
      }
    }

    # Create some helper collections
    $esxMoRef = $esx | %{$_.ExtensionData.MoRef}
    $vmMoRef = $vms | %{$_.ExtensionData.MoRef}

    # Set up a HBA hash table
    $hbaFCNodeWWN = @{}
    Get-VMHostHba -VMHost $esx -Type FibreChannel | %{
      $hbaFCNodeWWN["{0:X}" -f $_.NodeWorldWideName] = 0
    }

    # Handle all datastores
    foreach($ds in $datastores){
      $mpStatus = $usedSpace = $snapSpace = ""
      if($ds.Type -eq "VMFS"){
        $lun = $ds.ExtensionData.Info.Vmfs.Extent | %{$_.DiskName} |
          %{Get-ScsiLun -CanonicalName $_ -VmHost $esx -ErrorAction SilentlyContinue}
        $hbaFCCopy = $hbaFCNodeWWN.Clone()
        # Count Active and Standby paths
        # If there are 2 or more paths, the multi-path status is fully redundant
        Get-ScsiLunPath -ScsiLun $lun | where {"Active" ,"Standby" -contains $_.State} | %{
          $hbaFCCopy[$_.LunPath.Split(':')[0].Split('.')[1]] += 1
        }
        if(($hbaFCCopy.Values | where {$_} | Measure-Object).Count -gt 1){
          $mpStatus = "Full Redundancy"
        }
        else{
          $mpStatus = "Partial/No Redundancy"
        }
      }

      # Get the total used, snapshot, swap, shared and other space
      $usedSpace = 0
      $snapSpace = 0
      $vdiskSpace = 0
      $swapSpace = 0
      $otherSpace = 0
      $sharedSpace = 0
      if($ds.ExtensionData.Vm){
        Get-View $ds.ExtensionData.Vm | where {$vmMoRef -contains $_.MoRef -and $esxMoRef -contains $_.Runtime.Host} | %{
          $snapIndex = @()
          $dsPattern = '\[' + $ds.Name + '\]'
          $usedSpace += ($_.Storage.PerDatastoreUsage | where {$_.Datastore -eq $ds.ExtensionData.MoRef}).Committed
          if($_.LayoutEx.Snapshot){
            $snapIndex = $_.layoutEx.Disk | %{$_.Chain[1..($_.Chain.Count - 1)]} | %{$_.FileKey}
          }
          $snapIndex += $_.layoutEx.File | where {"snapshotList","snapshotData" -contains $_.Type -and $_.Name -match $dsPattern} | %{$_.Key}
          if($snapIndex){
            $snapSpace += ($_.LayoutEx.File | where {$snapIndex -contains $_.Key -and $_.Name -match $ds.Name} | Measure-Object -Property Size -Sum).Sum
          }
          $vdiskIndex = $_.layoutEx.File | where {"diskDescriptor","diskExtent" -contains $_.Type -and $_.Name -match $dsPattern} | %{$_.Key}
          if($vdiskIndex){
            $vdiskSpace += ($_.LayoutEx.File | where {$vdiskIndex -contains $_.Key} | Measure-Object -Property Size -Sum).Sum
          }
          $swapIndex = $_.LayoutEx.File | where {$_.Type -eq "swap" -and $_.Name -match $dsPattern} | %{$_.Key}
          if($swapIndex){
            $swapSpace += ($_.LayoutEx.File | where {$swapIndex -contains $_.Key} | Measure-Object -Property Size -Sum).Sum
          }
          $otherIndex = $_.LayoutEx.File | where {"log","config","extendedConfig","nvram","core" -contains $_.Type -and $_.Name -match $dsPattern} | %{$_.Key}
          if($otherIndex){
            $otherSpace += ($_.LayoutEx.File | where {$otherIndex -contains $_.Key} | Measure-Object -Property Size -Sum).Sum
          }
          $vmds = $_.Storage.PerDatastoreUsage | where {$_.Datastore -eq $ds.ExtensionData.MoRef}
          if($vmds){
            $sharedSpace += ($vmds.Committed - $vmds.Unshared)
          }
        }
      }

      # Collect all values for Storage Views - Datastores
      New-Object PSObject -Property @{
        VMHost = $esx.Name
        Datastore = $ds.Name
        "File System Type" = $ds.Type.Replace("NFS","NAS")
        "Connectivity Status" = &{if($ds.Accessible){"Up"}else{"Down"}}
        "Multipathing Status" = $mpStatus
        Capacity = Get-FriendlyUnit -Value ($ds.CapacityMB * 1MB)
        Free = Get-FriendlyUnit -Value ($ds.FreeSpaceMB * 1MB)
        Used = Get-FriendlyUnit -Value $usedSpace
        Snapshot = Get-FriendlyUnit -Value $snapSpace
        vDisk = Get-FriendlyUnit -Value ($vdiskSpace - $snapSpace)
        Swap = Get-FriendlyUnit -Value $swapSpace
        Other = Get-FriendlyUnit -Value $otherSpace
        Shared = Get-FriendlyUnit -Value $sharedSpace
      }
    }
  }
}

Annotations

Line 29-40: The function uses 5 parameter sets that allow you to get the Storage Views – Datastores values for a VM, an ESX(i) server, a ResourcePool, a Cluster or a Datacenter.

Line 44-85: For each of the parameter sets, this Switch block calculates the VMs, the ESX(i) host(s) and the datastore(s) that need to be queried. Notice that each of the 5 parameter sets support a kind of Object By Name (OBN), that means you can pass the name or the object itself.

Line 88-89: The script will use these MoRef collections later on to determine if a specific VM needs to be included in the calculations for the Storage Views values.

Line 92-95: To determine the Multipathing Status the script first creates a hash table for all the FC HBAs in the ESX(i) servers. The key for this hash table is the WWN, in hex format, for the HBA.

Line 98-171: The main loop over all the datastores that the script needs to handle.

Line 101-102: The script collects all the LUNs that are part of the datastore.

Line 103: The Multipathing logic works with a copy of the hash table that was created earlier.

Line 106-108: In the hash table the script increments the value for each LUN path that is Active or Standby.

Line 109-114: If a LUN has more then 1 path, the Multipathing Status is considered as Full Redundancy. Otherwise the status is Partial/No Redundancy.

Line 118-153: The script calculates all values that are related to the storage in use by the VMs.

Line 125: For a VM to be taken into account, the VM must use storage on the datastore and must be hosted by one of the ESX(i) host.

Line 127: This Regular Expression will be used later on to verify the a specific file is located on the datastore. Note that the square brackets need to be escaped.

Line 128: The Space Used value is the sum of all the Committed space for each of the VMs on that datastore.

Line 129-135: The space used by snapshots is the sum of all the VMDK snapshot files and the snapshotList and the snapshotData files.

Line 132: Note that we check for each file used for the Snapshot Space calculation, if it is located on the datastore with the -match operator. This is required since the administrator or user can define that snapshot files need to be stored on another datastore.

Line 136-139: All the files used for the vDisks of a VM.

Line 140-143: All swap related files

Line 144-147: All other files that are used by the VM.

Line 148-151: This block calculates the Shared Space for a VM. Examples of shared space usage are linked clones that are created from a snapshot with the createNewChildDiskBacking option for the diskMoveType (see the VirtualMachineRelocateSpec).

Line 156-170: Create the object, per datastore, with all the values. To have similar values that are presented in the Storage Views panel, the script uses the Get-FriendlyUnits function from my Friendly Units post.

Line 166: Note that for the Virtual Disk Space value we need to subtract the Snapshot Space from the total disk space.

Samples

You can pass the function the name of the object for which you want the Storage Views – Datastore values.

Get-StorageViewDatastore -Cluster MyCluster |
Select-Object Datastore,"File System Type","Connectivity Status","Multipathing Status",
  @{N="Capacity";E={"{0,7:f2} {1,2}" -f $_.Capacity.Value,$_.Capacity.Unit}},
  @{N="Free Space";E={"{0,7:f2} {1,2}" -f $_.Free.Value,$_.Free.Unit}},
  @{N="Space Used";E={if($_.Used.Value){"{0,7:f2} {1,2}" -f $_.Used.Value,$_.Used.Unit}}},
  @{N="Snapshot Space";E={if($_.Snapshot.Value){"{0,7:f2} {1,2}" -f $_.Snapshot.Value,$_.Snapshot.Unit}}},
  @{N="Virtual Disk Space";E={if($_.vDisk.Value){"{0,7:f2} {1,2}" -f $_.vDisk.Value,$_.vDisk.Unit}}},
  @{N="Swap Space";E={if($_.Swap.Value){"{0,7:f2} {1,2}" -f $_.Swap.Value,$_.Swap.Unit}}},
  @{N="Other VM Space";E={if($_.Other.Value){"{0,7:f2} {1,2}" -f $_.Other.Value,$_.Other.Unit}}},
  @{N="Shared Space";E={if($_.Shared.Value){"{0,7:f2} {1,2}" -f $_.Shared.Value,$_.Shared.Unit}}} |
Format-Table -AutoSize

Or you can pass the actual object.

$dc = Get-Datacenter -Name MyDC
Get-StorageViewDatastore -Datacenter $dc |
Select-Object Datastore,"File System Type","Connectivity Status","Multipathing Status",
  @{N="Capacity";E={"{0,7:f2} {1,2}" -f $_.Capacity.Value,$_.Capacity.Unit}},
  @{N="Free Space";E={"{0,7:f2} {1,2}" -f $_.Free.Value,$_.Free.Unit}},
  @{N="Space Used";E={if($_.Used.Value){"{0,7:f2} {1,2}" -f $_.Used.Value,$_.Used.Unit}}},
  @{N="Snapshot Space";E={if($_.Snapshot.Value){"{0,7:f2} {1,2}" -f $_.Snapshot.Value,$_.Snapshot.Unit}}},
  @{N="Virtual Disk Space";E={if($_.vDisk.Value){"{0,7:f2} {1,2}" -f $_.vDisk.Value,$_.vDisk.Unit}}},
  @{N="Swap Space";E={if($_.Swap.Value){"{0,7:f2} {1,2}" -f $_.Swap.Value,$_.Swap.Unit}}},
  @{N="Other VM Space";E={if($_.Other.Value){"{0,7:f2} {1,2}" -f $_.Other.Value,$_.Other.Unit}}},
  @{N="Shared Space";E={if($_.Shared.Value){"{0,7:f2} {1,2}" -f $_.Shared.Value,$_.Shared.Unit}}} |
Export-Csv "C:\SV-MyDC.csv" -NoTypeInformation -UseCulture

The previous example also showed how the results can be saved to a CSV file.
Such a CSV file looks as follows.

Enjoy the function !

  1. Brad S
    October 12th, 2012 at 17:55 | #1

    Great script any way to increase the performance? Running it seems to take forever =)

  2. Islander
    August 23rd, 2012 at 22:45 | #2

    LucD,

    I am still lost, I do not understand how to run this script, if you can explained one more time to me, I copied and saved the script as a getstorage-view.PS1 executed and does not do anything.

    I’ve looking for a a script that tells me what are the status of my SAN, my SAN currently is overflowing, because file overallocation, I would like to know how may I approach this problem. I found two scripts that are closed to what I am looking for, if may be there is a way to incorporate some values that will do the trick, here are the two:

    Here are the line of code that I would like to incorporate:

    get-vm |sort | % {$_.name ; ($_|get-harddisk | % {$_.filename;$_.capacitykb})}

    That is driving me crazy, again the main problem is that many of my LUN’s are running out of space, but if could identify, the VMDK’s or Template files, where and what are they, I should be able to find a solution.

    First Script:

    Import-Module VIProperty

    $users = “user@xyz.xyz” # List of users to email your report to (separate by comma)
    $fromemail = “user@xyz.xyz”
    $server = “smtp.server.xyz” #enter your own SMTP server DNS name / IP address here

    $datastore = $null
    $Report = @()
    $AllDatastores = Get-Datastore | Where {$_.Name -notlike “datastore*”} | Sort Name
    foreach ($datastore in $AllDatastores) {
    $Report += “”
    $row = “” | Select Name, FreeGB, CapacityGB, ProvisionedGB, PercentFree
    $row.Name = $datastore.Name
    $row.FreeGB = $datastore.FreeGB
    $row.CapacityGB = $datastore.CapacityGB
    $row.ProvisionedGB = $datastore.ProvisionedGB
    $row.PercentFree = $datastore.PercentFree
    $Report += $row | ConvertTo-Html

    #List VMs on the datastore if applicable
    If ($datastore.ExtensionData.Vm.Count -gt 0){
    $VMList = Get-View -Id $datastore.ExtensionData.Vm -Property Name | Select Name
    $Report += “”
    $Report += “VMs on this Datastore:
    $VMTable = @()
    foreach ($vm in $VMList) {
    $VMContentRow = “” | Select Name, PowerState
    $tmp = Get-VM $vm.Name
    $VMContentRow.Name = $tmp.Name

    Write-Host $tmp.Name
    if ($tmp.PowerState -eq “PoweredOn”) {
    $VMContentRow.PowerState = $tmp.PowerState
    }
    else {
    $VMContentRow.PowerState = $tmp.PowerState
    }

    $VMTable += $VMContentRow

    }

    $Report += $VMTable | ConvertTo-Html -Fragment

    }
    else {
    $Report += “”
    $Report += “VMs on this Datastore:
    $Report += “Name”
    $Report += “No VMs on this Datastore”

    }

    $Report += “” #Line break for clarity
    $Report += “”
    }

    # HTML formatting for email
    $HTMLmessage = @”

    Datastore Report

    body{font: .8em “”Lucida Grande”", Tahoma, Arial, Helvetica, sans-serif;}
    ol{margin:0;padding: 0 1.5em;}
    TABLE{border-width: 1px;border-style: solid;border-color: black;}
    TH{border-width: 1px;padding: 3px;border-style: solid;border-color: black;}
    TD{text-align:center;border-width: 1px;padding: 5px;border-style: solid;border-color: black;}
    table{color:#000;background:#99CCFF;border-collapse:collapse;width:647px;border:3px solid #900;}
    thead{}
    thead th{padding:1em 1em .5em;border-bottom:1px dotted #FFF;font-size:120%;text-align:left;}
    thead tr{}
    td{padding:.5em 1em;}
    tfoot{}
    tfoot td{padding-bottom:1.5em;}
    tfoot tr{}
    #middle{background-color:#900;}

    $Report

    “@

    $HTMLmessage | Out-File C:\DSReport.html #Also write to an HTML report on disk – with basic formatting, in addition to the email report you are sent.

    #Send the final report!
    send-mailmessage -from $fromemail -to $users -subject “Datastore Report” -BodyAsHTML -body $HTMLmessage -smtpServer $server

    NOTE: here is the line of code that I am struggling with, I do not know where to place it:

    get-vm |sort | % {$_.name ; ($_|get-harddisk | % {$_.filename;$_.capacitykb})}

    Here is a second script, it does the same as the first one, but this one has less lines with pretty much the same approach:

    Script 2:

    Import-Module VIProperty

    $users = “yourmail@yourdomain.com” # List of users to email your report to (separate by comma)
    $fromemail = “yourmail@yourdomain.com”
    $server = “yourmailserver.com” #enter your own SMTP server DNS name / IP address here

    $Report = @()
    ## done to exclude local datastores?
    $AllDatastores = Get-Datastore | Where {$_.Name -notlike “datastore*”} | Sort Name
    foreach ($datastore in $AllDatastores) {
    $Report += “”
    $row = New-Object -Type PSObject -Property @{
    Name = $datastore.Name
    FreeGB = $datastore.FreeGB
    CapacityGB = $datastore.CapacityGB
    PercentFree = $datastore.PercentFree
    } ## end new-object
    $Report += $row | ConvertTo-Html

    #List VMs on the datastore if applicable
    $Report += “”
    $Report += “VMs/Templates on this Datastore:
    if ($datastore.ExtensionData.Vm.Count -gt 0){
    $VMViewList = Get-View -Id $datastore.ExtensionData.Vm -Property Name,Runtime.PowerState,Config.Template
    $VMTable = @()
    foreach ($viewVM in $VMViewList) {
    Write-Host $viewVM.Name
    $VMContentRow = New-Object -Type PSObject -Property @{
    Name = $viewVM.Name
    PowerState = $viewVM.Runtime.PowerState
    Other = if ($viewVM.Config.Template) {“Template”} else {“VM”}
    } ## end new-object
    $VMTable += $VMContentRow
    }
    $Report += $VMTable | ConvertTo-Html -Fragment
    } ## end if
    else {
    $Report += ‘None on this Datastore’
    } ## end else

    $Report += “” #Line break for clarity
    $Report += “”
    } ## end foreach

    $Report | Set-Content C:\DSReport.html #Also write to an HTML report on disk – with basic formatting, in addition to the email report you are sent.

    Again, I would like to incorporate this line of code into the script:

    get-vm |sort | % {$_.name ; ($_|get-harddisk | % {$_.filename;$_.capacitykb})}

    Any assistance would be greatly appreciated.

    Thanks,

  3. Rue
    August 21st, 2012 at 12:04 | #3

    By specifying the HBA type as “Fiberchannel’ won’t this give an incorrect multipath status for iscsi connections ?

  4. Jen
    April 18th, 2012 at 18:11 | #4

    Hi, I am not sure where to post this question, I hope this is ok ;)

    I am trying to take datastore information, including the datacenter name, and export it to SQLServer. I am using both functions out-datatable and write-datatable which Marc van Orsouw has posted and someone else has modified (http://blogs.technet.com/b/heyscriptingguy/archive/2010/11/01/use-powershell-to-collect-server-data-and-write-to-sql.aspx).

    I am getting all datastores and putting them in to an array with the datacenter name. However, the datacenter name has a hyphen (datacenter-xx). This will not write to sql because it is not in quotes I am told by my db admin. I have not been able to figure out how to get around this, would you help? Thanks!

  5. Sallu
    December 24th, 2011 at 09:15 | #6

    This may be a stupid question but not sure how to run this script, its very important for me to make this script working.
    can somebody tell me how do I run this script, and in which part of the script do I need to supply the values!!

    • December 24th, 2011 at 10:31 | #7

      @Sallu, there are no stupid questions ;-)
      The script is written as a function. In this function there are 5 parameter sets, each with 1 parameter, the vSphere object for which you want to generate the Storage View. That vSphere object can be an ESX(i) host, a Datacenter, a Cluster, a ResourcePool or a VM.
      In the Samples section there are a couple of sample calls.

      But you need to make the function ‘known’ to the PowerShell engine.

      • Save the function as .PS1 file, for example ‘DSView.ps1′
      • I assume you are running the PowerCLI prompt
      • at the prompttype, note the blank between the 2 dots !
        . ./DSView.ps1
      • Call the function with the parameter, in this case the name of a Datacenter
        Get-StorageViewDatastore -Datacenter MyDatacenter
      • The function returns a numnber of objects, extract the information (properties) you require from these objects.
      • See the example Select-Object cmdlet in the Samples section

      I hope this helped you in running the function.

  6. bs
    November 15th, 2011 at 21:40 | #8

    great job!

  7. Marcel Nobel
    November 15th, 2011 at 10:37 | #9

    Another great script. Bedankt!

  1. No trackbacks yet.