PowerShell & Windows Clusters

I've been recently working with the Windows Clusters. There is the quick deployment guide https://technet.microsoft.com/en-us/library/mt126109.aspx is pretty good but when it comes to creation of the volumes, things become unclear. So i went and collected more information. Here it is.

What is the Storage Subsystem and how to find its name for the command New-StoragePool?

Basically it's a view of a set of the disks, each machine has 2 views:
- its own disks (with its own FQDN as a name) that are not shared with the cluster (i.e. the boot disk)
- all disks shared in the cluster (with the cluster FQDN as a name, same name as used in New-Cluster plus the domain name)

The patterns can be used for finding the storage subsystem by name, instead of the complete FQDN. In particular, the cluster name can be used as a pattern, to find its storage subsystem:

Get-StorageSubSystem -Name ((Get-Cluster).Name + ".*")

What if some disks cannot pool?

Usually it means either a defective disk or a disk that is already in the pool.

Here is how to find them with the explanation of the reason:

 Get-StorageSubSystem  -Name <Storage subsystem FQDN or pattern> | Get-PhysicalDisk | ? {!($_.CanPool)} | ft FriendlyName,SerialNumber,CannotPoolReason,OperationalStatus,HealthStatus,Usage,PhysicalLocation,EnclosureNumber

How to skip them in pooling:

 New-StoragePool  -StorageSubSystemName <FQDN> -FriendlyName <name> -ProvisioningTypeDefault Fixed -ResiliencySettingNameDefault Mirror -PhysicalDisk (Get-StorageSubSystem  -Name <FQDN> | Get-PhysicalDisk | ? {$_.CanPool} )

How to find, which disk is connected to which machine?

 Get-PhysicalDisk | % { [PsCustomObject]@{ FriendlyName=$_.FriendlyName; SerialNumber=$_.SerialNumber; PhysicalLocation=($_ | Get-StorageEnclosure).PhysicalLocation;}}

Although as of the WS2016 release I think the recipe above got broken. Here is the new one that should work instead although I have no way to test it now:

 Get-PhysicalDisk | % { [PsCustomObject]@{ FriendlyName=$_.FriendlyName; SerialNumber=$_.SerialNumber; PhysicalLocation=($_ | Get-StorageFaultDomain);}}

The list of storage nodes can be obtained with:


What is the meaning of the columns in the definition of the storage tier?

The detailed information can be found in http://social.technet.microsoft.com/wiki/contents/articles/15200.storage-spaces-designing-for-performance.aspx.

It's essentially the  stripe width. For the mirrored volumes this is independent of the mirroring count. But for the parity volumes this is connected to the redundancy: 7 columns with redundancy of 2 means that each parity unit will consist of 5 data stripes and 2 parity stripes. This is important for computing the storage size requirements (see below). For a parity tier, the cluster must have at least as many machines a (NumberOfColumns - PhysicalDiskRedundancy + 1).

While on the subject of tiers, the command Get-StorageTier shows that the tiers are not enclosure-aware but the explanation is that this is not important for Storage Spaces Direct clusters: they are always aware of the distribution of the physical disks by machines.

How to size the volumes?

The sizing guide from WS2012 is at: https://technet.microsoft.com/library/mt243829.aspx

The performance recommendation for the number of volumes is that there should be at least as many volumes as there are machines in the cluster (this distributes the volume access synchronization across the machines).

The size specified for the New-Volume command is the logical size that will be available for the filesystem (and to store the user data, minus the overhead of the filesystem itself). The physical space can be computed from the logical space approximately as follows (approximately because a small amount of volume management overhead will be added on top of it):

For the mirrored part of the volume:
LogicalSize * (PhysicalDiskRedundancy + 1) 

For the parity part of the volume:
LogicalSize * NumberOfColumns / (NumberOfColumns - PhysicalDiskRedundancy)

How to name the volume mount points?

You can't name them during creation but after the volume is created, its mount point can be renamed to any name, i.e.:

 move C:\ClusterStorage\Volume1 C:\ClusterStorage\MyVirtDisk

 How to find the free and used space, and connections between various elements?

With the following examples:

# this function will be used to add up the values
function sum {
    param( [Parameter(ValueFromPipeline = $true)] $Data)
    begin { $sum = 0 }
    process { $sum += $_ }
    end { $sum }


# list of only clustered healthy disks that contain data (skipping the journals)
function Get-PhysicalDataDisk
    Get-StorageSubSystem -Name ((Get-Cluster).Name + ".*") | Get-PhysicalDisk | ? { $_.Usage -eq "Auto-Select" -and $_.HealthStatus -eq "Healthy" -and $_.OperationalStatus -eq "OK" }
$pd = Get-PhysicalDataDisk


# Total size of physical disks:
$pd.Size | sum


# Total used size
$pd.AllocatedSize | sum


# Percentage used
100 * ($pd.AllocatedSize | sum) / ($pd.Size | sum)


# Total free size
$pd | % { $_.Size - $_.AllocatedSize } | sum


# disks enriched with location information
filter Get-PhysicalDiskLocation {
    param( [Parameter(ValueFromPipeline = $true)] $Disk)
    $Disk | % { [PsCustomObject]@{ FriendlyName=$_.FriendlyName; SerialNumber=$_.SerialNumber; Node=($_ | Get-StorageEnclosure).PhysicalLocation; Disk=$_;}}
$pdl = $pd | Get-PhysicalDiskLocation


# the free space information grouped by cluster node
function Get-ClusterFreeDiskSize {
    param( [Parameter(ValueFromPipeline = $true)] [array]$EnrichedDisks)
    begin { $pdl = @() }
    process { $pdl += $EnrichedDisks }
    end { $pdl | ? { $_.Node } | Group-Object Node | % {[PsCustomObject]@{ Node=$_.Name; Free=($_.Group | % { $_.Disk.Size - $_.Disk.AllocatedSize } | sum);}}}


# can accept the paremeters either way
Get-ClusterFreeDiskSize $pdl
$pdl | Get-ClusterFreeDiskSize


# find the mount point of a cluster volume
(Get-ClusterSharedVolume -Name "Cluster Virtual Disk (vdisk1)").SharedVolumeInfo.FriendlyVolumeName

# find the volume by mount point
Get-Item C:\ClusterStorage\Volume1 | Get-Volume
# the two above can be combined to get the volume object for a cluster shared volume;


# the cluster volume can be found by using the volume's FileSystemLabel:
Get-ClusterSharedVolume -Name ("Cluster Virtual Disk ("+ $volume.FileSystemLabel +")")

# find the virtual disk by volume
Get-VirtualDisk -FriendlyName (Get-Volume).FileSystemLabel -ea Ignore

# get the volume by virtual disk
Get-Volume -FileSystemLabel (Get-VirtualDisk).FriendlyName -ea Ignore


# get the physical disks used by a virtual disk
# (not very helpful because the cluster tries to spread a virtual disk
# to all the physical disks)
Get-VirtualDisk -FriendlyName vdisk1 | Get-PhysicalDisk
# get the virtual disks located on a physical disk
$pd[0] | Get-VirtualDisk


# Get the amount of space used on the physical disks by a virtual disk
# (with all the redundancy and overhead)
(Get-VirtualDisk -FriendlyName vdisk1).FootprintOnPool

Skip to main content