Monitor Storage Spaces Direct Pool Capacity with PowerShell

How to monitor Storage Spaces Direct pool capacity using the provided PowerShell script.

This article provides PowerShell code and examples to analyze Windows pools. The tool can return the current capacity, free space, and other values including the amount of free space required to automatically rebuild a failed disk.

Purpose

Monitoring Spaces pools for capacity and available free space is simple enough with 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 and .

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 (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!" }

Info

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

Download a copy of this PowerShell CmdLet. Right-click this link to save a copy of the Get-ClusterS2DStorageUsage script shown below. Then rename the text file to Get-ClusterS2DStorageUsage.psm1. This PowerShell CmdLet will be imported and used in the 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.

6 thoughts on “Monitor Storage Spaces Direct Pool Capacity with PowerShell”

  1. 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”

    1. 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.

    2. Robert Keith

      Hi Matt

      Please excuse the very late reply.

      You are correct, the script does not calculate correctly for a 3 tier configuration.

      And you are correct journal disks are not included in capacity, but NVNe and NVDIMM SCM drives can be capacity drives.

      It also does not take into account configurations which have multiple disk size/performances.

      Multiple types of capacity disks are valid but more complex to administer.

      We will fix and add the 3 tier options, and simply note the multi-capacity onfigurations.

  2. 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.

    1. Robert Keith

      Hi Matt

      You are exactly right.

      For a configuration with 4 10TB disks, you need at least 10TB reserve space.

      This script does not account for configurations with multiple types or sizes of capacity disks.

      1. I you have a configuration like this, you have to reserve 10TB per node.
        1 disk per node is the recommendation.

Leave a Reply

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