????
Current Path : C:/Windows/System32/WindowsPowerShell/v1.0/Modules/StorageBusCache/ |
Current File : C:/Windows/System32/WindowsPowerShell/v1.0/Modules/StorageBusCache/StorageBusCache.psm1 |
############################ # # Copyright (c) Microsoft Corporation # # Abstract: # SBL script cmdlets # ############################ using namespace Microsoft.Windows.Storage Set-StrictMode -Version 3.0 $clusbfltRegKeyPath = "HKLM:\SYSTEM\CurrentControlSet\Services\clusbflt\Parameters" $systemFlagsValueName = "SystemFlags" $busTypeValueName = "SupportedBusTypes" $cacheRegKeyPath = Join-Path $clusbfltRegKeyPath "StorageBusCache" # # Build relative device value table for cache/capacity identification # # Assign values to bus/media types s.t. bus * media type produces the relative cache/capacity # relationships hdd<ssd & sata<sas<nvme<scm # $mediaTypes = @( "HDD", "SSD", "SCM" ) $busTypes = @( "SAS", "SATA", "NVME", "SCM" ) $typeValue = @{} $v = 0 $mediaTypes |% { $typeValue[$_] = $v; $v++ } # media types hdd = 0 so its locked low $v = 1 $busTypes |% { $typeValue[$_] = $v; $v++ } # bus types 1-based, thus same-type SSD/HDD are ranked # # Build supported bustype mask # $supportedBusTypeFlags = 0 foreach ($busType in @( [Core.BusType]::Sas, [Core.BusType]::Sata, [Core.BusType]::Nvme, [Core.BusType]::SCM)) { $supportedBusTypeFlags = $supportedBusTypeFlags -bor (1 -shl $busType) } 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 } function LogVerbose { [CmdletBinding()] Param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Message ) Write-Verbose ((Get-Date).GetDateTimeFormats('s')[0] + ": $Message") } function IsS2DEnabled { $clusRegKeyPath = "HKLM:\Cluster" if (Test-Path $clusRegKeyPath) { $clusRegKey = Get-ItemProperty $clusRegKeyPath -Name S2DEnabled -ErrorAction SilentlyContinue if ($clusRegKey.S2DEnabled -eq 1) { return $true } } return $false } function EnforceNoS2D { $return = IsS2DEnabled if ($return) { $errorObject = CreateErrorRecord -ErrorId "Cluster S2D is enabled" ` -ErrorMessage "StorageBusCache Cmdlets cannot be run on a cluster S2D setup" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) } return $return } function EnforceTargetPresent { if (-not (Test-Path $clusbfltRegKeyPath)) { $errorObject = CreateErrorRecord -ErrorId "NotFound" ` -ErrorMessage "Storage Bus target driver registry key not found" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return $false } $true } function GetGuidFromObjectId { Param( [string] [Parameter( ValueFromPipeline = $true, Mandatory = $true)] $ObjectId ) process { if ($ObjectId -match "PD:({.*})") { $matches[1] } } } function GetDevicePath { [CmdletBinding()] Param( [Parameter(ParameterSetName = "ByGuid")] [ValidateNotNullOrEmpty()] [string] $Guid, [Parameter(ParameterSetName = "ByObjectId")] [ValidateNotNullOrEmpty()] [string] $ObjectId ) switch($PSCmdlet.ParameterSetName) { "ByGuid" { $guid = $Guid; break; } "ByObjectId" { $guid = GetGuidFromObjectId $ObjectId; break; } } "\\?\Disk" + $guid } function IsDeviceDisabled { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Guid ) # # Test whether a device is disabled/blocked (e.g. Disable-StorageBusDisk) # If a device is disabled the target will release it and therefore it is not queryable on # the storage bus; as a result, we must look directly at the persisted attributes in the # registry. # $devicePath = Join-Path $clusbfltRegKeyPath "Disks\$Guid" if (Test-Path $devicePath) { $deviceKey = Get-ItemProperty $devicePath if ($null -ne (Get-Member -Name Attributes -MemberType NoteProperty -InputObject $deviceKey) -and 0 -ne ($deviceKey.Attributes -band [StorageBusCache.DiskAttribute]::Disabled)) { # Device is disabled (blocklisted) return $true } } $false } function GetAllDisks { Param () $deviceTable = @{} # Get disk drives visible to PnP, skipping storage spaces virtual disks, if any $pnpDrives = Get-PnpDevice -Class DiskDrive | ? FriendlyName -ne "Microsoft Storage Space Device" $systemDisks = Get-Disk | ? { $_.IsSystem -eq $true -or $_.IsBoot -eq $true } $systemDisks = $systemDisks | Get-PhysicalDisk foreach ($drive in $pnpDrives) { # Obtain device guid $devicePathPrefix = "\\?\" + $($drive.DeviceID -replace "\\",'#') $devicePath = $devicePathPrefix + "#{" + [Core.DeviceMgmt]::GUID_DEVINTERFACE_DISK + "}" $disk = [StorageBusCache.DeviceMgmt]::GetStorageBusDisk($devicePath) if ($disk.Guid -eq [GUID]::Empty.ToString()) { $hiddenDevicePath = $devicePathPrefix + "#{" + [Core.DeviceMgmt]::GUID_DEVINTERFACE_HIDDEN_DISK + "}" $disk = [StorageBusCache.DeviceMgmt]::GetStorageBusDisk($hiddenDevicePath) if ($disk.Guid -eq [GUID]::Empty.ToString()) { continue } } # Check if this is boot or system drive $sysDisk = $systemDisks | ? ObjectId -match $disk.Guid if ($sysDisk) { # Skipping continue } # Add to device table if ($disk.Guid -notin $deviceTable.Keys) { $deviceTable.Add($disk.Guid, $disk) } } return $deviceTable } function Set-StorageBusCache { [CmdletBinding()] Param ( [System.UInt64] [Parameter( Mandatory = $false)] $CacheMetadataReserveBytes, [Microsoft.Windows.Storage.StorageBusCache.CacheModeSet] [Parameter( Mandatory = $false)] $CacheModeHDD, [Microsoft.Windows.Storage.StorageBusCache.CacheModeSet] [Parameter( Mandatory = $false)] $CacheModeSSD, [System.UInt32] [Parameter( Mandatory = $false)] [ValidateSet(8, 16, 32, 64)] $CachePageSizeKBytes, [System.UInt32] [Parameter( Mandatory = $false)] [ValidateRange(5, 90)] $SharedCachePercent, [Microsoft.Windows.Storage.StorageBusCache.ProvisionMode] [Parameter( Mandatory = $false)] $ProvisionMode ) # Get current parameters and dynamically build cache parameter list - there is a 1:1 correspondance of parameter/property/reg value $curParam = Get-StorageBusCache $cacheParam = (Get-Member -InputObject $curParam |? MemberType -eq Property).Name $updatedParam = @() if (-not (Test-Path $cacheRegKeyPath)) { $null = New-Item $cacheRegKeyPath -Force } # Modification guardrails if ($PSBoundParameters.ContainsKey("ProvisionMode") -and $curParam.Enabled) { $errorObject = CreateErrorRecord -ErrorId "InvalidOperation" ` -ErrorMessage "Provision mode may not be changed after storage bus is enabled" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } if ($PSBoundParameters.ContainsKey("CacheMetadataReserveBytes") -and $curParam.ProvisionMode -ne ([StorageBusCache.ProvisionMode]::Cache)) { $errorObject = CreateErrorRecord -ErrorId "InvalidOperation" ` -ErrorMessage "CacheMetadataReserveBytes is not applicable to current provisioning mode ($($curParam.ProvisionMode))" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } if ($PSBoundParameters.ContainsKey("SharedCachePercent") -and $curParam.ProvisionMode -ne ([StorageBusCache.ProvisionMode]::Shared)) { $errorObject = CreateErrorRecord -ErrorId "InvalidOperation" ` -ErrorMessage "SharedCachePercent is not applicable to current provisioning mode ($($curParam.ProvisionMode))" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } foreach ($boundParam in $PSBoundParameters.Keys) { # Only update changed cache parameters if ($boundParam -in $cacheParam -and $curParam.$boundParam -ne $PSBoundParameters[$boundParam]) { switch ($PSBoundParameters[$boundParam].GetTypeCode()) { ([TypeCode]::UInt32) { $t = [Microsoft.Win32.RegistryValueKind]::DWord } ([TypeCode]::UInt64) { $t = [Microsoft.Win32.RegistryValueKind]::QWord } default { $t = [Microsoft.Win32.RegistryValueKind]::String } } $updatedParam += $boundParam Set-ItemProperty -Path $cacheRegKeyPath -Name $boundParam -Value $PSBoundParameters[$boundParam] -Type $t } } if ($updatedParam.Count) { LogVerbose ("Updated {0}" -f $($updatedParam -join ", ")) # React to changes in CacheModeHDD/SSD # Per device type, enumerate bindings and set appropriate binding attributes } } function GetRegValue { [CmdletBinding()] Param ( [Parameter()] [object] $Container, [Parameter()] [string] $KeyName, # Parameter help description [Parameter()] [string] $ValueName, # Parameter help description [Parameter()] [object] $DefaultValue ) if (Test-Path $KeyName) { # Get-ItemPropertyValue and Get-ItemProperty will emit errors for values that do not exist, # so unpack it directly from the key object. It will be $null if no values are present, and # Get-Member also cleanly returns $null if DNE. $k = Get-ItemProperty -Path $KeyName if ($null -ne $k -and ($k | Get-Member -Name $ValueName)) { $v = $k.$ValueName } else { $v = $DefaultValue } } else { $v = $DefaultValue } # Place in container or return direct if ($null -ne $Container) { $Container.$ValueName = $v } else { $v } } function Get-StorageBusCache { [CmdletBinding()] Param ( ) [StorageBusCache.StorageBusCacheParameters] $p = [StorageBusCache.StorageBusCacheParameters]::new() GetRegValue $p $cacheRegKeyPath 'ProvisionMode' ([StorageBusCache.ProvisionMode]::Shared) GetRegValue $p $cacheRegKeyPath 'CacheMetadataReserveBytes' 32GB GetRegValue $p $cacheRegKeyPath 'SharedCachePercent' 15 GetRegValue $p $cacheRegKeyPath 'CacheModeHDD' ([StorageBusCache.CacheMode]::ReadWrite) GetRegValue $p $cacheRegKeyPath 'CacheModeSSD' ([StorageBusCache.CacheMode]::WriteOnly) GetRegValue $p $cacheRegKeyPath 'CachePageSizeKBytes' 16 # Cache is enabled if the supported bus types have been armed/set non zero. if (0 -ne (GetRegValue $null $clusbfltRegKeyPath $busTypeValueName 0)) { $p.Enabled = $true } else { $p.Enabled = $false } $p } function Get-StorageBusTargetDevice { # Deconstruction alias for the BFlt instance # Filter converts the empty case from $null to an actual absence of objects $i = Get-StorageBusTargetDeviceInstance if ($null -ne $i -and $null -ne $i.PathInfo) { $i.PathInfo } } function Get-StorageBusTargetCacheStore { # Deconstruction alias for the BFlt instance # Filter converts the empty case from $null to an actual absence of objects $i = Get-StorageBusTargetCacheStoresInstance if ($null -ne $i -and $null -ne $i.CacheStoreInfo) { $i.CacheStoreInfo } } # State transitions for validating arrival/departure of devices from the Storage Bus enum SurfaceStates { Undefined = 0 SurfaceToTarget = 1 SurfaceToClient = 2 SurfaceToProvider = 3 SurfaceCacheStore = 4 CheckPartition = 100 } function WaitForDeviceSurface { [CmdletBinding()] Param ( [Parameter( Mandatory = $true)] [string[]] $DeviceGuid, # Timeout (s) [Parameter()] [int] $Timeout = 300, # Progress Activity (null -> no progress reporting) [Parameter( Mandatory = $true)] [string] $Activity, # Base percentage for reporting progress [Parameter( Mandatory = $true)] [int] $BasePercentage, # Phase percentage for reporting progress [Parameter( Mandatory = $true)] [int] $PhasePercentage ) $timeStart = Get-Date # Verify progress through the surfacing process. This is done through a basic state machine tracking the asynchronous process. # arrival at target (target device) - Get-StorageBusTargetDevice # arrival at client (client device) - Get-StorageBusClientDevice # arrival at spaceport/provider (physical disk) - Get-PhysicalDisk (unscoped) $n = 0 $nt = $DeviceGuid.Count # Empty state data and all devices validating target # Device data provides a scratch pad for a given device $stateData = @{} $deviceState = @{} $deviceData = @{} foreach ($device in $DeviceGuid) { $deviceState[$device] = [SurfaceStates]::SurfaceToTarget } LogVerbose "Starting wait for device surfacing ($nt devices)" while ($deviceState.Count) { foreach ($device in @($deviceState.Keys)) { do { $curState = $deviceState[$device] $data = $stateData[$curState] LogVerbose "$device : $curState" switch ($curState) { ([SurfaceStates]::SurfaceToTarget) { # Acquire data to validate state? if ($null -eq $data) { $data = @(Get-StorageBusTargetDevice) LogVerbose "Acquired data for $_ ($($data.Count) elements)" if ($data.Count -eq 0) { break } $stateData[$_] = $data } # If device is in the target, move to client validation if ($data.DeviceGuid -contains $device) { $d = $data |? DeviceGuid -eq $device if ($d.Attributes -contains 'Orphan') { # Allow 10 seconds for orphan devices to bind, then give up # and assume this is permanent (cache device absent) $deviceData[$device] += 1 if ($deviceData[$device] -gt 10) { $deviceState.Remove($device) LogVerbose "$device : ORPHANED" $n += 1 } break } # Clean up device data if present $deviceData.Remove($device) # If the device arrives cache stores, their load is next; # else move directly to client surfacing. if ($d.Attributes -contains 'HasCachePartition') { $deviceState[$device] = [SurfaceStates]::SurfaceCacheStore } else { $deviceState[$device] = [SurfaceStates]::SurfaceToClient } LogVerbose "$device : $curState -> $($deviceState[$device])" } break } ([SurfaceStates]::SurfaceCacheStore) { # Acquire data to validate state? if ($null -eq $data) { $data = @(Get-StorageBusTargetCacheStore) LogVerbose "Acquired data for $_ ($($data.Count) elements)" if ($data.Count -eq 0) { break } $stateData[$_] = $data } # Wait for any cache stores pending load on this device if ($data |? DeviceGuid -eq $device |? Status -eq STATUS_PENDING) { break } $deviceState[$device] = [SurfaceStates]::SurfaceToClient LogVerbose "$device : $curState -> $($deviceState[$device])" break } ([SurfaceStates]::SurfaceToClient) { # Acquire data to validate state? if ($null -eq $data) { $data = @(Get-StorageBusClientDevice) LogVerbose "Acquired data for $_ ($($data.Count) elements)" if ($data.Count -eq 0) { break } $stateData[$_] = $data } # If device is in the client, move to provider validation if ($data.DeviceGuid -contains $device) { $deviceState[$device] = [SurfaceStates]::SurfaceToProvider LogVerbose "$device : $curState -> $($deviceState[$device])" } break } ([SurfaceStates]::SurfaceToProvider) { # Acquire data to validate state? if ($null -eq $data) { $data = @(Get-PhysicalDisk) LogVerbose "Acquired data for $_ ($($data.Count) elements)" if ($data.Count -eq 0) { break } $stateData[$_] = $data } # If device is in the provider, we are complete - remove it from the set requiring validation if ($data |? ObjectId -match $device) { $deviceState.Remove($device) LogVerbose "$device : SURFACED" $n += 1 } break } } # continue if device moved to a new state } while ($deviceState.Contains($device) -and $deviceState[$device] -ne $curState) } # Empty the state data so that we refresh on the next tick (if needed) $stateData.Clear() # Check timeout if not complete? if ($deviceState.Count) { $waited = [int] ((Get-Date) - $timeStart).TotalSeconds if (0 -lt $Timeout -and $waited -gt $Timeout) { $errorObject = CreateErrorRecord -ErrorId "Timeout" ` -ErrorMessage "Timed out waiting for device(s) to be surfaced: $(($deviceState.Keys |% { $_, $deviceState[$_] -join ':'}) -join ', ')" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::OperationTimeout) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) break } Write-Progress -Activity $Activity -Status "Device arrival on Storage Bus ($n of $nt))" -CurrentOperation "Waiting $($waited)s so far$(if (0 -ne $Timeout) {" ($($Timeout)s timeout)"})" -PercentComplete ($BasePercentage + $PhasePercentage*($n/$nt)) Start-Sleep -Milliseconds 500 } } Write-Progress -Activity $Activity -Status "Device arrival on Storage Bus" -CurrentOperation "Complete" -PercentComplete ($BasePercentage + $PhasePercentage*($n/$nt)) } function WaitForDeviceDeparture { [CmdletBinding()] Param ( [Parameter( Mandatory = $true)] [string[]] $DeviceGuid, # Timeout (s) [Parameter()] [int] $Timeout = 300, # Progress Activity (null -> no progress reporting) [Parameter( Mandatory = $true)] [string] $Activity, # Base percentage for reporting progress [Parameter( Mandatory = $true)] [int] $BasePercentage, # Phase percentage for reporting progress [Parameter( Mandatory = $true)] [int] $PhasePercentage ) $timeStart = Get-Date $dm = Get-WmiObject -namespace "root\wmi" ClusBfltDeviceMethods # Verify progress through the departure process. This is done through a basic state machine tracking the asynchronous process. # leaving the client (client device) - Get-StorageBusClientDevice # leaving the target (target device) - Get-StorageBusTargetDevice # entering the provider (if unpartitioned/disabled) - GetPhysicalDisk (unscoped) $n = 0 $nt = $DeviceGuid.Count # Empty state data and all devices validating client $stateData = @{} $deviceState = @{} foreach ($device in $DeviceGuid) { $deviceState[$device] = [SurfaceStates]::CheckPartition } LogVerbose "Starting wait for device departure ($nt devices)" while ($deviceState.Count) { foreach ($device in @($deviceState.Keys)) { do { $curState = $deviceState[$device] $data = $stateData[$curState] LogVerbose "$device : $curState" switch ($curState) { ([SurfaceStates]::CheckPartition) { # Check device for a capacity metadata partition - if present and the device is not disabled, it # will remain claimed by the target to guard against data partitions being mounted and diverging # from the state in the cache. The wait is complete if this is true. $devicePath = GetDevicePath -Guid $device $deviceLayout = [Core.DeviceMgmt]::GetDriveLayout($devicePath) # Any device we claimed will be GPT, but be safe about the unions. if ($deviceLayout.PartitionStyle -eq 'GPT' -and ($deviceLayout.PartitionEntry |? { $_.Gpt.PartitionName -eq 'Microsoft SBL Cache Hdd' }) -and -not (IsDeviceDisabled $device)) { $deviceState.Remove($device) LogVerbose "$device : INITIALIZED CAPACITY" $n += 1 } else { # Move to client departure validation $deviceState[$device] = [SurfaceStates]::SurfaceToClient LogVerbose "$device : $curState -> $($deviceState[$device])" } break } ([SurfaceStates]::SurfaceToClient) { # Acquire data to validate state? if ($null -eq $data) { $data = @(Get-StorageBusClientDevice) LogVerbose "Acquired data for $_ ($($data.Count) elements)" if ($data.Count -eq 0) { break } $stateData[$_] = $data } # If device has left the client, move to target departure validation if ($data.DeviceGuid -notcontains $device) { $deviceState[$device] = [SurfaceStates]::SurfaceToTarget LogVerbose "$device : $curState -> $($deviceState[$device])" } break } ([SurfaceStates]::SurfaceToTarget) { # Acquire data to validate state? if ($null -eq $data) { $data = @(Get-StorageBusTargetDevice) LogVerbose "Acquired data for $_ ($($data.Count) elements)" if ($data.Count -eq 0) { break } $stateData[$_] = $data } # If device has left the target, move to provider arrival validation if ($data.DeviceGuid -notcontains $device) { $deviceState[$device] = [SurfaceStates]::SurfaceToProvider LogVerbose "$device : $curState -> $($deviceState[$device])" } break } ([SurfaceStates]::SurfaceToProvider) { # Acquire data to validate state? if ($null -eq $data) { $data = @(Get-PhysicalDisk) LogVerbose "Acquired data for $_ ($($data.Count) elements)" if ($data.Count -eq 0) { break } $stateData[$_] = $data } # If device is in the provider, we are complete - remove it from the set requiring validation if ($data |? ObjectId -match $device) { $deviceState.Remove($device) LogVerbose "$device : SURFACED" $n += 1 } break } } # continue if device moved to a new state } while ($deviceState.Contains($device) -and $deviceState[$device] -ne $curState) } # Empty the state data so that we refresh on the next tick (if needed) $stateData.Clear() # Check timeout if not complete? if ($deviceState.Count) { $waited = [int] ((Get-Date) - $timeStart).TotalSeconds if (0 -lt $Timeout -and $waited -gt $Timeout) { $errorObject = CreateErrorRecord -ErrorId "Timeout" ` -ErrorMessage "Timed out waiting for device(s) to be depart: $(($deviceState.Keys |% { $_, $deviceState[$_] -join ':'}) -join ', ')" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::OperationTimeout) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) break } Write-Progress -Activity $Activity -Status "Device departure from Storage Bus ($n of $nt))" -CurrentOperation "Waiting $($waited)s so far$(if (0 -ne $Timeout) {" ($($Timeout)s timeout)"})" -PercentComplete ($BasePercentage + $PhasePercentage*($n/$nt)) Start-Sleep -Milliseconds 500 } } Write-Progress -Activity $Activity -Status "Device departure from Storage Bus" -CurrentOperation "Complete" -PercentComplete ($BasePercentage + $PhasePercentage*($n/$nt)) } function SetStorageBusProfile { [CmdletBinding()] Param ( [System.Boolean] [Parameter( Mandatory = $false)] $ClaimFlash = $true ) # Set Profile try { $systemFlags = 0; if ($ClaimFlash) { $systemFlags = [StorageBusCache.DeviceMgmt]::Standalone_Profile_Claim_Flash } else { $systemFlags = [StorageBusCache.DeviceMgmt]::Standalone_Profile_Default } # Set profile in memory via clusbflt control [StorageBusCache.DeviceMgmt]::SetClusBFltSystemFlags($systemFlags) | Out-Null # Set the bus types registry key in clusbflt registry if (Test-Path $clusbfltRegKeyPath) { New-ItemProperty -Path $clusbfltRegKeyPath -Name $systemFlagsValueName -Value $systemFlags -PropertyType DWord -Force | Out-Null } else { $errorObject = CreateErrorRecord -ErrorId "NotFound" ` -ErrorMessage "ClusBFlt registry key not found" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } } catch { $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId ` -ErrorMessage $null ` -ErrorCategory $_.CategoryInfo.Category ` -Exception $_.Exception ` -TargetObject $_.TargetObject $psCmdlet.WriteError($errorObject) return } } function Set-StorageBusProfile { [CmdletBinding(ConfirmImpact="High")] Param ( [System.Boolean] [Parameter( Mandatory = $false)] $ClaimFlash = $true, [System.Boolean] [Parameter( Mandatory = $false)] $AutoConfig = $true ) if (EnforceNoS2D -or -not (EnforceTargetPresent)) { return } # Set Profile SetStorageBusProfile -ClaimFlash $ClaimFlash # Enable after Setting profile Enable-StorageBusCache -AutoConfig $AutoConfig } function Enable-StorageBusCache { [CmdletBinding(ConfirmImpact="High")] Param ( # Timeout for surfacing [Parameter( Mandatory = $false)] [int] $Timeout = 300, [System.Boolean] [Parameter( Mandatory = $false)] $AutoConfig = $true ) if (EnforceNoS2D -or -not (EnforceTargetPresent)) { return } # Set the default profile if it has not been set yet (note calling the utility function, not the exposed cmdlet) if ($null -eq (Get-ItemProperty $clusbfltRegKeyPath | Get-Member -Name $systemFlagsValueName )) { SetStorageBusProfile } # Set the bus types registry key in clusbflt registry New-ItemProperty -Path $clusbfltRegKeyPath -Name $busTypeValueName -Value $supportedBusTypeFlags -Force | Out-Null # Claim disks try { Write-Progress -Activity "Enable Storage Bus Cache" -Status "Get devices" -PercentComplete 0 # Enumerate all eligible disks $devices = @(Get-StorageBusDisk) $candidateDevices = @() # 0-10 : get & candidate checks $basePercentage = 0 $phasePercentage = 10 $n = 1 $nt = $devices.Count foreach ($device in $devices) { Write-Progress -Activity "Enable Storage Bus Cache" -Status "Check candidate device ($n of $nt)" -CurrentOperation "Check device $($device.Guid)" -PercentComplete ($basePercentage + $phasePercentage*($n/$nt)) # Check if disk is clusbflt candidate $devicePath = GetDevicePath -Guid $device.Guid if (-not [StorageBusCache.DeviceMgmt]::IsClusBFltCandidate($devicePath, $supportedBusTypeFlags)) { continue } $n += 1 $candidateDevices += $device } # 10-20: reauction trigger for candidate devices $basePercentage = 10 $phasePercentage = 10 $n = 1 $nt = $candidateDevices.Count foreach ($device in $candidateDevices) { Write-Progress -Activity "Enable Storage Bus Cache" -Status "Enable device ($n of $nt)" -CurrentOperation "Claim device $($device.Guid)" -PercentComplete ($basePercentage + $phasePercentage*($n/$nt)) # Reauction the disk $devicePath = GetDevicePath -Guid $device.Guid [Core.DeviceMgmt]::ReauctionDevice($devicePath) | Out-Null $n += 1 } if ($candidateDevices.Count -eq 0) { $errorObject = CreateErrorRecord -ErrorId "ObjectNotFound" ` -ErrorMessage "No devices found to for the storage bus: $($busTypes -join ', ') supported" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } # 20-100: wait for surfacing WaitForDeviceSurface -DeviceGuid $candidateDevices.Guid -Timeout $Timeout -Activity "Enable Storage BusCache" -BasePercentage 20 -PhasePercentage 80 if ($AutoConfig) { Update-StorageBusCache } } catch { $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId ` -ErrorMessage $null ` -ErrorCategory $_.CategoryInfo.Category ` -Exception $_.Exception ` -TargetObject $_.TargetObject $psCmdlet.WriteError($errorObject) return } Write-Progress -Activity "Enable Storage Bus Cache" -Completed } function Disable-StorageBusCache { [CmdletBinding(ConfirmImpact="High")] Param ( # Timeout for departure [Parameter( Mandatory = $false)] [int] $Timeout = 300, [Parameter( Mandatory = $false)] [switch] $Force ) if (EnforceNoS2D -or -not (EnforceTargetPresent)) { return } # We should not release any bound devices unless -Force is specified if (-not $Force) { Write-Progress -Activity "Disable Storage Bus Cache" -Status "Check for existing bindings" -CurrentOperation "Get existing bindings" -PercentComplete 0 if (Get-StorageBusBinding) { $errorObject = CreateErrorRecord -ErrorId "InvalidOperation" ` -ErrorMessage "Found existing cache bindings" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } Write-Progress -Activity "Disable Storage Bus Cache" -Status "Check for existing bindings" -CurrentOperation "Complete" -PercentComplete 20 } # Clear the bus types registry key in clusbflt registry New-ItemProperty -Path $clusbfltRegKeyPath -Name $busTypeValueName -Value 0 -Force | Out-Null # Now release all devices. try { # Enumerate all eligible disks - only claimed disks? $devices = @(Get-StorageBusDisk) # 20-40 : reauction to unclaim $basePercentage = 20 $phasePercentage = 20 $n = 1 $nt = $devices.Count foreach ($device in $devices) { Write-Progress -Activity "Disable Storage Bus Cache" -Status "Disable device ($n of $nt)" -CurrentOperation "Check device $($device.Guid)" -PercentComplete ($basePercentage + $phasePercentage*($n/$nt)) $devicePath = GetDevicePath -Guid $device.Guid # Check if disk is clusbflt candidate if (-not [StorageBusCache.DeviceMgmt]::IsClusBFltCandidate($devicePath, $supportedBusTypeFlags)) { continue } Write-Progress -Activity "Disable Storage Bus Cache" -Status "Disable device ($n of $($nt)" -CurrentOperation "Release device $($device.Guid)" -PercentComplete ($basePercentage + $phasePercentage*($n/$nt)) # Reauction the disk [Core.DeviceMgmt]::ReauctionDevice($devicePath) | Out-Null $n += 1 } # 40-100: wait for departure WaitForDeviceDeparture -DeviceGuid $devices.Guid -Timeout $Timeout -Activity "Disable Storage Bus Cache" -BasePercentage 40 -PhasePercentage 60 } catch { $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId ` -ErrorMessage $null ` -ErrorCategory $_.CategoryInfo.Category ` -Exception $_.Exception ` -TargetObject $_.TargetObject $psCmdlet.WriteError($errorObject) return } Write-Progress -Activity "Disable Storage Bus Cache" -Completed } function Get-StorageBusDisk { [CmdletBinding( DefaultParameterSetName = "ByGuid")] Param ( #### -------------------- Parameter sets ------------------------------------- #### [System.String] [Parameter( ParameterSetName = 'ByGuid', Mandatory = $false)] [ValidateNotNullOrEmpty()] [Alias("Id")] $Guid, [System.UInt32] [Parameter( ParameterSetName = 'ByNumber', Mandatory = $false)] [ValidateNotNullOrEmpty()] $Number, [Microsoft.Management.Infrastructure.CimInstance] [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_PhysicalDisk")] [Parameter( ParameterSetName = 'ByPhysicalDisk', ValueFromPipeline = $true, Mandatory = $false)] [ValidateNotNullOrEmpty()] $PhysicalDisk ) $pm = Get-WmiObject -namespace "root\wmi" ClusBfltPathMethods $paths = Get-StorageBusTargetDevice # Get all disks on this system $devices = GetAllDisks $physicalDisks = Get-PhysicalDisk # If device is claimed, update its properties based on path properties foreach ($deviceGuid in $devices.Keys) { $phyDisk = $physicalDisks | ? ObjectId -Match $deviceGuid if ($phyDisk) { # Set properties based on physical disk information $devices[$deviceGuid].BusType = $phyDisk.BusType $devices[$deviceGuid].Number = $phyDisk.DeviceId } $path = @($paths | ? DeviceGuid -Match $deviceGuid) if ($path.Count -eq 0) { # Device is not claimed, but check if there is a claimed cache partition if ($phyDisk) { # If the partition is claimed, the device guid on the path $path = @($paths | ? DeviceNumber -eq $phyDisk.DeviceId) } } if ($path.Count) { # Only first is needed $path = $path[0] if (-not $phyDisk) { # Set properties based on SBL path information since # device is claimed but not exposed above SBL $devices[$deviceGuid].BusType = $path.BusType $devices[$deviceGuid].Number = $path.DeviceNumber } # The following properties are SBL specific if ($path.Attributes -notcontains 'Partition') { # Path does not correspond to cache partition from an unclaimed device $devices[$deviceGuid].IsClaimed = $true } if ($path.Attributes -contains 'Solid' -or $path.Attributes -contains 'Partition') { $devices[$deviceGuid].IsCache = $true } else { $devices[$deviceGuid].IsCache = $false } } } # Enumerate the paths based on input # Emit any matching devices directly on the pipeline so that empty v. $null is respected switch ($psCmdlet.ParameterSetName) { "ByGuid" { $devices.Values | ? Guid -match $Guid } "ByNumber" { $devices.Values | ? Number -eq $Number } "ByPhysicalDisk" { $diskGuid = $PhysicalDisk.ObjectId -Replace '.*:' -Replace '"' $devices.Values | ? Guid -match $diskGuid } } } function Get-StorageBusBinding { [CmdletBinding( DefaultParameterSetName = "ByGuid", ConfirmImpact="None" )] Param ( #### -------------------- Parameter sets -------------------------------------#### [System.String] [Parameter( ParameterSetName = 'ByGuid', ValueFromPipeline = $true, Mandatory = $false)] [Alias("Id")] $Guid, [System.UInt16] [Parameter( ParameterSetName = 'ByNumber', ValueFromPipeline = $true, Mandatory = $true)] [ValidateNotNullOrEmpty()] $Number, [Microsoft.Management.Infrastructure.CimInstance] [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_PhysicalDisk")] [Parameter( ParameterSetName = 'ByPhysicalDisk', ValueFromPipeline = $true, Mandatory = $true)] [ValidateNotNullOrEmpty()] $PhysicalDisk ) begin { # Get once and pivot out required devices as needed $disks = @(Get-StorageBusDisk) } process { switch ($psCmdlet.ParameterSetName) { "ByGuid" { # Get all bound? Iterate cache and orphan devices, else query given device. if ($Guid.Length -eq 0) { $devices = @($disks |? IsCache) Get-StorageBusTargetDevice |? { $_.PathType -eq "Engaged" -and $_.Attributes -contains "Orphan" } |% { $devices += @($disks |? Guid -eq $_.DeviceGuid) } } else { $devices = @($disks |? Guid -eq $Guid) } break } "ByNumber" { $devices = @($disks |? Number -eq $Number); break; } "ByPhysicalDisk" { $devices = @($disks |? Number -eq $PhysicalDisk.DeviceId); break; } } $pm = Get-WmiObject -namespace "root\wmi" ClusBfltPathMethods $paths = @(Get-StorageBusTargetDevice) $cs = @(Get-StorageBusTargetCacheStore) try { foreach ($device in $devices) { if ($device.IsCache) { $pathid = $pm.GetPathIdByDeviceGuid($device.Guid, [StorageBusCache.PathType]::Read_Write, 0, 0, 0) if ($pathid.PathId -eq 0) { # This could be unclaimed cache device with claimed cache partition $pathid.PathId = ($paths | ? DeviceNumber -eq $device.Number).Id } if ($pathid.PathId -ne 0 -and $null -ne $cs) { $cache = $cs | ? PathId -eq $pathid.PathId if ($cache) { $ssdBindingRecords = $pm.QuerySsdBindingRecords($pathid.PathId, $cache.Id).BindingRecords.BindingRecords foreach ($bindingRecord in $ssdBindingRecords) { if (($bindingRecord.Flags -band [StorageBusCache.CacheStoreBindingAttribute]::Enabled) -ne 0) { $capacityDevice = $disks |? Guid -eq $bindingRecord.DeviceGuid [StorageBusCache.StorageBusBinding]$storageBusBinding = [StorageBusCache.StorageBusBinding]::new() $storageBusBinding.DeviceGuid = $capacityDevice.Guid $storageBusBinding.DeviceNumber = $capacityDevice.Number $storageBusBinding.CacheDeviceGuid = $device.Guid $storageBusBinding.CacheDeviceNumber = $device.Number $storageBusBinding.CacheStoreGuid = $cache.Id $storageBusBinding.psBase.Attributes = $bindingRecord.Flags $storageBusBinding.CacheMode = GetCacheModeFromBindingAttributes $bindingRecord.Flags $storageBusBinding.IsCacheSuspended = $false if (($bindingRecord.Flags -band [StorageBusCache.CacheStoreBindingAttribute]::Disable_Write_Cache) -ne 0) { $storageBusBinding.IsCacheSuspended = $true } $storageBusBinding.DirtyByteCount = $bindingRecord.cDirtyPages * $cache.PageSize $storageBusBinding.TotalByteCount = $bindingRecord.cDirtyPages * $cache.PageSize $storageBusBinding.HotReadByteCount = $bindingRecord.cPagesL2 * $cache.PageSize # Return binding $storageBusBinding } } } } } else { $egPath = $pm.GetPathIdByDeviceGuid($device.Guid, [StorageBusCache.PathType]::Engaged, 0, 0, 0) if ($egPath.PathId -ne 0) { $hddBinding = $pm.QueryHddBinding($egPath.PathId) $cache = $cs | ? Id -eq $hddBinding.BindingInfo.CacheStoreId # If cache is present, look up the full binding information # If cache is not present, this is an orphaned capacity device bound to a removed/failed/absent cache device/store # and we will return information limited to the binding record on the capacity device if ($null -ne $cache) { $cachePath = $paths | ? Id -eq $cache.PathId $cacheDevice = $disks |? Guid -eq $cachePath.DeviceGuid if (-not $cacheDevice) { # This could be unclaimed cache device with claimed cache partition $cacheDevice = $disks |? Number -eq $cachePath.DeviceNumber } # Obtain binding record that matches binding Id $bindingRecord = $pm.QuerySsdBindingRecords($cache.PathId, $cache.Id).BindingRecords.BindingRecords | ? BindingId -eq $hddBinding.BindingInfo.BindingId } [StorageBusCache.StorageBusBinding]$storageBusBinding = [StorageBusCache.StorageBusBinding]::new() $storageBusBinding.DeviceGuid = $device.Guid $storageBusBinding.DeviceNumber = $device.Number $storageBusBinding.CacheDeviceGuid = $hddBinding.BindingInfo.CacheStoreDeviceId $storageBusBinding.CacheStoreGuid = $hddBinding.BindingInfo.CacheStoreId # Cache present, use device/binding information if ($null -ne $cache) { $storageBusBinding.CacheDeviceNumber = $cacheDevice.Number $storageBusBinding.psBase.Attributes = $bindingRecord.Flags $storageBusBinding.CacheMode = GetCacheModeFromBindingAttributes $bindingRecord.Flags $storageBusBinding.IsCacheSuspended = $false if (($bindingRecord.Flags -band [StorageBusCache.CacheStoreBindingAttribute]::Disable_Write_Cache) -ne 0) { $storageBusBinding.IsCacheSuspended = $true } $storageBusBinding.DirtyByteCount = $bindingRecord.cDirtyPages * $cache.PageSize $storageBusBinding.TotalByteCount = $bindingRecord.cDirtyPages * $cache.PageSize $storageBusBinding.HotReadByteCount = $bindingRecord.cPagesL2 * $cache.PageSize } # Cache absent, indicate orphan/unknown else { $storageBusBinding.CacheDeviceNumber = [System.UInt32]::MaxValue $storageBusBinding.psBase.Attributes = [StorageBusCache.CacheStoreBindingAttribute]::Default $storageBusBinding.CacheMode = [StorageBusCache.CacheMode]::Orphaned $storageBusBinding.IsCacheSuspended = $true # Strictly unknown - missing path information? $storageBusBinding.DirtyByteCount = 0 $storageBusBinding.TotalByteCount = 0 $storageBusBinding.HotReadByteCount = 0 } # Return binding $storageBusBinding } } } } catch { $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId ` -ErrorMessage $null ` -ErrorCategory $_.CategoryInfo.Category ` -Exception $_.Exception ` -TargetObject $_.TargetObject $psCmdlet.WriteError($errorObject) return } } } function Enable-StorageBusDisk { [CmdletBinding(ConfirmImpact="None")] Param ( #### -------------------- Parameter sets -------------------------------------#### [System.String] [Parameter( ParameterSetName = 'ByGuid', ValueFromPipeline = $true, Mandatory = $true)] [ValidateNotNullOrEmpty()] [Alias("Id")] $Guid, [System.UInt16] [Parameter( ParameterSetName = 'ByNumber', Mandatory = $true)] [ValidateNotNullOrEmpty()] $Number, [Microsoft.Management.Infrastructure.CimInstance] [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_PhysicalDisk")] [Parameter( ParameterSetName = 'ByPhysicalDisk', ValueFromPipeline = $true, Mandatory = $true)] [ValidateNotNullOrEmpty()] $PhysicalDisk ) begin { if (EnforceNoS2D) { return } $devices = @() $dm = Get-WmiObject -namespace "root\wmi" ClusBfltDeviceMethods } process { switch ($psCmdlet.ParameterSetName) { "ByGuid" { $device = Get-StorageBusDisk -Guid $Guid; break; } "ByNumber" { $device = Get-StorageBusDisk -Number $Number; break; } "ByPhysicalDisk" { $device = Get-StorageBusDisk -Guid (GetGuidFromObjectId $PhysicalDisk.ObjectId); break; } } if ($null -eq $device) { $errorObject = CreateErrorRecord -ErrorId "NotFound" ` -ErrorMessage "Device not found" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } # Filter out claimed devices if ($device.IsClaimed) { return } try { # Have clusbflt claim the drive $dm.SetDeviceAttributes($device.Guid, [StorageBusCache.DiskAttribute]::Default, [StorageBusCache.DiskAttribute]::All) # Reauction the disk $devicePath = GetDevicePath -Guid $device.Guid [Core.DeviceMgmt]::ReauctionDevice($devicePath) | Out-Null $devices += $device.Guid } catch { $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId ` -ErrorMessage $null ` -ErrorCategory $_.CategoryInfo.Category ` -Exception $_.Exception ` -TargetObject $_.TargetObject $psCmdlet.WriteError($errorObject) return } } end { try { # Wait for devices to surface if ($devices.Count) { WaitForDeviceSurface -DeviceGuid $devices -Activity "Enable Storage Bus Disk" -BasePercentage 0 -PhasePercentage 100 } } catch { $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId ` -ErrorMessage $null ` -ErrorCategory $_.CategoryInfo.Category ` -Exception $_.Exception ` -TargetObject $_.TargetObject $psCmdlet.WriteError($errorObject) return } } } function Disable-StorageBusDisk { [CmdletBinding(ConfirmImpact="High")] Param ( #### -------------------- Parameter sets -------------------------------------#### [System.String] [Parameter( ParameterSetName = 'ByGuid', ValueFromPipeline = $true, Mandatory = $true)] [ValidateNotNullOrEmpty()] [Alias("Id")] $Guid, [System.UInt16] [Parameter( ParameterSetName = 'ByNumber', Mandatory = $true)] [ValidateNotNullOrEmpty()] $Number, [Microsoft.Management.Infrastructure.CimInstance] [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_PhysicalDisk")] [Parameter( ParameterSetName = 'ByPhysicalDisk', ValueFromPipeline = $true, Mandatory = $true)] [ValidateNotNullOrEmpty()] $PhysicalDisk ) begin { if (EnforceNoS2D) { return } $devices = @() $dm = Get-WmiObject -namespace "root\wmi" ClusBfltDeviceMethods } process { switch ($psCmdlet.ParameterSetName) { "ByGuid" { $device = @(Get-StorageBusDisk -Guid $Guid); break; } "ByNumber" { $device = @(Get-StorageBusDisk -Number $Number); break; } "ByPhysicalDisk" { $device = @(Get-StorageBusDisk -Guid (GetGuidFromObjectId $PhysicalDisk.ObjectId)); break; } } if ($device.Count -eq 0) { $errorObject = CreateErrorRecord -ErrorId "NotFound" ` -ErrorMessage "Device not found" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } # Filter out unclaimed devices if (-not $device.IsClaimed) { return } if ($device.IsCache) { # Reject unbinding bound cache devices $boundDevices = Get-StorageBusBinding -Guid $device.Guid if ($boundDevices) { $errorObject = CreateErrorRecord -ErrorId "InvalidOperation" ` -ErrorMessage "Cache device is currently bound" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } } else { # Remove bindings from capacity dev Remove-StorageBusBinding -Guid $device.Guid } try { # Have clusbflt unclaim the drive $dm.SetDeviceAttributes($device.Guid, [StorageBusCache.DiskAttribute]::Disabled, [StorageBusCache.DiskAttribute]::All) # Reauction the disk $devicePath = GetDevicePath -Guid $device.Guid [Core.DeviceMgmt]::ReauctionDevice($devicePath) | Out-Null $devices += $device.Guid } catch { $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId ` -ErrorMessage $null ` -ErrorCategory $_.CategoryInfo.Category ` -Exception $_.Exception ` -TargetObject $_.TargetObject $psCmdlet.WriteError($errorObject) return } } end { try { # Wait for devices to surface if ($devices.Count) { WaitForDeviceDeparture -DeviceGuid $devices -Activity "Disable Storage Bus Disk" -BasePercentage 0 -PhasePercentage 100 } } catch { $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId ` -ErrorMessage $null ` -ErrorCategory $_.CategoryInfo.Category ` -Exception $_.Exception ` -TargetObject $_.TargetObject $psCmdlet.WriteError($errorObject) return } } } function Resume-StorageBusDisk { [CmdletBinding(ConfirmImpact="Low")] Param ( #### -------------------- Parameter sets -------------------------------------#### [System.String] [Parameter( ParameterSetName = 'ByGuid', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Alias("Id")] $Guid, [System.UInt16] [Parameter( ParameterSetName = 'ByNumber', Mandatory = $true)] [ValidateNotNullOrEmpty()] $Number, [Microsoft.Management.Infrastructure.CimInstance] [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_PhysicalDisk")] [Parameter( ParameterSetName = 'ByPhysicalDisk', ValueFromPipeline = $true, Mandatory = $true)] [ValidateNotNullOrEmpty()] $PhysicalDisk ) begin { if (EnforceNoS2D) { return } $pm = Get-WmiObject -namespace "root\wmi" ClusBfltPathMethods } process { switch ($psCmdlet.ParameterSetName) { "ByGuid" { $device = Get-StorageBusDisk -Guid $Guid; break; } "ByNumber" { $device = Get-StorageBusDisk -Number $Number; break; } "ByPhysicalDisk" { $device = Get-StorageBusDisk -Number $PhysicalDisk.DeviceId; break; } } if ($null -eq $device) { $errorObject = CreateErrorRecord -ErrorId "NotFound" ` -ErrorMessage "Device not found" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } if ($device.IsCache) { # Resume all bound capacity devices $boundDevices = Get-StorageBusBinding -Guid $device.Guid foreach ($boundDevice in $boundDevices) { Resume-StorageBusDisk -Guid $boundDevice.DeviceGuid } } else { try { $egPath = $pm.GetPathIdByDeviceGuid($device.Guid, [StorageBusCache.PathType]::Engaged, 0, 0, 0) if ($egPath.PathId -ne 0) { # Obtain the capacity device binding info $binding = $pm.QueryHddBinding($egPath.PathId).BindingInfo # Obtain the cache store $cs = Get-WmiObject -namespace "root\wmi" ClusBfltCacheStoresInformation $cache = $cs.CacheStoreInfo | ? Id -Match $binding.CacheStoreId # Obtain binding record that matches binding Id $bindingRecord = $pm.QuerySsdBindingRecords($cache.PathId, $cache.Id).BindingRecords.BindingRecords | ? BindingId -Match $binding.BindingId if ($null -ne $bindingRecord) { # Set cache binding attribute back to default $pm.SetSsdBindingAttributes($cache.PathId, [StorageBusCache.CacheStoreBindingAttribute]::Default, [StorageBusCache.CacheStoreBindingAttribute]::Disable_Write_Cache, $cache.Id, $binding.BindingId) } } } catch { $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId ` -ErrorMessage $null ` -ErrorCategory $_.CategoryInfo.Category ` -Exception $_.Exception ` -TargetObject $_.TargetObject $psCmdlet.WriteError($errorObject) return } } } } function Suspend-StorageBusDisk { [CmdletBinding(ConfirmImpact="High")] Param ( #### -------------------- Parameter sets -------------------------------------#### [System.String] [Parameter( ParameterSetName = 'ByGuid', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Alias("Id")] $Guid, [System.UInt16] [Parameter( ParameterSetName = 'ByNumber', Mandatory = $true)] [ValidateNotNullOrEmpty()] $Number, [Microsoft.Management.Infrastructure.CimInstance] [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_PhysicalDisk")] [Parameter( ParameterSetName = 'ByPhysicalDisk', ValueFromPipeline = $true, Mandatory = $true)] [ValidateNotNullOrEmpty()] $PhysicalDisk ) begin { if (EnforceNoS2D) { return } $pm = Get-WmiObject -namespace "root\wmi" ClusBfltPathMethods` } process { # Any device to suspend should be surfaced to the client side of the bus switch ($psCmdlet.ParameterSetName) { "ByGuid" { $device = Get-StorageBusClientDevice |? DeviceGuid -eq $Guid; break; } "ByNumber" { $device = Get-StorageBusClientDevice |? DeviceNumber -eq $Number; break; } "ByPhysicalDisk" { $device = Get-StorageBusClientDevice |? DeviceNumber -eq $PhysicalDisk.DeviceId; break; } } if ($null -eq $device) { $errorObject = CreateErrorRecord -ErrorId "NotFound" ` -ErrorMessage "Device not found" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } if ($device.Attributes -contains "HasCachePartition") { # First suspend all bound capacity devices $boundDevices = Get-StorageBusBinding -Guid $device.DeviceGuid foreach ($boundDevice in $boundDevices) { Suspend-StorageBusDisk -Guid $boundDevice.DeviceGuid } } else { try { $egPath = $pm.GetPathIdByDeviceGuid($device.DeviceGuid, [StorageBusCache.PathType]::Engaged, 0, 0, 0) if ($egPath.PathId -ne 0) { # Obtain the capacity device binding info $binding = $pm.QueryHddBinding($egPath.PathId).BindingInfo # Obtain the cache store $cache = Get-StorageBusTargetCacheStore | ? Id -Match $binding.CacheStoreId # Obtain binding record that matches binding Id $bindingRecord = $pm.QuerySsdBindingRecords($cache.PathId, $cache.Id).BindingRecords.BindingRecords | ? BindingId -Match $binding.BindingId if ($null -ne $bindingRecord -and ($bindingRecord.Flags -band [StorageBusCache.CacheStoreBindingAttribute]::Enabled) -ne 0) { # Disable Write cache $pm.SetSsdBindingAttributes($cache.PathId, [StorageBusCache.CacheStoreBindingAttribute]::Disable_Write_Cache, [StorageBusCache.CacheStoreBindingAttribute]::Disable_Write_Cache, $cache.Id, $binding.BindingId) # Wait for dirty page count to go down to 0 for this cache binding # This should be split out to an end clause and done in parallel for all suspended devices while ($true) { if ($bindingRecord.cDirtyPages -eq 0 -and $bindingRecord.cDirtySlots -eq 0) { break } Start-Sleep -Seconds 1 $bindingRecord = $pm.QuerySsdBindingRecords($cache.PathId, $cache.Id).BindingRecords.BindingRecords | ? BindingId -Match $binding.BindingId } } } } catch { $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId ` -ErrorMessage $null ` -ErrorCategory $_.CategoryInfo.Category ` -Exception $_.Exception ` -TargetObject $_.TargetObject $psCmdlet.WriteError($errorObject) return } } } } function New-StorageBusCacheStore { [CmdletBinding(ConfirmImpact="Low")] Param ( #### -------------------- Parameter sets -------------------------------------#### [System.String] [Parameter( ParameterSetName = 'ByGuid', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Alias('Id')] $Guid, [System.UInt32] [Parameter( ParameterSetName = 'ByNumber', Mandatory = $true)] [ValidateNotNullOrEmpty()] $Number, #### -------------------- Common parameters -------------------------------------#### [System.UInt32] [Parameter( ParameterSetName = 'ByGuid', Mandatory = $false)] [Parameter( ParameterSetName = 'ByNumber', Mandatory = $false)] [ValidateSet(8, 16, 32, 64)] [Alias('PageSize')] $CachePageSizeKBytes, [System.UInt64] [Parameter( ParameterSetName = 'ByGuid', Mandatory = $false)] [Parameter( ParameterSetName = 'ByNumber', Mandatory = $false)] [Alias('$ReserveCapacity')] $CacheMetadataReserveBytes, [System.UInt32] [Parameter( ParameterSetName = 'ByGuid', Mandatory = $false)] [Parameter( ParameterSetName = 'ByNumber', Mandatory = $false)] [ValidateRange(5, 90)] $SharedCachePercent ) if (EnforceNoS2D) { return } switch ($psCmdlet.ParameterSetName) { "ByGuid" { $cacheDevice = Get-StorageBusClientDevice |? DeviceGuid -eq $Guid; break; } "ByNumber" { $cacheDevice = Get-StorageBusClientDevice |? Number -eq $Number; break; } } # Ensure exactly one cache device is obtained if ($null -eq $cacheDevice) { $errorObject = CreateErrorRecord -ErrorId "NotFound" ` -ErrorMessage "Specified cache device not found" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } if ($cacheDevice.Attributes -notcontains "Solid") { $errorObject = CreateErrorRecord -ErrorId "InvalidArgument" ` -ErrorMessage "Specified cache device is not a solid state device" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidArgument) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } Write-Progress -Activity "Create New Cache Store" -PercentComplete 0 # Get cache parameters for autoconfiguration and populate with overrides provided on command line # Note that this does NOT persist a change, it is a common property bag to land the effective values $curParam = Get-StorageBusCache $cacheParam = (Get-Member -InputObject $curParam |? MemberType -eq Property).Name foreach ($p in $PSBoundParameters.Keys) { if ($p -in $cacheParam) { $curParam.$_ = $PSBoundParameters[$_] } } $pm = Get-WmiObject -namespace "root\wmi" ClusBfltPathMethods $dm = Get-WmiObject -namespace "root\wmi" ClusBfltDeviceMethods # Determine how much reserve we will leave based on provisioning mode: # Cache - fixed value ; Shared - percentage of capacity if ($curParam.ProvisionMode -eq [StorageBusCache.ProvisionMode]::Cache) { $reserve = $curParam.CacheMetadataReserveBytes; } else { $pd = Get-PhysicalDisk -DeviceNumber $cachedevice.DeviceNumber if ($null -eq $pd) { $errorObject = CreateErrorRecord -ErrorId "NotFound" ` -ErrorMessage "Physical disk object corresponding to storage bus device number $($cachedevice.DeviceNumber) not found" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } # Round down to nearest 128MiB - note that shared cache percent is the percent for cache, and the reserve is everything remaining (100 - sp%) $reserve = [math]::Floor((100 - $curParam.SharedCachePercent) * $pd.Size / 128MB / 100) * 128MB; } $deviceAttributes = $dm.GetDeviceAttributes($cacheDevice.DeviceGuid) try { # Create cache store $dm.SetDeviceAttributes($cacheDevice.DeviceGuid, [StorageBusCache.DiskAttribute]::Maintenance, [StorageBusCache.DiskAttribute]::All) $mmPath = $pm.GetPathIdByDeviceGuid($cacheDevice.DeviceGuid, [StorageBusCache.PathType]::Maintenance, 0, 0, 0) if ($mmPath) { # Pre-initialize the device if raw/unpartitioned $devicePath = GetDevicePath -Guid $cacheDevice.DeviceGuid $deviceLayout = [Core.DeviceMgmt]::GetDriveLayout($devicePath) if ($deviceLayout.PartitionStyle -eq [Core.PartitionStyle]::Raw) { $pm.ReInitializeDisk($mmPath.PathId, 0) } $pm.CreateSsdCacheStore($mmPath.PathId, 0, $reserve, $curParam.CachePageSizeKBytes * 1KB) } else { $errorObject = CreateErrorRecord -ErrorId "NotFound" ` -ErrorMessage "Device path not found after setting Maintenance mode" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } } catch { $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId ` -ErrorMessage $null ` -ErrorCategory $_.CategoryInfo.Category ` -Exception $_.Exception ` -TargetObject $_.TargetObject $psCmdlet.WriteError($errorObject) return } finally { $dm.SetDeviceAttributes($cacheDevice.DeviceGuid, $deviceAttributes.Attributes, [StorageBusCache.DiskAttribute]::All) } try { WaitForDeviceSurface -Activity "Create New Cache Store" -DeviceGuid $cacheDevice.DeviceGuid -BasePercentage 50 -PhasePercentage 50 } catch { $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId ` -ErrorMessage $null ` -ErrorCategory $_.CategoryInfo.Category ` -Exception $_.Exception ` -TargetObject $_.TargetObject $psCmdlet.WriteError($errorObject) return } } # Forward map of cache mode to applied binding attributes function GetBindingAttributesFromCacheMode { [CmdletBinding()] Param ( [Parameter( Mandatory = $true)] [Microsoft.Windows.Storage.StorageBusCache.CacheMode] $CacheMode, [Parameter( Mandatory = $true)] [System.Boolean] $IsSolid ) # Persistent readahead is always disabled. Replaced with VRC (as whole-system flag, not binding) in current releases. $mode = [StorageBusCache.CacheStoreBindingAttribute]::Enabled -bor [StorageBusCache.CacheStoreBindingAttribute]::Disable_Read_Ahead_Cache switch ($CacheMode) { ([StorageBusCache.CacheMode]::ReadOnly) { $mode = $mode -bor [StorageBusCache.CacheStoreBindingAttribute]::Disable_Write_Cache break } ([StorageBusCache.CacheMode]::ReadWrite) { # nothing break } ([StorageBusCache.CacheMode]::WriteOnly) { $mode = $mode -bor [StorageBusCache.CacheStoreBindingAttribute]::Disable_Read_Cache break } } # Apply delayed destage to solid state devices with write cache if ($IsSolid -and -not ($mode -band [StorageBusCache.CacheStoreBindingAttribute]::Disable_Write_Cache)) { $mode = $mode -bor [StorageBusCache.CacheStoreBindingAttribute]::Delay_Destage } $mode } # Reverse map of binding attributes to displayed cache mode function GetCacheModeFromBindingAttributes { [CmdletBinding()] param ( [Parameter()] [Microsoft.Windows.Storage.StorageBusCache.CacheStoreBindingAttribute] $CacheStoreBindingAttributes ) if (-not ($CacheStoreBindingAttributes -band [StorageBusCache.CacheStoreBindingAttribute]::Enabled)) { return [StorageBusCache.CacheMode]::Disabled } else { if ($CacheStoreBindingAttributes -band [StorageBusCache.CacheStoreBindingAttribute]::Disable_Write_Cache) { # we do not expect write and read to be disabled without actually being disabled, but for the sake of it: if (-not ($CacheStoreBindingAttributes -band [StorageBusCache.CacheStoreBindingAttribute]::Disable_Read_Cache)) { return [StorageBusCache.CacheMode]::ReadOnly } } elseif ($CacheStoreBindingAttributes -band [StorageBusCache.CacheStoreBindingAttribute]::Disable_Read_Cache) { return [StorageBusCache.CacheMode]::WriteOnly } return [StorageBusCache.CacheMode]::ReadWrite } } function New-StorageBusBinding { [CmdletBinding(ConfirmImpact="Low")] Param ( #### -------------------- Parameter sets -------------------------------------#### [System.String] [Parameter( ParameterSetName = 'ByGuid', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Alias('CacheId')] $CacheGuid, [System.String] [Parameter( ParameterSetName = 'ByGuid', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Alias('CapacityId')] $CapacityGuid, [System.String] [Parameter( ParameterSetName = 'ByNumber', Mandatory = $true)] [ValidateNotNullOrEmpty()] $CacheNumber, [System.String] [Parameter( ParameterSetName = 'ByNumber', Mandatory = $true)] [ValidateNotNullOrEmpty()] $CapacityNumber ) if (EnforceNoS2D) { return } switch ($psCmdlet.ParameterSetName) { "ByGuid" { $cacheDevice = Get-StorageBusClientDevice |? DeviceGuid -eq $CacheGuid; $capacityDevice = Get-StorageBusClientDevice |? DeviceGuid -eq $CapacityGuid; break; } "ByNumber" { $cacheDevice = Get-StorageBusClientDevice |? Number -eq $CacheNumber; $capacityDevice = Get-StorageBusClientDevice |? Number -eq $CapacityNumber; break; } } # Ensure exactly one cache device is obtained if ($null -eq $cacheDevice) { $errorObject = CreateErrorRecord -ErrorId "NotFound" ` -ErrorMessage "Specified cache device not found" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } if ($cacheDevice.Attributes -notcontains "Solid") { $errorObject = CreateErrorRecord -ErrorId "InvalidArgument" ` -ErrorMessage "Specified cache device is not a solid state device" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidArgument) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } # Ensure exactly one capacity device is obtained if (-not $capacityDevice) { $errorObject = CreateErrorRecord -ErrorId "NotFound" ` -ErrorMessage "Specified capacity device not found" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } # Get cache parameters for autoconfiguration and populate with overrides provided on command line # Note that this does NOT persist a change, it is a common property bag to land the effective values $curParam = Get-StorageBusCache $cacheParam = (Get-Member -InputObject $curParam |? MemberType -eq Property).Name foreach ($p in $PSBoundParameters.Keys) { if ($p -in $cacheParam) { $curParam.$_ = $PSBoundParameters[$_] } } $dm = Get-WmiObject -namespace "root\wmi" ClusBfltDeviceMethods $pm = Get-WmiObject -namespace "root\wmi" ClusBfltPathMethods $cs = Get-StorageBusTargetCacheStore $paths = Get-StorageBusTargetDevice $deviceAttributes = $dm.GetDeviceAttributes($capacityDevice.DeviceGuid) try { # Obtain the cache store $cache = $cs | ? DeviceGuid -Match $cacheDevice.DeviceGuid if ($null -eq $cache) { # This could be unclaimed cache device with claimed cache partition $cachePath = $paths | ? DeviceNumber -eq $cacheDevice.Number if ($cachePath) { $cache = $cs | ? DeviceGuid -Match $cachePath.DeviceGuid } } if ($null -eq $cache) { # No cache store found so create cache store New-StorageBusCacheStore -Guid $cacheDevice.DeviceGuid # Obtain the cache store $cs = Get-StorageBusTargetCacheStore $cache = $cs | ? DeviceGuid -Match $cacheDevice.DeviceGuid if (-not $cache) { $errorObject = CreateErrorRecord -ErrorId "NotFound" ` -ErrorMessage "No cache store found on cache device " + $cacheDevice.DeviceGuid ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } } # Check if the capacity device is already bound $egPath = $pm.GetPathIdByDeviceGuid($capacityDevice.DeviceGuid, [StorageBusCache.PathType]::Engaged, 0, 0, 0) if ($egPath.PathId -eq 0) { # Bind Capacity device to cache store $dm.SetDeviceAttributes($capacityDevice.DeviceGuid, [StorageBusCache.DiskAttribute]::Maintenance, [StorageBusCache.DiskAttribute]::All) $mmPath = $pm.GetPathIdByDeviceGuid($capacityDevice.DeviceGuid, [StorageBusCache.PathType]::Maintenance, 0, 0, 0) if ($mmPath.PathId -ne 0) { # Determine cache attributes to apply based on capacity device type $capacityPath = @($paths |? DeviceGuid -eq $capacityDevice.DeviceGuid)[0] if ($capacityPath.Attributes -contains 'Solid') { $cacheAttributes = GetBindingAttributesFromCacheMode $curParam.CacheModeSSD $true } else { $cacheAttributes = GetBindingAttributesFromCacheMode $curParam.CacheModeHDD $false } $pm.PrepareHddForCache($mmPath.PathId, 0) $pm.BindHddToCacheStore($mmPath.PathId, $cacheAttributes, $cache.Id) } else { $errorObject = CreateErrorRecord -ErrorId "NotFound" ` -ErrorMessage "Device path not found after setting Maintenance mode" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } } else { $errorObject = CreateErrorRecord -ErrorId "InvalidOperation" ` -ErrorMessage "Capacity device is already bound" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } } catch { $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId ` -ErrorMessage $null ` -ErrorCategory $_.CategoryInfo.Category ` -Exception $_.Exception ` -TargetObject $_.TargetObject $psCmdlet.WriteError($errorObject) return } finally { $dm.SetDeviceAttributes($capacityDevice.DeviceGuid, $deviceAttributes.Attributes, [StorageBusCache.DiskAttribute]::All) } try { WaitForDeviceSurface -DeviceGuid $capacityDevice.DeviceGuid -Activity "Surfacing newly bound device" -BasePercentage 50 -PhasePercentage 50 } catch { $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId ` -ErrorMessage $null ` -ErrorCategory $_.CategoryInfo.Category ` -Exception $_.Exception ` -TargetObject $_.TargetObject $psCmdlet.WriteError($errorObject) return } } function Remove-StorageBusBinding { [CmdletBinding(ConfirmImpact="High")] Param ( #### -------------------- Parameter sets -------------------------------------#### [System.String] [Parameter( ParameterSetName = 'ByGuid', ValueFromPipeline = $true, Mandatory = $true)] [ValidateNotNullOrEmpty()] [Alias("Id")] $Guid, [System.UInt16] [Parameter( ParameterSetName = 'ByNumber', ValueFromPipeline = $true, Mandatory = $true)] [ValidateNotNullOrEmpty()] $Number, [Microsoft.Management.Infrastructure.CimInstance] [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_PhysicalDisk")] [Parameter( ParameterSetName = 'ByPhysicalDisk', ValueFromPipeline = $true, Mandatory = $true)] [ValidateNotNullOrEmpty()] $PhysicalDisk, [Microsoft.Windows.Storage.StorageBusCache.StorageBusBinding] [Parameter( ParameterSetName = 'ByBinding', ValueFromPipeline = $true, Mandatory = $true)] [ValidateNotNullOrEmpty()] $Binding ) begin { if (EnforceNoS2D) { return } $devices = @() } process { switch ($psCmdlet.ParameterSetName) { "ByGuid" { $device = Get-StorageBusDisk -Guid $Guid; break; } "ByNumber" { $device = Get-StorageBusDisk -Number $Number; break; } "ByPhysicalDisk" { $device = Get-StorageBusDisk -Guid (GetGuidFromObjectId $PhysicalDisk.ObjectId); break; } "ByBinding" { $device = Get-StorageBusDisk -Guid $Binding.DeviceGuid; break } } # Filter out unclaimed devices if (-not $device.IsClaimed) { return } $dm = Get-WmiObject -namespace "root\wmi" ClusBfltDeviceMethods $pm = Get-WmiObject -namespace "root\wmi" ClusBfltPathMethods if ($device.IsCache) { # First unbind all bound capacity devices $boundDevices = Get-StorageBusBinding -Guid $device.Guid foreach ($boundDevice in $boundDevices) { Remove-StorageBusBinding -Guid $boundDevice.DeviceGuid } } else { try { $deviceAttributes = $dm.GetDeviceAttributes($device.Guid) $egPath = $pm.GetPathIdByDeviceGuid($device.Guid, [StorageBusCache.PathType]::Engaged, 0, 0, 0) if ($egPath.PathId -ne 0) { # Disable write cache and drain IOs Suspend-StorageBusDisk -Guid $device.Guid # Unbind HDD $dm.SetDeviceAttributes($device.Guid, [StorageBusCache.DiskAttribute]::Maintenance, [StorageBusCache.DiskAttribute]::All) $mmPath = $pm.GetPathIdByDeviceGuid($device.Guid, [StorageBusCache.PathType]::Maintenance, 0, 0, 0) if ($mmPath.PathId -ne 0) { $pm.UnBindHdd($mmPath.PathId, 0) $devices += $device.Guid } else { $errorObject = CreateErrorRecord -ErrorId "NotFound" ` -ErrorMessage "Device path not found after setting Maintenance mode" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } } } catch { $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId ` -ErrorMessage $null ` -ErrorCategory $_.CategoryInfo.Category ` -Exception $_.Exception ` -TargetObject $_.TargetObject $psCmdlet.WriteError($errorObject) return } finally { $dm.SetDeviceAttributes($device.Guid, $deviceAttributes.Attributes, [StorageBusCache.DiskAttribute]::All) } } } end { try { # Wait for devices to surface if ($devices.Count) { WaitForDeviceSurface -DeviceGuid $devices -Activity "Surfacing unbound device" -BasePercentage 50 -PhasePercentage 50 } } catch { $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId ` -ErrorMessage $null ` -ErrorCategory $_.CategoryInfo.Category ` -Exception $_.Exception ` -TargetObject $_.TargetObject $psCmdlet.WriteError($errorObject) return } } } function Clear-StorageBusDisk { [CmdletBinding(ConfirmImpact="High")] Param ( #### -------------------- Parameter sets -------------------------------------#### [System.String] [Parameter( ParameterSetName = 'ByGuid', ValueFromPipeline = $true, Mandatory = $true)] [ValidateNotNullOrEmpty()] [Alias("Id")] $Guid, [System.UInt16] [Parameter( ParameterSetName = 'ByNumber', ValueFromPipeline = $true, Mandatory = $true)] [ValidateNotNullOrEmpty()] $Number, [Microsoft.Management.Infrastructure.CimInstance] [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_PhysicalDisk")] [Parameter( ParameterSetName = 'ByPhysicalDisk', ValueFromPipeline = $true, Mandatory = $true)] [ValidateNotNullOrEmpty()] $PhysicalDisk ) if (EnforceNoS2D) { return } switch ($psCmdlet.ParameterSetName) { "ByGuid" { $devices = Get-StorageBusDisk -Guid $Guid; break; } "ByNumber" { $devices = Get-StorageBusDisk -Number $Number; break; } "ByPhysicalDisk" { $devices = Get-StorageBusDisk -Number $PhysicalDisk.DeviceId; break; } } # Filter out unclaimed devices $devices = $devices | ? IsClaimed -eq $true $dm = Get-WmiObject -namespace "root\wmi" ClusBfltDeviceMethods $pm = Get-WmiObject -namespace "root\wmi" ClusBfltPathMethods foreach ($device in $devices) { $deviceAttributes = $dm.GetDeviceAttributes($device.Guid) try { $dm.SetDeviceAttributes($device.Guid, [StorageBusCache.DiskAttribute]::Maintenance, [StorageBusCache.DiskAttribute]::All) $mmPath = $pm.GetPathIdByDeviceGuid($device.Guid, [StorageBusCache.PathType]::Maintenance, 0, 0, 0) if ($mmPath.PathId -ne 0) { # Remove all cache partitions on the disk $devicePath = GetDevicePath -Guid $device.Guid [StorageBusCache.DeviceMgmt]::CleanupSblCachePartitions($devicePath) | Out-Null } else { $errorObject = CreateErrorRecord -ErrorId "NotFound" ` -ErrorMessage "Device path not found after setting Maintenance mode" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } } catch { $errorObject = CreateErrorRecord -ErrorId $_.FullyQualifiedErrorId ` -ErrorMessage $null ` -ErrorCategory $_.CategoryInfo.Category ` -Exception $_.Exception ` -TargetObject $_.TargetObject $psCmdlet.WriteError($errorObject) return } finally { $dm.SetDeviceAttributes($device.Guid, $deviceAttributes.Attributes, [StorageBusCache.DiskAttribute]::All) } } } function ClassifyPhysicalDisk { [CmdletBinding()] param( [Microsoft.Management.Infrastructure.CimInstance] [PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_PhysicalDisk")] [Parameter( ValueFromPipeline = $true, Mandatory = $false)] [ValidateNotNullOrEmpty()] $PhysicalDisk ) begin { $disk = @() } process { # Assign relative value for each device by bus/mediatype # Should account for a set cachedevicemodel $disk += $PhysicalDisk | Add-Member -Force -NotePropertyName SbValue -NotePropertyValue ($typeValue[$_.BusType] * $typeValue[$_.MediaType]) -PassThru } end { # Group to identify cache/capacity devices and slice into first/best and rest ($best, $rest) = $disk | Group-Object SbValue | Sort-Object @{ Expression = { [int]$_.Name}} -Descending # If there is only one group then there are no identified cache devices if ($null -eq $rest) { return $null, $best.Group } # Return cache and capacity elements # Note that there may be more than one capacity/rest group; return deconstructs the groups $best.Group, $rest.Group } } function Update-StorageBusCache { [CmdletBinding()] Param ( [switch] $NoBind, [switch] $NoPool, [switch] $NoCacheUsage, [switch] $NoTiers ) if (EnforceNoS2D) { return } $curParam = Get-StorageBusCache # check for bus enabled if (-not $curParam.Enabled) { $errorObject = CreateErrorRecord -ErrorId "NotEnabled" ` -ErrorMessage "Storage Bus is not enabled" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::NotEnabled) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } $subSystem = Get-StorageSubSystem -Model 'Windows Storage' if ($null -eq $subSystem) { $errorObject = CreateErrorRecord -ErrorId "NotFound" ` -ErrorMessage "The Windows storage subsystem is not available" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } # Get client devices and corresponding physicaldisk # Note this autoexcludes disabled devices $clientDisk = @(Get-StorageBusClientDevice |? DeviceType -eq Disk) $physicalDisk = @($subSystem | Get-PhysicalDisk) $sbusPhysicalDisk = @($physicalDisk |? DeviceId -in $clientDisk.Number) # Classify physicaldisk into cache/capacity $cacheDisk, $capacityDisk = $sbusPhysicalDisk | ClassifyPhysicalDisk if ($null -eq $cacheDisk) { $errorObject = CreateErrorRecord -ErrorId "NotFound" ` -ErrorMessage "Storage Bus does not have identifiable cache devices" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::ObjectNotFound) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } # Look for existing pools using these physicaldisk $pools = @($sbusPhysicalDisk | Get-StoragePool |? IsPrimordial -eq $false | Sort-Object -Unique -Property ObjectId) switch ($pools.Count) { 0 { $pool = $null; break } 1 { $pool = $pools[0]; break } default { $errorObject = CreateErrorRecord -ErrorId "InvalidOperation" ` -ErrorMessage "Storage Bus contains more than one pool, autoconfiguration of pool not possible" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } } # If there are client devices which are neither hybrid (cached) nor have cache partitions # (are cache devices), attempt to bind. Need to handle orphan, which will only be at the target. if (-not $NoBind -and ($clientDisk |? { $_.Attributes -notcontains 'Hybrid' -and $_.Attributes -notcontains 'HasCachePartition'})) { # Build list of unbound capacity devices $binding = @(Get-StorageBusBinding) if ($binding.Count) { $unboundPhysicalDisk = @($capacityDisk |? DeviceId -notin $binding.DeviceNumber) } else { $unboundPhysicalDisk = $capacityDisk } # Build table of binding counts. Initialize with all cache disks at zero, then update with # any existing bindings. This is used for binding and any required cache initialization. $bindCount = @{} $cacheDisk.ObjectId | GetGuidFromObjectId |% { $bindCount[$_] = 0 } foreach ($b in $binding) { $bindCount[$b.CacheDeviceGuid] += 1 } # Now if there are actually unbound capacity devices, bind them. if ($unboundPhysicalDisk.Count) { # Build ordered list of cache devices to bind to $bindOrder = @($bindCount.Keys | Sort-Object @{ Expression = {$bindCount[$_]}}) # Now bind unbound capacity devices starting at the first candidate cache device $nextCache = 0 foreach ($ubd in $unboundPhysicalDisk) { New-StorageBusBinding -CacheGuid $bindOrder[$nextCache] -CapacityGuid (GetGuidFromObjectId $ubd.ObjectId) $bindCount[$bindOrder[$nextCache]] += 1 # Loop back to first cache device if we're at the end. if (($nextCache + 1) -eq $bindOrder.Count) { $nextCache = 0 continue } # If this device now has more bound than its neighbor, move forward in the list. # Else we keep binding to the current device. if ($bindCount[$bindOrder[$nextCache]] -gt $bindCount[$bindOrder[$nextCache + 1]]) { $nextCache += 1 } } } # Now, if there are cache devices with no bound capacity devices, make sure there are cache stores on them # so that their cache reserve will be claimable by Spaces/other use. # # Note that it is strictly unlikely there are no cache stores at all at this point since we asserted that # there were both identifiable cache and capacity devices, so there must be some bindings. Be safe though. $cs = @(Get-StorageBusTargetCacheStore) foreach ($ubd in @($bindCount.Keys |? { $bindCount[$_] -eq 0 })) { $cacheDeviceGuid = (GetGuidFromObjectId $ubd.ObjectId) if ($cs.Count -eq 0 -or $cacheDeviceGuid -notin $cs.DeviceGuid) { New-StorageBusCacheStore -Guid $cacheDeviceGuid } } } # If there are poolable physicaldisks, attempt to pool. Note that binding will not have # changed this. $poolablePhysicalDisks = @($sbusPhysicalDisk |? CanPool) if (-not $NoPool -and $poolablePhysicalDisks.Count) { # Pool autoconfiguration is not possible if there are unbound/initialized disks - if this was permitted, # Spaces would claim the entire disk and make it impossible to bind later without a wipe/redo. # # Refresh list of bindings capacity devices (perhaps due to binding errors). $binding = @(Get-StorageBusBinding) if (-not $binding.Count -or ($capacityDisk |? DeviceId -notin $binding.DeviceNumber)) { $errorObject = CreateErrorRecord -ErrorId "InvalidOperation" ` -ErrorMessage "Storage Bus has unbound capacity devices, autoconfiguration of pool not possible" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } # List of uninitialized cache devices $cs = @(Get-StorageBusTargetCacheStore) if (-not $cs.Count -or ($cacheDisk |? { (GetGuidFromObjectId $_.ObjectId) -notin $cs.DeviceGuid })) { $errorObject = CreateErrorRecord -ErrorId "InvalidOperation" ` -ErrorMessage "Storage Bus has uninitialized cache devices, autoconfiguration of pool not possible" ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $null ` -TargetObject $null $psCmdlet.WriteError($errorObject) return } # Create pool? if ($null -eq $pool) { $pool = New-StoragePool -FriendlyName "Storage Bus Cache on $env:COMPUTERNAME" ` -PhysicalDisks $poolablePhysicalDisks ` -FaultDomainAwarenessDefault PhysicalDisk ` -ResiliencySettingNameDefault Simple ` -ProvisioningTypeDefault Fixed ` -StorageSubSystemUniqueId $subSystem.UniqueId } else { Add-PhysicalDisk -StoragePool $pool -PhysicalDisks $poolablePhysicalDisks } } if (-not $NoCacheUsage) { # Cache provisioning mode has minimum reserve and sets cache devices for journal-only usage. # Shared provisioning mode puts the devices in autoselect to be allocated based on tiers. if ($curParam.ProvisionMode -eq [StorageBusCache.ProvisionMode]::Cache) { $cacheDisk |? Usage -ne Journal | Set-PhysicalDisk -Usage Journal } else { $cacheDisk |? Usage -ne AutoSelect | Set-PhysicalDisk -Usage AutoSelect } } if (-not $NoTiers) { $curTiers = $pool | Get-StorageTier # Group and sort autoselect media by type relative value # This handles both cache mode (no journal/cache devices) and shared mode (everything) $mediaGroup = @($pool | Get-PhysicalDisk -Usage AutoSelect | Group-Object -Property MediaType -NoElement | Sort-Object { $typeValue[$_.Name] }) # Tiers are only defined for shared mode if there are two types of allocatable media. # Tiers cannot distinguish media if everything is a single media type. if ($curParam.ProvisionMode -eq [StorageBusCache.ProvisionMode]::Cache -or $mediaGroup.Count -gt 1) { # Lay out the <Resilience>on<Media> named tiers foreach ($mt in $mediaGroup.Name) { foreach ($rt in "Mirror", "Parity") { $tierName = "$($rt)On$($mt)" # Create tier if it does not already exist if ($null -eq ($curTiers |? FriendlyName -eq $tierName)) { # Use 1-fault resilient layouts if insufficient devices are available for 2-fault if (($rt -eq "Mirror" -and @($sbusPhysicalDisk |? MediaType -eq $mt).Count -gt 2) -or ($rt -eq "Parity" -and @($sbusPhysicalDisk |? MediaType -eq $mt).Count -gt 3)) { $fdr = 2 } else { $fdr = 1 } $null = $pool | New-StorageTier -FriendlyName $tierName -PhysicalDiskRedundancy $fdr -ResiliencySettingName $rt -MediaType $mt } } } # Lay out the role-named tiers # Lay out single or multiple media types, depending on media content # If Shared, already know there are two (SSD/HDD, SCM/SSD, etc.). Cache may have only one. $ct = $mediaGroup[0].Name if ($mediaGroup.Count -gt 1) { $pt = $mediaGroup[1].Name } else { $pt = $ct } foreach ($tierName in "Capacity", "Performance") { switch ($tierName) { "Capacity" { $mt = $ct; $rt = "Parity" } "Performance" { $mt = $pt; $rt = "Mirror" } } if ($null -eq ($curTiers |? FriendlyName -eq $tierName)) { if (($rt -eq "Mirror" -and @($sbusPhysicalDisk |? MediaType -eq $mt).Count -gt 2) -or ($rt -eq "Parity" -and @($sbusPhysicalDisk |? MediaType -eq $mt).Count -gt 3)) { $fdr = 2 } else { $fdr = 1 } $null = $pool | New-StorageTier -FriendlyName $tierName -PhysicalDiskRedundancy $fdr -ResiliencySettingName $rt -MediaType $mt } } } } }