Contents
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:
- Select the Storage Pool
- Retrieve every disk within this pool
- 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
1 2 3 4 5 6 7 8 9 10 11 | 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
1 2 3 4 5 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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:
1 2 | $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.
1 2 3 4 5 | $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!" } |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | 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.
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”
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.
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.
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.
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.
I you have a configuration like this, you have to reserve 10TB per node.
1 disk per node is the recommendation.