Modules/StorageDsc.Common/StorageDsc.Common.psm1
<#
.SYNOPSIS This method is used to compare current and desired values for any DSC resource. .PARAMETER CurrentValues This is hash table of the current values that are applied to the resource. .PARAMETER DesiredValues This is a PSBoundParametersDictionary of the desired values for the resource. .PARAMETER ValuesToCheck This is a list of which properties in the desired values list should be checked. If this is empty then all values in DesiredValues are checked. #> function Test-DscParameterState { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $CurrentValues, [Parameter(Mandatory = $true)] [System.Object] $DesiredValues, [Parameter()] [System.Array] $ValuesToCheck ) $returnValue = $true if (($DesiredValues.GetType().Name -ne 'HashTable') ` -and ($DesiredValues.GetType().Name -ne 'CimInstance') ` -and ($DesiredValues.GetType().Name -ne 'PSBoundParametersDictionary')) { $errorMessage = $script:localizedData.PropertyTypeInvalidForDesiredValues -f $($DesiredValues.GetType().Name) New-InvalidArgumentException -ArgumentName 'DesiredValues' -Message $errorMessage } if (($DesiredValues.GetType().Name -eq 'CimInstance') -and ($null -eq $ValuesToCheck)) { $errorMessage = $script:localizedData.PropertyTypeInvalidForValuesToCheck New-InvalidArgumentException -ArgumentName 'ValuesToCheck' -Message $errorMessage } if (($null -eq $ValuesToCheck) -or ($ValuesToCheck.Count -lt 1)) { $keyList = $DesiredValues.Keys } else { $keyList = $ValuesToCheck } $keyList | ForEach-Object -Process { if (($_ -ne 'Verbose')) { if (($CurrentValues.ContainsKey($_) -eq $false) ` -or ($CurrentValues.$_ -ne $DesiredValues.$_) ` -or (($DesiredValues.GetType().Name -ne 'CimInstance' -and $DesiredValues.ContainsKey($_) -eq $true) -and ($null -ne $DesiredValues.$_ -and $DesiredValues.$_.GetType().IsArray))) { if ($DesiredValues.GetType().Name -eq 'HashTable' -or ` $DesiredValues.GetType().Name -eq 'PSBoundParametersDictionary') { $checkDesiredValue = $DesiredValues.ContainsKey($_) } else { # If DesiredValue is a CimInstance. $checkDesiredValue = $false if (([System.Boolean]($DesiredValues.PSObject.Properties.Name -contains $_)) -eq $true) { if ($null -ne $DesiredValues.$_) { $checkDesiredValue = $true } } } if ($checkDesiredValue) { $desiredType = $DesiredValues.$_.GetType() $fieldName = $_ if ($desiredType.IsArray -eq $true) { if (($CurrentValues.ContainsKey($fieldName) -eq $false) ` -or ($null -eq $CurrentValues.$fieldName)) { Write-Verbose -Message ($script:localizedData.PropertyValidationError -f $fieldName) -Verbose $returnValue = $false } else { $arrayCompare = Compare-Object -ReferenceObject $CurrentValues.$fieldName ` -DifferenceObject $DesiredValues.$fieldName if ($null -ne $arrayCompare) { Write-Verbose -Message ($script:localizedData.PropertiesDoesNotMatch -f $fieldName) -Verbose $arrayCompare | ForEach-Object -Process { Write-Verbose -Message ($script:localizedData.PropertyThatDoesNotMatch -f $_.InputObject, $_.SideIndicator) -Verbose } $returnValue = $false } } } else { switch ($desiredType.Name) { 'String' { if (-not [System.String]::IsNullOrEmpty($CurrentValues.$fieldName) -or ` -not [System.String]::IsNullOrEmpty($DesiredValues.$fieldName)) { Write-Verbose -Message ($script:localizedData.ValueOfTypeDoesNotMatch ` -f $desiredType.Name, $fieldName, $($CurrentValues.$fieldName), $($DesiredValues.$fieldName)) -Verbose $returnValue = $false } } 'Int32' { if (-not ($DesiredValues.$fieldName -eq 0) -or ` -not ($null -eq $CurrentValues.$fieldName)) { Write-Verbose -Message ($script:localizedData.ValueOfTypeDoesNotMatch ` -f $desiredType.Name, $fieldName, $($CurrentValues.$fieldName), $($DesiredValues.$fieldName)) -Verbose $returnValue = $false } } { $_ -eq 'Int16' -or $_ -eq 'UInt16' -or $_ -eq 'Single' } { if (-not ($DesiredValues.$fieldName -eq 0) -or ` -not ($null -eq $CurrentValues.$fieldName)) { Write-Verbose -Message ($script:localizedData.ValueOfTypeDoesNotMatch ` -f $desiredType.Name, $fieldName, $($CurrentValues.$fieldName), $($DesiredValues.$fieldName)) -Verbose $returnValue = $false } } 'Boolean' { if ($CurrentValues.$fieldName -ne $DesiredValues.$fieldName) { Write-Verbose -Message ($script:localizedData.ValueOfTypeDoesNotMatch ` -f $desiredType.Name, $fieldName, $($CurrentValues.$fieldName), $($DesiredValues.$fieldName)) -Verbose $returnValue = $false } } default { Write-Warning -Message ($script:localizedData.UnableToCompareProperty ` -f $fieldName, $desiredType.Name) $returnValue = $false } } } } } } } return $returnValue } <# .SYNOPSIS Retrieves the localized string data based on the machine's culture. Falls back to en-US strings if the machine's culture is not supported. .PARAMETER ResourceName The name of the resource as it appears before '.strings.psd1' of the localized string file. For example: For WindowsOptionalFeature: MSFT_WindowsOptionalFeature For Service: MSFT_ServiceResource For Registry: MSFT_RegistryResource For Helper: SqlServerDscHelper .PARAMETER ScriptRoot Optional. The root path where to expect to find the culture folder. This is only needed for localization in helper modules. This should not normally be used for resources. .NOTES To be able to use localization in the helper function, this function must be first in the file, before Get-LocalizedData is used by itself to load localized data for this helper module (see directly after this function). #> function Get-LocalizedData { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $ResourceName, [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $ScriptRoot ) if (-not $ScriptRoot) { $dscResourcesFolder = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'DSCResources' $resourceDirectory = Join-Path -Path $dscResourcesFolder -ChildPath $ResourceName } else { $resourceDirectory = $ScriptRoot } $localizedStringFileLocation = Join-Path -Path $resourceDirectory -ChildPath $PSUICulture if (-not (Test-Path -Path $localizedStringFileLocation)) { # Fallback to en-US $localizedStringFileLocation = Join-Path -Path $resourceDirectory -ChildPath 'en-US' } Import-LocalizedData ` -BindingVariable 'localizedData' ` -FileName "$ResourceName.strings.psd1" ` -BaseDirectory $localizedStringFileLocation return $localizedData } <# .SYNOPSIS Creates and throws an invalid argument exception. .PARAMETER Message The message explaining why this error is being thrown. .PARAMETER ArgumentName The name of the invalid argument that is causing this error to be thrown. #> function New-InvalidArgumentException { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Message, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $ArgumentName ) $argumentException = New-Object -TypeName 'ArgumentException' ` -ArgumentList @($Message, $ArgumentName) $newObjectParameters = @{ TypeName = 'System.Management.Automation.ErrorRecord' ArgumentList = @($argumentException, $ArgumentName, 'InvalidArgument', $null) } $errorRecord = New-Object @newObjectParameters throw $errorRecord } <# .SYNOPSIS Creates and throws an invalid operation exception. .PARAMETER Message The message explaining why this error is being thrown. .PARAMETER ErrorRecord The error record containing the exception that is causing this terminating error. #> function New-InvalidOperationException { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Message, [Parameter()] [ValidateNotNull()] [System.Management.Automation.ErrorRecord] $ErrorRecord ) if ($null -eq $ErrorRecord) { $invalidOperationException = New-Object -TypeName 'InvalidOperationException' ` -ArgumentList @($Message) } else { $invalidOperationException = New-Object -TypeName 'InvalidOperationException' ` -ArgumentList @($Message, $ErrorRecord.Exception) } $newObjectParameters = @{ TypeName = 'System.Management.Automation.ErrorRecord' ArgumentList = @( $invalidOperationException.ToString(), 'MachineStateIncorrect', 'InvalidOperation', $null ) } $errorRecordToThrow = New-Object @newObjectParameters throw $errorRecordToThrow } <# .SYNOPSIS Creates and throws an object not found exception. .PARAMETER Message The message explaining why this error is being thrown. .PARAMETER ErrorRecord The error record containing the exception that is causing this terminating error. #> function New-ObjectNotFoundException { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Message, [Parameter()] [ValidateNotNull()] [System.Management.Automation.ErrorRecord] $ErrorRecord ) if ($null -eq $ErrorRecord) { $exception = New-Object -TypeName 'System.Exception' ` -ArgumentList @($Message) } else { $exception = New-Object -TypeName 'System.Exception' ` -ArgumentList @($Message, $ErrorRecord.Exception) } $newObjectParameters = @{ TypeName = 'System.Management.Automation.ErrorRecord' ArgumentList = @( $exception.ToString(), 'MachineStateIncorrect', 'ObjectNotFound', $null ) } $errorRecordToThrow = New-Object @newObjectParameters throw $errorRecordToThrow } <# .SYNOPSIS Creates and throws an invalid result exception. .PARAMETER Message The message explaining why this error is being thrown. .PARAMETER ErrorRecord The error record containing the exception that is causing this terminating error. #> function New-InvalidResultException { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Message, [Parameter()] [ValidateNotNull()] [System.Management.Automation.ErrorRecord] $ErrorRecord ) if ($null -eq $ErrorRecord) { $exception = New-Object -TypeName 'System.Exception' ` -ArgumentList @($Message) } else { $exception = New-Object -TypeName 'System.Exception' ` -ArgumentList @($Message, $ErrorRecord.Exception) } $newObjectParameters = @{ TypeName = 'System.Management.Automation.ErrorRecord' ArgumentList = @( $exception.ToString(), 'MachineStateIncorrect', 'InvalidResult', $null ) } $errorRecordToThrow = New-Object @newObjectParameters throw $errorRecordToThrow } <# .SYNOPSIS Creates and throws a not implemented exception. .PARAMETER Message The message explaining why this error is being thrown. .PARAMETER ErrorRecord The error record containing the exception that is causing this terminating error. #> function New-NotImplementedException { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Message, [Parameter()] [ValidateNotNull()] [System.Management.Automation.ErrorRecord] $ErrorRecord ) if ($null -eq $ErrorRecord) { $invalidOperationException = New-Object -TypeName 'NotImplementedException' ` -ArgumentList @($Message) } else { $invalidOperationException = New-Object -TypeName 'NotImplementedException' ` -ArgumentList @($Message, $ErrorRecord.Exception) } $newObjectParameters = @{ TypeName = 'System.Management.Automation.ErrorRecord' ArgumentList = @( $invalidOperationException.ToString(), 'MachineStateIncorrect', 'NotImplemented', $null ) } $errorRecordToThrow = New-Object @newObjectParameters throw $errorRecordToThrow } <# .SYNOPSIS Restarts a System Service .PARAMETER Name Name of the service to be restarted. #> function Restart-ServiceIfExists { [CmdletBinding()] param ( [Parameter()] [System.String] $Name ) Write-Verbose -Message ($script:localizedData.GetServiceInformation -f $Name) -Verbose $servicesService = Get-Service @PSBoundParameters -ErrorAction Continue if ($servicesService) { Write-Verbose -Message ($script:localizedData.RestartService -f $Name) -Verbose $servicesService | Restart-Service -Force -ErrorAction Stop -Verbose } else { Write-Verbose -Message ($script:localizedData.UnknownService -f $Name) -Verbose } } <# .SYNOPSIS Validates a Drive Letter, removing or adding the trailing colon if required. .PARAMETER DriveLetter The Drive Letter string to validate. .PARAMETER Colon Will ensure the returned string will include or exclude a colon. #> function Assert-DriveLetterValid { [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $DriveLetter, [Parameter()] [Switch] $Colon ) $matches = @([regex]::matches($DriveLetter, '^([A-Za-z]):?$', 'IgnoreCase')) if (-not $matches) { # DriveLetter format is invalid New-InvalidArgumentException ` -Message $($script:localizedData.InvalidDriveLetterFormatError -f $DriveLetter) ` -ArgumentName 'DriveLetter' } # This is the drive letter without a colon $DriveLetter = $matches.Groups[1].Value if ($Colon) { $DriveLetter = $DriveLetter + ':' } # if return $DriveLetter } # end function Assert-DriveLetterValid <# .SYNOPSIS Validates an Access Path, removing or adding the trailing slash if required. If the Access Path does not exist or is not a folder then an exception will be thrown. .PARAMETER AccessPath The Access Path string to validate. .PARAMETER Slash Will ensure the returned path will include or exclude a slash. #> function Assert-AccessPathValid { [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $AccessPath, [Parameter()] [Switch] $Slash ) if (-not (Test-Path -Path $AccessPath -PathType Container)) { # AccessPath is invalid New-InvalidArgumentException ` -Message $($script:localizedData.InvalidAccessPathError -f $AccessPath) ` -ArgumentName 'AccessPath' } # if # Remove or Add the trailing slash if ($AccessPath.EndsWith('\')) { if (-not $Slash) { $AccessPath = $AccessPath.TrimEnd('\') } # if } else { if ($Slash) { $AccessPath = "$AccessPath\" } # if } # if return $AccessPath } # end function Assert-AccessPathValid <# .SYNOPSIS Retrieves a Disk object matching the disk Id and Id type provided. .PARAMETER DiskId Specifies the disk identifier for the disk to retrieve. .PARAMETER DiskIdType Specifies the identifier type the DiskId contains. Defaults to Number. #> function Get-DiskByIdentifier { [CmdletBinding()] [OutputType([Microsoft.Management.Infrastructure.CimInstance])] param ( [Parameter(Mandatory = $true)] [System.String] $DiskId, [Parameter()] [ValidateSet('Number','UniqueId','Guid','Location')] [System.String] $DiskIdType = 'Number' ) switch -regex ($DiskIdType) { 'Number|UniqueId' # for filters supported by the Get-Disk CmdLet { $diskIdParameter = @{ $DiskIdType = $DiskId } $disk = Get-Disk ` @diskIdParameter ` -ErrorAction SilentlyContinue break } default # for filters requiring Where-Object { $disk = Get-Disk -ErrorAction SilentlyContinue | Where-Object -Property $DiskIdType -EQ $DiskId } } return $disk } # end function Get-DiskByIdentifier <# .SYNOPSIS Tests if any of the access paths from a partition are assigned to a local path. .PARAMETER AccessPath Specifies the access paths that are assigned to the partition. #> function Test-AccessPathAssignedToLocal { [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] [System.String[]] $AccessPath ) $accessPathAssigned = $false foreach ($path in $AccessPath) { if ($path -match '[a-zA-Z]:\\') { $accessPathAssigned = $true break } } return $accessPathAssigned } # end function Test-AccessPathLocal $script:localizedData = Get-LocalizedData -ResourceName 'StorageDsc.Common' -ScriptRoot $PSScriptRoot Export-ModuleMember -Function @( 'Test-DscParameterState', 'New-InvalidArgumentException', 'New-InvalidOperationException', 'New-ObjectNotFoundException', 'New-InvalidResultException', 'New-NotImplementedException', 'Get-LocalizedData', 'Restart-ServiceIfExists', 'Assert-DriveLetterValid', 'Assert-AccessPathValid', 'Get-DiskByIdentifier', 'Test-AccessPathAssignedToLocal' ) |