Select Page

Monitor Storage Spaces Direct Pool Capacity with PowerShell

This article provides PowerShell code and examples to analyze Windows Storage Spaces Direct pools and returns the current storage capacity, free space and other values including the amount of free space required to automatically rebuild a failed disk.

Purpose

Monitoring Storage Spaces pools for capacity and available free space is simple enough with Windows Server Manager. Selecting the Storage Pool properties provides a graphical report.

Retrieving these values from PowerShell takes a bit more work. What is required for this operation is to:

  1. Select the Storage Pool
  2. Retrieve every disk within this pool
  3. Sum up all the capacity and free space on each disk

A trickier calculation is to evaluate if enough un-allocated space is available to survive a disk failure and automatically rebuild the storage pool to map around the missing disk. This is even more complex when there are multiple disk media types within your pool. An SSD failure can only be re-mapped with other SSD drives.

Get-ClusterS2DStorageUsage

The CmdLet included in this article provides a simple command to analyze pools and programmatically return pool statistics.

Command Syntax:

Get-ClusterS2DStorageUsage [-StoragePool string[ ] [-MediaType SSD|HDD|SCM]

Parameters

-StoragePool

Storage Pool friendly name. If not included all non-primordial storage pools will be processed.

-MediaType

Selects specific disk drive media types for processing. If not included, all non-Journal media types processed.

Example 1: Basic output

Get-ClusterS2DStorageUsage

Pool              : S2DPool
TotalCapacity     : 120
TotalUsedSpace    : 24
TotalFreeSpace    : 96
PercentUsed       : 20
UnitOfMeasure     : GB
DiskCount         : 24
SelectedMediaType : ALL
ReserveRequired   : 5

Example 2: Disk media type selected

Get-ClusterS2DStorageUsage -MediaType HDD | ft Pool, DiskCount, TotalCapacity, TotalFreeSpace, ReserveRequired

Pool        DiskCount     TotalCapacity       TotalFreeSpace      ReserveRequired
----        ----------    ------------        --------------      ---------------
S2DPool     16            80                  64                  5

The results of this command show the S2DPool has 16 HDD disks with 64GB free space and required 5GB free space to rebuild.

Example 3: Verbose output

Get-ClusterS2DStorageUsage -Verbose

VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE: Disk Name: [Msft Virtual Disk] Size: 5.00 GB Alloc: 1.00 GB Free Space: 4.00 GB
VERBOSE:
VERBOSE: -----------------------------------------------------
VERBOSE: Pool: S2DPool
VERBOSE:
VERBOSE: Total Space: 120.00 GB
VERBOSE: Total Alloc: 24.00 GB
VERBOSE: Free Space: 96.00 GB
VERBOSE: Percent Used: 20.00% GB

Automation

Since the CmdLet returns an object, the return values can be used for automation and DevOps scripts.

For example, the following script:

$Usage = Get-ClusterS2DStorageUsage -StoragePool S2DPool
if ($Usage.ReserveRequired -gt $Usage.TotalFreeSpace) { Write-Host "Call Home for Help!" }

Checks the S2DPool Storage Pool has enough excess capacity to rebuild.

This can be used in operations monitoring systems such as PowerShell Desired State Configuration (DSC) environments or potentially any monitoring environment.

Or potentially each disk drive media type can be tested as each disk type is likely to be different capacities.

$Usage = Get-ClusterS2DStorageUsage -StoragePool S2DPool -MediaType SSD
if ($Usage.ReserveRequired -gt $Usage.TotalFreeSpace)
   { Write-Host "Warning, SSD capacity below requirement to rebuild!" }
If ($Usage.PercentUsed -gt “90”)
   { Write-Host "Warning, low on SSD space!" }
Naturally you will replace the Write-Host command with something more appropriate and functional.

PowerShell CmdLet

The following code should be copied into a file called Get-ClusterS2DStorageUsage.psm1 and installed like any PowerShell module. The CmdLet can be used for testing.

Download a copy of this PowerShell CmdLet. Right-click this link to save the Get-ClusterS2DStorageUsage script. Then rename the text file to Get-ClusterS2DStorageUsage.psm1. This PowerShell CmdLet will be imported and used in following steps to create the storage server VMs.
function Get-ClusterS2DStorageUsage {
  <# .SYNOPSIS Return the Pool Storage Usage .DESCRIPTION Describe the function in more detail .EXAMPLE Give an example of how to use it .EXAMPLE Give another example of how to use it .PARAMETER poolname The computer name to query. Just one. .PARAMETER computername The name computer name to query #>
  [CmdletBinding()]
  param
  (
      [Parameter(
      ValueFromPipeline=$True,
      ValueFromPipelineByPropertyName=$True,
        HelpMessage='- FriendlyName ')]
      [Alias('StoragePool')]
      [string[]]$FriendlyName,

      [string]$MediaType = 'all'	
  )

    begin 
    {
        $MediaType = $MediaType.ToUpper()

        Write-Verbose "Checking Cluster Nodes exists"
        $ClusterNodes = Get-ClusterNode
        if ($ClusterNodes) {
            $Msg = $ClusterNodes.Count.ToString() + " cluster nodes found"
            Write-Verbose $Msg
        } else {
            $Msg = "No cluster nodes found"
            Write-Error $Msg
            Return
        }

        if (! $FriendlyName) {
            Write-Verbose "No StoragePool name entered - Getting all Pools"
            $SP = Get-StoragePool | ? IsPrimordial -eq $False
            if ($SP) {
                $FriendlyName = $SP.FriendlyName
                $Msg = "Storage Pool found [" + $FriendlyName + "]"
                Write-Verbose $Msg
            } else {
                Write-Error "Error - No Storage Pools available"
            }
        }
        $Obj = @()  
    }

    Process 
    {
        foreach ($Pool in $FriendlyName) 
        {
            $Msg = "Processing " + $Pool
            Write-Verbose $Msg


            Write-Verbose "Checking StoragePool exists"
            $thisPool = Get-StoragePool -FriendlyName $Pool -ErrorAction Stop

            Write-Verbose "Checking for disks with the MediaType"
            if ( $MediaType -eq "all" )
            {
                $CapacityDisks = $thisPool | Get-PhysicalDisk | ? MediaType -ne "Journal"
            } else {
                $CapacityDisks = $thisPool | Get-PhysicalDisk | ? MediaType -eq $MediaType
            }
            if ($CapacityDisks) {
                $Msg = $CapacityDisks.Count.ToString() + " Disks with MediaType " + $MediaType + " found"
                Write-Verbose $Msg
            } else {
                $Msg = "No qualifying disks found with MediaType:" + $MediaType
                Write-Error $Msg
                Return
            }

            $PoolDiskCount = $CapacityDisks.Count
            $Msg = ("Number of Pool Disks: " + $PoolDiskCount)
            Write-Verbose $Msg
            $TotalSize = 0  
            $TotalAlloc = 0
 
            ForEach ($Disk in $CapacityDisks)
            { 

                $DiskSize = $Disk.Size /1GB
                $DiskAlloc = $Disk.AllocatedSize /1GB
                $FreeSpace = $DiskSize - $DiskAlloc
                $TotalSize += $DiskSize 
                $TotalAlloc += $DiskAlloc 

                $Msg =  "Disk Name: [" + $Disk.FriendlyName + "] ",
                    ("Size: {0:n2} GB  " -f ($DiskSize) ),
                    ("Alloc: {0:n2} GB  " -f ($DiskAlloc)),
                    ("Free Space: {0:n2} GB" -f ($FreeSpace)) 
                Write-Verbose $Msg 
           }


            $PoolFree = $TotalSize - $TotalAlloc
            $PercentFull = $TotalAlloc / $TotalSize * 100

            Write-Verbose " "
            Write-Verbose "-----------------------------------------------------"
            $Msg  = "Pool: " + $thisPool.FriendlyName
            Write-Verbose $Msg
            Write-Verbose " "
            $Msg =  ("Total Space: {0:n2} GB" -f ($TotalSize))
            Write-Verbose $Msg
            $Msg =  ("Total Alloc: {0:n2} GB" -f ($TotalAlloc))
            Write-Verbose $Msg
            $Msg =  ("Free Space: {0:n2} GB" -f ($PoolFree))
            Write-Verbose $Msg
            $Msg =  ("Percent Used: {0:n2}% GB" -f ($PercentFull))
            Write-Verbose $Msg

            $ReserveRequired = $TotalSize / $PoolDiskCount

            $Properties = [Ordered]@{
                'Pool'=$thisPool.FriendlyName;
                'TotalCapacity'=$TotalSize;
                'TotalUsedSpace'=$TotalAlloc;
                'TotalFreeSpace'=$PoolFree;
                'PercentUsed'=$PercentFull
                'UnitOfMeasure'="GB";
                'DiskCount'=$PoolDiskCount;
                'SelectedMediaType'=$MediaType;
                'ReserveRequired'=$ReserveRequired;     
            }
            $Obj += New-Object –Typename PSObject –Property $Properties
        }
        
        Write-Output $Obj
    }
}

Please do provide feedback on errors or any other comments below.

 

Article Attachments

Was this article helpful?

3 Comments

  1. Matt

    not sure what version of windows server you are using in this article…..but…

    $CapacityDisks = $thisPool | Get-PhysicalDisk | ? MediaType -ne “Journal”

    MediaType doesn’t exist for me. I had to change this to “Usage”

    Reply
    • matt

      scratch that. it works but I wrongly presumed that the NVMe drives should not be included in my results if I had three tiers. I see that they are included in “all”

      I modified my script to not included NVMe in the default (run without -MediaType parameter) and only include them if I add -MediaType all)

      I did this since the journal drives are not really to be counted in space calculations from my understanding.

      Reply
  2. matt

    also, the reserve required doesn’t seem to match (at least in my case) the recommended reserve of one disk per node (up to four disks) of each disk type.

    https://docs.microsoft.com/en-us/windows-server/storage/storage-spaces/plan-volumes#choosing-how-many-volumes-to-create

    FTA: We recommend reserving the equivalent of one capacity drive per server, up to 4 drives. You may reserve more at your discretion, but this minimum recommendation guarantees an immediate, in-place, parallel repair can succeed after the failure of any drive.

    can you explain your reasoning for the usage of:

    $ReserveRequired = $TotalSize / $PoolDiskCount

    it should be something more like (pseudo-ish code):

    ReserveRequiredHDD = PoolDiskCount where type is HDD and not journal
    ReserveRequiredSSD = PoolDiskCount where type is SSD and not journal
    ReserveRequired = ReserveRequiredHDD + ReserveRequiredSSD

    I didn’t add the logic required to account for one drive type per node up to a total of four.

    the reason I ask is that my results from running your script were much lower than what every vendor has suggested I have for reserve space. I think it is that you are dividing by the total number of disks thus really only allowing for the capacity of the average of one of each drive type. in my case I have four 10TB HDD and four 1.9TB SSD per node in a six node cluster. the resultant reserve from your script said I should have 5551.125 which is roughly the average of one of each disk type combined. however, every vendor has recommended a minimum of 46TB which is the sum of 4 of each disk type.

    Reply

Submit a Comment

Your email address will not be published. Required fields are marked *