############################
#
# Copyright (c) Microsoft Corporation
#
# Abstract:
#   SMAPI script cmdlets
#
############################

using namespace System.Management.Automation.Runspaces

import-module Storage\StorageHealth.cdxml
import-module Storage\StorageSubSystem.cdxml

$StorageNamespace = 'root\microsoft\windows\storage'
$WmiNamespace     = 'root\wmi'
$ClusterNamespace = 'root\mscluster'

$Global:StorageHistoryCharts = [Hashtable]::Synchronized(@{})

function CreateErrorRecord
{
    Param
    (
        [String]
        $ErrorId,

        [String]
        $ErrorMessage,

        [System.Management.Automation.ErrorCategory]
        $ErrorCategory,

        [Exception]
        $Exception,

        [Object]
        $TargetObject
    )

    if($Exception -eq $null)
    {
        $Exception = New-Object System.Management.Automation.RuntimeException $ErrorMessage
    }

    $errorRecord = New-Object System.Management.Automation.ErrorRecord @($Exception, $ErrorId, $ErrorCategory, $TargetObject)
    return $errorRecord
}

class StorageHistory
{
    [UInt16]$Version
    [String]$FriendlyName
    [String]$SerialNumber
    [String]$AdapterSerialNumber
    [String]$FirmwareRevision
    [String]$DeviceGuid
    [String]$DeviceNumber
    [String]$BusType
    [String]$MediaType
    [DateTime]$EventTime
    [UInt32]$MaxQueueCount
    [UInt32]$MaxOutstandingCount
    [UInt64]$TotalIoCount
    [UInt64]$SuccessIoCount
    [UInt64]$FailedIoCount
    [UInt64]$TotalReadBytes
    [UInt64]$TotalWriteBytes
    [UInt64]$TotalIoLatency
    [UInt64]$AvgIoLatency
    [UInt64]$MaxIoLatency
    [UInt64]$MaxFlushLatency
    [UInt64]$MaxUnmapLatency
    [UInt64]$TotalQueueIoCount
    [UInt64]$TotalQueueIoWaitTime
    [UInt64]$AvgQueueIoWaitTime
    [UInt64]$MaxQueueIoWaitTime
    [UInt32]$IoTimeout
    [UInt64]$QueueIoWaitExceededTimeoutCount
    [UInt64]$QueueIoBusyCount
    [UInt64]$QueueIoPausedCount
    [UInt64]$QueueIoUntaggedCommandOutstandingCount
    [UInt64]$QueueIoPausedForUntaggedCount
    [UInt64]$TotalErrors
    [UInt64]$TotalReadWriteErrors
    [UInt64]$TotalImpendingDeviceFailureErrors
    [UInt64]$TotalDeviceFailureErrors
    [UInt16]$BucketCount
    [UInt64[]]$BucketIoLatency
    [UInt64[]]$BucketSuccessIoCount
    [UInt64[]]$BucketFailedIoCount
    [UInt64[]]$BucketTotalIoCount
    [UInt64[]]$BucketTotalIoTime
    [Single[]]$BucketIoPercent
    [UInt16]$HighestLatencyBucket
    [System.Collections.ArrayList]$UnresponsiveTime
    [System.Collections.ArrayList]$ResponsiveTime
}

class StorageHistoryAggregate
{
    [UInt16]$Version
    [String]$FriendlyName
    [String]$SerialNumber
    [String]$AdapterSerialNumber
    [String]$FirmwareRevision
    [String]$DeviceGuid
    [String]$DeviceNumber
    [String]$BusType
    [String]$MediaType
    [DateTime]$StartTime
    [DateTime]$EndTime
    [UInt32]$EventCount
    [UInt32]$MaxQueueCount
    [UInt32]$MaxOutstandingCount
    [Double]$TotalIoCount
    [Double]$SuccessIoCount
    [Double]$FailedIoCount
    [Double]$TotalReadBytes
    [Double]$TotalWriteBytes
    [Double]$TotalIoLatency
    [UInt64]$AvgIoLatency
    [UInt64]$MaxIoLatency
    [UInt64]$MaxFlushLatency
    [UInt64]$MaxUnmapLatency
    [Double]$TotalQueueIoCount
    [Double]$TotalQueueIoWaitTime
    [UInt64]$AvgQueueIoWaitTime
    [UInt64]$MaxQueueIoWaitTime
    [UInt32]$IoTimeout
    [Double]$QueueIoWaitExceededTimeoutCount
    [Double]$QueueIoBusyCount
    [Double]$QueueIoPausedCount
    [Double]$QueueIoUntaggedCommandOutstandingCount
    [Double]$QueueIoPausedForUntaggedCount
    [Double]$TotalErrors
    [Double]$TotalReadWriteErrors
    [Double]$TotalImpendingDeviceFailureErrors
    [Double]$TotalDeviceFailureErrors
    [UInt16]$BucketCount
    [UInt64[]]$BucketIoLatency
    [Double[]]$BucketSuccessIoCount
    [Double[]]$BucketFailedIoCount
    [Double[]]$BucketTotalIoCount
    [Double[]]$BucketTotalIoTime
    [UInt32[]]$BucketIoPercent
    [UInt32[]]$BucketHighestLatencyCount
}

class StorageError
{
    [UInt16]$Version
    [String]$FriendlyName
    [String]$SerialNumber
    [String]$AdapterSerialNumber
    [String]$FirmwareRevision
    [String]$DeviceGuid
    [String]$DeviceNumber
    [String]$BusType
    [String]$MediaType
    [DateTime]$EventTime
    [UInt32]$ServiceDuration
    [UInt32]$QueueWaitDuration
    [Byte]$Command
    [Byte]$SrbStatus
    [Byte]$ScsiStatus
    [Byte]$SenseKey
    [Byte]$SenseCode
    [Byte]$SenseCodeQualifier
    [UInt32]$IoSize
    [UInt32]$QueueDepth
    [UInt64]$LBA
}


$PopulateStorageError = {

    Param
    (
        [Ref]
        $Object,

        [System.Diagnostics.Eventing.Reader.EventRecord]
        $EventRecord,

        [System.String]
        $FriendlyName,

        [System.String]
        $SerialNumber,

        [System.String]
        $AdapterSerialNumber,

        [System.String]
        $FirmwareRevision,

        [System.String]
        $DeviceGuid,

        [System.String]
        $Number,

        [System.String]
        $BusType,

        [System.String]
        $MediaType
    )

    $eventXml = [XML]$EventRecord.ToXml()
    $tempObject = [PsCustomObject]@{}

    for ($index = 0; $index -lt $eventXml.Event.EventData.Data.Count; $index++)
    {
        $tempObject | Add-Member -MemberType NoteProperty `
                                 -Name $eventXml.Event.EventData.Data[$index].Name `
                                 -Value $eventXml.Event.EventData.Data[$index].'#Text'
    }

    $Object.Value.PSObject.TypeNames.Insert( 0, "Microsoft.Windows.StorageManagement.StorageError" )
    $Object.Value.PSObject.TypeNames.Insert( 1, "Microsoft.Windows.StorageManagement.StorageError_v1" )

    $Object.Value.Version = [UInt16]$tempObject.Version

    if ([String]::IsNullOrEmpty($FriendlyName) -eq $false)
    {
        $Object.Value.FriendlyName = $FriendlyName
    }
    else
    {
        $Object.Value.FriendlyName = $tempObject.ProductId
    }

    if ([String]::IsNullOrEmpty($SerialNumber) -eq $false)
    {
        $Object.Value.SerialNumber = $SerialNumber
    }
    else
    {
        $Object.Value.SerialNumber = $tempObject.SerialNumber
    }

    if ([String]::IsNullOrEmpty($AdapterSerialNumber) -eq $false)
    {
        $Object.Value.AdapterSerialNumber = $AdapterSerialNumber
    }
    else
    {
        $Object.Value.AdapterSerialNumber = $tempObject.AdapterSerialNumber
    }

    if ([String]::IsNullOrEmpty($FirmwareRevision) -eq $false)
    {
        $Object.Value.FirmwareRevision = $FirmwareRevision
    }

    $Object.Value.DeviceGuid          = $DeviceGuid
    $Object.Value.DeviceNumber        = $Number

    if ([String]::IsNullOrEmpty($BusType) -eq $false)
    {
        $Object.Value.BusType         = $BusType
    }
    else
    {
        switch ($tempObject.BusType)
        {
            "8"     { $Object.Value.BusType = "RAID" }
            "10"    { $Object.Value.BusType = "SAS" }
            "11"    { $Object.Value.BusType = "SATA" }
            "14"    { $Object.Value.BusType = "Virtual" }
            "17"    { $Object.Value.BusType = "NVMe" }
            "19"    { $Object.Value.BusType = "UFS" }
            default { $Object.Value.BusType = "Unknown" }
        }
    }

    $Object.Value.MediaType          = $MediaType
    $Object.Value.EventTime          = $event.TimeCreated
    $Object.Value.ServiceDuration    = $tempObject.RequestDuration_ms
    $Object.Value.QueueWaitDuration  = $tempObject.WaitDuration_ms
    $Object.Value.Command            = $tempObject.Command
    $Object.Value.SrbStatus          = $tempObject.SrbStatus
    $Object.Value.ScsiStatus         = $tempObject.ScsiStatus
    $Object.Value.SenseKey           = $tempObject.SenseKey
    $Object.Value.SenseCode          = $tempObject.AddSense
    $Object.Value.SenseCodeQualifier = $tempObject.AddSenseQ
    $Object.Value.IoSize             = $tempObject.IoSize
    $Object.Value.QueueDepth         = $tempObject.QueueDepth
    $Object.Value.LBA                = $tempObject.LBA
}


$PopulateStorageHistory = {

    Param
    (
        [Ref]
        $Object,

        [System.Diagnostics.Eventing.Reader.EventRecord]
        $EventRecord,

        [System.String]
        $FriendlyName,

        [System.String]
        $SerialNumber,

        [System.String]
        $AdapterSerialNumber,

        [System.String]
        $FirmwareRevision,

        [System.String]
        $DeviceGuid,

        [System.String]
        $Number,

        [System.String]
        $BusType,

        [System.String]
        $MediaType
    )

    $eventXml = [XML]$EventRecord.ToXml()
    $tempObject = [PsCustomObject]@{}

    for ($index = 0; $index -lt $eventXml.Event.EventData.Data.Count; $index++)
    {
        $tempObject | Add-Member -MemberType NoteProperty `
                                 -Name $eventXml.Event.EventData.Data[$index].Name `
                                 -Value $eventXml.Event.EventData.Data[$index].'#Text'
    }

    $Object.Value.PSObject.TypeNames.Insert( 0, "Microsoft.Windows.StorageManagement.StorageHistory" )

    $Object.Value.Version = [UInt16]$tempObject.Version

    if ([String]::IsNullOrEmpty($FriendlyName) -eq $false)
    {
        $Object.Value.FriendlyName = $FriendlyName
    }
    else
    {
        $Object.Value.FriendlyName = $tempObject.ProductId
    }

    if ([String]::IsNullOrEmpty($SerialNumber) -eq $false)
    {
        $Object.Value.SerialNumber = $SerialNumber
    }
    else
    {
        $Object.Value.SerialNumber = $tempObject.SerialNumber
    }

    if ([String]::IsNullOrEmpty($AdapterSerialNumber) -eq $false)
    {
        $Object.Value.AdapterSerialNumber = $AdapterSerialNumber
    }
    else
    {
        $Object.Value.AdapterSerialNumber = $tempObject.AdapterSerialNumber
    }

    if ([String]::IsNullOrEmpty($FirmwareRevision) -eq $false)
    {
        $Object.Value.FirmwareRevision = $FirmwareRevision
    }
    else
    {
        $Object.Value.FirmwareRevision = $tempObject.FirmwareRevision
    }

    $Object.Value.DeviceGuid          = $DeviceGuid
    $Object.Value.DeviceNumber        = $Number

    if ([String]::IsNullOrEmpty($BusType) -eq $false)
    {
        $Object.Value.BusType         = $BusType
    }
    else
    {
        switch ($tempObject.BusType)
        {
            "8"     { $Object.Value.BusType = "RAID" }
            "10"    { $Object.Value.BusType = "SAS" }
            "11"    { $Object.Value.BusType = "SATA" }
            "14"    { $Object.Value.BusType = "Virtual" }
            "17"    { $Object.Value.BusType = "NVMe" }
            "19"    { $Object.Value.BusType = "UFS" }
            default { $Object.Value.BusType = "Unknown" }
        }
    }

    $Object.Value.MediaType           = $MediaType
    $Object.Value.EventTime           = $event.TimeCreated
    $Object.Value.MaxQueueCount       = [UInt32]$tempObject.MaxDeviceQueueCount
    $Object.Value.MaxOutstandingCount = [UInt32]$tempObject.MaxOutstandingCount

    if ($Object.Value.Version -eq 5)
    {
        $Object.Value.PSObject.TypeNames.Insert( 1, "Microsoft.Windows.StorageManagement.StorageHistory_v5" )

        $Object.Value.BucketCount          = 5
        $Object.Value.BucketIoLatency      = New-Object -TypeName System.UInt64[] $Object.Value.BucketCount
        $Object.Value.BucketSuccessIoCount = New-Object -TypeName System.UInt64[] $Object.Value.BucketCount
        $Object.Value.BucketFailedIoCount  = New-Object -TypeName System.UInt64[] $Object.Value.BucketCount
        $Object.Value.BucketTotalIoCount   = New-Object -TypeName System.UInt64[] $Object.Value.BucketCount
        $Object.Value.BucketTotalIoTime    = New-Object -TypeName System.UInt64[] $Object.Value.BucketCount
        $Object.Value.BucketIoPercent      = New-Object -TypeName System.UInt64[] $Object.Value.BucketCount

        $Object.Value.BucketIoLatency[0] =    16 * 10000 # 16 ms in 100ns
        $Object.Value.BucketIoLatency[1] =    64 * 10000 # 64 ms in 100ns
        $Object.Value.BucketIoLatency[2] =  2048 * 10000 # 2048 ms in 100ns
        $Object.Value.BucketIoLatency[3] =  5120 * 10000 # 5120 ms in 100ns
        $Object.Value.BucketIoLatency[4] = 10000 * 10000 # 5120+ ms approx to 10000 ms in 100ns

        for ($index = 0; $index -lt $Object.Value.BucketCount; $index++)
        {
            $field = "BucketIoCount" + [System.String]($index + 1)
            $Object.Value.BucketTotalIoCount[$index] = $($tempObject.$field)
            $Object.Value.BucketTotalIoTime[$index]  = $Object.Value.BucketTotalIoCount[$index] * $Object.Value.BucketIoLatency[$index]

            $Object.Value.TotalIoCount   += $Object.Value.BucketTotalIoCount[$index]
            $Object.Value.TotalIoLatency += $Object.Value.BucketTotalIoTime[$index]

            if ($Object.Value.BucketTotalIoCount[$index] -gt 0)
            {
                $Object.Value.MaxIoLatency         = $Object.Value.BucketIoLatency[$index]
                $Object.Value.HighestLatencyBucket = $index
            }
        }

        if ($Object.Value.TotalIoCount -gt 0)
        {
            for ($index = 0; $index -lt $Object.Value.BucketCount; $index++)
            {
                $Object.Value.BucketIoPercent[$index] = ($Object.Value.BucketTotalIoCount[$index] * 100) / $Object.Value.TotalIoCount
            }
        }
    }
    elseif (($Object.Value.Version -ge 9) -or  ($Object.Value.Version -le 11))
    {
        $Object.Value.PSObject.TypeNames.Insert( 1, "Microsoft.Windows.StorageManagement.StorageHistory_v11" )

        $Object.Value.TotalIoCount    = $tempObject.TotalIoCount
        $Object.Value.TotalReadBytes  = $tempObject.TotalReadBytes
        $Object.Value.TotalWriteBytes = $tempObject.TotalWriteBytes
        $Object.Value.MaxIoLatency    = $tempObject.MaxReadWriteLatency_100ns
        $Object.Value.MaxFlushLatency = $tempObject.MaxFlushLatency_100ns
        $Object.Value.MaxUnmapLatency = $tempObject.MaxUnmapLatency_100ns

        if ($Object.Value.Version -eq 11)
        {
            $Object.Value.TotalQueueIoCount                       = $tempObject.TotalDeviceQueueIoCount
            $Object.Value.TotalQueueIoWaitTime                    = $tempObject.TotalDeviceQueueIoWaitDuration_100ns
            $Object.Value.MaxQueueIoWaitTime                      = $tempObject.MaxDeviceQueueIoWaitDuration_100ns
            $Object.Value.IoTimeout                               = $tempObject.IoTimeout_s
            $Object.Value.QueueIoWaitExceededTimeoutCount         = $tempObject.DeviceQueueIoWaitExceededTimeoutCount
            $Object.Value.QueueIoBusyCount                        = $tempObject.DeviceQueueIoBusyCount
            $Object.Value.QueueIoPausedCount                      = $tempObject.DeviceQueueIoPausedCount
            $Object.Value.QueueIoUntaggedCommandOutstandingCount  = $tempObject.DeviceQueueIoUntaggedCommandOutstandingCount
            $Object.Value.QueueIoPausedForUntaggedCount           = $tempObject.DeviceQueueIoPausedForUntaggedCount
        }

        $Object.Value.BucketCount          = 12
        $Object.Value.BucketIoLatency      = New-Object -TypeName System.UInt64[] $Object.Value.BucketCount
        $Object.Value.BucketSuccessIoCount = New-Object -TypeName System.UInt64[] $Object.Value.BucketCount
        $Object.Value.BucketFailedIoCount  = New-Object -TypeName System.UInt64[] $Object.Value.BucketCount
        $Object.Value.BucketTotalIoCount   = New-Object -TypeName System.UInt64[] $Object.Value.BucketCount
        $Object.Value.BucketTotalIoTime    = New-Object -TypeName System.UInt64[] $Object.Value.BucketCount
        $Object.Value.BucketIoPercent      = New-Object -TypeName System.UInt64[] $Object.Value.BucketCount

        $Object.Value.BucketIoLatency[0]  =    256 * 10    # 256 us in 100ns
        $Object.Value.BucketIoLatency[1]  =      1 * 10000 # 1 ms in 100ns
        $Object.Value.BucketIoLatency[2]  =      4 * 10000 # 4 ms in 100ns
        $Object.Value.BucketIoLatency[3]  =     16 * 10000 # 16 ms in 100ns
        $Object.Value.BucketIoLatency[4]  =     64 * 10000 # 64 ms in 100ns
        $Object.Value.BucketIoLatency[5]  =    128 * 10000 # 128 ms in 100ns
        $Object.Value.BucketIoLatency[6]  =    256 * 10000 # 256 ms in 100ns
        $Object.Value.BucketIoLatency[7]  =   2000 * 10000 # 2000 ms in 100ns
        $Object.Value.BucketIoLatency[8]  =   6000 * 10000 # 6000 ms in 100ns
        $Object.Value.BucketIoLatency[9]  =  10000 * 10000 # 10000 ms in 100ns
        $Object.Value.BucketIoLatency[10] =  20000 * 10000 # 20000 ms in 100ns
        $Object.Value.BucketIoLatency[11] =  30000 * 10000 # 20000+ ms approx to 30000 ms in 100ns

        for ($index = 0; $index -lt $Object.Value.BucketCount; $index++)
        {
            $field = "BucketIoSuccess" + [System.String]($index + 1)
            $Object.Value.BucketSuccessIoCount[$index] = $($tempObject.$field)

            $field = "BucketIoFailed" + [System.String]($index + 1)
            $Object.Value.BucketFailedIoCount[$index] = $($tempObject.$field)

            $field = "BucketIoLatency" + [System.String]($index + 1) + "_100ns"
            $Object.Value.BucketTotalIoTime[$index] = $($tempObject.$field)

            $Object.Value.BucketTotalIoCount[$index] = $Object.Value.BucketSuccessIoCount[$index] + $Object.Value.BucketFailedIoCount[$index]

            $Object.Value.SuccessIoCount         += $Object.Value.BucketSuccessIoCount[$index]
            $Object.Value.FailedIoCount          += $Object.Value.BucketFailedIoCount[$index]
            $Object.Value.TotalIoLatency         += $Object.Value.BucketTotalIoTime[$index]

            if ($Object.Value.TotalIoCount -gt 0)
            {
                $Object.Value.BucketIoPercent[$index] = ($Object.Value.BucketTotalIoCount[$index] * 100) / $Object.Value.TotalIoCount
            }

            if ($Object.Value.BucketTotalIoCount[$index] -gt 0)
            {
                $Object.Value.HighestLatencyBucket = $index
            }
        }
    }

    if ($Object.Value.TotalIoCount -gt 0)
    {
        $Object.Value.AvgIoLatency = $Object.Value.TotalIoLatency / $Object.Value.TotalIoCount
    }

    if (($Object.Value.Version -eq 11) -and ($Object.Value.TotalQueueIoCount -gt 0))
    {
        $Object.Value.AvgQueueIoWaitTime = $Object.Value.TotalQueueIoWaitTime / $Object.Value.TotalQueueIoCount
    }
}


$AugmentStorageHistory = {

    Param
    (
        [Ref]
        $Object,

        [System.Diagnostics.Eventing.Reader.EventRecord]
        $EventRecord
    )

    $eventXml = [XML]$EventRecord.ToXml()
    $tempObject = [PsCustomObject]@{}

    for ($index = 0; $index -lt $eventXml.Event.EventData.Data.Count; $index++)
    {
        $tempObject | Add-Member -MemberType NoteProperty `
                                 -Name $eventXml.Event.EventData.Data[$index].Name `
                                 -Value $eventXml.Event.EventData.Data[$index].'#Text'
    }

    if ($EventRecord.Id -eq 502)
    {
        if ($Object.Value.UnresponsiveTime -eq $null)
        {
            $Object.Value.UnresponsiveTime = New-Object System.Collections.ArrayList
        }

        $Object.Value.UnresponsiveTime.Add($EventRecord.TimeCreated)
    }
    elseif ($EventRecord.Id -eq 503)
    {
        if ($Object.Value.ResponsiveTime -eq $null)
        {
            $Object.Value.ResponsiveTime = New-Object System.Collections.ArrayList
        }

        $Object.Value.ResponsiveTime.Add($EventRecord.TimeCreated)
    }
    elseif ($EventRecord.Id -eq 504)
    {
        $Object.Value.TotalErrors                       = $tempObject.TotalErrors
        $Object.Value.TotalReadWriteErrors              = $tempObject.TotalReadWriteErrors
        $Object.Value.TotalImpendingDeviceFailureErrors = $tempObject.TotalImpendingDeviceFailureErrors
        $Object.Value.TotalDeviceFailureErrors          = $tempObject.TotalDeviceFailureErrors
    }
}


$PopulateStorageHistoryAggregate = {

    Param
    (
        [Ref]
        $AggregateObject,

        [Ref]
        $Object
    )

    if ($AggregateObject.Value.Version -eq 0)
    {
        $AggregateObject.Value.PSObject.TypeNames.Insert( 0, "Microsoft.Windows.StorageManagement.StorageHistoryAggregate" )

        if ($Object.Value.Version -eq 5)
        {
            $AggregateObject.Value.PSObject.TypeNames.Insert( 1, "Microsoft.Windows.StorageManagement.StorageHistoryAggregate_v5" )
        }
        elseif (($Object.Value.Version -ge 9) -or  ($Object.Value.Version -le 11))
        {
            $AggregateObject.Value.PSObject.TypeNames.Insert( 1, "Microsoft.Windows.StorageManagement.StorageHistoryAggregate_v11" )
        }

        $AggregateObject.Value.Version                  = $Object.Value.Version
        $AggregateObject.Value.FriendlyName             = $Object.Value.FriendlyName
        $AggregateObject.Value.SerialNumber             = $Object.Value.SerialNumber
        $AggregateObject.Value.AdapterSerialNumber      = $Object.Value.AdapterSerialNumber
        $AggregateObject.Value.FirmwareRevision         = $Object.Value.FirmwareRevision
        $AggregateObject.Value.DeviceGuid               = $Object.Value.DeviceGuid
        $AggregateObject.Value.DeviceNumber             = $Object.Value.DeviceNumber
        $AggregateObject.Value.BusType                  = $Object.Value.BusType
        $AggregateObject.Value.MediaType                = $Object.Value.MediaType
        $AggregateObject.Value.StartTime                = $Object.Value.EventTime
        $AggregateObject.Value.EndTime                  = $Object.Value.EventTime
        $AggregateObject.Value.EventCount               = 1
        $AggregateObject.Value.MaxQueueCount            = $Object.Value.MaxQueueCount
        $AggregateObject.Value.MaxOutstandingCount      = $Object.Value.MaxOutstandingCount
        $AggregateObject.Value.TotalIoCount             = $Object.Value.TotalIoCount
        $AggregateObject.Value.SuccessIoCount           = $Object.Value.SuccessIoCount
        $AggregateObject.Value.FailedIoCount            = $Object.Value.FailedIoCount
        $AggregateObject.Value.TotalReadBytes           = $Object.Value.TotalReadBytes
        $AggregateObject.Value.TotalWriteBytes          = $Object.Value.TotalWriteBytes
        $AggregateObject.Value.TotalIoLatency           = $Object.Value.TotalIoLatency
        $AggregateObject.Value.MaxIoLatency             = $Object.Value.MaxIoLatency
        $AggregateObject.Value.MaxFlushLatency          = $Object.Value.MaxFlushLatency
        $AggregateObject.Value.MaxUnmapLatency          = $Object.Value.MaxUnmapLatency
        $AggregateObject.Value.TotalQueueIoCount        = $Object.Value.TotalQueueIoCount
        $AggregateObject.Value.TotalQueueIoWaitTime     = $Object.Value.TotalQueueIoWaitTime
        $AggregateObject.Value.MaxQueueIoWaitTime       = $Object.Value.MaxQueueIoWaitTime

        if ($Object.Value.IoTimeout)
        {
            $AggregateObject.Value.IoTimeout = $Object.Value.IoTimeout
        }

        $AggregateObject.Value.QueueIoWaitExceededTimeoutCount        = $Object.Value.QueueIoWaitExceededTimeoutCount
        $AggregateObject.Value.QueueIoBusyCount                       = $Object.Value.QueueIoBusyCount
        $AggregateObject.Value.QueueIoPausedCount                     = $Object.Value.QueueIoPausedCount
        $AggregateObject.Value.QueueIoUntaggedCommandOutstandingCount = $Object.Value.QueueIoUntaggedCommandOutstandingCount
        $AggregateObject.Value.QueueIoPausedForUntaggedCount          = $Object.Value.QueueIoPausedForUntaggedCount
        $AggregateObject.Value.TotalErrors                            = $Object.Value.TotalErrors
        $AggregateObject.Value.TotalReadWriteErrors                   = $Object.Value.TotalReadWriteErrors
        $AggregateObject.Value.TotalImpendingDeviceFailureErrors      = $Object.Value.TotalImpendingDeviceFailureErrors
        $AggregateObject.Value.TotalDeviceFailureErrors               = $Object.Value.TotalDeviceFailureErrors
        $AggregateObject.Value.BucketCount                            = $Object.Value.BucketCount

        $AggregateObject.Value.BucketIoLatency               = New-Object -TypeName System.UInt64[] $AggregateObject.Value.BucketCount
        $AggregateObject.Value.BucketSuccessIoCount          = New-Object -TypeName System.UInt64[] $AggregateObject.Value.BucketCount
        $AggregateObject.Value.BucketFailedIoCount           = New-Object -TypeName System.UInt64[] $AggregateObject.Value.BucketCount
        $AggregateObject.Value.BucketTotalIoCount            = New-Object -TypeName System.UInt64[] $AggregateObject.Value.BucketCount
        $AggregateObject.Value.BucketTotalIoTime             = New-Object -TypeName System.UInt64[] $AggregateObject.Value.BucketCount
        $AggregateObject.Value.BucketIoPercent               = New-Object -TypeName System.UInt64[] $AggregateObject.Value.BucketCount
        $AggregateObject.Value.BucketHighestLatencyCount     = New-Object -TypeName System.UInt32[] $AggregateObject.Value.BucketCount

        for ($index = 0; $index -lt $AggregateObject.Value.BucketCount; $index++)
        {
            $AggregateObject.Value.BucketIoLatency[$index]      = $Object.Value.BucketIoLatency[$index]
            $AggregateObject.Value.BucketSuccessIoCount[$index] = $Object.Value.BucketSuccessIoCount[$index]
            $AggregateObject.Value.BucketFailedIoCount[$index]  = $Object.Value.BucketFailedIoCount[$index]
            $AggregateObject.Value.BucketTotalIoCount[$index]   = $Object.Value.BucketTotalIoCount[$index]
            $AggregateObject.Value.BucketTotalIoTime[$index]    = $Object.Value.BucketTotalIoTime[$index]
        }

        $AggregateObject.Value.BucketHighestLatencyCount[$($Object.Value.HighestLatencyBucket)]++

    }
    else
    {
        if ($Object.Value.Version -ne $AggregateObject.Value.Version)
        {
            return
        }

        if ($Object.Value.EventTime -lt $AggregateObject.Value.StartTime)
        {
            $AggregateObject.Value.StartTime = $Object.Value.EventTime
        }

        if ($Object.Value.EventTime -gt $AggregateObject.Value.EndTime)
        {
            $AggregateObject.Value.EndTime = $Object.Value.EventTime
        }

        $AggregateObject.Value.EventCount++

        if ($Object.Value.MaxQueueCount -gt $AggregateObject.Value.MaxQueueCount)
        {
            $AggregateObject.Value.MaxQueueCount = $Object.Value.MaxQueueCount
        }

        if ($Object.Value.MaxOutstandingCount -gt $AggregateObject.Value.MaxOutstandingCount)
        {
            $AggregateObject.Value.MaxOutstandingCount = $Object.Value.MaxOutstandingCount
        }

        $AggregateObject.Value.TotalIoCount    += $Object.Value.TotalIoCount
        $AggregateObject.Value.SuccessIoCount  += $Object.Value.SuccessIoCount
        $AggregateObject.Value.FailedIoCount   += $Object.Value.FailedIoCount
        $AggregateObject.Value.TotalReadBytes  += $Object.Value.TotalReadBytes
        $AggregateObject.Value.TotalWriteBytes += $Object.Value.TotalWriteBytes
        $AggregateObject.Value.TotalIoLatency  += $Object.Value.TotalIoLatency

        if ($Object.Value.MaxIoLatency -gt $AggregateObject.Value.MaxIoLatency)
        {
            $AggregateObject.Value.MaxIoLatency = $Object.Value.MaxIoLatency
        }

        if ($Object.Value.MaxFlushLatency -gt $AggregateObject.Value.MaxFlushLatency)
        {
            $AggregateObject.Value.MaxFlushLatency = $Object.Value.MaxFlushLatency
        }

        if ($Object.Value.MaxUnmapLatency -gt $AggregateObject.Value.MaxUnmapLatency)
        {
            $AggregateObject.Value.MaxUnmapLatency = $Object.Value.MaxUnmapLatency
        }

        $AggregateObject.Value.TotalQueueIoCount    += $Object.Value.TotalQueueIoCount
        $AggregateObject.Value.TotalQueueIoWaitTime += $Object.Value.TotalQueueIoWaitTime

        if ($Object.Value.MaxQueueIoWaitTime -gt $AggregateObject.Value.MaxQueueIoWaitTime)
        {
            $AggregateObject.Value.MaxQueueIoWaitTime = $Object.Value.MaxQueueIoWaitTime
        }

        if ($Object.Value.IoTimeout)
        {
            $AggregateObject.Value.IoTimeout = $Object.Value.IoTimeout
        }

        $AggregateObject.Value.QueueIoWaitExceededTimeoutCount        += $Object.Value.QueueIoWaitExceededTimeoutCount
        $AggregateObject.Value.QueueIoBusyCount                       += $Object.Value.QueueIoBusyCount
        $AggregateObject.Value.QueueIoPausedCount                     += $Object.Value.QueueIoPausedCount
        $AggregateObject.Value.QueueIoUntaggedCommandOutstandingCount += $Object.Value.QueueIoUntaggedCommandOutstandingCount
        $AggregateObject.Value.QueueIoPausedForUntaggedCount          += $Object.Value.QueueIoPausedForUntaggedCount
        $AggregateObject.Value.TotalErrors                            += $Object.Value.TotalErrors
        $AggregateObject.Value.TotalReadWriteErrors                   += $Object.Value.TotalReadWriteErrors
        $AggregateObject.Value.TotalImpendingDeviceFailureErrors      += $Object.Value.TotalImpendingDeviceFailureErrors
        $AggregateObject.Value.TotalDeviceFailureErrors               += $Object.Value.TotalDeviceFailureErrors

        for ($index = 0; $index -lt $AggregateObject.Value.BucketCount; $index++)
        {
            $AggregateObject.Value.BucketSuccessIoCount[$index] += $Object.Value.BucketSuccessIoCount[$index]
            $AggregateObject.Value.BucketFailedIoCount[$index]  += $Object.Value.BucketFailedIoCount[$index]
            $AggregateObject.Value.BucketTotalIoCount[$index]   += $Object.Value.BucketTotalIoCount[$index]
            $AggregateObject.Value.BucketTotalIoTime[$index]    += $Object.Value.BucketTotalIoTime[$index]
        }

        $AggregateObject.Value.BucketHighestLatencyCount[$($Object.Value.HighestLatencyBucket)]++
    }
}


function Get-StorageHistory
{
    Param
    (
        #### -------------------- Parameter sets -------------------------------------####

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_PhysicalDisk")]
        [Parameter(
            ParameterSetName  = 'ByPhysicalDisk',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $PhysicalDisk,

        [System.String]
        [Parameter(
            ParameterSetName  = 'ByDeviceGuid',
            ValueFromPipeline = $false,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias("DeviceId")]
        $DeviceGuid,

        [System.String]
        [Parameter(
            ParameterSetName  = 'ByDeviceNumber',
            ValueFromPipeline = $false,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $DeviceNumber,

        #### ------------------------- PhysicalDisk parameters -----------------------####

        [System.Management.Automation.PSCredential]
        [Parameter(
            ParameterSetName = 'ByPhysicalDisk',
            Mandatory        = $false)]
        $Credential,

        #### ------------------------- Common method parameters ----------------------####

        [System.String]
        [Parameter(
            ParameterSetName = 'ByPhysicalDisk',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDeviceGuid',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDeviceNumber',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $LogFile,

        [UInt32]
        [Parameter(
            ParameterSetName = 'ByPhysicalDisk',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDeviceGuid',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDeviceNumber',
            Mandatory        = $false)]
        $NumberOfHours,

        [Switch]
        [Parameter(
            ParameterSetName = 'ByPhysicalDisk',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDeviceGuid',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDeviceNumber',
            Mandatory        = $false)]
        $Disaggregate,

        [Switch]
        [Parameter(
            ParameterSetName = 'ByPhysicalDisk',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDeviceGuid',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDeviceNumber',
            Mandatory        = $false)]
        $Errors
    )

    Begin
    {
    }

    Process
    {
        #
        # Get the device id based on the parameter set
        #

        switch -regex ($psCmdlet.ParameterSetName)
        {
            { @(                  `
                "ByPhysicalDisk", `
                "ByDeviceNumber"  `
                ) -contains $_    `
            }
            {
                #
                # If device number is specified,
                # get the corresponding device
                #

                try
                {
                    if ($DeviceNumber)
                    {
                        $PhysicalDisk = Get-PhysicalDisk -DeviceNumber $DeviceNumber
                    }
                }
                catch
                {
                    $errorObject = CreateErrorRecord -ErrorId "ObjectNotFound" `
                                                     -ErrorMessage "A physical disk matching the input criteria was not found" `
                                                     -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) `
                                                     -Exception $null `
                                                     -TargetObject $null

                    $psCmdlet.WriteError($errorObject)
                    return
                }

                #
                # If log file is not specified,
                # get the node name where the
                # device is connected
                #

                if (-not $LogFile)
                {
                    $subsystem = $PhysicalDisk | get-storagesubsystem

                    if ($subsystem.Model -eq "Clustered Windows Storage" -and
                        $subsystem.StorageConnectionType -eq "Local Storage")
                    {
                        # If the subsystem is clustered local (storage spaces direct) use
                        # SSU mapping since it works for 'Lost Communication' drives

                        $storageScaleUnit = $PhysicalDisk | Get-StorageScaleUnit

                        if (-not $storageScaleUnit)
                        {
                            $errorObject = CreateErrorRecord -ErrorId "ObjectNotFound" `
                                                             -ErrorMessage "A storage scale unit was not found for the physical disk" `
                                                             -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) `
                                                             -Exception $null `
                                                             -TargetObject $null

                            $psCmdlet.WriteError($errorObject)
                            return
                        }

                        $computerName = $storageScaleUnit.FriendlyName
                    }
                    else
                    {
                        $storageNode = $PhysicalDisk | Get-StorageNode -PhysicallyConnected

                        if (-not $storageNode)
                        {
                            $errorObject = CreateErrorRecord -ErrorId "ObjectNotFound" `
                                                             -ErrorMessage "A storage node was not found for the physical disk" `
                                                             -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) `
                                                             -Exception $null `
                                                             -TargetObject $null

                            $psCmdlet.WriteError($errorObject)
                            return
                        }

                        $computerName = $storageNode.Name
                    }
                }

                # Extract the device guid from the object id
                # It is of the format ' <>:PD:<DeviceGuid>" '
                $guid = $PhysicalDisk.ObjectId.Split(":")[2].TrimEnd('"')

                $friendlyName        = $PhysicalDisk.FriendlyName
                $serialNumber        = $PhysicalDisk.SerialNumber
                $adapterSerialNumber = $PhysicalDisk.AdapterSerialNumber
                $firmwareRevision    = $PhysicalDisk.FirmwareVersion

                if ($PhysicalDisk.DeviceId -ge 0)
                {
                    $deviceNumber = $PhysicalDisk.DeviceId
                }

                $busType      = $PhysicalDisk.BusType
                $mediaType    = $PhysicalDisk.MediaType
            }

            { @(                 `
                "ByDeviceGuid"   `
                ) -contains $_   `
            }
            {
                try
                {
                    [System.Guid]::Parse($DeviceGuid) | Out-Null
                    $guid = $DeviceGuid
                }
                catch
                {
                    $errorObject = CreateErrorRecord -ErrorId "InvalidArgument" `
                                                     -ErrorMessage "DeviceGuid (or DeviceId) should be a GUID of the format 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' or '{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}'" `
                                                     -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidArgument) `
                                                     -Exception $null `
                                                     -TargetObject $null

                    $psCmdlet.WriteError($errorObject)
                    return
                }
            }
        }

        #
        # Build the query to look for the specific events in storport log
        #

        if ($PSBoundParameters.ContainsKey("Errors"))
        {
            $eventFilter = "EventID=549"
        }
        else
        {
            $eventFilter = "EventID=502 or `
                            EventID=503 or `
                            EventID=504 or `
                            EventID=505"
        }

        if ($NumberOfHours)
        {
            $query = "*[System[($eventFilter) and TimeCreated[timediff(@SystemTime) <= $($NumberOfHours*3600*1000)]]] and *[EventData[Data[@Name='ClassDeviceGuid'] and (Data='$guid')]]"
        }
        else
        {
            $query = "*[System[($eventFilter)]] and *[EventData[Data[@Name='ClassDeviceGuid'] and (Data='$guid')]]"
        }

        #
        # Retrive the events depending on the parameters
        #

        Write-Progress -Activity "Get-StorageHistory" -PercentComplete 0 -CurrentOperation "Gathering events" -Status "0/2"

        try
        {
            if ($computerName)
            {
                if ($Credential)
                {
                    $events = Get-WinEvent -LogName Microsoft-Windows-Storage-Storport/Operational `
                                           -FilterXPath $query `
                                           -ComputerName $computerName `
                                           -Credential $Credential `
                                           -ErrorAction Stop `
                                           -Oldest
                }
                else
                {
                    $events = Get-WinEvent -LogName Microsoft-Windows-Storage-Storport/Operational `
                                           -FilterXPath $query `
                                           -ComputerName $computerName `
                                           -ErrorAction Stop `
                                           -Oldest
                }
            }
            else
            {
                if ($LogFile)
                {
                    $events = Get-WinEvent -Path $LogFile `
                                           -FilterXPath $query `
                                           -ErrorAction Stop `
                                           -Oldest
                }
                else
                {
                    $events = Get-WinEvent -LogName Microsoft-Windows-Storage-Storport/Operational `
                                           -FilterXPath $query `
                                           -ErrorAction Stop `
                                           -Oldest
                }
            }
        }
        catch
        {
            $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId `
                                             -ErrorMessage $null `
                                             -ErrorCategory $_.CategoryInfo.Category `
                                             -Exception $_.Exception `
                                             -TargetObject $_.TargetObject

            $psCmdlet.WriteError($errorObject)
            return
        }

        #
        # Construct StorageHistory and StorageHistoryAggregate objects
        #

        Write-Progress -Activity "Get-StorageHistory" -PercentComplete 0 -CurrentOperation "Constructing storage history objects" -Status "1/2"

        if ($PSBoundParameters.ContainsKey("Errors"))
        {
            for ($index = 0; $index -lt $events.count;)
            {
                $event = $events[$index]
                $index++

                [StorageError]$data = [StorageError]::new()

                &$PopulateStorageError -Object ([ref]$data) `
                                       -EventRecord $event `
                                       -FriendlyName $friendlyName `
                                       -SerialNumber $serialNumber `
                                       -AdapterSerialNumber $adapterSerialNumber `
                                       -FirmwareRevision $firmwareRevision `
                                       -DeviceGuid $guid `
                                       -Number $deviceNumber `
                                       -BusType $busType `
                                       -MediaType $mediaType | Out-Null

                $psCmdlet.WriteObject($data)
            }
        }
        else
        {
            $firstEvent = $true

            for ($index = 0; $index -lt $events.count;)
            {
                $event = $events[$index]
                $index++

                #
                # A StorageHistory object is created for every 505 event.
                # Following non-505 events are collaped into it till
                # the next 505 event is encountered. All non-505 events
                # before the first 505 event are ignored
                #
                if ($event.Id -ne 505)
                {
                    continue
                }

                [StorageHistory]$data = [StorageHistory]::new()

                &$PopulateStorageHistory -Object ([ref]$data) `
                                         -EventRecord $event `
                                         -FriendlyName $friendlyName `
                                         -SerialNumber $serialNumber `
                                         -AdapterSerialNumber $adapterSerialNumber `
                                         -FirmwareRevision $firmwareRevision `
                                         -DeviceGuid $guid `
                                         -Number $deviceNumber `
                                         -BusType $busType `
                                         -MediaType $mediaType | Out-Null

                #
                # If there are no IOs, skip
                #

                if ($data.TotalIoCount -eq 0)
                {
                    continue
                }

                #
                # Check if there are any non-505 events following it
                #

                for (; $index -lt $events.count; $index++)
                {
                    $event = $events[$index]

                    if ($event.Id -eq 505)
                    {
                        break
                    }

                    &$AugmentStorageHistory -Object ([ref]$data) `
                                            -EventRecord $event | Out-Null
                }

                if ($PSBoundParameters.ContainsKey("Disaggregate"))
                {
                    $psCmdlet.WriteObject($data)
                }
                else
                {
                    if ($firstEvent)
                    {
                        [StorageHistoryAggregate]$aggregateData = [StorageHistoryAggregate]::new()
                        $firstEvent = $false
                    }

                    &$PopulateStorageHistoryAggregate -AggregateObject ([ref]$aggregateData) `
                                                    -Object ([ref]$data) | Out-Null
                }
            }

            if ($aggregateData)
            {
                $aggregateData.AvgIoLatency     = $aggregateData.TotalIoLatency / $aggregateData.TotalIoCount

                if ($aggregateData.TotalQueueIoCount -gt 0)
                {
                    $aggregateData.AvgQueueIoWaitTime = $aggregateData.TotalQueueIoWaitTime / $aggregateData.TotalQueueIoCount
                }

                for ($index = 0; $index -lt $aggregateData.BucketCount; $index++)
                {
                    $aggregateData.BucketIoPercent[$index] = ($aggregateData.BucketTotalIoCount[$index] * 100) / $aggregateData.TotalIoCount
                }

                $psCmdlet.WriteObject($aggregateData)
            }
        }

        Write-Progress -Activity "Get-StorageHistory" -Completed -Status "2/2"
    }
}


$DisplayTimeSeriesChart = {

    Param
    (
        [System.String]
        $ChartId,

        [System.String]
        $Title,

        [System.Collections.ArrayList]
        $StorageHistory,

        [System.Boolean]
        $ShowDefault,

        [System.Boolean]
        $ShowAvgLatency,

        [System.Boolean]
        $ShowMaxLatency
    )

    #
    # Build a device table with the
    # data to chart.
    #

    $deviceTable = New-Object System.Collections.Hashtable

    ForEach ($data in $StorageHistory)
    {
        if ($deviceTable.Contains($data.DeviceId) -eq $false)
        {
            $deviceData = New-Object -TypeName PsObject
            $deviceData | Add-Member -NotePropertyName "DeviceNumber" -NotePropertyValue $data.DeviceNumber
            $deviceData | Add-Member -NotePropertyName "FriendlyName" -NotePropertyValue $data.FriendlyName
            $deviceData | Add-Member -NotePropertyName "SerialNumber" -NotePropertyValue $data.SerialNumber

            $dataList = New-Object System.Collections.ArrayList
            $deviceData | Add-Member -NotePropertyName "DataList" -NotePropertyValue $dataList

            $deviceTable.Add($data.DeviceId, $deviceData)
        }
        else
        {
            $deviceData = $deviceTable[$data.DeviceId]
            $dataList = $deviceData.DataList
        }

        $dataPoint = New-Object -TypeName PsObject
        $dataPoint | Add-Member -NotePropertyName "DateTime"         -NotePropertyValue $data.EventTime
        $dataPoint | Add-Member -NotePropertyName "AvgIoLatency"     -NotePropertyValue ($data.AvgIoLatency/10)
        $dataPoint | Add-Member -NotePropertyName "MaxIoLatency"     -NotePropertyValue ($data.MaxIoLatency/10)
        $dataPoint | Add-Member -NotePropertyName "MaxQueueDepth"    -NotePropertyValue $data.MaxOutstandingCount
        $dataPoint | Add-Member -NotePropertyName "UnresponsiveTime" -NotePropertyValue $data.UnresponsiveTime
        $dataPoint | Add-Member -NotePropertyName "ResponsiveTime"   -NotePropertyValue $data.ResponsiveTime

        [void]$dataList.Add($dataPoint)
    }

    #
    # Create a chart
    #

    $chart = New-object Windows.Forms.DataVisualization.Charting.Chart
    $chart.Anchor = [Windows.Forms.AnchorStyles]::Bottom -bor
                    [Windows.Forms.AnchorStyles]::Right -bor
                    [Windows.Forms.AnchorStyles]::Top -bor
                    [Windows.Forms.AnchorStyles]::Left

    $chart.Width = 1000
    $chart.Height = 800
    $chart.Left = 40
    $chart.Top = 30
    $chart.BackColor = [Drawing.Color]::White
    [void]$chart.Titles.Add($Title)
    $chart.Titles[0].Font = "segoeuilight,12pt"

    #
    # Create a chart area to draw on
    #

    $chartArea = New-Object Windows.Forms.DataVisualization.Charting.ChartArea
    $chartarea.Name = "TimeSeries"
    $chartarea.AxisX.IntervalType = [Windows.Forms.DataVisualization.Charting.DateTimeIntervalType]::Hours
    $chartarea.AxisX.IntervalAutoMode = [Windows.Forms.DataVisualization.Charting.IntervalAutoMode]::VariableCount
    $chartarea.AxisX.MajorGrid.Enabled = $false
    $chartarea.AxisX.LabelStyle.Format = "yyyy/MM/dd h tt"
    $chartarea.AxisX.ScaleView.Zoomable = $true
    $chartarea.AxisX.ScrollBar.IsPositionedInside = $true
    $chartarea.AxisX.ScrollBar.ButtonStyle = [Windows.Forms.DataVisualization.Charting.ScrollBarButtonStyles]::All
    $chartarea.CursorX.IsUserEnabled = $true
    $chartarea.CursorX.IsUserSelectionEnabled = $true
    $chartarea.CursorX.IntervalType = [Windows.Forms.DataVisualization.Charting.DateTimeIntervalType]::Hours
    $chartarea.CursorX.AutoScroll = $true
    $chartArea.AxisY.Title = "Latency (us)"
    $chartArea.AxisY.TitleFont = "segoeuilight,12pt"
    $chartarea.AxisY.LabelStyle.Format = "N0"
    $chartarea.AxisY.MinorGrid.Enabled = $true
    $chartarea.AxisY.MinorGrid.LineDashStyle = [Windows.Forms.DataVisualization.Charting.ChartDashStyle]::Dot

    $chart.ChartAreas.Add($chartArea)

    $legend = New-Object Windows.Forms.DataVisualization.Charting.Legend
    $legend.Name = "TimeSeries"
    $legend.Docking = [Windows.Forms.DataVisualization.Charting.Docking]::Right
    $legend.LegendStyle = [Windows.Forms.DataVisualization.Charting.LegendStyle]::Column
    $chart.Legends.Add($legend)

    #
    # Build the data to chart
    #

    if ($ShowDefault -eq $true)
    {
        $ShowAvgLatency = $true

        if ($deviceTable.Count -eq 1)
        {
            $ShowMaxLatency = $true
        }
        else
        {
            $ShowMaxLatency = $false
        }
    }

    ForEach ($deviceId in $deviceTable.Keys)
    {
        $deviceData = $deviceTable[$deviceId]
        $dataList = $deviceData.DataList

        if ($deviceTable.Count -eq 1)
        {
            [void]$chart.Titles.Add("Latency plot for " +
                                    $deviceData.DeviceNumber + " : " +
                                    $deviceData.FriendlyName + " : " +
                                    $deviceData.SerialNumber)
        }

        #
        # Show max and avg latencies
        #

        if ($ShowAvgLatency -eq $true)
        {
            if ($deviceTable.Count -eq 1)
            {
                $seriesName = "Avg"
            }
            else
            {
                $seriesName = "$($deviceData.DeviceNumber) : $($deviceData.SerialNumber) : Avg"
            }

            [void]$chart.Series.Add($seriesName)
            $chart.Series[$seriesName].ChartType = [Windows.Forms.DataVisualization.Charting.SeriesChartType]::Line
            $chart.Series[$seriesName].XValueType = [Windows.Forms.DataVisualization.Charting.ChartValueType]::DateTime
            $chart.Series[$seriesName].Legend = "TimeSeries"
            $chart.Series[$seriesName].MarkerStyle = [Windows.Forms.DataVisualization.Charting.MarkerStyle]::Circle

            if ($deviceTable.Count -eq 1)
            {
                $chart.Series[$seriesName].Color = [Drawing.Color]::Green
            }

            ForEach ($dataPoint in $dataList)
            {
                $point = New-Object Windows.Forms.DataVisualization.Charting.DataPoint
                $point.SetValueXY($dataPoint.DateTime, $dataPoint.AvgIoLatency)
                $point.Tooltip = "Value: #VALY{N0} us\n" +
                                 "At: #VALX{yyyy/MM/dd h:mm tt}\n" +
                                 "Max QD: $($dataPoint.MaxQueueDepth)"

                $chart.Series[$seriesName].Points.Add($point)
            }
        }

        if ($ShowMaxLatency -eq $true)
        {
            if ($deviceTable.Count -eq 1)
            {
                $seriesName = "Max"
            }
            else
            {
                $seriesName = "$($deviceData.DeviceNumber) : $($deviceData.SerialNumber) : Max"
            }

            [void]$chart.Series.Add($seriesName)
            $chart.Series[$seriesName].ChartType = [Windows.Forms.DataVisualization.Charting.SeriesChartType]::Line
            $chart.Series[$seriesName].XValueType = [Windows.Forms.DataVisualization.Charting.ChartValueType]::DateTime
            $chart.Series[$seriesName].Legend = "TimeSeries"
            $chart.Series[$seriesName].MarkerStyle = [Windows.Forms.DataVisualization.Charting.MarkerStyle]::Circle

            if ($deviceTable.Count -eq 1)
            {
                $chart.Series[$seriesName].Color = [Drawing.Color]::Orange
            }

            ForEach ($dataPoint in $dataList)
            {
                $point = New-Object Windows.Forms.DataVisualization.Charting.DataPoint
                $point.SetValueXY($dataPoint.DateTime, $dataPoint.MaxIoLatency)
                $point.Tooltip = "Value: #VALY{N0} us\n" +
                                 "At: #VALX{yyyy/MM/dd h:mm tt}\n" +
                                 "Max QD: $($dataPoint.MaxQueueDepth)"

                $chart.Series[$seriesName].Points.Add($point)
            }
        }

        #
        # Show interesting events when there
        # is a single device
        #

        if ($deviceTable.Count -eq 1)
        {
            $seriesName = "Events"

            [void]$chart.Series.Add($seriesName)
            $chart.Series[$seriesName].ChartType = [Windows.Forms.DataVisualization.Charting.SeriesChartType]::Point
            $chart.Series[$seriesName].XValueType = [Windows.Forms.DataVisualization.Charting.ChartValueType]::DateTime
            $chart.Series[$seriesName].Legend = "TimeSeries"
            $chart.Series[$seriesName].MarkerStyle = [Windows.Forms.DataVisualization.Charting.MarkerStyle]::Square
            $chart.Series[$seriesName].MarkerSize = 5
            $chart.Series[$seriesName].Color = [Drawing.Color]::Red

            #
            # Find the level at which to show the points
            #

            $value = 0

            ForEach ($dataPoint in $dataList)
            {
                if ($ShowMaxLatency -eq $true)
                {
                    if ($dataPoint.MaxIoLatency -gt $value)
                    {
                        $value = $dataPoint.MaxIoLatency
                    }
                }
                else
                {
                    if ($dataPoint.AvgIoLatency -gt $value)
                    {
                        $value = $dataPoint.AvgIoLatency
                    }
                }
            }

            ForEach ($dataPoint in $dataList)
            {
                ForEach ($time in $dataPoint.UnresponsiveTime)
                {
                    $point = New-Object Windows.Forms.DataVisualization.Charting.DataPoint
                    $point.SetValueXY($time, $value)
                    $point.Tooltip = "Marked unresponsive at #VALX{yyyy/MM/dd h:mm tt}\n"

                    $chart.Series[$seriesName].Points.Add($point)
                }

                ForEach ($time in $dataPoint.ResponsiveTime)
                {
                    $point = New-Object Windows.Forms.DataVisualization.Charting.DataPoint
                    $point.SetValueXY($time, $value)
                    $point.Tooltip = "Marked responsive at #VALX{yyyy/MM/dd h:mm tt}\n"

                    $chart.Series[$seriesName].Points.Add($point)
                }
            }
        }
    }

    $chart.ChartAreas[0].AxisX.LabelStyle.Angle = -45

    #
    # Display the chart on a form
    #

    $form = New-Object Windows.Forms.Form
    $form.Text = "Storage Performance Chart"
    $form.Width = 1100
    $form.Height = 900
    $form.controls.add($chart)
    $form.Add_Shown({$form.Activate()})

    [void]$form.ShowDialog()
    $form.Close()
    $form.Dispose()

    [void]$StorageHistoryCharts.Host.Runspace.Events.GenerateEvent("StorageHistoryChart.Close", $null, $null, $ChartId)
}


$DisplaySummaryChart = {

    Param
    (
        [System.String]
        $ChartId,

        [System.String]
        $Title,

        [System.Collections.ArrayList]
        $StorageHistory,

        [System.Boolean]
        $ShowDefault,

        [System.Boolean]
        $ShowAvgLatency,

        [System.Boolean]
        $ShowMaxLatency
    )

    #
    # Create a chart
    #

    $chart = New-object Windows.Forms.DataVisualization.Charting.Chart
    $chart.Anchor = [Windows.Forms.AnchorStyles]::Bottom -bor
                    [Windows.Forms.AnchorStyles]::Right -bor
                    [Windows.Forms.AnchorStyles]::Top -bor
                    [Windows.Forms.AnchorStyles]::Left

    $chart.Width = 1000
    $chart.Height = 800
    $chart.Left = 40
    $chart.Top = 30
    $chart.BackColor = [Drawing.Color]::White
    [void]$chart.Titles.Add($Title)
    $chart.Titles[0].Font = "segoeuilight,12pt"

    if ($StorageHistory.Count -eq 1)
    {
        [void]$chart.Titles.Add("IO Distribution for " +
                                $StorageHistory[0].DeviceNumber + " : " +
                                $StorageHistory[0].FriendlyName + " : " +
                                $StorageHistory[0].SerialNumber)
    }
    else
    {
        [void]$chart.Titles.Add("Latency comparison")
    }

    #
    # Create a chart area to draw on
    #

    $chartArea = New-Object Windows.Forms.DataVisualization.Charting.ChartArea
    $chartarea.Name = "Summary"
    $chartarea.AxisX.Interval = 1
    $chartarea.AxisX.IntervalAutoMode = [Windows.Forms.DataVisualization.Charting.IntervalAutoMode]::FixedCount
    $chartarea.AxisX.MajorGrid.Enabled = $false

    # For a single device show IO distribution.
    # For multiple devices show avg and max latency.

    if ($StorageHistory.Count -eq 1)
    {
        $chartArea.AxisX.Title = "Latency buckets (us)"
        $chartArea.AxisX.TitleFont = "segoeuilight,12pt"
        $chartArea.AxisY.Title = "IO Count"
    }
    else
    {
        $chartarea.AxisX.ScaleView.Size = [Math]::Min($StorageHistory.Count, 40)
        $chartarea.AxisX.ScaleView.Zoomable = $true
        $chartarea.AxisX.ScrollBar.IsPositionedInside = $true
        $chartarea.AxisX.ScrollBar.ButtonStyle = [Windows.Forms.DataVisualization.Charting.ScrollBarButtonStyles]::All
        $chartarea.CursorX.IsUserEnabled = $true
        $chartarea.CursorX.IsUserSelectionEnabled = $true
        $chartarea.CursorX.AutoScroll = $true
        $chartArea.AxisY.Title = "Latency (us)"
    }

    $chartArea.AxisY.TitleFont = "segoeuilight,12pt"
    $chartarea.AxisY.LabelStyle.Format = "N0"
    $chartarea.AxisY.MinorGrid.Enabled = $true
    $chartarea.AxisY.MinorGrid.LineDashStyle = [Windows.Forms.DataVisualization.Charting.ChartDashStyle]::Dot
    $chart.ChartAreas.Add($chartArea)

    $legend = New-Object Windows.Forms.DataVisualization.Charting.Legend
    $legend.Name = "Summary"
    $legend.Docking = [Windows.Forms.DataVisualization.Charting.Docking]::Right
    $legend.LegendStyle = [Windows.Forms.DataVisualization.Charting.LegendStyle]::Column
    $chart.Legends.Add($legend)

    #
    # Build the data to chart
    #

    if ($StorageHistory.Count -eq 1)
    {
        $data = $StorageHistory[0]

        $seriesName = "SuccessIoCount"

        [void]$chart.Series.Add($seriesName)
        $chart.Series[$seriesName].ChartType = [Windows.Forms.DataVisualization.Charting.SeriesChartType]::StackedColumn
        $chart.Series[$seriesName].XValueType = [Windows.Forms.DataVisualization.Charting.ChartValueType]::String
        $chart.Series[$seriesName].Legend = "Summary"
        $chart.Series[$seriesName].Color = [Drawing.Color]::Green

        for ($index = 0; $index -lt $data.BucketCount; $index++)
        {
            $point = New-Object Windows.Forms.DataVisualization.Charting.DataPoint
            $point.SetValueXY([String]::Format('{0:N0}', $data.BucketIoLatency[$index] / 10),
                              $data.BucketSuccessIoCount[$index])
            $point.Tooltip = [String]::Format('Success: {0:N0}\nTotal: {1:N0}',
                                              $data.BucketSuccessIoCount[$index],
                                              $data.BucketTotalIoCount[$index])

            $chart.Series[$seriesName].Points.Add($point)
        }

        $seriesName = "FailedIoCount"

        [void]$chart.Series.Add($seriesName)
        $chart.Series[$seriesName].ChartType = [Windows.Forms.DataVisualization.Charting.SeriesChartType]::StackedColumn
        $chart.Series[$seriesName].XValueType = [Windows.Forms.DataVisualization.Charting.ChartValueType]::String
        $chart.Series[$seriesName].Legend = "Summary"
        $chart.Series[$seriesName].Color = [Drawing.Color]::Red

        for ($index = 0; $index -lt $data.BucketCount; $index++)
        {
            $point = New-Object Windows.Forms.DataVisualization.Charting.DataPoint
            $point.SetValueXY([String]::Format('{0:N0}', $data.BucketIoLatency[$index] / 10),
                              $data.BucketFailedIoCount[$index])
            $point.Tooltip = [String]::Format('Failed: {0:N0}\nTotal: {1:N0}',
                                              $data.BucketFailedIoCount[$index],
                                              $data.BucketTotalIoCount[$index])

            $chart.Series[$seriesName].Points.Add($point)
        }
    }
    else
    {
        if ($ShowDefault -eq $true)
        {
            $ShowAvgLatency = $true
            $ShowMaxLatency = $true
        }

        if ($ShowAvgLatency -eq $true)
        {
            $seriesName = "Avg"

            [void]$chart.Series.Add($seriesName)
            $chart.Series[$seriesName].ChartType = [Windows.Forms.DataVisualization.Charting.SeriesChartType]::Column
            $chart.Series[$seriesName].XValueType = [Windows.Forms.DataVisualization.Charting.ChartValueType]::String
            $chart.Series[$seriesName].Legend = "Summary"
            $chart.Series[$seriesName].Color = [Drawing.Color]::Green

            ForEach ($data in $StorageHistory)
            {
                $point = New-Object Windows.Forms.DataVisualization.Charting.DataPoint
                $point.SetValueXY($data.DeviceNumber + " : " + $data.SerialNumber,
                                  $data.AvgIoLatency / 10)
                $point.Tooltip = "Avg: #VALY{N0} us\n" +
                                 [String]::Format('Total Io Count: {0:N0}\nSuccess Io Count: {1:N0}\nFailed Io Count: {2:N0}',
                                                  $data.TotalIoCount, $data.SuccessIoCount, $data.FailedIoCount)

                $chart.Series[$seriesName].Points.Add($point)
            }
        }

        if ($ShowMaxLatency -eq $true)
        {
            $seriesName = "Max"

            [void]$chart.Series.Add($seriesName)
            $chart.Series[$seriesName].ChartType = [Windows.Forms.DataVisualization.Charting.SeriesChartType]::Column
            $chart.Series[$seriesName].XValueType = [Windows.Forms.DataVisualization.Charting.ChartValueType]::String
            $chart.Series[$seriesName].Legend = "Summary"
            $chart.Series[$seriesName].Color = [Drawing.Color]::Orange

            ForEach ($data in $StorageHistory)
            {
                $point = New-Object Windows.Forms.DataVisualization.Charting.DataPoint
                $point.SetValueXY($data.DeviceNumber + " : " + $data.SerialNumber,
                                  $data.MaxIoLatency / 10)
                $point.Tooltip = "Max: #VALY{N0} us\n" +
                                 [String]::Format('Total Io Count: {0:N0}\nSuccess Io Count: {1:N0}\nFailed Io Count: {2:N0}',
                                                  $data.TotalIoCount, $data.SuccessIoCount, $data.FailedIoCount)

                $chart.Series[$seriesName].Points.Add($point)
            }
        }

        $chart.ChartAreas[0].AxisX.LabelStyle.Angle = -45
    }

    #
    # Display the chart on a form
    #

    $form = New-Object Windows.Forms.Form
    $form.Text = "Storage Performance Chart"
    $form.Width = 1100
    $form.Height = 900
    $form.controls.add($chart)
    $form.Add_Shown({$form.Activate()})

    [void]$form.ShowDialog()
    $form.Close()
    $form.Dispose()

    [void]$StorageHistoryCharts.Host.Runspace.Events.GenerateEvent("StorageHistoryChart.Close", $null, $null, $ChartId)
}


$StorageHistoryChartClose = {

    $chartId = $event.MessageData
    $runspace = $Global:StorageHistoryCharts[$chartId]

    $runspace.Close()
    $runspace.Dispose()

    $Global:StorageHistoryCharts.Remove($chartId)
}


function Show-StorageHistory
{
    Param
    (
        #### -------------------- Parameter sets -------------------------------------####

        [System.Collections.ArrayList]
        [Parameter(
            ParameterSetName  = 'ByObjects',
            ValueFromPipeline = $false,
            Mandatory         = $true,
            Position          = 0)]
        [ValidateNotNullOrEmpty()]
        $Objects,

        #### ------------------------- Common method parameters ----------------------####

        [System.String]
        [Parameter(
            ParameterSetName = 'ByObjects',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $Title,

        [Switch]
        [Parameter(
            ParameterSetName = 'ByObjects',
            Mandatory        = $false)]
        $DisplayAvgLatency,

        [Switch]
        [Parameter(
            ParameterSetName = 'ByObjects',
            Mandatory        = $false)]
        $DisplayMaxLatency
    )

    Begin
    {
    }

    Process
    {
        if ($PSEdition -ne "Desktop")
        {
            #
            # Full PowerShell is required for charting
            #
            $errorObject = CreateErrorRecord -ErrorId "NotSupported" `
                                             -ErrorMessage "This cmdlet requires full powershell support" `
                                             -ErrorCategory ([System.Management.Automation.ErrorCategory]::NotEnabled) `
                                             -Exception $null `
                                             -TargetObject $null

            $psCmdlet.WriteError($errorObject)
            return
        }

        #
        # Global state initialization
        #

        Add-Type -AssemblyName System.Windows.Forms
        Add-Type -AssemblyName System.Windows.Forms.DataVisualization

        $Global:StorageHistoryCharts.Host = $Host

        $subscriber = Get-EventSubscriber -SourceIdentifier "StorageHistoryChart.Close" `
                                          -ErrorAction SilentlyContinue

        if ($subscriber -eq $null)
        {
            Register-EngineEvent -SourceIdentifier "StorageHistoryChart.Close" `
                                 -Action $StorageHistoryChartClose | Out-Null
        }

        #
        # Display chart based on the data passed
        #

        $type = [String]::Empty
        $storageHistory = New-Object System.Collections.ArrayList

        ForEach ($object in $Objects)
        {
            if ([String]::IsNullOrEmpty($type))
            {
                $type = $object.PSObject.TypeNames[0]
            }

            if ($object.PSObject.TypeNames[0] -eq $type)
            {
                [void]$storageHistory.Add($object)
            }
        }

        $chartId = [System.Guid]::newguid().ToString()

        $runspace = [RunspaceFactory]::CreateRunspace()
        $runspace.ApartmentState = "STA"
        $runspace.ThreadOptions = "ReuseThread"
        $runspace.Open()
        $runspace.SessionStateProxy.SetVariable("StorageHistoryCharts",$Global:StorageHistoryCharts)

        $Global:StorageHistoryCharts.Add($chartId, $runspace)

        $powershell = [PowerShell]::Create()

        $showDefault = $true
        $showAvgLatency = $false
        $showMaxLatency = $false

        if ($PSBoundParameters.ContainsKey("DisplayAvgLatency"))
        {
            $showDefault = $false
            $showAvgLatency = $true
        }

        if ($PSBoundParameters.ContainsKey("DisplayMaxLatency"))
        {
            $showDefault = $false
            $showMaxLatency = $true
        }

        if ($type -eq "Microsoft.Windows.StorageManagement.StorageHistory")
        {
            [void]$powershell.AddScript($DisplayTimeSeriesChart)
        }
        elseif ($type -eq "Microsoft.Windows.StorageManagement.StorageHistoryAggregate")
        {
            [void]$powershell.AddScript($DisplaySummaryChart)
        }
        else
        {
            return
        }

        [void]$powershell.AddArgument($chartId)
        [void]$powershell.AddArgument($Title)
        [void]$powershell.AddArgument($storageHistory)
        [void]$powershell.AddArgument($showDefault)
        [void]$powershell.AddArgument($showAvgLatency)
        [void]$powershell.AddArgument($showMaxLatency)

        $powershell.Runspace = $runspace

        [void]$powershell.BeginInvoke()
    }
}


function Get-PhysicalDisk
{
    [CmdletBinding( DefaultParameterSetName = "ByUniqueId" )]
    Param
    (
        #### -------------------- Parameter sets -------------------------------------####

        [System.String]
        [Parameter(
            ParameterSetName                = 'ByUniqueId',
            ValueFromPipeline               = $true,
            ValueFromPipelineByPropertyName = $true,
            Mandatory                       = $false)]
        [ValidateNotNullOrEmpty()]
        [Alias("Id")]
        $UniqueId,

        [System.String]
        [Parameter(
            ParameterSetName  = 'ByObjectId',
            ValueFromPipeline = $true,
            Mandatory         = $false)]
        [ValidateNotNullOrEmpty()]
        [Alias("PhysicalDiskObjectId")]
        $ObjectId,

        [System.String]
        [Parameter(
            ParameterSetName  = 'ByDeviceNumber',
            ValueFromPipeline = $false,
            Mandatory         = $false)]
        [ValidateNotNullOrEmpty()]
        $DeviceNumber,

        [System.String]
        [Parameter(
            ParameterSetName  = 'ByName',
            ValueFromPipeline = $true,
            Mandatory         = $false,
            Position          = 0)]
        [ValidateNotNullOrEmpty()]
        $FriendlyName,

        [System.String]
        [Parameter(
            ParameterSetName  = 'ByName',
            ValueFromPipeline = $true,
            Mandatory         = $false,
            Position          = 1)]
        [ValidateNotNullOrEmpty()]
        $SerialNumber,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_PhysicalDisk")]
        [Parameter(
            ParameterSetName  = 'ByInputObject',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $InputObject,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageSubsystem")]
        [Parameter(
            ParameterSetName  = 'ByStorageSubsystem',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageSubsystem,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageEnclosure")]
        [Parameter(
            ParameterSetName  = 'ByStorageEnclosure',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageEnclosure,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageScaleUnit")]
        [Parameter(
            ParameterSetName  = 'ByStorageScaleUnit',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageScaleUnit,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageChassis")]
        [Parameter(
            ParameterSetName  = 'ByStorageChassis',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageChassis,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageRack")]
        [Parameter(
            ParameterSetName  = 'ByStorageRack',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageRack,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageSite")]
        [Parameter(
            ParameterSetName  = 'ByStorageSite',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageSite,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageNode")]
        [Parameter(
            ParameterSetName  = 'ByStorageNode',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageNode,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StoragePool")]
        [Parameter(
            ParameterSetName  = 'ByStoragePool',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StoragePool,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_VirtualDisk")]
        [Parameter(
            ParameterSetName  = 'ByVirtualDisk',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $VirtualDisk,

        #### ------------------ VirtualDisk association parameters -------------------####

        [System.UInt64]
        [Parameter(
            ParameterSetName = 'ByVirtualDisk',
            Mandatory        = $false)]
        $VirtualRangeMin,

        [System.UInt64]
        [Parameter(
            ParameterSetName = 'ByVirtualDisk',
            Mandatory        = $false)]
        $VirtualRangeMax,

        [System.Boolean]
        [Parameter(
            ParameterSetName = 'ByVirtualDisk',
            Mandatory        = $false)]
        $HasAllocations,

        [System.Boolean]
        [Parameter(
            ParameterSetName = 'ByVirtualDisk',
            Mandatory        = $false)]
        $SelectedForUse,

        [Switch]
        [Parameter(
            ParameterSetName = 'ByVirtualDisk',
            Mandatory        = $false)]
        $NoRedundancy,

        #### --------- StoragePool and VirtualDisk association parameters ------------####

        [Switch]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByVirtualDisk',
            Mandatory        = $false)]
        $HasMetadata,

        #### ------------------ StorageNode association parameters -------------------####

        [Switch]
        [Parameter(
            ParameterSetName = 'ByStorageNode',
            Mandatory        = $false)]
        $PhysicallyConnected,

        #### ------------------------- Common parameters -----------------------------####

        [PhysicalDiskUsage]
        [Parameter(
            ParameterSetName = 'ByUniqueId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByObjectId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDeviceNumber',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubsystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageEnclosure',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageScaleUnit',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageChassis',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageRack',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSite',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageNode',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByVirtualDisk',
            Mandatory        = $false)]
        $Usage,

        [System.String]
        [Parameter(
            ParameterSetName = 'ByUniqueId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByObjectId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDeviceNumber',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubsystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageEnclosure',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageScaleUnit',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageChassis',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageRack',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSite',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageNode',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByVirtualDisk',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $Description,

        [System.String]
        [Parameter(
            ParameterSetName = 'ByUniqueId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByObjectId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDeviceNumber',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubsystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageEnclosure',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageScaleUnit',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageChassis',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageRack',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSite',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageNode',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByVirtualDisk',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $Manufacturer,

        [System.String]
        [Parameter(
            ParameterSetName = 'ByUniqueId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByObjectId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDeviceNumber',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubsystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageEnclosure',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageScaleUnit',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageChassis',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageRack',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSite',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageNode',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByVirtualDisk',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $Model,

        [System.Boolean]
        [Parameter(
            ParameterSetName = 'ByUniqueId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByObjectId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDeviceNumber',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubsystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageEnclosure',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageScaleUnit',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageChassis',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageRack',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSite',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageNode',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByVirtualDisk',
            Mandatory        = $false)]
        $CanPool,

        [PhysicalDiskHealthStatus]
        [Parameter(
            ParameterSetName = 'ByUniqueId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByObjectId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDeviceNumber',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubsystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageEnclosure',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageScaleUnit',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageChassis',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageRack',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSite',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageNode',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByVirtualDisk',
            Mandatory        = $false)]
        $HealthStatus,

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession
    )

    Begin
    {
        [flagsattribute()]
        Enum PhysicalDiskUsage
        {
            Unknown      = 0
            AutoSelect   = 1
            ManualSelect = 2
            HotSpare     = 3
            Retired      = 4
            Journal      = 5
        }

        [flagsattribute()]
        Enum PhysicalDiskHealthStatus
        {
            Healthy   = 0
            Warning   = 1
            Unhealthy = 2
            Unknown   = 5
        }
    }

    Process
    {
        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        ## Gather the instance objects.

        $options = New-Object Microsoft.Management.Infrastructure.Options.CimOperationOptions

        switch -regex ($psCmdlet.ParameterSetName)
        {
            { @(                `
                "ByInputObject" `
                ) -contains $_  `
            }
            {
                $cimInstance = New-Object Microsoft.Management.Infrastructure.CimInstance("MSFT_PhysicalDisk")

                $cimInstance.CimInstanceProperties.Add([Microsoft.Management.Infrastructure.CimProperty]::Create("ObjectId", $InputObject.ObjectId, "String", "Key"))

                $instance = $CimSession.GetInstance($StorageNamespace,
                                                    $cimInstance);
                break;
            }

            { @(                  `
                "ByObjectId",     `
                "ByUniqueId",     `
                "ByDeviceNumber", `
                "ByName"          `
                ) -contains $_    `
            }
            {
                $results = $CimSession.EnumerateInstances($StorageNamespace,
                                                          "MSFT_PhysicalDisk");
                break;
            }

            { @(                      `
                "ByStorageSubsystem", `
                "ByStoragePool",      `
                "ByStorageEnclosure", `
                "ByStorageNode",      `
                "ByVirtualDisk"       `
                ) -contains $_        `
            }
            {
                if ($PSBoundParameters.ContainsKey("StorageSubsystem"))
                {
                    $subsystem = $StorageSubsystem
                }
                elseif ($PSBoundParameters.ContainsKey("StoragePool"))
                {
                    $subsystem = $StoragePool | get-storagesubsystem -CimSession $CimSession
                }
                elseif ($PSBoundParameters.ContainsKey("StorageEnclosure"))
                {
                    $subsystem = $StorageEnclosure | get-storagesubsystem -CimSession $CimSession
                }
                elseif ($PSBoundParameters.ContainsKey("StorageNode"))
                {
                    $subsystem = $StorageNode | get-storagesubsystem -CimSession $CimSession
                }
                elseif ($PSBoundParameters.ContainsKey("VirtualDisk"))
                {
                    $subsystem = $VirtualDisk | get-storagesubsystem -CimSession $CimSession
                }

                # If the subsystem model is "Windows Storage",
                # perform associations using enumeration

                if ($PSBoundParameters.ContainsKey("StorageSubsystem"))
                {
                    $options.SetCustomOption("InputClassName", "MSFT_StorageSubsystem", $false)
                    $options.SetCustomOption("InputObjectId", $StorageSubsystem.ObjectId, $false)

                    if ($subsystem.Model -like "*Windows Storage*")
                    {
                        $results = $CimSession.EnumerateInstances($StorageNamespace,
                                                                  "MSFT_PhysicalDisk",
                                                                  $options)
                    }
                    else
                    {
                        $results = $CimSession.EnumerateAssociatedInstances($StorageNamespace,
                                                                            $StorageSubsystem,
                                                                            "MSFT_StorageSubsystemToPhysicalDisk",
                                                                            "MSFT_PhysicalDisk",
                                                                            "StorageSubsystem",
                                                                            "PhysicalDisk",
                                                                            $options)
                    }
                }
                elseif ($PSBoundParameters.ContainsKey("StoragePool"))
                {
                    $options.SetCustomOption("InputClassName", "MSFT_StoragePool", $false)
                    $options.SetCustomOption("InputObjectId", $StoragePool.ObjectId, $false)

                    if ($PSBoundParameters.ContainsKey("HasMetadata"))
                    {
                        $options.SetCustomOption("HasMetadata", $HasMetadata, $false)
                    }

                    if ($subsystem.Model -like "*Windows Storage*")
                    {
                        $results = $CimSession.EnumerateInstances($StorageNamespace,
                                                                  "MSFT_PhysicalDisk",
                                                                  $options)
                    }
                    else
                    {
                        $results = $CimSession.EnumerateAssociatedInstances($StorageNamespace,
                                                                            $StoragePool,
                                                                            "MSFT_StoragePoolToPhysicalDisk",
                                                                            "MSFT_PhysicalDisk",
                                                                            "StoragePool",
                                                                            "PhysicalDisk",
                                                                            $options)
                    }
                }
                elseif ($PSBoundParameters.ContainsKey("StorageEnclosure"))
                {
                    $options.SetCustomOption("InputClassName", "MSFT_StorageEnclosure", $false)
                    $options.SetCustomOption("InputObjectId", $StorageEnclosure.ObjectId, $false)

                    if ($subsystem.Model -like "*Windows Storage*")
                    {
                        $results = $CimSession.EnumerateInstances($StorageNamespace,
                                                                  "MSFT_PhysicalDisk",
                                                                  $options);
                    }
                    else
                    {
                        $results = $CimSession.EnumerateAssociatedInstances($StorageNamespace,
                                                                            $StorageEnclosure,
                                                                            "MSFT_StorageEnclosureToPhysicalDisk",
                                                                            "MSFT_PhysicalDisk",
                                                                            "StorageEnclosure",
                                                                            "PhysicalDisk",
                                                                            $options);
                    }
                }
                elseif ($PSBoundParameters.ContainsKey("StorageNode"))
                {
                    $options.SetCustomOption("InputClassName", "MSFT_StorageNode", $false)
                    $options.SetCustomOption("InputObjectId", $StorageNode.ObjectId, $false)

                    if ($PSBoundParameters.ContainsKey("PhysicallyConnected"))
                    {
                        $options.SetCustomOption("PhysicallyConnected", $true, $false)
                    }

                    if ($subsystem.Model -like "*Windows Storage*")
                    {
                        $results = $CimSession.EnumerateInstances($StorageNamespace,
                                                                  "MSFT_PhysicalDisk",
                                                                  $options);
                    }
                    else
                    {
                        $results = $CimSession.EnumerateAssociatedInstances($StorageNamespace,
                                                                            $StorageNode,
                                                                            "MSFT_StorageNodeToPhysicalDisk",
                                                                            "MSFT_PhysicalDisk",
                                                                            "StorageNode",
                                                                            "PhysicalDisk",
                                                                            $options);
                    }
                }
                elseif ($PSBoundParameters.ContainsKey("VirtualDisk"))
                {
                    $options.SetCustomOption("InputClassName", "MSFT_VirtualDisk", $false)
                    $options.SetCustomOption("InputObjectId", $VirtualDisk.ObjectId, $false)

                    if ($PSBoundParameters.ContainsKey("VirtualRangeMin"))
                    {
                        $options.SetCustomOption("VirtualRangeMin", $VirtualRangeMin, [Microsoft.Management.Infrastructure.CimType]::UInt64, $false)
                    }

                    if ($PSBoundParameters.ContainsKey("VirtualRangeMax"))
                    {
                        $options.SetCustomOption("VirtualRangeMax", $VirtualRangeMax, [Microsoft.Management.Infrastructure.CimType]::UInt64, $false)
                    }

                    if ($PSBoundParameters.ContainsKey("HasAllocations"))
                    {
                        $options.SetCustomOption("HasAllocations", $HasAllocations, $false)
                    }

                    if ($PSBoundParameters.ContainsKey("SelectedForUse"))
                    {
                        $options.SetCustomOption("SelectedForUse", $SelectedForUse, $false)
                    }

                    if ($PSBoundParameters.ContainsKey("HasMetadata"))
                    {
                        $options.SetCustomOption("HasMetadata", $HasMetadata, $false)
                    }

                    if ($PSBoundParameters.ContainsKey("NoRedundancy"))
                    {
                        $options.SetCustomOption("NoRedundancy", $true, $false)
                    }

                    if ($subsystem.Model -like "*Windows Storage*")
                    {
                        $results = $CimSession.EnumerateInstances($StorageNamespace,
                                                                  "MSFT_PhysicalDisk",
                                                                  $options);
                    }
                    else
                    {
                        $results = $CimSession.EnumerateAssociatedInstances($StorageNamespace,
                                                                            $VirtualDisk,
                                                                            "MSFT_VirtualDiskToPhysicalDisk",
                                                                            "MSFT_PhysicalDisk",
                                                                            "VirtualDisk",
                                                                            "PhysicalDisk",
                                                                            $options);
                    }
                }

                break;
            }
            { @(                      `
                "ByStorageScaleUnit", `
                "ByStorageChassis",   `
                "ByStorageRack",      `
                "ByStorageSite"       `
                ) -contains $_        `
            }
            {
                if ($PSBoundParameters.ContainsKey("StorageScaleUnit"))
                {
                    $inputObject = $StorageScaleUnit
                }
                elseif ($PSBoundParameters.ContainsKey("StorageChassis"))
                {
                    $inputObject = $StorageChassis
                }
                elseif ($PSBoundParameters.ContainsKey("StorageRack"))
                {
                    $inputObject = $StorageRack
                }
                elseif ($PSBoundParameters.ContainsKey("StorageSite"))
                {
                    $inputObject = $StorageSite
                }

                # If the subsystem model is "Windows Storage",
                # perform associations using enumeration

                $subsystem = $inputObject | get-storagesubsystem -CimSession $CimSession

                if ($subsystem.Model -like "*Windows Storage*")
                {
                    $options.SetCustomOption("InputClassName", $inputObject.ClassName, $false)
                    $options.SetCustomOption("InputObjectId", $inputObject.ObjectId, $false)

                    $results = $CimSession.EnumerateInstances($StorageNamespace,
                                                              "MSFT_PhysicalDisk",
                                                              $options);
                }
                else
                {
                    $options.SetCustomOption("ResultObject", "MSFT_PhysicalDisk", $false)

                    $results = $CimSession.EnumerateAssociatedInstances($StorageNamespace,
                                                                        $inputObject,
                                                                        "MSFT_StorageFaultDomainToStorageFaultDomain",
                                                                        "MSFT_PhysicalDisk",
                                                                        "SourceStorageFaultDomain",
                                                                        "TargetStorageFaultDomain",
                                                                        $options);
                }

                break;
            }
        }

        $instances  = @()

        if ($psCmdlet.ParameterSetName -eq "ByInputObject")
        {
            $instances += $instance
        }
        else
        {
            $enumerator = $results.GetEnumerator()

            while ($enumerator.MoveNext())
            {
                $instance = $enumerator.Current

                ## Filter by optional input parameters

                if (($PSBoundParameters.ContainsKey("ObjectId")) -and
                    ($instance.ObjectId -notlike $ObjectId))
                {
                    continue
                }

                if (($PSBoundParameters.ContainsKey("UniqueId")) -and
                    ($instance.UniqueId -notlike $UniqueId))
                {
                    continue
                }

                if (($PSBoundParameters.ContainsKey("DeviceNumber")) -and
                    ($instance.DeviceId -ne $DeviceNumber))
                {
                    continue
                }

                if (($PSBoundParameters.ContainsKey("FriendlyName")) -and
                    ($instance.FriendlyName -notlike $FriendlyName))
                {
                    continue
                }

                if (($PSBoundParameters.ContainsKey("SerialNumber")) -and
                    ($instance.SerialNumber -notlike $SerialNumber))
                {
                    continue
                }

                if ($PSBoundParameters.ContainsKey("Usage"))
                {
                    $matchFound = $true

                    switch -regex ($Usage)
                    {
                        { @(               `
                            "Unknown",     `
                            "Retired",     `
                            "Journal"      `
                            ) -contains $_ `
                        }
                        {
                            if ( $instance.Usage -ne $Usage )
                            {
                                $matchFound = $false
                                break
                            }
                        }

                        { @(               `
                            "AutoSelect"   `
                            ) -contains $_ `
                        }
                        {
                            if ( $instance.Usage -ne "Auto-Select" )
                            {
                                $matchFound = $false
                                break
                            }
                        }

                        { @(               `
                            "ManualSelect" `
                            ) -contains $_ `
                        }
                        {
                            if ( $instance.Usage -ne "Manual-Select" )
                            {
                                $matchFound = $false
                                break
                            }
                        }

                        { @(               `
                            "HotSpare"     `
                            ) -contains $_ `
                        }
                        {
                            if ( $instance.Usage -ne "Hot Spare" )
                            {
                                $matchFound = $false
                                break
                            }
                        }
                    }

                    if ( $matchFound -eq $false )
                    {
                        continue
                    }
                }

                if (($PSBoundParameters.ContainsKey("Description")) -and
                    ($instance.Description -notlike $Description))
                {
                    continue
                }

                if (($PSBoundParameters.ContainsKey("Manufacturer")) -and
                    ($instance.Manufacturer -ne $Manufacturer))
                {
                    continue
                }

                if (($PSBoundParameters.ContainsKey("Model")) -and
                    ($instance.Model -ne $Model))
                {
                    continue
                }

                if (($PSBoundParameters.ContainsKey("CanPool")) -and
                    ($instance.CanPool -ne $CanPool))
                {
                    continue
                }

                if (($PSBoundParameters.ContainsKey("HealthStatus")) -and
                    ($instance.HealthStatus -ne $HealthStatus))
                {
                    continue
                }

                $instances += $instance
            }
        }

        $instances
    }
}


function Get-PhysicalExtent
{
    Param
    (
        #### -------------------- Parameter sets -------------------------------------####

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_VirtualDisk")]
        [Parameter(
            ParameterSetName  = 'ByVirtualDisk',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $VirtualDisk,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageTier")]
        [Parameter(
            ParameterSetName  = 'ByStorageTier',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageTier,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_PhysicalDisk")]
        [Parameter(
            ParameterSetName  = 'ByPhysicalDisk',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $PhysicalDisk,

        #### -------------------- Powershell parameters ------------------------------####

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession
    )

    Process
    {
        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        ## Gather the instance objects

        switch ($psCmdlet.ParameterSetName)
        {
            "ByVirtualDisk"  { $inputObject = $VirtualDisk;  break; }
            "ByStorageTier"  { $inputObject = $StorageTier;  break; }
            "ByPhysicalDisk" { $inputObject = $PhysicalDisk; break; }
        }

        $instances = @()

        try
        {
            $output     = Invoke-CimMethod -CimSession $CimSession -InputObject $inputObject -MethodName "GetPhysicalExtent" -ErrorAction Stop
            $enumerator = $output.PhysicalExtents.GetEnumerator()

            while ($enumerator.MoveNext())
            {
                $instance = $enumerator.Current
                $instance.PSObject.TypeNames.Insert(0, "Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_PhysicalExtent")

                $instances += $instance
            }
        }
        catch
        {
            $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId `
                                             -ErrorMessage $null `
                                             -ErrorCategory $_.CategoryInfo.Category `
                                             -Exception $_.Exception `
                                             -TargetObject $_.TargetObject

            $psCmdlet.WriteError($errorObject)
        }

        $instances
    }
}


function Get-PhysicalExtentAssociation
{
    Param
    (
        #### -------------------- Parameter sets -------------------------------------####

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_PhysicalExtent")]
        [Parameter(
            ParameterSetName  = 'ByInputObject',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $InputObject,

        #### -------------------- Powershell parameters ------------------------------####

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession
    )

    Process
    {
        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        $virtualDisk = Get-VirtualDisk -CimSession $CimSession -UniqueId $InputObject.VirtualDiskUniqueId -ErrorAction Stop

        if ($InputObject.StorageTierUniqueId -ne $null)
        {
            $storageTier = Get-StorageTier -CimSession $CimSession -UniqueId $InputObject.StorageTierUniqueId -ErrorAction Stop
        }

        $physicalDisk = Get-PhysicalDisk -CimSession $CimSession -UniqueId $InputObject.PhysicalDiskUniqueId -ErrorAction Stop

        $associations = [pscustomobject]@{
            VirtualDisk  = $virtualDisk;
            StorageTier  = $storageTier;
            PhysicalDisk = $physicalDisk;
        }

        $associations
    }
}


function Enable-PhysicalDiskIdentification
{
    [CmdletBinding( DefaultParameterSetName = "ByName", ConfirmImpact="Low" )]
    [Alias("Enable-PhysicalDiskIndication")]
    Param
    (
        #### -------------------- Parameter sets -------------------------------------####

        [System.String]
        [Parameter(
            ParameterSetName  = 'ByUniqueId',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias("Id")]
        $UniqueId,

        [System.String]
        [Parameter(
            ParameterSetName  = 'ByName',
            ValueFromPipeline = $true,
            Mandatory         = $true,
            Position          = 0)]
        [ValidateNotNullOrEmpty()]
        $FriendlyName,

        [Microsoft.Management.Infrastructure.CimInstance[]]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_PhysicalDisk")]
        [Parameter(
            ParameterSetName  = 'ByInputObject',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $InputObject,

        #### ------------------------- Common method parameters ----------------------####

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession,

        # Provided for compatibility with CDXML cmdlets, not actually used.
        [Int32]
        $ThrottleLimit,

        [Switch]
        $AsJob
    )

    Begin
    {
        $SourceCaller = "Microsoft.PowerShell"
    }

    Process
    {
        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        if (-not $AsJob)
        {
            Write-Progress -Activity "Enable-PhysicalDiskIdentification" -PercentComplete 0 -CurrentOperation "Gathering objects" -Status "0/2"
        }

        ## Gather the instance objects

        switch ($psCmdlet.ParameterSetName)
        {
            "ByInputObject" { $Instances = $InputObject; break; }
            "ByUniqueId"    { $Instances = Get-PhysicalDisk -UniqueId $UniqueId -CimSession $CimSession -ErrorAction Stop; break; }
            "ByName"        { $Instances = Get-PhysicalDisk -FriendlyName $FriendlyName -CimSession $CimSession -ErrorAction Stop; break; }
        }

        # Would use a closure here, but jobs are run in their own session state.
        $methodblock = {
            param($session, $asjob, $instances)

            # Start-Job serializes/deserializes the CimSession,
            # which means it shows up here as having type Deserialized.CimSession.
            # Must recreate or cast the object in order to pass it to Get-CimInstance.
            if ($asjob)
            {
                $session = $session | New-CimSession
            }

            $arguments = @{ "EnableIndication" = $true }
            $errorObject = $null

            if (-not $asjob)
            {
                Write-Progress -Activity "Enable-PhysicalDiskIdentification" -PercentComplete 30 -CurrentOperation "Executing method" -Status "1/2"
            }

            ForEach ( $instance in $instances )
            {
                try
                {
                    $progressPreference = "silentlyContinue"

                    $output = Invoke-CimMethod -CimSession $session -InputObject $instance -MethodName Maintenance -Arguments $arguments -ErrorAction Stop

                    $progressPreference = "Continue"
                }
                catch
                {
                    $progressPreference = "Continue"

                    $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId `
                                                     -ErrorMessage $null `
                                                     -ErrorCategory $_.CategoryInfo.Category `
                                                     -Exception $_.Exception `
                                                     -TargetObject $_.TargetObject

                    $psCmdlet.WriteError($errorObject)
                }
            }

            if (-not $asjob)
            {
                Write-Progress -Activity "Enable-PhysicalDiskIdentification" -Completed -Status "2/2"
            }
        }

        if ($asjob)
        {
            $p = $true

            Start-Job -Name EnablePhysicalDiskIdentification -ScriptBlock $methodblock -ArgumentList @($CimSession, $p, $Instances)
        }
        else
        {
            &$methodblock $CimSession $p $Instances
        }
    }
}


function Disable-PhysicalDiskIdentification
{
    [CmdletBinding( DefaultParameterSetName = "ByName", ConfirmImpact="Low" )]
    [Alias("Disable-PhysicalDiskIndication")]
    Param
    (
        #### -------------------- Parameter sets -------------------------------------####

        [System.String]
        [Parameter(
            ParameterSetName  = 'ByUniqueId',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias("Id")]
        $UniqueId,

        [System.String]
        [Parameter(
            ParameterSetName  = 'ByName',
            ValueFromPipeline = $true,
            Mandatory         = $true,
            Position          = 0)]
        [ValidateNotNullOrEmpty()]
        $FriendlyName,

        [Microsoft.Management.Infrastructure.CimInstance[]]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_PhysicalDisk")]
        [Parameter(
            ParameterSetName  = 'ByInputObject',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $InputObject,

        #### ------------------------- Common method parameters ----------------------####

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession,

        # Provided for compatibility with CDXML cmdlets, not actually used.
        [Int32]
        $ThrottleLimit,

        [Switch]
        $AsJob
    )

    Begin
    {
        $SourceCaller = "Microsoft.PowerShell"
    }

    Process
    {
        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        if (-not $AsJob)
        {
            Write-Progress -Activity "Disable-PhysicalDiskIdentification" -PercentComplete 0 -CurrentOperation "Gathering objects" -Status "0/2"
        }

        ## Gather the instance objects

        switch ($psCmdlet.ParameterSetName)
        {
            "ByInputObject" { $Instances = $InputObject; break; }
            "ByUniqueId"    { $Instances = Get-PhysicalDisk -UniqueId $UniqueId -CimSession $CimSession -ErrorAction Stop; break; }
            "ByName"        { $Instances = Get-PhysicalDisk -FriendlyName $FriendlyName -CimSession $CimSession -ErrorAction Stop; break; }
        }

        # Would use a closure here, but jobs are run in their own session state.
        $methodblock = {
            param($session, $asjob, $instances)

            # Start-Job serializes/deserializes the CimSession,
            # which means it shows up here as having type Deserialized.CimSession.
            # Must recreate or cast the object in order to pass it to Get-CimInstance.
            if ($asjob)
            {
                $session = $session | New-CimSession
            }

            $arguments = @{ "EnableIndication" = $false }
            $errorObject = $null

            if (-not $asjob)
            {
                Write-Progress -Activity "Disable-PhysicalDiskIdentification" -PercentComplete 30 -CurrentOperation "Executing method" -Status "1/2"
            }

            ForEach ( $instance in $instances )
            {
                try
                {
                    $progressPreference = "silentlyContinue"

                    $output = Invoke-CimMethod -CimSession $session -InputObject $instance -MethodName Maintenance -Arguments $arguments -ErrorAction Stop

                    $progressPreference = "Continue"
                }
                catch
                {
                    $progressPreference = "Continue"

                    $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId `
                                                     -ErrorMessage $null `
                                                     -ErrorCategory $_.CategoryInfo.Category `
                                                     -Exception $_.Exception `
                                                     -TargetObject $_.TargetObject

                    $psCmdlet.WriteError($errorObject)
                }
            }

            if (-not $asjob)
            {
                Write-Progress -Activity "Disable-PhysicalDiskIdentification" -Completed -Status "2/2"
            }
        }

        if ($asjob)
        {
            $p = $true

            Start-Job -Name DisablePhysicalDiskIdentification -ScriptBlock $methodblock -ArgumentList @($CimSession, $p, $Instances)
        }
        else
        {
            &$methodblock $CimSession $p $Instances
        }
    }
}


function Reset-PhysicalDisk
{
    [CmdletBinding( DefaultParameterSetName = "ByName", ConfirmImpact="Medium" )]
    Param
    (
        #### -------------------- Parameter sets -------------------------------------####

        [System.String]
        [Parameter(
            ParameterSetName  = 'ByUniqueId',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias("Id")]
        $UniqueId,

        [System.String]
        [Parameter(
            ParameterSetName  = 'ByName',
            ValueFromPipeline = $true,
            Mandatory         = $true,
            Position          = 0)]
        [ValidateNotNullOrEmpty()]
        $FriendlyName,

        [Microsoft.Management.Infrastructure.CimInstance[]]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_PhysicalDisk")]
        [Parameter(
            ParameterSetName  = 'ByInputObject',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $InputObject,

        #### ------------------------- Common method parameters ----------------------####

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession,

        # Provided for compatibility with CDXML cmdlets, not actually used.
        [Int32]
        $ThrottleLimit,

        [Switch]
        $AsJob
    )

    Begin
    {
        $SourceCaller = "Microsoft.PowerShell"
    }

    Process
    {
        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        if (-not $AsJob)
        {
            Write-Progress -Activity "Reset-PhysicalDisk" -PercentComplete 0 -CurrentOperation "Gathering objects" -Status "0/2"
        }

        ## Gather the instance objects

        switch ($psCmdlet.ParameterSetName)
        {
            "ByInputObject" { $Instances = $InputObject; break; }
            "ByUniqueId"    { $Instances = Get-PhysicalDisk -UniqueId $UniqueId -CimSession $CimSession -ErrorAction Stop; break; }
            "ByName"        { $Instances = Get-PhysicalDisk -FriendlyName $FriendlyName -CimSession $CimSession -ErrorAction Stop; break; }
        }

        # Would use a closure here, but jobs are run in their own session state.
        $methodblock = {
            param($session, $asjob, $instances)

            # Start-Job serializes/deserializes the CimSession,
            # which means it shows up here as having type Deserialized.CimSession.
            # Must recreate or cast the object in order to pass it to Get-CimInstance.
            if ($asjob)
            {
                $session = $session | New-CimSession
            }

            $errorObject = $null

            if (-not $asjob)
            {
                Write-Progress -Activity "Reset-PhysicalDisk" -PercentComplete 30 -CurrentOperation "Executing method" -Status "1/2"
            }

            ForEach ( $instance in $instances )
            {
                try
                {
                    $progressPreference = "silentlyContinue"

                    $output = Invoke-CimMethod -CimSession $session -InputObject $instance -MethodName Reset -ErrorAction Stop

                    $progressPreference = "Continue"
                }
                catch
                {
                    $progressPreference = "Continue"

                    $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId `
                                                     -ErrorMessage $null `
                                                     -ErrorCategory $_.CategoryInfo.Category `
                                                     -Exception $_.Exception `
                                                     -TargetObject $_.TargetObject

                    $psCmdlet.WriteError($errorObject)
                }
            }

            if (-not $asjob)
            {
                Write-Progress -Activity "Reset-PhysicalDisk" -Completed -Status "2/2"
            }
        }

        if ($asjob)
        {
            $p = $true

            Start-Job -Name ResetPhysicalDisk -ScriptBlock $methodblock -ArgumentList @($CimSession, $p, $Instances)
        }
        else
        {
            &$methodblock $CimSession $p $Instances
        }
    }
}


function Get-StorageFirmwareInformation
{
    [CmdletBinding( DefaultParameterSetName = "ByPhysicalDiskFriendlyName", ConfirmImpact="None" )]
    Param
    (
        #### -------------------- Parameter sets -------------------------------------####

        [System.String]
        [Parameter(
            ParameterSetName  = 'ByPhysicalDiskUniqueId',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias("Id")]
        [Alias("PhysicalDiskUniqueId")]
        $UniqueId,

        [System.String]
        [Parameter(
            ParameterSetName  = 'ByPhysicalDiskFriendlyName',
            ValueFromPipeline = $true,
            Mandatory         = $true,
            Position          = 0)]
        [ValidateNotNullOrEmpty()]
        [Alias("PhysicalDiskFriendlyName")]
        $FriendlyName,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_PhysicalDisk")]
        [Parameter(
            ParameterSetName  = 'ByPhysicalDisk',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias("PhysicalDisk")]
        $InputObject,

        [System.String]
        [Parameter(
            ParameterSetName  = 'ByStorageEnclosureUniqueId',
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageEnclosureUniqueId,

        [System.String]
        [Parameter(
            ParameterSetName  = 'ByStorageEnclosureFriendlyName',
            Mandatory         = $true,
            Position          = 0)]
        [ValidateNotNullOrEmpty()]
        $StorageEnclosureFriendlyName,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageEnclosure")]
        [Parameter(
            ParameterSetName  = 'ByStorageEnclosure',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageEnclosure,

        #### ------------------------- Common method parameters ----------------------####

        #### -------------------- Powershell parameters ------------------------------####

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession,

        # Provided for compatibility with CDXML cmdlets, not actually used.
        [Int32]
        $ThrottleLimit,

        [Switch]
        $AsJob
    )

    Begin
    {
        $SourceCaller = "Microsoft.PowerShell"
    }

    Process
    {
        $info = $resources.info
        $p = $null

        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        ## Gather the instance object

        switch ($psCmdlet.ParameterSetName)
        {
            "ByPhysicalDisk"                 { $PhysicalDiskInstance = $InputObject; break; }
            "ByPhysicalDiskUniqueId"         { $PhysicalDiskInstance = Get-PhysicalDisk -UniqueId $UniqueId -CimSession $CimSession -ErrorAction Stop; break; }
            "ByPhysicalDiskFriendlyName"     { $PhysicalDiskInstance = Get-PhysicalDisk -FriendlyName $FriendlyName -CimSession $CimSession -ErrorAction Stop; break; }

            "ByStorageEnclosure"             { $StorageEnclosureInstance = $StorageEnclosure; break; }
            "ByStorageEnclosureUniqueId"     { $StorageEnclosureInstance = Get-StorageEnclosure -UniqueId $StorageEnclosureUniqueId -CimSession $CimSession -ErrorAction Stop; break; }
            "ByStorageEnclosureFriendlyName" { $StorageEnclosureInstance = Get-StorageEnclosure -FriendlyName $StorageEnclosureFriendlyName -CimSession $CimSession -ErrorAction Stop; break; }
        }

        if (-not $AsJob)
        {
            Write-Progress -Activity "Get-StorageFirmwareInformation" -PercentComplete 0 -CurrentOperation "Validating parameters" -Status "0/2"
        }

        # Would use a closure here, but jobs are run in their own session state.
        $methodblock = {
            param($session, $asjob, $inputobject)

            # Start-Job serializes/deserializes the CimSession,
            # which means it shows up here as having type Deserialized.CimSession.
            # Must recreate or cast the object in order to pass it to Get-CimInstance.
            if ($asjob)
            {
                $session = $session | New-CimSession
            }

            $errorObject = $null

            if (-not $asjob)
            {
                Write-Progress -Activity "Get-StorageFirmwareInformation" -PercentComplete 30 -CurrentOperation "Getting storage firmware information" -Status "1/2"
            }

            try
            {
                $progressPreference = "silentlyContinue"

                $output = Invoke-CimMethod -CimSession $session -InputObject $inputobject -MethodName GetFirmwareInformation -ErrorAction Stop

                $progressPreference = "Continue"

                $firmwareInformation = [pscustomobject]@{
                    Object                = $inputobject;
                    SupportsUpdate        = $output.SupportsUpdate;
                    NumberOfSlots         = $output.NumberOfSlots;
                    ActiveSlotNumber      = $output.ActiveSlotNumber;
                    SlotNumber            = $output.SlotNumber;
                    IsSlotWritable        = $output.IsSlotWritable;
                    FirmwareVersionInSlot = $output.FirmwareVersionInSlot;
                }

                $psCmdlet.WriteObject($firmwareInformation);
            }
            catch
            {
                $progressPreference = "Continue"

                $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId `
                                                 -ErrorMessage $null `
                                                 -ErrorCategory $_.CategoryInfo.Category `
                                                 -Exception $_.Exception `
                                                 -TargetObject $_.TargetObject

                $psCmdlet.WriteError($errorObject)
            }

            if (-not $asjob)
            {
                Write-Progress -Activity "Get-StorageFirmwareInformation" -Completed -Status "2/2"
            }
        }

        if ($asjob)
        {
            $p = $true

            if ($PhysicalDiskInstance)
            {
                Start-Job -Name GetStorageFirmwareInformation -ScriptBlock $methodblock -ArgumentList @($CimSession, $p, $PhysicalDiskInstance)
            }
            elseif ($StorageEnclosureInstance)
            {
                Start-Job -Name GetStorageFirmwareInformation -ScriptBlock $methodblock -ArgumentList @($CimSession, $p, $StorageEnclosureInstance)
            }
        }
        else
        {
            if ($PhysicalDiskInstance)
            {
                &$methodblock $CimSession $p $PhysicalDiskInstance
            }
            elseif ($StorageEnclosureInstance)
            {
                &$methodblock $CimSession $p $StorageEnclosureInstance
            }
        }
    }
}


function Update-StorageFirmware
{
    [CmdletBinding( DefaultParameterSetName = "ByPhysicalDiskFriendlyName", ConfirmImpact="Medium" )]
    Param
    (
        #### -------------------- Parameter sets -------------------------------------####

        [System.String]
        [Parameter(
            ParameterSetName  = 'ByPhysicalDiskUniqueId',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias("Id")]
        [Alias("PhysicalDiskUniqueId")]
        $UniqueId,

        [System.String]
        [Parameter(
            ParameterSetName  = 'ByPhysicalDiskFriendlyName',
            ValueFromPipeline = $true,
            Mandatory         = $true,
            Position          = 0)]
        [ValidateNotNullOrEmpty()]
        [Alias("PhysicalDiskFriendlyName")]
        $FriendlyName,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_PhysicalDisk")]
        [Parameter(
            ParameterSetName  = 'ByPhysicalDisk',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias("PhysicalDisk")]
        $InputObject,

        [System.String]
        [Parameter(
            ParameterSetName  = 'ByStorageEnclosureUniqueId',
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageEnclosureUniqueId,

        [System.String]
        [Parameter(
            ParameterSetName  = 'ByStorageEnclosureFriendlyName',
            Mandatory         = $true,
            Position          = 0)]
        [ValidateNotNullOrEmpty()]
        $StorageEnclosureFriendlyName,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageEnclosure")]
        [Parameter(
            ParameterSetName  = 'ByStorageEnclosure',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageEnclosure,

        #### ------------------------- Common method parameters ----------------------####

        [System.String]
        [Parameter(
            ParameterSetName = 'ByPhysicalDiskUniqueId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByPhysicalDiskFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByPhysicalDisk',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageEnclosureUniqueId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageEnclosureFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageEnclosure',
            Mandatory        = $false)]
        $ImagePath,

        [System.UInt16]
        [Parameter(
            ParameterSetName = 'ByPhysicalDiskUniqueId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByPhysicalDiskFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByPhysicalDisk',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageEnclosureUniqueId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageEnclosureFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageEnclosure',
            Mandatory        = $false)]
        $SlotNumber,

        #### -------------------- Powershell parameters ------------------------------####

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession,

        # Provided for compatibility with CDXML cmdlets, not actually used.
        [Int32]
        $ThrottleLimit,

        [Switch]
        $AsJob
    )

    Begin
    {
        $SourceCaller = "Microsoft.PowerShell"
    }

    Process
    {
        $info = $resources.info
        $p = $null

        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        ## Gather the instance object

        switch ($psCmdlet.ParameterSetName)
        {
            "ByPhysicalDisk"                 { $PhysicalDiskInstance = $InputObject; break; }
            "ByPhysicalDiskUniqueId"         { $PhysicalDiskInstance = Get-PhysicalDisk -UniqueId $UniqueId -CimSession $CimSession -ErrorAction Stop; break; }
            "ByPhysicalDiskFriendlyName"     { $PhysicalDiskInstance = Get-PhysicalDisk -FriendlyName $FriendlyName -CimSession $CimSession -ErrorAction Stop; break; }

            "ByStorageEnclosure"             { $StorageEnclosureInstance = $StorageEnclosure; break; }
            "ByStorageEnclosureUniqueId"     { $StorageEnclosureInstance = Get-StorageEnclosure -UniqueId $StorageEnclosureUniqueId -CimSession $CimSession -ErrorAction Stop; break; }
            "ByStorageEnclosureFriendlyName" { $StorageEnclosureInstance = Get-StorageEnclosure -FriendlyName $StorageEnclosureFriendlyName -CimSession $CimSession -ErrorAction Stop; break; }
        }

        if (-not $AsJob)
        {
            Write-Progress -Activity "Update-StorageFirmware" -PercentComplete 0 -CurrentOperation "Validating parameters" -Status "0/2"
        }

        ## Populate arguments list

        $arguments = @{}

        if ($PSBoundParameters.ContainsKey("ImagePath"))
        {
            $arguments.Add("ImagePath", $ImagePath)
        }

        if ($PSBoundParameters.ContainsKey("SlotNumber"))
        {
            $arguments.Add("SlotNumber", $SlotNumber)
        }

        # Would use a closure here, but jobs are run in their own session state.
        $methodblock = {
            param($session, $asjob, $inputobject, $arguments)

            # Start-Job serializes/deserializes the CimSession,
            # which means it shows up here as having type Deserialized.CimSession.
            # Must recreate or cast the object in order to pass it to Get-CimInstance.
            if ($asjob)
            {
                $session = $session | New-CimSession
            }

            $errorObject = $null

            if (-not $asjob)
            {
                Write-Progress -Activity "Update-StorageFirmware" -PercentComplete 30 -CurrentOperation "Updating storage firmware" -Status "1/2"
            }

            try
            {
                $progressPreference = "silentlyContinue"

                $output = Invoke-CimMethod -CimSession $session -InputObject $inputobject -MethodName UpdateFirmware -Arguments $arguments -ErrorAction Stop

                $progressPreference = "Continue"
            }
            catch
            {
                $progressPreference = "Continue"

                $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId `
                                                 -ErrorMessage $null `
                                                 -ErrorCategory $_.CategoryInfo.Category `
                                                 -Exception $_.Exception `
                                                 -TargetObject $_.TargetObject

                $psCmdlet.WriteError($errorObject)
            }

            if (-not $asjob)
            {
                Write-Progress -Activity "Update-StorageFirmware" -Completed -Status "2/2"
            }
        }

        if ($asjob)
        {
            $p = $true

            if ($PhysicalDiskInstance)
            {
                Start-Job -Name UpdateStorageFirmware -ScriptBlock $methodblock -ArgumentList @($CimSession, $p, $PhysicalDiskInstance, $arguments)
            }
            elseif ($StorageEnclosureInstance)
            {
                Start-Job -Name UpdateStorageFirmware -ScriptBlock $methodblock -ArgumentList @($CimSession, $p, $StorageEnclosureInstance, $arguments)
            }
        }
        else
        {
            if ($PhysicalDiskInstance)
            {
                &$methodblock $CimSession $p $PhysicalDiskInstance $arguments
            }
            elseif ($StorageEnclosureInstance)
            {
                &$methodblock $CimSession $p $StorageEnclosureInstance $arguments
            }
        }
    }
}


function Get-PhysicalDiskStorageNodeView
{
    [CmdletBinding()]
    Param
    (
        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageNode")]
        [Parameter(
            ValueFromPipeline = $true)]
        $StorageNode,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_PhysicalDisk")]
        [Parameter(
            ValueFromPipeline = $true)]
        $PhysicalDisk,

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession
    )

    Process
    {
        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        ## Gather the association objects
        $options = New-Object Microsoft.Management.Infrastructure.Options.CimOperationOptions

        if ($PSBoundParameters.ContainsKey("StorageNode"))
        {
            $options.SetCustomOption("InputClassName", "MSFT_StorageNode", $false)
            $options.SetCustomOption("InputObjectId", $StorageNode.ObjectId, $false)
        }
        elseif ($PSBoundParameters.ContainsKey("PhysicalDisk"))
        {
            $options.SetCustomOption("InputClassName", "MSFT_PhysicalDisk", $false)
            $options.SetCustomOption("InputObjectId", $PhysicalDisk.ObjectId, $false)
        }

        $results = $CimSession.EnumerateInstances($StorageNamespace,
                                                  "MSFT_StorageNodeToPhysicalDisk",
                                                  $options
                                                  )

        $enumerator = $results.GetEnumerator()
        $instances  = @()

        while ($enumerator.MoveNext())
        {
            $instance = $enumerator.Current

            if ($PSBoundParameters.ContainsKey("StorageNode"))
            {
                if ($instance.StorageNodeObjectId -ne $StorageNode.ObjectId)
                {
                    continue
                }
            }
            elseif ($PSBoundParameters.ContainsKey("PhysicalDisk"))
            {
                if ($instance.PhysicalDiskObjectId -ne $PhysicalDisk.ObjectId)
                {
                    continue
                }
            }

            $instances += $instance
        }

        $instances
    }
}


function Get-DiskStorageNodeView
{
    [CmdletBinding()]
    Param
    (
        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageNode")]
        [Parameter(
            ValueFromPipeline = $true)]
        $StorageNode,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_Disk")]
        [Parameter(
            ValueFromPipeline = $true)]
        $Disk,

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession
    )

    Process
    {
        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        ## Gather the association objects
        $options = New-Object Microsoft.Management.Infrastructure.Options.CimOperationOptions

        if ($PSBoundParameters.ContainsKey("StorageNode"))
        {
            $options.SetCustomOption("InputClassName", "MSFT_StorageNode", $false)
            $options.SetCustomOption("InputObjectId", $StorageNode.ObjectId, $false)
        }
        elseif ($PSBoundParameters.ContainsKey("Disk"))
        {
            $options.SetCustomOption("InputClassName", "MSFT_Disk", $false)
            $options.SetCustomOption("InputObjectId", $Disk.ObjectId, $false)
        }

        $results = $CimSession.EnumerateInstances($StorageNamespace,
                                                  "MSFT_StorageNodeToDisk",
                                                  $options
                                                  )

        $enumerator = $results.GetEnumerator()
        $instances  = @()

        while ($enumerator.MoveNext())
        {
            $instance = $enumerator.Current

            if ($PSBoundParameters.ContainsKey("StorageNode"))
            {
                if ($instance.StorageNodeObjectId -ne $StorageNode.ObjectId)
                {
                    continue
                }
            }
            elseif ($PSBoundParameters.ContainsKey("Disk"))
            {
                if ($instance.DiskObjectId -ne $Disk.ObjectId)
                {
                    continue
                }
            }

            $instances += $instance
        }

        $instances
    }
}


function Get-StorageEnclosureStorageNodeView
{
    [CmdletBinding()]
    Param
    (
        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageNode")]
        [Parameter(
            ValueFromPipeline = $true)]
        $StorageNode,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageEnclosure")]
        [Parameter(
            ValueFromPipeline = $true)]
        $StorageEnclosure,

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession
    )

    Process
    {
        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        ## Gather the association objects
        $options = New-Object Microsoft.Management.Infrastructure.Options.CimOperationOptions

        if ($PSBoundParameters.ContainsKey("StorageNode"))
        {
            $options.SetCustomOption("InputClassName", "MSFT_StorageNode", $false)
            $options.SetCustomOption("InputObjectId", $StorageNode.ObjectId, $false)
        }
        elseif ($PSBoundParameters.ContainsKey("StorageEnclosure"))
        {
            $options.SetCustomOption("InputClassName", "MSFT_StorageEnclosure", $false)
            $options.SetCustomOption("InputObjectId", $StorageEnclosure.ObjectId, $false)
        }

        $results = $CimSession.EnumerateInstances($StorageNamespace,
                                                  "MSFT_StorageNodeToStorageEnclosure",
                                                  $options
                                                  )

        $enumerator = $results.GetEnumerator()
        $instances  = @()

        while ($enumerator.MoveNext())
        {
            $instance = $enumerator.Current

            if ($PSBoundParameters.ContainsKey("StorageNode"))
            {
                if ($instance.StorageNodeObjectId -ne $StorageNode.ObjectId)
                {
                    continue
                }
            }
            elseif ($PSBoundParameters.ContainsKey("StorageEnclosure"))
            {
                if ($instance.StorageEnclosureObjectId -ne $StorageEnclosure.ObjectId)
                {
                    continue
                }
            }

            $instances += $instance
        }

        $instances
    }
}


function Get-StorageHealthAction
{
    [CmdletBinding()]
    Param
    (
        [string[]]
        [Parameter(
            Mandatory         = $true,
            ValueFromPipeline = $true,
            ParameterSetName  = "ByUniqueId")]
         $UniqueId,

        [CimInstance]
        [Parameter(
            Mandatory         = $false,
            ValueFromPipeline = $true,
            ParameterSetName  = "InputObject")]
        $InputObject,

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession,

        # Provided for compatibility with CDXML cmdlets, not actually used.
        [Int32]
        $ThrottleLimit,

        [Switch]
        $AsJob
    )

    Begin{}

    Process
    {
        $info = $resources.info
        $p = $null
        $methodName = "GetActions"

        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        if ($UniqueId)
        {
            # Would use a closure here, but jobs are run in their own session state.
            $block = {
                param($session, $asjob, $ns, $id)

                # Start-Job serializes/deserializes the CimSession,
                # which means it shows up here as having type Deserialized.CimSession.
                # Must recreate or cast the object in order to pass it to Get-CimInstance.
                if ($asjob)
                {
                    $session = $session | New-CimSession
                }

                Get-CimInstance -CimSession $session -Query "Select * From MSFT_HealthAction Where UniqueId='$id'" -Namespace $ns

            }

            if ($asjob)
            {
                $p = $true
                Start-Job -ScriptBlock $block -ArgumentList @($CimSession, $p, $StorageNamespace, $UniqueId)
            }
            else
            {
                if ($pscmdlet.ShouldProcess($info, $resources.warning, $info))
                {
                    &$block $CimSession $p $StorageNamespace $UniqueId
                }
            }
        }
        elseif ($InputObject.CimClass.CimClassName -eq "MSFT_HealthAction")
        {
            # Would use a closure here, but jobs are run in their own session state.
            $block = {
                param($session, $asjob, $io)

                # Start-Job serializes/deserializes the CimSession,
                # which means it shows up here as having type Deserialized.CimSession.
                # Must recreate or cast the object in order to pass it to Get-CimInstance.
                if ($asjob)
                {
                    $session = $session | New-CimSession
                }

                $io | Get-CimInstance -CimSession $session

            }

            if ($asjob)
            {
                $p = $true
                Start-Job -ScriptBlock $block -ArgumentList @($CimSession, $p, $InputObject)
            }
            else
            {
                if ($pscmdlet.ShouldProcess($info, $resources.warning, $info))
                {
                    &$block $CimSession $p $InputObject
                }
            }
        }
        elseif (-not $InputObject -or ( $InputObject.CimClass.CimClassName -eq "MSFT_StorageHealth" ))
        {
            $className = "MSFT_HealthAction"
            $block = {
                param($session, $ns, $asjob, $cn)

                # Start-Job serializes/deserializes the CimSession,
                # which means it shows up here as having type Deserialized.CimSession.
                # Must recreate or cast the object in order to pass it to Get-CimInstance.
                if ($asjob)
                {
                    $session = $session | New-CimSession
                }

                Get-CimInstance -CimSession $session -Namespace $ns -ClassName $cn
            }

            if ($asjob)
            {
                $p = $true
                Start-Job -ScriptBlock $block -ArgumentList @($CimSession, $StorageNamespace, $p, $className)
            }
            else
            {
                if ($pscmdlet.ShouldProcess($info, $resources.warning, $info))
                {
                    &$block $CimSession $StorageNamespace $p $className
                }
            }
        }
        else
        {
            # Would use a closure here, but jobs are run in their own session state.
            $block = {
                param($session, $ns, $asjob, $mn, $io)

                # Start-Job serializes/deserializes the CimSession,
                # which means it shows up here as having type Deserialized.CimSession.
                # Must recreate or cast the object in order to pass it to Get-CimInstance.
                if ($asjob)
                {
                    $session = $session | New-CimSession
                }

                $r = Invoke-CimMethod -CimSession $session -InputObject $io -MethodName $mn -Confirm:$false

                for($i = 0; $i -lt $r.Count - 1; $i++)
                {
                    $r[$i].ItemValue
                }
            }

            if ($asjob)
            {
                $p = $true
                Start-Job -ScriptBlock $block -ArgumentList @($CimSession, $StorageNamespace, $p, $methodName, $InputObject)
            }
            else
            {
                if ($pscmdlet.ShouldProcess($info, $resources.warning, $info))
                {
                    &$block $CimSession $StorageNamespace $p $methodName $InputObject
                }
            }
        }
    }
}


function Get-StorageFaultDomain
{
    [CmdletBinding( DefaultParameterSetName = "EnumerateFaultDomains" )]
    Param
    (
        #### -------------------- Parameter sets -------------------------------------####

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageSubsystem")]
        [Parameter(
            ParameterSetName  = 'ByStorageSubsystem',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageSubsystem,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageFaultDomain")]
        [Parameter(
            ParameterSetName  = 'ByStorageFaultDomain',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageFaultDomain,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_VirtualDisk")]
        [Parameter(
            ParameterSetName  = 'ByVirtualDisk',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $VirtualDisk,

        #### ------------------------- Common parameters -----------------------------####

        [System.String]
        [Parameter(
            ParameterSetName = 'EnumerateFaultDomains',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubsystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageFaultDomain',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByVirtualDisk',
            Mandatory        = $false)]
        $FriendlyName,

        [System.String]
        [Parameter(
            ParameterSetName = 'EnumerateFaultDomains',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubsystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageFaultDomain',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByVirtualDisk',
            Mandatory        = $false)]
        $SerialNumber,

        [StorageFaultDomainType]
        [Parameter(
            ParameterSetName = 'EnumerateFaultDomains',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubsystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageFaultDomain',
            Mandatory        = $false)]
        $Type,

        [System.String]
        [Parameter(
            ParameterSetName = 'EnumerateFaultDomains',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubsystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageFaultDomain',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByVirtualDisk',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $PhysicalLocation,

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession
    )

    Begin
    {
        [flagsattribute()]
        Enum StorageFaultDomainType
        {
            PhysicalDisk     = 1
            StorageEnclosure = 2
            StorageScaleUnit = 3
            StorageChassis   = 4
            StorageRack      = 5
            StorageSite      = 6
        }

        $className = "MSFT_StorageFaultDomain"

        switch ($Type)
        {
            PhysicalDisk     { $className = "MSFT_PhysicalDisk" }
            StorageEnclosure { $className = "MSFT_StorageEnclosure" }
            StorageScaleUnit { $className = "MSFT_StorageScaleUnit" }
            StorageChassis   { $className = "MSFT_StorageChassis" }
            StorageRack      { $className = "MSFT_StorageRack" }
            StorageSite      { $className = "MSFT_StorageSite" }
        }
    }

    Process
    {
        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        ## Gather the instance objects.

        $options = New-Object Microsoft.Management.Infrastructure.Options.CimOperationOptions

        if ($PSBoundParameters.ContainsKey("Type"))
        {
            $options.SetCustomOption("ResultObject", $className, $false)
        }

        switch ($psCmdlet.ParameterSetName)
        {
            "ByStorageSubsystem"
            {
                if (($className -eq "MSFT_PhysicalDisk") -and
                    ($StorageSubsystem.Model -like "*Windows Storage*"))
                {
                    $options.SetCustomOption("InputClassName", "MSFT_StorageSubsystem", $false)
                    $options.SetCustomOption("InputObjectId", $StorageSubsystem.ObjectId, $false)

                    $results = $CimSession.EnumerateInstances($StorageNamespace,
                                                              "MSFT_PhysicalDisk",
                                                              $options)
                }
                else
                {
                    $results = $CimSession.EnumerateAssociatedInstances($StorageNamespace,
                                                                        $StorageSubsystem,
                                                                        "MSFT_StorageSubsystemToStorageFaultDomain",
                                                                        $className,
                                                                        "StorageSubSystem",
                                                                        "StorageFaultDomain",
                                                                        $options)
                }

                break;
            }

            "ByStorageFaultDomain"
            {
                $subsystem = $StorageFaultDomain | get-storagesubsystem -CimSession $CimSession

                if (($className -eq "MSFT_PhysicalDisk") -and
                    ($subsystem.Model -like "*Windows Storage*"))
                {
                    $options.SetCustomOption("InputClassName", $StorageFaultDomain.ClassName, $false)
                    $options.SetCustomOption("InputObjectId", $StorageFaultDomain.ObjectId, $false)

                    $results = $CimSession.EnumerateInstances($StorageNamespace,
                                                              "MSFT_PhysicalDisk",
                                                              $options)
                }
                else
                {
                    $results = $CimSession.EnumerateAssociatedInstances($StorageNamespace,
                                                                        $StorageFaultDomain,
                                                                        "MSFT_StorageFaultDomainToStorageFaultDomain",
                                                                        $className,
                                                                        "SourceStorageFaultDomain",
                                                                        "TargetStorageFaultDomain",
                                                                        $options)
                }

                break;
            }

            "ByVirtualDisk"
            {
                $results = $CimSession.EnumerateAssociatedInstances($StorageNamespace,
                                                                    $VirtualDisk,
                                                                    "MSFT_VirtualDiskToStorageFaultDomain",
                                                                    "MSFT_StorageFaultDomain",
                                                                    "VirtualDisk",
                                                                    "StorageFaultDomain")
            }

            "EnumerateFaultDomains"
            {
                $results = $CimSession.EnumerateInstances($StorageNamespace,
                                                          $className)
                break;
            }
        }

        $enumerator = $results.GetEnumerator()
        $instances  = @()

        while ($enumerator.MoveNext())
        {
            $instance = $enumerator.Current

            if (($PSBoundParameters.ContainsKey("FriendlyName")) -and
                ($instance.FriendlyName -notlike $FriendlyName))
            {
                continue
            }

            if (($PSBoundParameters.ContainsKey("SerialNumber")) -and
                ($instance.SerialNumber -notlike $SerialNumber))
            {
                continue
            }

            ## Filter by physical location if required and ensure
            ## the formatting selected is for MSFT_StorageFaultDomain

            if (($PSBoundParameters.ContainsKey("PhysicalLocation")) -and
                ($instance.PhysicalLocation -notlike $PhysicalLocation))
            {
                continue
            }

            $typename = $instance.PSObject.TypeNames.GetEnumerator()
            $index = 0;

            while ($typename.MoveNext())
            {
                if ($typename.Current.Equals("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageObject",
                                             [System.StringComparison]::OrdinalIgnoreCase) -eq $true)
                {
                    break
                }

                $index++
            }

            $instance.PSObject.TypeNames.Insert($index + 1, "Microsoft.Management.Infrastructure.CimInstance#MSFT_StorageFaultDomain")
            $instances += $instance
        }

        $instances
    }
}


function Get-StorageScaleUnit
{
    [CmdletBinding( DefaultParameterSetName = "EnumerateFaultDomains" )]
    Param
    (
        #### -------------------- Parameter sets -------------------------------------####

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageSubsystem")]
        [Parameter(
            ParameterSetName  = 'ByStorageSubsystem',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageSubsystem,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageFaultDomain")]
        [Parameter(
            ParameterSetName  = 'ByStorageFaultDomain',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageFaultDomain,

        #### ------------------------- Common parameters -----------------------------####

        [System.String]
        [Parameter(
            ParameterSetName = 'EnumerateFaultDomains',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubsystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageFaultDomain',
            Mandatory        = $false)]
        $FriendlyName,

        [System.String]
        [Parameter(
            ParameterSetName = 'EnumerateFaultDomains',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubsystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageFaultDomain',
            Mandatory        = $false)]
        $SerialNumber,

        [System.String]
        [Parameter(
            ParameterSetName = 'EnumerateFaultDomains',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubsystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageFaultDomain',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $PhysicalLocation,

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession
    )

    Begin
    {
    }

    Process
    {
        $arguments = @{}

        $arguments.Add("Type", 'StorageScaleUnit')

        if ($PSBoundParameters.ContainsKey("FriendlyName"))
        {
            $arguments.Add("FriendlyName", $FriendlyName)
        }

        if ($PSBoundParameters.ContainsKey("SerialNumber"))
        {
            $arguments.Add("SerialNumber", $SerialNumber)
        }

        if ($PSBoundParameters.ContainsKey("PhysicalLocation"))
        {
            $arguments.Add("PhysicalLocation", $PhysicalLocation)
        }

        if ($PSBoundParameters.ContainsKey("StorageSubsystem"))
        {
            $arguments.Add("StorageSubsystem", $StorageSubsystem)
        }

        if ($PSBoundParameters.ContainsKey("StorageFaultDomain"))
        {
            $arguments.Add("StorageFaultDomain", $StorageFaultDomain)
        }

        if ($PSBoundParameters.ContainsKey("CimSession"))
        {
            $arguments.Add("CimSession", $CimSession)
        }

        Get-StorageFaultDomain @arguments
    }
}


function Get-StorageChassis
{
    [CmdletBinding( DefaultParameterSetName = "EnumerateFaultDomains" )]
    Param
    (
        #### -------------------- Parameter sets -------------------------------------####

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageSubsystem")]
        [Parameter(
            ParameterSetName  = 'ByStorageSubsystem',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageSubsystem,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageFaultDomain")]
        [Parameter(
            ParameterSetName  = 'ByStorageFaultDomain',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageFaultDomain,

        #### ------------------------- Common parameters -----------------------------####

        [System.String]
        [Parameter(
            ParameterSetName = 'EnumerateFaultDomains',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubsystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageFaultDomain',
            Mandatory        = $false)]
        $FriendlyName,

        [System.String]
        [Parameter(
            ParameterSetName = 'EnumerateFaultDomains',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubsystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageFaultDomain',
            Mandatory        = $false)]
        $SerialNumber,

        [System.String]
        [Parameter(
            ParameterSetName = 'EnumerateFaultDomains',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubsystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageFaultDomain',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $PhysicalLocation,

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession
    )

    Begin
    {
    }

    Process
    {
        $arguments = @{}

        $arguments.Add("Type", 'StorageChassis')

        if ($PSBoundParameters.ContainsKey("FriendlyName"))
        {
            $arguments.Add("FriendlyName", $FriendlyName)
        }

        if ($PSBoundParameters.ContainsKey("SerialNumber"))
        {
            $arguments.Add("SerialNumber", $SerialNumber)
        }

        if ($PSBoundParameters.ContainsKey("PhysicalLocation"))
        {
            $arguments.Add("PhysicalLocation", $PhysicalLocation)
        }

        if ($PSBoundParameters.ContainsKey("StorageSubsystem"))
        {
            $arguments.Add("StorageSubsystem", $StorageSubsystem)
        }

        if ($PSBoundParameters.ContainsKey("StorageFaultDomain"))
        {
            $arguments.Add("StorageFaultDomain", $StorageFaultDomain)
        }

        if ($PSBoundParameters.ContainsKey("CimSession"))
        {
            $arguments.Add("CimSession", $CimSession)
        }

        Get-StorageFaultDomain @arguments
    }
}


function Get-StorageRack
{
    [CmdletBinding( DefaultParameterSetName = "EnumerateFaultDomains" )]
    Param
    (
        #### -------------------- Parameter sets -------------------------------------####

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageSubsystem")]
        [Parameter(
            ParameterSetName  = 'ByStorageSubsystem',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageSubsystem,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageFaultDomain")]
        [Parameter(
            ParameterSetName  = 'ByStorageFaultDomain',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageFaultDomain,

        #### ------------------------- Common parameters -----------------------------####

        [System.String]
        [Parameter(
            ParameterSetName = 'EnumerateFaultDomains',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubsystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageFaultDomain',
            Mandatory        = $false)]
        $FriendlyName,

        [System.String]
        [Parameter(
            ParameterSetName = 'EnumerateFaultDomains',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubsystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageFaultDomain',
            Mandatory        = $false)]
        $SerialNumber,

        [System.String]
        [Parameter(
            ParameterSetName = 'EnumerateFaultDomains',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubsystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageFaultDomain',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $PhysicalLocation,

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession
    )

    Begin
    {
    }

    Process
    {
        $arguments = @{}

        $arguments.Add("Type", 'StorageRack')

        if ($PSBoundParameters.ContainsKey("FriendlyName"))
        {
            $arguments.Add("FriendlyName", $FriendlyName)
        }

        if ($PSBoundParameters.ContainsKey("SerialNumber"))
        {
            $arguments.Add("SerialNumber", $SerialNumber)
        }

        if ($PSBoundParameters.ContainsKey("PhysicalLocation"))
        {
            $arguments.Add("PhysicalLocation", $PhysicalLocation)
        }

        if ($PSBoundParameters.ContainsKey("StorageSubsystem"))
        {
            $arguments.Add("StorageSubsystem", $StorageSubsystem)
        }

        if ($PSBoundParameters.ContainsKey("StorageFaultDomain"))
        {
            $arguments.Add("StorageFaultDomain", $StorageFaultDomain)
        }

        if ($PSBoundParameters.ContainsKey("CimSession"))
        {
            $arguments.Add("CimSession", $CimSession)
        }

        Get-StorageFaultDomain @arguments
    }
}


function Get-StorageSite
{
    [CmdletBinding( DefaultParameterSetName = "EnumerateFaultDomains" )]
    Param
    (
        #### -------------------- Parameter sets -------------------------------------####

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageSubsystem")]
        [Parameter(
            ParameterSetName  = 'ByStorageSubsystem',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageSubsystem,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageFaultDomain")]
        [Parameter(
            ParameterSetName  = 'ByStorageFaultDomain',
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageFaultDomain,

        #### ------------------------- Common parameters -----------------------------####

        [System.String]
        [Parameter(
            ParameterSetName = 'EnumerateFaultDomains',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubsystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageFaultDomain',
            Mandatory        = $false)]
        $FriendlyName,

        [System.String]
        [Parameter(
            ParameterSetName = 'EnumerateFaultDomains',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubsystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageFaultDomain',
            Mandatory        = $false)]
        $SerialNumber,

        [System.String]
        [Parameter(
            ParameterSetName = 'EnumerateFaultDomains',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubsystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageFaultDomain',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $PhysicalLocation,

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession
    )

    Begin
    {
    }

    Process
    {
        $arguments = @{}

        $arguments.Add("Type", 'StorageSite')

        if ($PSBoundParameters.ContainsKey("FriendlyName"))
        {
            $arguments.Add("FriendlyName", $FriendlyName)
        }

        if ($PSBoundParameters.ContainsKey("SerialNumber"))
        {
            $arguments.Add("SerialNumber", $SerialNumber)
        }

        if ($PSBoundParameters.ContainsKey("PhysicalLocation"))
        {
            $arguments.Add("PhysicalLocation", $PhysicalLocation)
        }

        if ($PSBoundParameters.ContainsKey("StorageSubsystem"))
        {
            $arguments.Add("StorageSubsystem", $StorageSubsystem)
        }

        if ($PSBoundParameters.ContainsKey("StorageFaultDomain"))
        {
            $arguments.Add("StorageFaultDomain", $StorageFaultDomain)
        }

        if ($PSBoundParameters.ContainsKey("CimSession"))
        {
            $arguments.Add("CimSession", $CimSession)
        }

        Get-StorageFaultDomain @arguments
    }
}


function ValidateFaultDomainsExist
{
    Param
    (
        [String[]]
        $StorageFaultDomainFriendlyNames,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageSubsystem")]
        $StorageSubsystem,

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession
    )

    [Microsoft.Management.Infrastructure.CimInstance[]] $faultdomains = @()

    $subsystemFDs = Get-CimAssociatedInstance -InputObject $StorageSubsystem `
                                              -Association MSFT_StorageSubsystemToStorageFaultDomain `
                                              -ResultClassName MSFT_StorageFaultDomain `
                                              -CimSession $CimSession `
                                              -ErrorAction Stop

    for ($i = 0; $i -lt $StorageFaultDomainFriendlyNames.Count; $i++)
    {
        $found = $false

        foreach ($fd in $subsystemFDs)
        {
            if ($fd.FriendlyName -eq $StorageFaultDomainFriendlyNames[$i])
            {
                $faultdomains += $fd
                $found = $true
                break
            }
        }

        if ($found -eq $false)
        {
            $errorObject = CreateErrorRecord -ErrorId "InvalidArgument" `
                                             -ErrorMessage "Could not find storage fault domain with the friendly name '$($StorageFaultDomainFriendlyNames[$i])' in the storage subsystem" `
                                             -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidArgument) `
                                             -Exception $null `
                                             -TargetObject $null

            $psCmdlet.WriteError($errorObject)

            $faultdomains = @()
            break
        }
    }

    return $faultdomains
}


function Add-StorageFaultDomain
{
    [CmdletBinding()]
    Param
    (
        #### -------------------- Parameter sets -------------------------------------####

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_VirtualDisk")]
        [Parameter(
            ParameterSetName  = "ByVirtualDisk",
            ValueFromPipeline = $true,
            Mandatory         = $true,
            Position          = 0)]
        [ValidateNotNullOrEmpty()]
        $VirtualDisk,

        [String]
        [Parameter(
            ParameterSetName  = "ByVirtualDiskFriendlyName",
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $VirtualDiskFriendlyName,

        [String]
        [Parameter(
            ParameterSetName  = "ByVirtualDiskName",
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $VirtualDiskName,

        [String]
        [Parameter(
            ParameterSetName  = "ByVirtualDiskUniqueId",
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $VirtualDiskUniqueId,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageTier")]
        [Parameter(
            ParameterSetName  = "ByStorageTier",
            ValueFromPipeline = $true,
            Mandatory         = $true,
            Position          = 0)]
        [ValidateNotNullOrEmpty()]
        $StorageTier,

        [String]
        [Parameter(
            ParameterSetName  = "ByStorageTierFriendlyName",
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageTierFriendlyName,

        [String]
        [Parameter(
            ParameterSetName  = "ByStorageTierUniqueId",
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageTierUniqueId,

        #### -------------------- Common method parameters ---------------------------####

        [Microsoft.Management.Infrastructure.CimInstance[]]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageFaultDomain")]
        [Parameter(
            ParameterSetName = 'ByVirtualDisk',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByVirtualDiskFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByVirtualDiskName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByVirtualDiskUniqueId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageTier',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageTierFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageTierUniqueId',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $StorageFaultDomains,

        [String[]]
        [Parameter(
            ParameterSetName = 'ByVirtualDisk',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByVirtualDiskFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByVirtualDiskName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByVirtualDiskUniqueId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageTier',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageTierFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageTierUniqueId',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $StorageFaultDomainFriendlyNames,

        #### -------------------- Powershell parameters ------------------------------####

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession,

        # Provided for compatibility with CDXML cmdlets, not actually used.
        [Int32]
        $ThrottleLimit,

        [Switch]
        $AsJob
    )

    Begin
    {
        $SourceCaller = "Microsoft.PowerShell"
    }

    Process
    {
        $info = $resources.info
        $p = $null

        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        ## Gather the instance object

        switch ($psCmdlet.ParameterSetName)
        {
            "ByVirtualDisk"             { $VirtualDiskInstance = $VirtualDisk; break; }
            "ByVirtualDiskFriendlyName" { $VirtualDiskInstance = Get-VirtualDisk -FriendlyName $VirtualDiskFriendlyName -CimSession $CimSession -ErrorAction Stop; break; }
            "ByVirtualDiskName"         { $VirtualDiskInstance = Get-VirtualDisk -Name $VirtualDiskName -CimSession $CimSession -ErrorAction Stop; break; }
            "ByVirtualDiskUniqueId"     { $VirtualDiskInstance = Get-VirtualDisk -UniqueId $VirtualDiskUniqueId -CimSession $CimSession -ErrorAction Stop; break; }

            "ByStorageTier"             { $StorageTierInstance = $StorageTier; break; }
            "ByStorageTierFriendlyName" { $StorageTierInstance = Get-StorageTier -FriendlyName $StorageTierFriendlyName -CimSession $CimSession -ErrorAction Stop; break; }
            "ByStorageTierUniqueId"     { $StorageTierInstance = Get-StorageTier -UniqueId $StorageTierUniqueId -CimSession $CimSession -ErrorAction Stop; break; }
        }

        if (-not $AsJob)
        {
            Write-Progress -Activity "Add-StorageFaultDomain" -PercentComplete 0 -CurrentOperation "Validating parameters" -Status "0/2"
        }

        if ($VirtualDiskInstance)
        {
            $subsystem = $VirtualDiskInstance | get-storagesubsystem -CimSession $CimSession
        }
        elseif ($StorageTierInstance)
        {
            $subsystem = $StorageTierInstance | get-storagesubsystem -CimSession $CimSession
        }

        ## Populate arguments list

        $arguments = @{}

        if ($PSBoundParameters.ContainsKey("StorageFaultDomains") -and
            $PSBoundParameters.ContainsKey("StorageFaultDomainFriendlyNames"))
        {
            $errorObject = CreateErrorRecord -ErrorId "InvalidArgument" `
                                             -ErrorMessage "Use either 'StorageFaultDomains' or 'StorageFaultDomainFriendlyNames' parameter" `
                                             -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidArgument) `
                                             -Exception $null `
                                             -TargetObject $null

            $psCmdlet.WriteError($errorObject)
            return
        }

        if ($PSBoundParameters.ContainsKey("StorageFaultDomains"))
        {
            $arguments.Add("StorageFaultDomains", $StorageFaultDomains)
        }
        elseif ($PSBoundParameters.ContainsKey("StorageFaultDomainFriendlyNames"))
        {
            [Microsoft.Management.Infrastructure.CimInstance[]] $faultdomains = @()

            $faultdomains = ValidateFaultDomainsExist -StorageFaultDomainFriendlyNames $StorageFaultDomainFriendlyNames `
                                                      -StorageSubsystem $subsystem `
                                                      -CimSession $CimSession

            if ($faultdomains.Count -eq 0)
            {
                return
            }

            $arguments.Add("StorageFaultDomains", $faultdomains)
        }

        # Would use a closure here, but jobs are run in their own session state.
        $addfdblock = {
            param($session, $asjob, $inputobject, $arguments)

            # Start-Job serializes/deserializes the CimSession,
            # which means it shows up here as having type Deserialized.CimSession.
            # Must recreate or cast the object in order to pass it to Get-CimInstance.
            if ($asjob)
            {
                $session = $session | New-CimSession
            }

            $errorObject = $null
            $output      = $null

            try
            {
                if (-not $asjob)
                {
                    Write-Progress -Activity "Add-StorageFaultDomain" -PercentComplete 10 -CurrentOperation "Adding storage fault domains" -Status "1/2"
                }

                $progressPreference = "silentlyContinue"

                $output = Invoke-CimMethod -CimSession $session -InputObject $inputobject -MethodName AddStorageFaultDomain -Arguments $arguments -ErrorAction Stop

                $progressPreference = "Continue"
            }
            catch
            {
                $progressPreference = "Continue"

                $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId `
                                                 -ErrorMessage $null `
                                                 -ErrorCategory $_.CategoryInfo.Category `
                                                 -Exception $_.Exception `
                                                 -TargetObject $_.TargetObject
            }
            finally
            {
                if (-not $asjob)
                {
                    Write-Progress -Activity "Add-StorageFaultDomain" -Completed -Status "2/2"
                }
            }

            if ($errorObject)
            {
                $psCmdlet.WriteError($errorObject)
            }
            else
            {
                $null
            }
        }

        if ($asjob)
        {
            $p = $true

            if ($VirtualDiskInstance)
            {
                Start-Job -Name AddStorageFaultDomain -ScriptBlock $addfdblock -ArgumentList @($CimSession, $p, $VirtualDiskInstance, $arguments)
            }
            elseif ($StorageTierInstance)
            {
                Start-Job -Name AddStorageFaultDomain -ScriptBlock $addfdblock -ArgumentList @($CimSession, $p, $StorageTierInstance, $arguments)
            }
        }
        else
        {
            if ($pscmdlet.ShouldProcess($info, $resources.warning, $info))
            {
                if ($VirtualDiskInstance)
                {
                    &$addfdblock $CimSession $p $VirtualDiskInstance $arguments
                }
                elseif ($StorageTierInstance)
                {
                    &$addfdblock $CimSession $p $StorageTierInstance $arguments
                }
            }
        }
    }
}


function Remove-StorageFaultDomain
{
    [CmdletBinding()]
    Param
    (
        #### -------------------- Parameter sets -------------------------------------####

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_VirtualDisk")]
        [Parameter(
            ParameterSetName  = "ByVirtualDisk",
            ValueFromPipeline = $true,
            Mandatory         = $true,
            Position          = 0)]
        [ValidateNotNullOrEmpty()]
        $VirtualDisk,

        [String]
        [Parameter(
            ParameterSetName  = "ByVirtualDiskFriendlyName",
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $VirtualDiskFriendlyName,

        [String]
        [Parameter(
            ParameterSetName  = "ByVirtualDiskName",
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $VirtualDiskName,

        [String]
        [Parameter(
            ParameterSetName  = "ByVirtualDiskUniqueId",
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $VirtualDiskUniqueId,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageTier")]
        [Parameter(
            ParameterSetName  = "ByStorageTier",
            ValueFromPipeline = $true,
            Mandatory         = $true,
            Position          = 0)]
        [ValidateNotNullOrEmpty()]
        $StorageTier,

        [String]
        [Parameter(
            ParameterSetName  = "ByStorageTierFriendlyName",
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageTierFriendlyName,

        [String]
        [Parameter(
            ParameterSetName  = "ByStorageTierUniqueId",
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageTierUniqueId,

        #### -------------------- Common method parameters ---------------------------####

        [Microsoft.Management.Infrastructure.CimInstance[]]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageFaultDomain")]
        [Parameter(
            ParameterSetName = 'ByVirtualDisk',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByVirtualDiskFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByVirtualDiskName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByVirtualDiskUniqueId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageTier',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageTierFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageTierUniqueId',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $StorageFaultDomains,

        [String[]]
        [Parameter(
            ParameterSetName = 'ByVirtualDisk',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByVirtualDiskFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByVirtualDiskName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByVirtualDiskUniqueId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageTier',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageTierFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageTierUniqueId',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $StorageFaultDomainFriendlyNames,

        #### -------------------- Powershell parameters ------------------------------####

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession,

        # Provided for compatibility with CDXML cmdlets, not actually used.
        [Int32]
        $ThrottleLimit,

        [Switch]
        $AsJob
    )

    Begin
    {
        $SourceCaller = "Microsoft.PowerShell"
    }

    Process
    {
        $info = $resources.info
        $p = $null

        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        ## Gather the instance object

        switch ($psCmdlet.ParameterSetName)
        {
            "ByVirtualDisk"             { $VirtualDiskInstance = $VirtualDisk; break; }
            "ByVirtualDiskFriendlyName" { $VirtualDiskInstance = Get-VirtualDisk -FriendlyName $VirtualDiskFriendlyName -CimSession $CimSession -ErrorAction Stop; break; }
            "ByVirtualDiskName"         { $VirtualDiskInstance = Get-VirtualDisk -Name $VirtualDiskName -CimSession $CimSession -ErrorAction Stop; break; }
            "ByVirtualDiskUniqueId"     { $VirtualDiskInstance = Get-VirtualDisk -UniqueId $VirtualDiskUniqueId -CimSession $CimSession -ErrorAction Stop; break; }

            "ByStorageTier"             { $StorageTierInstance = $StorageTier; break; }
            "ByStorageTierFriendlyName" { $StorageTierInstance = Get-StorageTier -FriendlyName $StorageTierFriendlyName -CimSession $CimSession -ErrorAction Stop; break; }
            "ByStorageTierUniqueId"     { $StorageTierInstance = Get-StorageTier -UniqueId $StorageTierUniqueId -CimSession $CimSession -ErrorAction Stop; break; }
        }

        if (-not $AsJob)
        {
            Write-Progress -Activity "Remove-StorageFaultDomain" -PercentComplete 0 -CurrentOperation "Validating parameters" -Status "0/2"
        }

        if ($VirtualDiskInstance)
        {
            $subsystem = $VirtualDiskInstance | get-storagesubsystem -CimSession $CimSession
        }
        elseif ($StorageTierInstance)
        {
            $subsystem = $StorageTierInstance | get-storagesubsystem -CimSession $CimSession
        }

        ## Populate arguments list

        $arguments = @{}

        if ($PSBoundParameters.ContainsKey("StorageFaultDomains") -and
            $PSBoundParameters.ContainsKey("StorageFaultDomainFriendlyNames"))
        {
            $errorObject = CreateErrorRecord -ErrorId "InvalidArgument" `
                                             -ErrorMessage "Use either 'StorageFaultDomains' or 'StorageFaultDomainFriendlyNames' parameter" `
                                             -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidArgument) `
                                             -Exception $null `
                                             -TargetObject $null

            $psCmdlet.WriteError($errorObject)
            return
        }

        if ($PSBoundParameters.ContainsKey("StorageFaultDomains"))
        {
            $arguments.Add("StorageFaultDomains", $StorageFaultDomains)
        }
        elseif ($PSBoundParameters.ContainsKey("StorageFaultDomainFriendlyNames"))
        {
            [Microsoft.Management.Infrastructure.CimInstance[]] $faultdomains = @()

            $faultdomains = ValidateFaultDomainsExist -StorageFaultDomainFriendlyNames $StorageFaultDomainFriendlyNames `
                                                      -StorageSubsystem $subsystem `
                                                      -CimSession $CimSession

            if ($faultdomains.Count -eq 0)
            {
                return
            }

            $arguments.Add("StorageFaultDomains", $faultdomains)
        }

        # Would use a closure here, but jobs are run in their own session state.
        $removefdblock = {
            param($session, $asjob, $inputobject, $arguments)

            # Start-Job serializes/deserializes the CimSession,
            # which means it shows up here as having type Deserialized.CimSession.
            # Must recreate or cast the object in order to pass it to Get-CimInstance.
            if ($asjob)
            {
                $session = $session | New-CimSession
            }

            $errorObject = $null
            $output      = $null

            try
            {
                if (-not $asjob)
                {
                    Write-Progress -Activity "Remove-StorageFaultDomain" -PercentComplete 10 -CurrentOperation "Removing storage fault domains" -Status "1/2"
                }

                $progressPreference = "silentlyContinue"

                $output = Invoke-CimMethod -CimSession $session -InputObject $inputobject -MethodName RemoveStorageFaultDomain -Arguments $arguments -ErrorAction Stop

                $progressPreference = "Continue"
            }
            catch
            {
                $progressPreference = "Continue"

                $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId `
                                                 -ErrorMessage $null `
                                                 -ErrorCategory $_.CategoryInfo.Category `
                                                 -Exception $_.Exception `
                                                 -TargetObject $_.TargetObject
            }
            finally
            {
                if (-not $asjob)
                {
                    Write-Progress -Activity "Remove-StorageFaultDomain" -Completed -Status "2/2"
                }
            }

            if ($errorObject)
            {
                $psCmdlet.WriteError($errorObject)
            }
            else
            {
                $null
            }
        }

        if ($asjob)
        {
            $p = $true

            if ($VirtualDiskInstance)
            {
                Start-Job -Name RemoveStorageFaultDomain -ScriptBlock $removefdblock -ArgumentList @($CimSession, $p, $VirtualDiskInstance, $arguments)
            }
            elseif ($StorageTierInstance)
            {
                Start-Job -Name RemoveStorageFaultDomain -ScriptBlock $removefdblock -ArgumentList @($CimSession, $p, $StorageTierInstance, $arguments)
            }
        }
        else
        {
            if ($pscmdlet.ShouldProcess($info, $resources.warning, $info))
            {
                if ($VirtualDiskInstance)
                {
                    &$removefdblock $CimSession $p $VirtualDiskInstance $arguments
                }
                elseif ($StorageTierInstance)
                {
                    &$removefdblock $CimSession $p $StorageTierInstance $arguments
                }
            }
        }
    }
}


function New-Volume
{
    [CmdletBinding( DefaultParameterSetName = "ByStoragePool" )]
    Param
    (
        #### -------------------- Parameter sets -------------------------------------####

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StoragePool")]
        [Parameter(
            ParameterSetName  = "ByStoragePool",
            ValueFromPipeline = $true,
            Mandatory         = $false,
            Position          = 0)]
        [ValidateNotNullOrEmpty()]
        $StoragePool,

        [String]
        [Parameter(
            ParameterSetName  = "ByStoragePoolFriendlyName",
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StoragePoolFriendlyName,

        [String]
        [Parameter(
            ParameterSetName  = "ByStoragePoolName",
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StoragePoolName,

        [String]
        [Parameter(
            ParameterSetName  = "ByStoragePoolUniqueId",
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StoragePoolUniqueId,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_Disk")]
        [Parameter(
            ParameterSetName  = "ByDisk",
            ValueFromPipeline = $true,
            Mandatory         = $true,
            Position          = 0)]
        [ValidateNotNullOrEmpty()]
        $Disk,

        [UInt32]
        [Parameter(
            ParameterSetName  = "ByDiskNumber",
            Mandatory         = $true,
            Position          = 0)]
        [ValidateNotNullOrEmpty()]
        $DiskNumber,

        [String]
        [Parameter(
            ParameterSetName  = "ByDiskPath",
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $DiskPath,

        [String]
        [Parameter(
            ParameterSetName  = "ByDiskUniqueId",
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $DiskUniqueId,

        #### -------------------- Common method parameters ---------------------------####

        [String]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $true)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolFriendlyName',
            Mandatory        = $true)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolName',
            Mandatory        = $true)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolUniqueId',
            Mandatory        = $true)]
        [Parameter(
            ParameterSetName = 'ByDisk',
            Mandatory        = $true)]
        [Parameter(
            ParameterSetName = 'ByDiskNumber',
            Mandatory        = $true)]
        [Parameter(
            ParameterSetName = 'ByDiskPath',
            Mandatory        = $true)]
        [Parameter(
            ParameterSetName = 'ByDiskUniqueId',
            Mandatory        = $true)]
        [ValidateNotNullOrEmpty()]
        $FriendlyName,

        [FileSystemType]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolUniqueId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDisk',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDiskNumber',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDiskPath',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDiskUniqueId',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $FileSystem,

        [String]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolUniqueId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDisk',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDiskNumber',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDiskPath',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDiskUniqueId',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $AccessPath,

        [Char]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolUniqueId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDisk',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDiskNumber',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDiskPath',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDiskUniqueId',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $DriveLetter,

        [UInt32]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolUniqueId',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDisk',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDiskNumber',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDiskPath',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByDiskUniqueId',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $AllocationUnitSize,

        #### -------------------- Storage pool parameters ----------------------------####

        [UInt64]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolUniqueId',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $Size,

        [String]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolUniqueId',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $ResiliencySettingName,

        [ProvisioningType]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolUniqueId',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $ProvisioningType,

        [MediaType]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolUniqueId',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $MediaType,

        [UInt16]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolUniqueId',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        [Alias("FaultDomainRedundancy")]
        $PhysicalDiskRedundancy,

        [UInt16]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolUniqueId',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $NumberOfDataCopies,

        [UInt16]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolUniqueId',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $NumberOfColumns,

        [UInt16]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolUniqueId',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $NumberOfGroups,

        [Microsoft.Management.Infrastructure.CimInstance[]]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageFaultDomain")]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolUniqueId',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $StorageFaultDomainsToUse,

        [String[]]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolUniqueId',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $StorageFaultDomainsToUseFriendlyNames,

        [Microsoft.Management.Infrastructure.CimInstance[]]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageTier")]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolUniqueId',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $StorageTiers,

        [String[]]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolUniqueId',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $StorageTierFriendlyNames,

        [UInt64[]]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolUniqueId',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $StorageTierSizes,

        [UInt64]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolUniqueId',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $WriteCacheSize,

        [UInt64]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolUniqueId',
            Mandatory        = $false)]
        [ValidateNotNullOrEmpty()]
        $ReadCacheSize,

        [Switch]
        [Parameter(
            ParameterSetName = 'ByStoragePool',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStoragePoolUniqueId',
            Mandatory        = $false)]
        $UseMaximumSize  = $false,

        #### -------------------- Disk parameters ------------------------------------####


        #### -------------------- Powershell parameters ------------------------------####

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession,

        # Provided for compatibility with CDXML cmdlets, not actually used.
        [Int32]
        $ThrottleLimit,

        [Switch]
        $AsJob
    )

    Begin
    {
        $SourceCaller = "Microsoft.PowerShell"

        [flagsattribute()]
        Enum FileSystemType
        {
            NTFS       = 14
            ReFS       = 15
            CSVFS_NTFS = 32768
            CSVFS_ReFS = 32769
        }

        [flagsattribute()]
        Enum ProvisioningType
        {
            Unknown = 0
            Thin    = 1
            Fixed   = 2
        }

        [flagsattribute()]
        Enum MediaType
        {
            HDD = 3
            SSD = 4
            SCM = 5
        }
    }

    Process
    {
        $info = $resources.info
        $p = $null

        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        ## Gather the instance object

        switch ($psCmdlet.ParameterSetName)
        {
            "ByStoragePool"
            {
                if ($StoragePool -ne $null)
                {
                    $PoolInstance = $StoragePool
                }
                else
                {
                    # Also accept the existence of a single non-primordial pool as unambiguous
                    $PoolInstance = Get-StoragePool |? IsPrimordial -eq $false
                    if ($PoolInstance -eq $null -or $PoolInstance.Count -gt 1)
                    {
                        $errorObject = CreateErrorRecord -ErrorId "InvalidArgument" `
                                                         -ErrorMessage "Storage pool must be specified by one of the 'StoragePool' parameters" `
                                                         -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidArgument) `
                                                         -Exception $null `
                                                         -TargetObject $null

                        $psCmdlet.WriteError($errorObject)
                        return
                    }
                }

                break
            }
            "ByStoragePoolFriendlyName" { $PoolInstance = Get-StoragePool -FriendlyName $StoragePoolFriendlyName -CimSession $CimSession -ErrorAction Stop; break; }
            "ByStoragePoolName"         { $PoolInstance = Get-StoragePool -Name $StoragePoolName -CimSession $CimSession -ErrorAction Stop; break; }
            "ByStoragePoolUniqueId"     { $PoolInstance = Get-StoragePool -UniqueId $StoragePoolUniqueId -CimSession $CimSession -ErrorAction Stop; break; }

            "ByDisk"                    { $DiskInstance = $Disk; break; }
            "ByDiskNumber"              { $DiskInstance = Get-Disk -Number $DiskNumber -CimSession $CimSession -ErrorAction Stop; break; }
            "ByDiskPath"                { $DiskInstance = Get-Disk -Path $DiskPath -CimSession $CimSession -ErrorAction Stop; break; }
            "ByDiskUniqueId"            { $DiskInstance = Get-Disk -UniqueId $DiskUniqueId -CimSession $CimSession -ErrorAction Stop; break; }
        }

        if (-not $AsJob)
        {
            if ($PoolInstance)
            {
                Write-Progress -Activity "New-Volume" -PercentComplete 0 -CurrentOperation "Validating parameters" -Status "0/3"
            }
            elseif ($DiskInstance)
            {
                Write-Progress -Activity "New-Volume" -PercentComplete 0 -CurrentOperation "Validating parameters" -Status "0/2"
            }
        }

        ## Populate arguments list

        $arguments = @{}

        if ($PSBoundParameters.ContainsKey("FriendlyName"))
        {
            $arguments.Add("FriendlyName", $FriendlyName)
        }

        if ($PSBoundParameters.ContainsKey("FileSystem"))
        {
            $arguments.Add("FileSystem", [System.UInt16]$FileSystem)
        }

        if ($PSBoundParameters.ContainsKey("AccessPath") -and
            $PSBoundParameters.ContainsKey("DriveLetter"))
        {
            $errorObject = CreateErrorRecord -ErrorId "InvalidArgument" `
                                             -ErrorMessage "Use either 'AccessPath' or 'DriveLetter' parameter" `
                                             -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidArgument) `
                                             -Exception $null `
                                             -TargetObject $null

            $psCmdlet.WriteError($errorObject)
            return
        }

        if ($PSBoundParameters.ContainsKey("AccessPath"))
        {
            $arguments.Add("AccessPath", $AccessPath)
        }
        elseif ($PSBoundParameters.ContainsKey("DriveLetter"))
        {
            $arguments.Add("AccessPath", $DriveLetter + ":")
        }

        if ($PSBoundParameters.ContainsKey("AllocationUnitSize"))
        {
            $arguments.Add("AllocationUnitSize", $AllocationUnitSize)
        }

        if ($PoolInstance)
        {
            if ($PSBoundParameters.ContainsKey("Size"))
            {
                $arguments.Add("Size", $Size)
            }

            if ($PSBoundParameters.ContainsKey("ResiliencySettingName"))
            {
                $arguments.Add("ResiliencySettingName", $ResiliencySettingName)
            }

            if ($PSBoundParameters.ContainsKey("ProvisioningType"))
            {
                $arguments.Add("ProvisioningType", [System.UInt16]$ProvisioningType)
            }

            if ($PSBoundParameters.ContainsKey("MediaType"))
            {
                $arguments.Add("MediaType", [System.UInt16]$MediaType)
            }

            if ($PSBoundParameters.ContainsKey("PhysicalDiskRedundancy"))
            {
                $arguments.Add("PhysicalDiskRedundancy", $PhysicalDiskRedundancy)
            }

            if ($PSBoundParameters.ContainsKey("NumberOfDataCopies"))
            {
                $arguments.Add("NumberOfDataCopies", $NumberOfDataCopies)
            }

            if ($PSBoundParameters.ContainsKey("NumberOfColumns"))
            {
                $arguments.Add("NumberOfColumns", $NumberOfColumns)
            }

            if ($PSBoundParameters.ContainsKey("NumberOfGroups"))
            {
                $arguments.Add("NumberOfGroups", $NumberOfGroups)
            }

            if ($PSBoundParameters.ContainsKey("StorageFaultDomainsToUse") -and
                $PSBoundParameters.ContainsKey("StorageFaultDomainsToUseFriendlyNames"))
            {
                $errorObject = CreateErrorRecord -ErrorId "InvalidArgument" `
                                                 -ErrorMessage "Use either 'StorageFaultDomainsToUse' or 'StorageFaultDomainsToUseFriendlyNames' parameter" `
                                                 -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidArgument) `
                                                 -Exception $null `
                                                 -TargetObject $null

                $psCmdlet.WriteError($errorObject)
                return
            }

            if ($PSBoundParameters.ContainsKey("StorageFaultDomainsToUse"))
            {
                $arguments.Add("StorageFaultDomainsToUse", $StorageFaultDomainsToUse)
            }
            elseif ($PSBoundParameters.ContainsKey("StorageFaultDomainsToUseFriendlyNames"))
            {
                [Microsoft.Management.Infrastructure.CimInstance[]] $faultdomains = @()

                $subsystem = $PoolInstance | get-storagesubsystem -CimSession $CimSession

                $faultdomains = ValidateFaultDomainsExist -StorageFaultDomainFriendlyNames $StorageFaultDomainsToUseFriendlyNames `
                                                          -StorageSubsystem $subsystem `
                                                          -CimSession $CimSession

                if ($faultdomains.Count -eq 0)
                {
                    return
                }

                $arguments.Add("StorageFaultDomainsToUse", $faultdomains)
            }

            if ($PSBoundParameters.ContainsKey("StorageTiers") -and
                $PSBoundParameters.ContainsKey("StorageTierFriendlyNames"))
            {
                $errorObject = CreateErrorRecord -ErrorId "InvalidArgument" `
                                                 -ErrorMessage "Use either 'StorageTiers' or 'StorageTierFriendlyNames' parameter" `
                                                 -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidArgument) `
                                                 -Exception $null `
                                                 -TargetObject $null

                $psCmdlet.WriteError($errorObject)
                return
            }

            if ($PSBoundParameters.ContainsKey("StorageTiers"))
            {
                $arguments.Add("StorageTiers", $StorageTiers)
            }
            elseif ($PSBoundParameters.ContainsKey("StorageTierFriendlyNames"))
            {
                [Microsoft.Management.Infrastructure.CimInstance[]] $tiers = @()

                $poolTiers = Get-CimAssociatedInstance -InputObject $PoolInstance `
                                                       -Association MSFT_StoragePoolToStorageTier `
                                                       -ResultClassName MSFT_StorageTier `
                                                       -CimSession $CimSession `
                                                       -ErrorAction Stop

                for ($i = 0; $i -lt $StorageTierFriendlyNames.Count; $i++)
                {
                    $found = $false

                    foreach ($tier in $poolTiers)
                    {
                        if ($tier.FriendlyName -eq $StorageTierFriendlyNames[$i])
                        {
                            $tiers += $tier
                            $found = $true
                            break
                        }
                    }

                    if ($found -eq $false)
                    {
                        $errorObject = CreateErrorRecord -ErrorId "InvalidArgument" `
                                                         -ErrorMessage "Could not find storage tier with the friendly name '$($StorageTierFriendlyNames[$i])' in the storage pool" `
                                                         -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidArgument) `
                                                         -Exception $null `
                                                         -TargetObject $null

                        $psCmdlet.WriteError($errorObject)
                        return
                    }
                }

                $arguments.Add("StorageTiers", $tiers)
            }

            if ($PSBoundParameters.ContainsKey("StorageTierSizes"))
            {
                $arguments.Add("StorageTierSizes", $StorageTierSizes)
            }

            if ($PSBoundParameters.ContainsKey("WriteCacheSize"))
            {
                $arguments.Add("WriteCacheSize", $WriteCacheSize)
            }

            if ($PSBoundParameters.ContainsKey("ReadCacheSize"))
            {
                $arguments.Add("ReadCacheSize", $ReadCacheSize)
            }

            if ($PSBoundParameters.ContainsKey("UseMaximumSize"))
            {
                $arguments.Add("UseMaximumSize", $UseMaximumSize.ToBool())
            }
        }

        # Would use a closure here, but jobs are run in their own session state.
        $poolblock = {
            param($session, $asjob, $pool, $arguments)

            # Start-Job serializes/deserializes the CimSession,
            # which means it shows up here as having type Deserialized.CimSession.
            # Must recreate or cast the object in order to pass it to Get-CimInstance.
            if ($asjob)
            {
                $session = $session | New-CimSession
            }

            $errorObject = $null
            $output      = $null
            $retry       = 0
            $time        = 1
            $virtualdisk = $null
            $volume      = $null

            $subsystem = $pool | get-storagesubsystem -CimSession $session

            # If the subsystem model is "Windows Storage",
            # perform the individual steps
            if ($subsystem.Model -like "*Windows Storage*")
            {
                $volCreateParams = @{}

                if ($arguments.ContainsKey("FriendlyName"))
                {
                    $volCreateParams.Add("FriendlyName", $arguments["FriendlyName"])
                }

                if ($arguments.ContainsKey("FileSystem"))
                {
                    $volCreateParams.Add("FileSystem", $arguments["FileSystem"])
                    $arguments.Remove("FileSystem")
                }
                elseif ($subsystem.Model -eq "Clustered Windows Storage" -and
                        $subsystem.StorageConnectionType -eq "Local Storage" -and
                        $subsystem.SupportedFileSystems -contains [FilesystemType]::CSVFS_ReFS)
                {
                    # If the subsystem is clustered local (storage spaces direct) and supports it,
                    # default to CSVFS + ReFS.
                    $volCreateParams.Add("FileSystem", [System.Uint16][FilesystemType]::CSVFS_ReFS)
                }

                if ($arguments.ContainsKey("AccessPath"))
                {
                    $volCreateParams.Add("AccessPath", $arguments["AccessPath"])
                    $arguments.Remove("AccessPath")
                }

                if ($arguments.ContainsKey("AllocationUnitSize"))
                {
                    $volCreateParams.Add("AllocationUnitSize", $arguments["AllocationUnitSize"])
                    $arguments.Remove("AllocationUnitSize")
                }

                try
                {
                    if (-not $asjob)
                    {
                        Write-Progress -Activity "New-Volume" -PercentComplete 10 -CurrentOperation "Creating virtual disk" -Status "1/3"
                    }

                    $progressPreference = "silentlyContinue"

                    $output = Invoke-CimMethod -CimSession $session -InputObject $pool -MethodName CreateVirtualDisk -Arguments $arguments -ErrorAction Stop

                    $progressPreference = "Continue"

                    $virtualdisk = $output.CreatedVirtualDisk

                    do
                    {
                        $disk = $output.CreatedVirtualDisk | get-disk -CimSession $session

                        if ($disk -or ($retry -ge 3))
                        {
                            break
                        }

                        Start-Sleep -Seconds $time

                        $retry++
                        $time = $time * 2
                    }
                    while ($disk -eq $null)

                    if ($disk -eq $null)
                    {
                        $errorObject = CreateErrorRecord -ErrorId "ObjectNotFound" `
                                                         -ErrorMessage "The disk associated with the virtual disk created could not be found." `
                                                         -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) `
                                                         -Exception $null `
                                                         -TargetObject $null

                        $psCmdlet.WriteError($errorObject)
                        return
                    }

                    if (-not $asjob)
                    {
                        Write-Progress -Activity "New-Volume" -PercentComplete 70 -CurrentOperation "Creating volume" -Status "2/3"
                    }

                    $progressPreference = "silentlyContinue"

                    $output = Invoke-CimMethod -CimSession $session -InputObject $disk -MethodName CreateVolume -Arguments $volCreateParams -ErrorAction Stop

                    $progressPreference = "Continue"

                    $volume = $output.CreatedVolume
                }
                catch
                {
                    $progressPreference = "Continue"

                    if (-not $asjob)
                    {
                        Write-Progress -Activity "New-Volume" -PercentComplete 90 -CurrentOperation "Error encountered. Checking if cleanup is required." -Status "2/3"
                    }

                    $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId `
                                                     -ErrorMessage $null `
                                                     -ErrorCategory $_.CategoryInfo.Category `
                                                     -Exception $_.Exception `
                                                     -TargetObject $_.TargetObject

                    # If virtual disk was created and volume was formatted, but
                    # adding to cluster failed or the cluster resource did not
                    # come online after successful addition, do not revert.

                    if (($virtualdisk) -and
                        ($_.FullyQualifiedErrorId -notmatch "46008") -and
                        ($_.FullyQualifiedErrorId -notmatch "46011"))
                    {
                        $progressPreference = "silentlyContinue"

                        $virtualdisk | Remove-VirtualDisk -CimSession $session -Confirm:$false

                        $progressPreference = "Continue"
                    }
                }
                finally
                {
                    if (-not $asjob)
                    {
                        Write-Progress -Activity "New-Volume" -Completed -Status "3/3"
                    }
                }
            }
            # Else fallback to the API on the storage pool
            else
            {
                try
                {
                    if (-not $asjob)
                    {
                        Write-Progress -Activity "New-Volume" -PercentComplete 10 -CurrentOperation "Creating volume" -Status "1/2"
                    }

                    $progressPreference = "silentlyContinue"

                    $output = Invoke-CimMethod -CimSession $session -InputObject $pool -MethodName CreateVolume -Arguments $arguments -ErrorAction Stop

                    $progressPreference = "Continue"

                    $volume = $output.CreatedVolume
                }
                catch
                {
                    $progressPreference = "Continue"

                    $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId `
                                                     -ErrorMessage $null `
                                                     -ErrorCategory $_.CategoryInfo.Category `
                                                     -Exception $_.Exception `
                                                     -TargetObject $_.TargetObject
                }
                finally
                {
                    if (-not $asjob)
                    {
                        Write-Progress -Activity "New-Volume" -Completed -Status "2/2"
                    }
                }
            }

            if ($errorObject)
            {
                $psCmdlet.WriteError($errorObject)
            }
            else
            {
                $volume
            }
        }

        # Would use a closure here, but jobs are run in their own session state.
        $diskblock = {
            param($session, $asjob, $disk, $arguments)

            # Start-Job serializes/deserializes the CimSession,
            # which means it shows up here as having type Deserialized.CimSession.
            # Must recreate or cast the object in order to pass it to Get-CimInstance.
            if ($asjob)
            {
                $session = $session | New-CimSession
            }

            $errorObject = $null
            $output      = $null
            $volume      = $null

            try
            {
                if (-not $asjob)
                {
                    Write-Progress -Activity "New-Volume" -PercentComplete 10 -CurrentOperation "Creating volume" -Status "1/2"
                }

                $progressPreference = "silentlyContinue"

                $output = Invoke-CimMethod -CimSession $session -InputObject $disk -MethodName CreateVolume -Arguments $arguments -ErrorAction Stop

                $progressPreference = "Continue"

                $volume = $output.CreatedVolume
            }
            catch
            {
                $progressPreference = "Continue"

                $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId `
                                                 -ErrorMessage $null `
                                                 -ErrorCategory $_.CategoryInfo.Category `
                                                 -Exception $_.Exception `
                                                 -TargetObject $_.TargetObject
            }
            finally
            {
                if (-not $asjob)
                {
                    Write-Progress -Activity "New-Volume" -Completed -Status "3/3"
                }
            }

            if ($errorObject)
            {
                $psCmdlet.WriteError($errorObject)
            }
            else
            {
                $volume
            }
        }

        if ($asjob)
        {
            $p = $true

            if ($PoolInstance)
            {
                Start-Job -Name CreateVolume -ScriptBlock $poolblock -ArgumentList @($CimSession, $p, $PoolInstance, $arguments)
            }
            elseif ($DiskInstance)
            {
                Start-Job -Name CreateVolume -ScriptBlock $diskblock -ArgumentList @($CimSession, $p, $DiskInstance, $arguments)
            }
        }
        else
        {
            if ($pscmdlet.ShouldProcess($info, $resources.warning, $info))
            {
                if ($PoolInstance)
                {
                    &$poolblock $CimSession $p $PoolInstance $arguments
                }
                elseif ($DiskInstance)
                {
                    &$diskblock $CimSession $p $DiskInstance $arguments
                }
            }
        }
    }
}


function Get-StorageExtendedStatus
{
    [CmdletBinding()]
    Param
    (
        #### -------------------- Parameter sets -------------------------------------####

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageJob")]
        [Parameter(
            ParameterSetName  = 'ByStorageJob',
            Mandatory         = $true,
            ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageJob,

        #### -------------------- Powershell parameters ------------------------------####

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession
    )

    Process
    {
        if (-not $CimSession)
        {
             $CimSession = New-CimSession
        }

        ## Gather the instance objects

        switch ($psCmdlet.ParameterSetName)
        {
            "ByStorageJob"  { $inputObject = $StorageJob;  break; }
        }

        $extendedStatus = $null

        try
        {
            $output = Invoke-CimMethod -MethodName "GetExtendedStatus" -InputObject $inputObject -CimSession $CimSession -ErrorAction Stop
            $extendedStatus = $output.ExtendedStatus
        }
        catch
        {
            $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId `
                                             -ErrorMessage $null `
                                             -ErrorCategory $_.CategoryInfo.Category `
                                             -Exception $_.Exception `
                                             -TargetObject $_.TargetObject

            $psCmdlet.WriteError($errorObject)
        }

        $extendedStatus
    }
}

function Get-StorageAdvancedProperty
{
    [CmdletBinding()]
    Param
    (
        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_PhysicalDisk")]
        [Parameter(
            ParameterSetName  = 'ByPhysicalDisk',
            Mandatory         = $true,
            ValueFromPipeline = $true)]
        $PhysicalDisk
    )

    Process
    {
        if ( $PhysicalDisk ) {

            $isDeviceCacheEnabled = $null
            $isPowerProtected     = $null

            $deviceCacheOutput    = Invoke-CimMethod -MethodName IsDeviceCacheEnabled -InputObject $PhysicalDisk -ErrorAction SilentlyContinue
            $powerProtectedOutput = Invoke-CimMethod -MethodName IsPowerProtected     -InputObject $PhysicalDisk -ErrorAction SilentlyContinue

            # DeviceCache error handling
            if ( $deviceCacheOutput -and $deviceCacheOutput.ReturnValue -ne 0 ) {

                Write-Warning "Retrieving IsDeviceCacheEnabled failed with ErrorCode $( $deviceCacheOutput.ReturnValue )."
            }
            else {
                $isDeviceCacheEnabled = $deviceCacheOutput.IsDeviceCacheEnabled
            }

            # PowerProtected error handling
            if ( $powerProtectedOutput -and $powerProtectedOutput.ReturnValue -ne 0 ) {

                Write-Warning "Retrieving IsPowerProtected failed with ErrorCode $( $powerProtectedOutput.ReturnValue )."
            }
            else {
                $isPowerProtected = $powerProtectedOutput.IsPowerProtected
            }

            $customDrive = [pscustomobject]@{
                PhysicalDisk         = $PhysicalDisk;
                IsPowerProtected     = $isPowerProtected;
                IsDeviceCacheEnabled = $isDeviceCacheEnabled;
            }

            $customDrive.PSObject.TypeNames.Insert( 0, "Microsoft.Windows.StorageManagement.PhysicalDiskAdvancedProperties" )

            $customDrive
        }
    }
}

function Get-StorageHealthReport
{
    [CmdletBinding()]
    Param
    (
        [CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageObject")]
        [Parameter(
            Mandatory         = $true,
            ValueFromPipeline = $true,
            ParameterSetName  = "InputObject")]
        $InputObject,

        [Int32]
        $Count = 1,

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession,

        # Provided for compatibility with CDXML cmdlets, not actually used.
        [Int32]
        $ThrottleLimit,

        [Switch]
        $AsJob
    )

    Begin{
        Write-Warning "This cmdlet is deprecated and may not be available in the future. Use Get-ClusterPerformanceHistory instead." 
    }

    Process
    {
        $info = $resources.info
        $p = $null
        $methodName = "GetReport"

        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        if ($InputObject.CimClass.CimClassName -ne "MSFT_StoragesubSystem" -and
            $InputObject.CimClass.CimClassName -ne "MSFT_StorageNode" -and
            $InputObject.CimClass.CimClassName -ne "MSFT_Volume" -and
            $InputObject.CimClass.CimClassName -ne "MSFT_FileShare")
        {
            $errorObject = CreateErrorRecord -ErrorId "InvalidArgument" `
                                             -ErrorMessage "The input object must be a StorageSubSystem, StorageNode, Volume or FileShare" `
                                             -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidArgument) `
                                             -Exception $null `
                                             -TargetObject $null

            $psCmdlet.WriteError($errorObject)
            return
        }

        if($InputObject.CimSystemProperties.ClassName.Equals("MSFT_FileShare"))
        {
            $InputObject = $InputObject | Get-Volume -CimSession $CimSession -ErrorAction Stop
        }

        $storageSubSystem = $InputObject | Get-StorageSubSystem -CimSession $CimSession -ErrorAction Stop

        $StorageHealth = Get-CimAssociatedInstance -ResultClassName MSFT_StorageHealth -InputObject $storageSubSystem -CimSession $CimSession -ErrorAction Stop

        # Would use a closure here, but jobs are run in their own session state.
        $block = {
            param($session, $ns, $asjob, $mn, $sh, $io, $co)

            # Start-Job serializes/deserializes the CimSession,
            # which means it shows up here as having type Deserialized.CimSession.
            # Must recreate or cast the object in order to pass it to Get-CimInstance.
            if ($asjob)
            {
                $session = $session | New-CimSession
            }

            Invoke-CimMethod -CimSession $session -InputObject $sh -MethodName $mn -Arguments @{TargetObject=$io;Count=[uint32]$co} -Confirm:$false
        }

        if ($asjob)
        {
            $p = $true
            Start-Job -ScriptBlock $block -ArgumentList @($CimSession, $StorageNamespace, $p, $methodName, $StorageHealth, $InputObject, $Count)
        }
        else
        {
            if ($pscmdlet.ShouldProcess($info, $resources.warning, $info))
            {
                &$block $CimSession $StorageNamespace $p $methodName $StorageHealth $InputObject $Count
            }
        }
    }
}

function Get-StorageHealthSetting
{
    [CmdletBinding()]
    Param
    (
        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageSubSystem")]
        [Parameter(
            Mandatory         = $true,
            ValueFromPipeline = $true,
            ParameterSetName  = "InputObject")]
        $InputObject,

        [String]
        $Name,

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession,

        # Provided for compatibility with CDXML cmdlets, not actually used.
        [Int32]
        $ThrottleLimit,

        [Switch]
        $AsJob
    )

    Begin{}

    Process
    {
        $info = $resources.info
        $p = $null

        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        $StorageHealth = $InputObject | Get-StorageHealth -CimSession $CimSession -ErrorAction Stop

        # Would use a closure here, but jobs are run in their own session state.
        $block = {
            param($session, $asjob, $sh, $na)

            # Start-Job serializes/deserializes the CimSession,
            # which means it shows up here as having type Deserialized.CimSession.
            # Must recreate or cast the object in order to pass it to Get-CimInstance.
            if ($asjob)
            {
                # We need to load the module or it fails with CMDLET Not Found
                import-module Storage\StorageHealth.cdxml
                $session = $session | New-CimSession
            }

            $sh | Get-StorageHealthSettingInternal -Name $na -CimSession $session
        }

        if ($asjob)
        {
            $p = $true
            Start-Job -ScriptBlock $block -ArgumentList @($CimSession, $p, $StorageHealth, $Name)
        }
        else
        {
            if ($pscmdlet.ShouldProcess($info, $resources.warning, $info))
            {
                &$block $CimSession $p $StorageHealth $Name
            }
        }
    }
}

function Set-StorageHealthSetting
{
    [CmdletBinding()]
    Param
    (
        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageSubSystem")]
        [Parameter(
            Mandatory         = $true,
            ValueFromPipeline = $true,
            ParameterSetName  = "InputObject")]
        $InputObject,

        [String]
        [Parameter(
            Mandatory = $true)]
        $Name,

        [String]
        [Parameter(
            Mandatory = $true)]
        $Value,

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession,

        # Provided for compatibility with CDXML cmdlets, not actually used.
        [Int32]
        $ThrottleLimit,

        [Switch]
        $AsJob
    )

    Begin{}

    Process
    {
        $info = $resources.info
        $p = $null

        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        $StorageHealth = $InputObject | Get-StorageHealth -CimSession $CimSession -ErrorAction Stop

        # Would use a closure here, but jobs are run in their own session state.
        $block = {
            param($session, $asjob, $sh, $na, $va)

            # Start-Job serializes/deserializes the CimSession,
            # which means it shows up here as having type Deserialized.CimSession.
            # Must recreate or cast the object in order to pass it to Get-CimInstance.
            if ($asjob)
            {
                # We need to load the module or it fails with CMDLET Not Found
                import-module Storage\StorageHealth.cdxml
                $session = $session | New-CimSession
            }

            $sh | Set-StorageHealthSettingInternal -Name $na -Value $va -CimSession $session
        }

        if ($asjob)
        {
            $p = $true
            Start-Job -ScriptBlock $block -ArgumentList @($CimSession, $p, $StorageHealth, $Name, $Value)
        }
        else
        {
            if ($pscmdlet.ShouldProcess($info, $resources.warning, $info))
            {
                &$block $CimSession $p $StorageHealth $Name $Value
            }
        }
    }
}

function Remove-StorageHealthSetting
{
    [CmdletBinding()]
    Param
    (
        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageSubSystem")]
        [Parameter(
            Mandatory         = $true,
            ValueFromPipeline = $true,
            ParameterSetName  = "InputObject")]
        $InputObject,

        [String]
        [Parameter(
            Mandatory = $true)]
        $Name,

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession,

        # Provided for compatibility with CDXML cmdlets, not actually used.
        [Int32]
        $ThrottleLimit,

        [Switch]
        $AsJob
    )

    Begin{}

    Process
    {
        $info = $resources.info
        $p = $null

        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        $StorageHealth = $InputObject | Get-StorageHealth -CimSession $CimSession -ErrorAction Stop

        # Would use a closure here, but jobs are run in their own session state.
        $block = {
            param($session, $asjob, $sh, $na)

            # Start-Job serializes/deserializes the CimSession,
            # which means it shows up here as having type Deserialized.CimSession.
            # Must recreate or cast the object in order to pass it to Get-CimInstance.
            if ($asjob)
            {
                import-module Storage\StorageHealth.cdxml
                $session = $session | New-CimSession
            }

            $sh | Remove-StorageHealthSettingInternal -Name $na -CimSession $session
        }

        if ($asjob)
        {
            $p = $true
            Start-Job -ScriptBlock $block -ArgumentList @($CimSession, $p, $StorageHealth, $Name)
        }
        else
        {
            if ($pscmdlet.ShouldProcess($info, $resources.warning, $info))
            {
                &$block $CimSession $p $StorageHealth $Name
            }
        }
    }
}

function Debug-StorageSubSystem
{
    [CmdletBinding( DefaultParameterSetName="ByFriendlyName" )]
    Param
    (
        [string[]]
        [Parameter(
            Mandatory         = $true,
            ValueFromPipeline = $true,
            ParameterSetName  = "ByFriendlyName",
            Position          = 0)]
        $StorageSubSystemFriendlyName,

        [string[]]
        [Parameter(
            Mandatory         = $true,
            ValueFromPipeline = $true,
            ParameterSetName  = "ByUniqueId")]
        $StorageSubSystemUniqueId,

        [string[]]
        [Parameter(
            Mandatory         = $true,
            ValueFromPipeline = $true,
            ParameterSetName  = "ByName")]
        $StorageSubSystemName,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageSubSystem")]
        [Parameter(
            Mandatory         = $true,
            ValueFromPipeline = $true,
            ParameterSetName  = "InputObject")]
        $InputObject,

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession,

        # Provided for compatibility with CDXML cmdlets, not actually used.
        [Int32]
        $ThrottleLimit,

        [Switch]
        $AsJob
    )

    Begin{
        Write-Warning "This cmdlet is deprecated and may not be available in the future. Use Get-HealthFault instead." 
    }

    Process
    {
        $info = $resources.info
        $p = $null

        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        switch ($PsCmdlet.ParameterSetName)
        {
            "ByName"         { $InputObject = Get-StorageSubSystem -Name $StorageSubSystemName -CimSession $CimSession; break; }
            "ByUniqueId"     { $InputObject = Get-StorageSubSystem -UniqueId $StorageSubSystemUniqueId -CimSession $CimSession; break; }
            "ByFriendlyName" { $InputObject = Get-StorageSubSystem -FriendlyName $StorageSubSystemFriendlyName -CimSession $CimSession; break; }
        }

        # Would use a closure here, but jobs are run in their own session state.
        $block = {
            param($session, $asjob, $io)

            # Start-Job serializes/deserializes the CimSession,
            # which means it shows up here as having type Deserialized.CimSession.
            # Must recreate or cast the object in order to pass it to Get-CimInstance.
            if ($asjob)
            {
                $session = $session | New-CimSession
            }

            $result = @()
            $output = Invoke-CimMethod -MethodName Diagnose -InputObject $io -CimSession $session
            foreach($i in $output){$result += $i.ItemValue}
            $result | Sort-Object -Property PerceivedSeverity
        }

        if ($asjob)
        {
            $p = $true
            Start-Job -ScriptBlock $block -ArgumentList @($CimSession, $p, $InputObject)
        }
        else
        {
            if ($pscmdlet.ShouldProcess($info, $resources.warning, $info))
            {
                &$block $CimSession $p $InputObject
            }
        }
    }
}

function Debug-FileShare
{
    [CmdletBinding( DefaultParameterSetName="ByName" )]
    Param
    (
        [string[]]
        [Parameter(
            Mandatory         = $true,
            ValueFromPipeline = $true,
            ParameterSetName  = "ByName",
            Position          = 0 )]
        $Name,

        [string[]]
        [Parameter(
            Mandatory         = $true,
            ValueFromPipeline = $true,
            ParameterSetName  = "ByUniqueId")]
        $UniqueId,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_FileShare")]
        [Parameter(
            Mandatory         = $true,
            ValueFromPipeline = $true,
            ParameterSetName  = "InputObject")]
        $InputObject,

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession,

        # Provided for compatibility with CDXML cmdlets, not actually used.
        [Int32]
        $ThrottleLimit,

        [Switch]
        $AsJob
    )

    Begin{
        Write-Warning "This cmdlet is deprecated and may not be available in the future. Use Get-HealthFault instead." 
    }

    Process
    {
        $info = $resources.info
        $p = $null

        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        switch ($PsCmdlet.ParameterSetName)
        {
            "ByName"     { $InputObject = Get-FileShare -Name $Name -CimSession $CimSession -ErrorAction stop; break; }
            "ByUniqueId" { $InputObject = Get-FileShare -UniqueId $UniqueId -CimSession $CimSession -ErrorAction stop; break; }
        }

        # Would use a closure here, but jobs are run in their own session state.
        $block = {
            param($session, $asjob, $io)

            # Start-Job serializes/deserializes the CimSession,
            # which means it shows up here as having type Deserialized.CimSession.
            # Must recreate or cast the object in order to pass it to Get-CimInstance.
            if ($asjob)
            {
                $session = $session | New-CimSession
            }

            $result = @()
            $output = Invoke-CimMethod -MethodName Diagnose -InputObject $io -CimSession $session
            foreach($i in $output){$result += $i.ItemValue}
            $result | Sort-Object -Property PerceivedSeverity
        }

        if ($asjob)
        {
            $p = $true
            Start-Job -ScriptBlock $block -ArgumentList @($CimSession, $p, $InputObject)
        }
        else
        {
            if ($pscmdlet.ShouldProcess($info, $resources.warning, $info))
            {
                &$block $CimSession $p $InputObject
            }
        }
    }
}

function Debug-Volume
{
    [CmdletBinding( DefaultParameterSetName="ByDriveLetter" )]
    Param
    (
        [char[]]
        [Parameter(
            Mandatory         = $true,
            ValueFromPipeline = $true,
            ParameterSetName  = "ByDriveLetter",
            Position          = 0)]
        $DriveLetter,

        [string[]]
        [Parameter(
            Mandatory         = $true,
            ValueFromPipeline = $true,
            ParameterSetName  = "ById")]
        $ObjectId,

        [string[]]
        [Parameter(
            Mandatory         = $true,
            ValueFromPipeline = $true,
            ParameterSetName  = "ByPaths")]
        $Path,

        [string[]]
        [Parameter(
            Mandatory         = $true,
            ValueFromPipeline = $true,
            ParameterSetName  = "ByLabel")]
        [Alias("FriendlyName")]
        $FileSystemLabel,

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_Volume")]
        [Parameter(
            Mandatory         = $true,
            ValueFromPipeline = $true,
            ParameterSetName  = "InputObject")]
        $InputObject,

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession,

        # Provided for compatibility with CDXML cmdlets, not actually used.
        [Int32]
        $ThrottleLimit,

        [Switch]
        $AsJob
    )

    Begin{
        Write-Warning "This cmdlet is deprecated and may not be available in the future. Use Get-HealthFault instead." 
    }

    Process
    {
        $info = $resources.info
        $p = $null

        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        switch ($PsCmdlet.ParameterSetName)
        {
            "ByDriveLetter" { $io = Get-Volume -CimSession $CimSession -DriveLetter $DriveLetter -ErrorAction stop; break;}
            "ById"          { $io = Get-Volume -CimSession $CimSession -ObjectId $ObjectId -ErrorAction stop; break; }
            "ByPaths"       { $io = Get-Volume -CimSession $CimSession -Path $Path -ErrorAction stop; break;}
            "ByLabel"       { $io = Get-Volume -CimSession $CimSession -FileSystemLabel $FileSystemLabel -ErrorAction stop; break;}
        }

        # Would use a closure here, but jobs are run in their own session state.
        $block = {
            param($session, $asjob, $io)

            # Start-Job serializes/deserializes the CimSession,
            # which means it shows up here as having type Deserialized.CimSession.
            # Must recreate or cast the object in order to pass it to Get-CimInstance.
            if ($asjob)
            {
                $session = $session | New-CimSession
            }

            $result = @()
            $output = Invoke-CimMethod -MethodName Diagnose -InputObject $io -CimSession $session
            foreach($i in $output){$result += $i.ItemValue}
            $result | Sort-Object -Property PerceivedSeverity
        }

        if ($asjob)
        {
            $p = $true
            Start-Job -ScriptBlock $block -ArgumentList @($CimSession, $p, $InputObject)
        }
        else
        {
            if ($pscmdlet.ShouldProcess($info, $resources.warning, $info))
            {
                &$block $CimSession $p $InputObject
            }
        }
    }
}

function Enable-StorageMaintenanceMode
{
    [CmdletBinding()]
    Param
    (
        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageFaultDomain")]
        [Parameter(
            Mandatory         = $true,
            ValueFromPipeline = $true,
            ParameterSetName  = "InputObject")]
         $InputObject,

        [Switch]
        $IgnoreDetachedVirtualDisks,

        [System.Nullable[Bool]]
        $ValidateVirtualDisksHealthy,

        [String]
        $Model,

        [String]
        $Manufacturer,

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession,

        [System.Nullable[Bool]]
        $ValidateMaintenanceMode,

        [Switch]
        $AsJob
    )

    Begin
    {
        [flagsattribute()]
        Enum ValidationFlags
        {
            None                  = 0
            VirtualDisksHealthy   = 1
        }
    }

    Process
    {
        $info = $resources.info
        $p = $null

        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        if ($InputObject.CimClass.CimClassName -ne "MSFT_PhysicalDisk" -and
            $InputObject.CimClass.CimClassName -ne "MSFT_StorageEnclosure" -and
            $InputObject.CimClass.CimClassName -ne "MSFT_StorageScaleUnit")
        {
            $errorObject = CreateErrorRecord -ErrorId "InvalidArgument" `
                                             -ErrorMessage "The input object must be a PhysicalDisk, StorageEnclosure or StorageScaleUnit" `
                                             -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidArgument) `
                                             -Exception $null `
                                             -TargetObject $null

            $psCmdlet.WriteError($errorObject)
            return
        }

        $arguments = @{}

        $arguments.Add("EnableMaintenanceMode", $true)
        $arguments.Add("IgnoreDetachedVirtualDisks", $IgnoreDetachedVirtualDisks.IsPresent)
        if ($InputObject.CimClass.CimClassName -ne "MSFT_PhysicalDisk")
        {
            if ($PSBoundParameters.ContainsKey("Model"))
            {
                $arguments.Add("Model", $Model)
            }

            if ($PSBoundParameters.ContainsKey("Manufacturer"))
            {
                $arguments.Add("Manufacturer", $Manufacturer)
            }
        }

        $includeValidationFlags = $false
        if ($ValidateVirtualDisksHealthy -ne $null)
        {
            $includeValidationFlags = $true
            if($ValidateVirtualDisksHealthy)
            {
                $validationFlags += [ValidationFlags]::VirtualDisksHealthy
            }
            else
            {
                $validationFlags += [ValidationFlags]::None
            }
        }

        if ($includeValidationFlags)
        {
          $arguments.Add("ValidationFlags", $validationFlags)
        }

        if ($ValidateMaintenanceMode -ne $null)
        {
            $arguments.Add("ValidateMaintenanceMode", $ValidateMaintenanceMode)
        }

        $storageSubSystem = $InputObject | Get-StorageSubSystem -CimSession $CimSession

        if ($storageSubSystem.Model -eq "Clustered Windows Storage")
        {
            $storageHealth = Get-CimAssociatedInstance -ResultClassName MSFT_StorageHealth -InputObject $storageSubSystem -CimSession $CimSession

            $arguments.Add("TargetObject", $InputObject)
        }
        else
        {
            if ($arguments["ValidationFlags"] -ne $null)
            {
                $errorObject = CreateErrorRecord -ErrorId "InvalidArgument" `
                                                 -ErrorMessage "The parameter 'ValidateVirtualDisksHealthy' is not supported in this subsystem. Invoke again without this parameter." `
                                                 -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidArgument) `
                                                 -Exception $null `
                                                 -TargetObject $null

                $psCmdlet.WriteError($errorObject)
                return
            }
        }

        # Would use a closure here, but jobs are run in their own session state.
        $block = {
            param($session, $asjob, $io, $arg, $ss, $sh)

            # Start-Job serializes/deserializes the CimSession,
            # which means it shows up here as having type Deserialized.CimSession.
            # Must recreate or cast the object in order to pass it to Get-CimInstance.
            if ($asjob)
            {
                $session = $session | New-CimSession
            }

            if ($ss.Model -eq "Clustered Windows Storage")
            {
                Invoke-CimMethod -MethodName "Maintenance" -Arguments $arg -InputObject $sh -CimSession $session | Out-Null
            }
            else
            {
                Invoke-CimMethod -MethodName "Maintenance" -Arguments $arg -InputObject $io -CimSession $session | Out-Null
            }
        }

        if ($asjob)
        {
            $p = $true
            Start-Job -ScriptBlock $block -ArgumentList @($CimSession, $p, $InputObject, $arguments, $storageSubSystem, $storageHealth)
        }
        else
        {
            if ($pscmdlet.ShouldProcess($info, $resources.warning, $info))
            {
                &$block $CimSession $p $InputObject $arguments $storageSubSystem $storageHealth
            }
        }
    }
}

function Disable-StorageMaintenanceMode
{
    [CmdletBinding()]
    Param
    (
        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageFaultDomain")]
        [Parameter(
            Mandatory         = $true,
            ValueFromPipeline = $true,
            ParameterSetName  = "InputObject")]
        $InputObject,

        [string]
        $Model,

        [string]
        $Manufacturer,

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession,

        [Switch]
        $AsJob
    )

    Begin{}

    Process
    {
        $info = $resources.info
        $p = $null

        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        if ($InputObject.CimClass.CimClassName -ne "MSFT_PhysicalDisk" -and
            $InputObject.CimClass.CimClassName -ne "MSFT_StorageEnclosure" -and
            $InputObject.CimClass.CimClassName -ne "MSFT_StorageScaleUnit")
        {
            $errorObject = CreateErrorRecord -ErrorId "InvalidArgument" `
                                             -ErrorMessage "The input object must be a PhysicalDisk, StorageEnclosure or StorageScaleUnit" `
                                             -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidArgument) `
                                             -Exception $null `
                                             -TargetObject $null

            $psCmdlet.WriteError($errorObject)
            return
        }

        $arguments = @{}

        $arguments.Add("EnableMaintenanceMode", $false)

        if($InputObject.CimClass.CimClassName -ne "MSFT_PhysicalDisk")
        {
            if ($PSBoundParameters.ContainsKey("Model"))
            {
                $arguments.Add("Model", $Model)
            }

            if ($PSBoundParameters.ContainsKey("Manufacturer"))
            {
                $arguments.Add("Manufacturer", $Manufacturer)
            }
        }

        $storageSubSystem = $InputObject | Get-StorageSubSystem -CimSession $CimSession

        if ($storageSubSystem.Model -eq "Clustered Windows Storage")
        {
            $arguments.Add("TargetObject", $InputObject)

            $storageHealth = Get-CimAssociatedInstance -ResultClassName MSFT_StorageHealth -InputObject $storageSubSystem -CimSession $CimSession
        }

        # Would use a closure here, but jobs are run in their own session state.
        $block = {
            param($session, $asjob, $io, $arg, $ss, $sh)

            # Start-Job serializes/deserializes the CimSession,
            # which means it shows up here as having type Deserialized.CimSession.
            # Must recreate or cast the object in order to pass it to Get-CimInstance.
            if ($asjob)
            {
                $session = $session | New-CimSession
            }

            if ($ss.Model -eq "Clustered Windows Storage")
            {
                Invoke-CimMethod -MethodName "Maintenance" -Arguments $arg -InputObject $sh -CimSession $session | Out-Null
            }
            else
            {
                Invoke-CimMethod -MethodName "Maintenance" -Arguments $arg -InputObject $io -CimSession $session | Out-Null
            }
        }

        if ($asjob)
        {
            $p = $true
            Start-Job -ScriptBlock $block -ArgumentList @($CimSession, $p, $InputObject, $arguments, $storageSubSystem, $storageHealth)
        }
        else
        {
            if ($pscmdlet.ShouldProcess($info, $resources.warning, $info))
            {
                &$block $CimSession $p $InputObject $arguments $storageSubSystem $storageHealth
            }
        }
    }
}


function Get-StorageDiagnosticInfo
{
    [CmdletBinding()]
    Param
    (
        #### -------------------- Parameter sets -------------------------------------####

        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageSubSystem")]
        [Parameter(
            ParameterSetName  = "ByStorageSubSystem",
            ValueFromPipeline = $true,
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $InputObject,

        [String]
        [Parameter(
            ParameterSetName  = "ByStorageSubSystemFriendlyName",
            Mandatory         = $true,
            Position          = 0)]
        [ValidateNotNullOrEmpty()]
        $StorageSubSystemFriendlyName,

        [String]
        [Parameter(
            ParameterSetName  = "ByStorageSubSystemName",
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        $StorageSubSystemName,

        [String]
        [Parameter(
            ParameterSetName  = "ByStorageSubSystemUniqueId",
            Mandatory         = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias("StorageSubSystemId")]
        $StorageSubSystemUniqueId,

        #### -------------------- Method parameters ----------------------------------####

        [String]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystem',
            Mandatory        = $true)]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystemFriendlyName',
            Mandatory        = $true)]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystemName',
            Mandatory        = $true)]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystemUniqueId',
            Mandatory        = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias("Path")]
        $DestinationPath,

        [UInt32]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystemFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystemName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystemUniqueId',
            Mandatory        = $false)]
        $TimeSpan,

        [String]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystemFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystemName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystemUniqueId',
            Mandatory        = $false)]
        $ActivityId,

        [System.Management.Automation.SwitchParameter]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystemFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystemName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystemUniqueId',
            Mandatory        = $false)]
        $ExcludeOperationalLog = $false,

        [System.Management.Automation.SwitchParameter]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystemFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystemName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystemUniqueId',
            Mandatory        = $false)]
        $ExcludeDiagnosticLog = $false,

        [System.Management.Automation.SwitchParameter]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystem',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystemFriendlyName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystemName',
            Mandatory        = $false)]
        [Parameter(
            ParameterSetName = 'ByStorageSubSystemUniqueId',
            Mandatory        = $false)]
        $IncludeLiveDump = $false,

        #### -------------------- Powershell parameters ------------------------------####

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession,

        # Provided for compatibility with CDXML cmdlets, not actually used.
        [Int32]
        $ThrottleLimit,

        [Switch]
        $AsJob
    )

    Process
    {
        $info = $resources.info
        $p = $null

        if (-not $CimSession)
        {
            $CimSession = New-CimSession
        }

        if (-not $AsJob)
        {
            Write-Progress -Activity "Get-StorageDiagnosticInfo" -Id 0 -PercentComplete 0 -CurrentOperation "Validating parameters" -Status "0/2"
        }

        ## Gather the instance object

        switch ($psCmdlet.ParameterSetName)
        {
            "ByStorageSubSystem"             { $SubsystemInstance = $InputObject; break; }
            "ByStorageSubSystemFriendlyName" { $SubsystemInstance = Get-StorageSubsystem -FriendlyName $StorageSubSystemFriendlyName -CimSession $CimSession -ErrorAction Stop; break; }
            "ByStorageSubSystemName"         { $SubsystemInstance = Get-StorageSubsystem -Name $StorageSubSystemName -CimSession $CimSession -ErrorAction Stop; break; }
            "ByStorageSubSystemUniqueId"     { $SubsystemInstance = Get-StorageSubsystem -UniqueId $StorageSubSystemUniqueId -CimSession $CimSession -ErrorAction Stop; break; }
        }

        # Would use a closure here, but jobs are run in their own session state.
        $methodblock = {
            param($session, $asjob, $uniqueId, $arguments)

            # Start-Job serializes/deserializes the CimSession,
            # which means it shows up here as having type Deserialized.CimSession.
            # Must recreate or cast the object in order to pass it to Get-CimInstance.
            if ($asjob)
            {
                # We need to load the module or it fails with CMDLET Not Found
                import-module Storage\StorageSubSystem.cdxml

                $session = $session | New-CimSession
            }

            $parameters = @{}

            $parameters.Add("CimSession", $session )
            $parameters.Add("StorageSubSystemUniqueId", $uniqueId )
            $parameters.Add("DestinationPath", $arguments["DestinationPath"] )

            if ($arguments.ContainsKey("TimeSpan"))
            {
                $parameters.Add("TimeSpan", $arguments["TimeSpan"])
            }

            if ($arguments.ContainsKey("ActivityId"))
            {
                $parameters.Add("ActivityId", $arguments["ActivityId"])
            }

            if ($arguments.ContainsKey("ExcludeOperationalLog"))
            {
                $parameters.Add("ExcludeOperationalLog", $arguments["ExcludeOperationalLog"])
            }

            if ($arguments.ContainsKey("ExcludeDiagnosticLog"))
            {
                $parameters.Add("ExcludeDiagnosticLog", $arguments["ExcludeDiagnosticLog"])
            }

            if ($arguments.ContainsKey("IncludeLiveDump"))
            {
                $parameters.Add("IncludeLiveDump", $arguments["IncludeLiveDump"])
            }

            if (-not $asjob)
            {
                Write-Progress -Activity "Get-StorageDiagnosticInfo" -Id 0 -PercentComplete 10 -CurrentOperation "Gathering storage diagnostic logs" -Status "1/2"
            }

            Get-StorageDiagnosticInfoInternal @parameters

            if (-not $asjob)
            {
                Write-Progress -Activity "Get-StorageDiagnosticInfo" -Completed -Status "2/2"
            }
        }

        if ($asjob)
        {
            $p = $true

            Start-Job -Name GetDiagnosticInfo `
                      -ScriptBlock $methodblock `
                      -ArgumentList @($CimSession, $p, $SubsystemInstance.UniqueId, $PSBoundParameters)
        }
        else
        {
            &$methodblock $CimSession $p $SubsystemInstance.UniqueId $PSBoundParameters
        }
    }
}

function Remove-StorageHealthIntent
{
    [CmdletBinding(
        SupportsShouldProcess=$true,
        ConfirmImpact="High"
    )]
    Param
    (
        [Microsoft.Management.Infrastructure.CimInstance]
        [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StorageObject")]
        [Parameter(
            Mandatory         = $true,
            ValueFromPipeline = $true,
            ParameterSetName  = "InputObject")]
         $InputObject,

        [Microsoft.Management.Infrastructure.CimSession]
        $CimSession,

        [Switch]
        $AsJob
    )

    Process
    {
        $info = $resources.info

        if (-not $CimSession)
        {
            $CimSession = New-CimSession -Verbose:$false
        }

        $arguments = @{}

        $storageSubSystem = $InputObject | Get-StorageSubSystem -CimSession $CimSession

        if ($storageSubSystem.Model -eq "Clustered Windows Storage")
        {
            $storageHealth = Get-CimAssociatedInstance -ResultClassName MSFT_StorageHealth -InputObject $storageSubSystem -CimSession $CimSession

            $arguments.Add("TargetObject", $InputObject)
        }
        else
        {
            $errorObject = CreateErrorRecord -ErrorId "InvalidSubsystem" `
                                             -ErrorMessage "This method is not supported for this subsystem." `
                                             -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidArgument) `
                                             -Exception $null `
                                             -TargetObject $null

            $psCmdlet.WriteError($errorObject)
            return
        }

        # Would use a closure here, but jobs are run in their own session state.
        $block = {
            param($session, $asjob, $io, $arg, $sh)

            # Start-Job serializes/deserializes the CimSession,
            # which means it shows up here as having type Deserialized.CimSession.
            # Must recreate or cast the object in order to pass it to Get-CimInstance.
            if ($asjob)
            {
                $session = $session | New-CimSession -Verbose:$false
            }

            Invoke-CimMethod -MethodName "RemoveIntent" -Arguments $arg -InputObject $sh -CimSession $session | Out-Null
        }

        if ($asjob)
        {
            Start-Job -ScriptBlock $block -ArgumentList @($CimSession, $true, $InputObject, $arguments, $storageHealth)
        }
        else
        {
            if ($pscmdlet.ShouldProcess($info, $resources.warning, $info))
            {
                &$block $CimSession $false $InputObject $arguments $storageHealth
            }
        }
    }
}

New-Alias Get-PhysicalDiskSNV Get-PhysicalDiskStorageNodeView
New-Alias Get-DiskSNV Get-DiskStorageNodeView
New-Alias Get-StorageEnclosureSNV Get-StorageEnclosureStorageNodeView

Export-ModuleMember -Alias * -Function *