From 6ff71c1ff39304f02e6e8286664cd6b679176518 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Mon, 18 Sep 2023 17:42:16 -0700 Subject: [PATCH 01/21] Add capability to format volume as a Dev Drive --- CHANGELOG.md | 2 + source/DSCResources/DSC_Disk/DSC_Disk.psm1 | 44 +++++++- .../DSCResources/DSC_Disk/DSC_Disk.schema.mof | 1 + .../StorageDsc.Common/StorageDsc.Common.psm1 | 103 +++++++++++++++++- .../en-US/StorageDsc.Common.strings.psd1 | 3 + source/StorageDsc.psd1 | 2 +- 6 files changed, 150 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be4be481..eb88bf7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Updated DSC_Disk to allow volumes to be formatted as Dev Drives + ## [5.1.0] - 2023-02-22 ### Changed diff --git a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 index 6ba3c59c..5ddf7e93 100644 --- a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 +++ b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 @@ -51,6 +51,10 @@ $script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' Specifies if the disks partition schema should be removed entirely, even if data and OEM partitions are present. Only possible with AllowDestructive enabled. This parameter is not used in Get-TargetResource. + + .PARAMETER DevDrive + Specifies if the volume should be formatted as a Dev Drive. + This parameter is not used in Get-TargetResource. #> function Get-TargetResource { @@ -99,7 +103,11 @@ function Get-TargetResource [Parameter()] [System.Boolean] - $ClearDisk + $ClearDisk, + + [Parameter()] + [System.Boolean] + $DevDrive ) Write-Verbose -Message ( @( @@ -173,6 +181,9 @@ function Get-TargetResource .PARAMETER ClearDisk Specifies if the disks partition schema should be removed entirely, even if data and OEM partitions are present. Only possible with AllowDestructive enabled. + + .PARAMETER DevDrive + Specifies if the volume should be formatted as a Dev Drive. #> function Set-TargetResource { @@ -222,7 +233,11 @@ function Set-TargetResource [Parameter()] [System.Boolean] - $ClearDisk + $ClearDisk, + + [Parameter()] + [System.Boolean] + $DevDrive ) Write-Verbose -Message ( @( @@ -560,6 +575,11 @@ function Set-TargetResource $($script:localizedData.FormattingVolumeMessage -f $formatVolumeParameters.FileSystem) ) -join '' ) + if ($DevDrive) + { + $formatVolumeParameters['DevDrive'] = $DevDrive + } + # Format the volume $volume = $partition | Format-Volume @formatVolumeParameters } @@ -597,6 +617,11 @@ function Set-TargetResource $formatParam.Add('AllocationUnitSize', $AllocationUnitSize) } + if ($PSBoundParameters.ContainsKey('DevDrive')) + { + $formatParam.Add('DevDrive', $DevDrive) + } + $Volume | Format-Volume @formatParam } } # if @@ -671,6 +696,9 @@ function Set-TargetResource .PARAMETER ClearDisk Specifies if the disks partition schema should be removed entirely, even if data and OEM partitions are present. Only possible with AllowDestructive enabled. + + .PARAMETER DevDrive + Specifies if the volume should be formatted as a Dev Drive. #> function Test-TargetResource { @@ -719,7 +747,11 @@ function Test-TargetResource [Parameter()] [System.Boolean] - $ClearDisk + $ClearDisk, + + [Parameter()] + [System.Boolean] + $DevDrive ) Write-Verbose -Message ( @( @@ -917,6 +949,12 @@ function Test-TargetResource } # if } # if + if ($PSBoundParameters.ContainsKey('DevDrive')) + { + Assert-DevDriveFeatureAvailable + Assert-DevDriveFormatOnReFsFileSystemOnly $FSFormat + } + return $true } # Test-TargetResource diff --git a/source/DSCResources/DSC_Disk/DSC_Disk.schema.mof b/source/DSCResources/DSC_Disk/DSC_Disk.schema.mof index 01341025..7d06a357 100644 --- a/source/DSCResources/DSC_Disk/DSC_Disk.schema.mof +++ b/source/DSCResources/DSC_Disk/DSC_Disk.schema.mof @@ -12,4 +12,5 @@ class DSC_Disk : OMI_BaseResource [Write, Description("Specifies the file system format of the new volume."), ValueMap{"NTFS","ReFS"}, Values{"NTFS","ReFS"}] String FSFormat; [Write, Description("Specifies if potentially destructive operations may occur.")] Boolean AllowDestructive; [Write, Description("Specifies if the disks partition schema should be removed entirely, even if data and OEM partitions are present. Only possible with AllowDestructive enabled.")] Boolean ClearDisk; + [Write, Description("Specifies if the volume should be formatted as a Dev Drive.")] Boolean DevDrive; }; diff --git a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 index 87eb8c64..fb369296 100644 --- a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 +++ b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 @@ -222,10 +222,111 @@ function Test-AccessPathAssignedToLocal return $accessPathAssigned } # end function Test-AccessPathLocal +<# + .SYNOPSIS + Validates whether the Dev Drive feature is available and enabled on the system. +#> +function Assert-DevDriveFeatureAvailable +{ + [CmdletBinding()] + [OutputType([System.Void])] + param + () + + $DevDriveDefinitions = @' + using System.Runtime.InteropServices; + namespace DevDrive + { + public enum DEVELOPER_DRIVE_ENABLEMENT_STATE + { + DeveloperDriveEnablementStateError = 0, + DeveloperDriveEnabled = 1, + DeveloperDriveDisabledBySystemPolicy = 2, + DeveloperDriveDisabledByGroupPolicy = 3, + } + + public class DevDriveHelper + { + [DllImport("api-ms-win-core-apiquery-l2-1-0.dll", ExactSpelling = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + public static extern bool IsApiSetImplemented(string Contract); + + [DllImport("api-ms-win-core-sysinfo-l1-2-6.dll")] + public static extern DEVELOPER_DRIVE_ENABLEMENT_STATE GetDeveloperDriveEnablementState(); + } + } +'@ + + Add-Type -TypeDefinition $DevDriveDefinitions + + $IsApiSetImplemented = [DevDrive.DevDriveHelper]::IsApiSetImplemented("api-ms-win-core-sysinfo-l1-2-6") + + if ($IsApiSetImplemented) + { + switch ([DevDrive.DevDriveHelper]::GetDeveloperDriveEnablementState()) + { + ([DevDrive.DEVELOPER_DRIVE_ENABLEMENT_STATE]::DeveloperDriveEnablementStateError) + { + throw $script:localizedData.DevDriveEnablementUnknownError; + } + ([DevDrive.DEVELOPER_DRIVE_ENABLEMENT_STATE]::DeveloperDriveDisabledBySystemPolicy) + { + throw $script:localizedData.DevDriveDisabledBySystemPolicyError; + } + ([DevDrive.DEVELOPER_DRIVE_ENABLEMENT_STATE]::DeveloperDriveDisabledByGroupPolicy) + { + throw $script:localizedData.DevDriveDisabledByGroupPolicyError; + } + ([DevDrive.DEVELOPER_DRIVE_ENABLEMENT_STATE]::DeveloperDriveEnabled) + { + return; + } + Default { + throw $script:localizedData.DevDriveEnablementUnknownError; + } + } + } + + # If apiset isn't implemented we should throw since the feature isn't available here. + throw $script:localizedData.DevDriveFeatureNotImplementedError; + +} # end function Assert-DevDriveFeatureAvailable + +<# + .SYNOPSIS + Validates that ReFs is supplied when attempting to format a volume as a Dev Drive. + + .PARAMETER FSFormat + Specifies the file system format of the new volume. +#> +function Assert-DevDriveFormatOnReFsFileSystemOnly +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateSet('NTFS', 'ReFS')] + [System.String] + $FSFormat + ) + + if ($FSFormat -ne 'ReFS') + { + + New-InvalidArgumentException ` + -Message $($script:localizedData.DevDriveOnlyAvailableForReFsError -f 'ReFS', $FSFormat) ` + -ArgumentName 'FSFormat' + } + +} # end function Assert-DevDriveFormatOnReFsFileSystemOnly + Export-ModuleMember -Function @( 'Restart-ServiceIfExists', 'Assert-DriveLetterValid', 'Assert-AccessPathValid', 'Get-DiskByIdentifier', - 'Test-AccessPathAssignedToLocal' + 'Test-AccessPathAssignedToLocal', + 'Assert-DevDriveFeatureAvailable', + 'Assert-DevDriveFormatOnReFsFileSystemOnly' ) diff --git a/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 b/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 index bb3dde13..6fe0fd5b 100644 --- a/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 +++ b/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 @@ -4,4 +4,7 @@ ConvertFrom-StringData @' UnknownService = Unable to find the desired service. InvalidDriveLetterFormatError = Drive Letter format '{0}' is not valid. InvalidAccessPathError = Access Path '{0}' is not found. + DevDriveEnablementUnknownError = Unable to get Dev Drive enablement status. + DevDriveDisabledBySystemPolicyError = Dev Drive feature disabled due to system policy. + DevDriveDisabledByGroupPolicyError = Dev Drive feature disabled due to group policy. '@ diff --git a/source/StorageDsc.psd1 b/source/StorageDsc.psd1 index 2b9632f2..c1beb719 100644 --- a/source/StorageDsc.psd1 +++ b/source/StorageDsc.psd1 @@ -52,7 +52,7 @@ Prerelease = '' # Tags applied to this module. These help with module discovery in online galleries. - Tags = @('DesiredStateConfiguration', 'DSC', 'DSCResource', 'Disk', 'Storage', 'Partition', 'Volume') + Tags = @('DesiredStateConfiguration', 'DSC', 'DSCResource', 'Disk', 'Storage', 'Partition', 'Volume', 'Dev Drive') # A URL to the license for this module. LicenseUri = 'https://github.com/dsccommunity/StorageDsc/blob/main/LICENSE' From c05382059e12a761f2548cfec74a702ab94791d6 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Mon, 18 Sep 2023 18:26:47 -0700 Subject: [PATCH 02/21] update parameter call --- source/DSCResources/DSC_Disk/DSC_Disk.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 index 5ddf7e93..18b607c2 100644 --- a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 +++ b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 @@ -952,7 +952,7 @@ function Test-TargetResource if ($PSBoundParameters.ContainsKey('DevDrive')) { Assert-DevDriveFeatureAvailable - Assert-DevDriveFormatOnReFsFileSystemOnly $FSFormat + Assert-DevDriveFormatOnReFsFileSystemOnly -FSFormat $FSFormat } return $true From 26ce7c39c9162fcfa1c4597f3a96b347e121d5ed Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Tue, 19 Sep 2023 00:51:07 -0700 Subject: [PATCH 03/21] update disk resource to accommodate creating a partition and volume on unallocated space --- source/DSCResources/DSC_Disk/DSC_Disk.psm1 | 54 +++++++++--- .../DSCResources/DSC_Disk/DSC_Disk.schema.mof | 1 + .../StorageDsc.Common/StorageDsc.Common.psm1 | 85 +++++++++++++------ .../en-US/StorageDsc.Common.strings.psd1 | 4 + 4 files changed, 106 insertions(+), 38 deletions(-) diff --git a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 index 18b607c2..1b28abc1 100644 --- a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 +++ b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 @@ -55,6 +55,10 @@ $script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' .PARAMETER DevDrive Specifies if the volume should be formatted as a Dev Drive. This parameter is not used in Get-TargetResource. + + .PARAMETER UseUnallocatedSpace + Specifies that a new partition and volume should be formatted onto unallocated space on the disk. + This parameter is not used in Get-TargetResource. #> function Get-TargetResource { @@ -107,7 +111,11 @@ function Get-TargetResource [Parameter()] [System.Boolean] - $DevDrive + $DevDrive, + + [Parameter()] + [System.Boolean] + $UseUnallocatedSpace ) Write-Verbose -Message ( @( @@ -136,14 +144,16 @@ function Get-TargetResource -ErrorAction SilentlyContinue).BlockSize return @{ - DiskId = $DiskId - DiskIdType = $DiskIdType - DriveLetter = $partition.DriveLetter - PartitionStyle = $disk.PartitionStyle - Size = $partition.Size - FSLabel = $volume.FileSystemLabel - AllocationUnitSize = $blockSize - FSFormat = $volume.FileSystem + DiskId = $DiskId + DiskIdType = $DiskIdType + DriveLetter = $partition.DriveLetter + PartitionStyle = $disk.PartitionStyle + Size = $partition.Size + FSLabel = $volume.FileSystemLabel + AllocationUnitSize = $blockSize + FSFormat = $volume.FileSystem + DevDrive = $DevDrive + UseUnallocatedSpace = $UseUnallocatedSpace } } # Get-TargetResource @@ -184,6 +194,9 @@ function Get-TargetResource .PARAMETER DevDrive Specifies if the volume should be formatted as a Dev Drive. + + .PARAMETER UseUnallocatedSpace + Specifies that a new partition and volume should be formatted onto unallocated space on the disk. #> function Set-TargetResource { @@ -237,7 +250,11 @@ function Set-TargetResource [Parameter()] [System.Boolean] - $DevDrive + $DevDrive, + + [Parameter()] + [System.Boolean] + $UseUnallocatedSpace ) Write-Verbose -Message ( @( @@ -410,8 +427,11 @@ function Set-TargetResource } # if } # if - # Do we need to create a new partition? - if (-not $partition) + <# + Create a new partition when there are no partitions matching user input or if the user has + advised us that they want to create a new partition and volume on the disks unallocated space. + #> + if (-not $partition -bor $UseUnallocatedSpace) { # Attempt to create a new partition $partitionParams = @{ @@ -699,6 +719,9 @@ function Set-TargetResource .PARAMETER DevDrive Specifies if the volume should be formatted as a Dev Drive. + + .PARAMETER UseUnallocatedSpace + Specifies that a new partition and volume should be formatted onto unallocated space on the disk. #> function Test-TargetResource { @@ -751,7 +774,11 @@ function Test-TargetResource [Parameter()] [System.Boolean] - $DevDrive + $DevDrive, + + [Parameter()] + [System.Boolean] + $UseUnallocatedSpace ) Write-Verbose -Message ( @( @@ -953,6 +980,7 @@ function Test-TargetResource { Assert-DevDriveFeatureAvailable Assert-DevDriveFormatOnReFsFileSystemOnly -FSFormat $FSFormat + Assert-DevDriveSizeMeetsMinimumRequirement -Size $Size } return $true diff --git a/source/DSCResources/DSC_Disk/DSC_Disk.schema.mof b/source/DSCResources/DSC_Disk/DSC_Disk.schema.mof index 7d06a357..d9c00b12 100644 --- a/source/DSCResources/DSC_Disk/DSC_Disk.schema.mof +++ b/source/DSCResources/DSC_Disk/DSC_Disk.schema.mof @@ -13,4 +13,5 @@ class DSC_Disk : OMI_BaseResource [Write, Description("Specifies if potentially destructive operations may occur.")] Boolean AllowDestructive; [Write, Description("Specifies if the disks partition schema should be removed entirely, even if data and OEM partitions are present. Only possible with AllowDestructive enabled.")] Boolean ClearDisk; [Write, Description("Specifies if the volume should be formatted as a Dev Drive.")] Boolean DevDrive; + [Write, Description("Specifies that a new partition and volume should be formatted onto unallocated space in the disk.")] Boolean UseUnallocatedSpace; }; diff --git a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 index fb369296..45b0cd2c 100644 --- a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 +++ b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 @@ -258,38 +258,46 @@ function Assert-DevDriveFeatureAvailable '@ Add-Type -TypeDefinition $DevDriveDefinitions + Write-Verbose -Message ($script:localizedData.CheckingDevDriveEnablementMessage) $IsApiSetImplemented = [DevDrive.DevDriveHelper]::IsApiSetImplemented("api-ms-win-core-sysinfo-l1-2-6") - if ($IsApiSetImplemented) { - switch ([DevDrive.DevDriveHelper]::GetDeveloperDriveEnablementState()) + try { - ([DevDrive.DEVELOPER_DRIVE_ENABLEMENT_STATE]::DeveloperDriveEnablementStateError) - { - throw $script:localizedData.DevDriveEnablementUnknownError; - } - ([DevDrive.DEVELOPER_DRIVE_ENABLEMENT_STATE]::DeveloperDriveDisabledBySystemPolicy) - { - throw $script:localizedData.DevDriveDisabledBySystemPolicyError; - } - ([DevDrive.DEVELOPER_DRIVE_ENABLEMENT_STATE]::DeveloperDriveDisabledByGroupPolicy) - { - throw $script:localizedData.DevDriveDisabledByGroupPolicyError; - } - ([DevDrive.DEVELOPER_DRIVE_ENABLEMENT_STATE]::DeveloperDriveEnabled) + switch ([DevDrive.DevDriveHelper]::GetDeveloperDriveEnablementState()) { - return; - } - Default { - throw $script:localizedData.DevDriveEnablementUnknownError; + ([DevDrive.DEVELOPER_DRIVE_ENABLEMENT_STATE]::DeveloperDriveEnablementStateError) + { + throw $script:localizedData.DevDriveEnablementUnknownError + } + ([DevDrive.DEVELOPER_DRIVE_ENABLEMENT_STATE]::DeveloperDriveDisabledBySystemPolicy) + { + throw $script:localizedData.DevDriveDisabledBySystemPolicyError + } + ([DevDrive.DEVELOPER_DRIVE_ENABLEMENT_STATE]::DeveloperDriveDisabledByGroupPolicy) + { + throw $script:localizedData.DevDriveDisabledByGroupPolicyError + } + ([DevDrive.DEVELOPER_DRIVE_ENABLEMENT_STATE]::DeveloperDriveEnabled) + { + Write-Verbose -Message ($script:localizedData.DevDriveEnabledMessage) + return + } + Default + { + throw $script:localizedData.DevDriveEnablementUnknownError + } } } + catch [System.EntryPointNotFoundException] # function may not exist in some versions of Windows in the above dll + { + Write-Error $_.Exception.Message + } } - # If apiset isn't implemented we should throw since the feature isn't available here. - throw $script:localizedData.DevDriveFeatureNotImplementedError; - + # If apiset isn't implemented or we get the EntryPointNotFoundException we should throw since the feature isn't available here. + throw $script:localizedData.DevDriveFeatureNotImplementedError } # end function Assert-DevDriveFeatureAvailable <# @@ -302,7 +310,6 @@ function Assert-DevDriveFeatureAvailable function Assert-DevDriveFormatOnReFsFileSystemOnly { [CmdletBinding()] - [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] @@ -313,7 +320,6 @@ function Assert-DevDriveFormatOnReFsFileSystemOnly if ($FSFormat -ne 'ReFS') { - New-InvalidArgumentException ` -Message $($script:localizedData.DevDriveOnlyAvailableForReFsError -f 'ReFS', $FSFormat) ` -ArgumentName 'FSFormat' @@ -321,6 +327,34 @@ function Assert-DevDriveFormatOnReFsFileSystemOnly } # end function Assert-DevDriveFormatOnReFsFileSystemOnly +<# + .SYNOPSIS + Validates that the user entered a size greater than the minimum for Dev Drive volumes. + (The minimum is 50 Gb) + + .PARAMETER FSFormat + Specifies the size the user wants to create the Dev Drive with. +#> +function Assert-DevDriveSizeMeetsMinimumRequirement +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.UInt64] + $Size + ) + + # 50 Gb is the minimum size for Dev Drive volumes + if ($Size -lt 50Gb) + { + New-InvalidArgumentException ` + -Message $($script:localizedData.DevDriveMinimumSizeError) ` + -ArgumentName 'Size' + } + +} # end function Assert-DevDriveSizeMeetsMinimumRequirement + Export-ModuleMember -Function @( 'Restart-ServiceIfExists', 'Assert-DriveLetterValid', @@ -328,5 +362,6 @@ Export-ModuleMember -Function @( 'Get-DiskByIdentifier', 'Test-AccessPathAssignedToLocal', 'Assert-DevDriveFeatureAvailable', - 'Assert-DevDriveFormatOnReFsFileSystemOnly' + 'Assert-DevDriveFormatOnReFsFileSystemOnly', + 'Assert-DevDriveSizeMeetsMinimumRequirement' ) diff --git a/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 b/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 index 6fe0fd5b..b971ee6e 100644 --- a/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 +++ b/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 @@ -7,4 +7,8 @@ ConvertFrom-StringData @' DevDriveEnablementUnknownError = Unable to get Dev Drive enablement status. DevDriveDisabledBySystemPolicyError = Dev Drive feature disabled due to system policy. DevDriveDisabledByGroupPolicyError = Dev Drive feature disabled due to group policy. + DevDriveEnabledMessage = Dev Drive feature is enabled on this system. + CheckingDevDriveEnablementMessage = Checking if the Dev Drive feature is available and enabled on the system. + DevDriveFeatureNotImplementedError = Dev Drive feature is not implemented on this system. + DevDriveMinimumSizeError = Dev Drive volumes must be 50Gb or more in size. '@ From 16e5f6931851f9dec9d145bd35f4ced50af63d17 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Wed, 20 Sep 2023 19:18:48 -0700 Subject: [PATCH 04/21] update where dev drive check happens --- source/DSCResources/DSC_Disk/DSC_Disk.psm1 | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 index 1b28abc1..33060016 100644 --- a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 +++ b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 @@ -789,6 +789,14 @@ function Test-TargetResource # Validate the DriveLetter parameter $DriveLetter = Assert-DriveLetterValid -DriveLetter $DriveLetter + # Check Dev Drive requirements if DevDrive parameter supplied + if ($PSBoundParameters.ContainsKey('DevDrive')) + { + Assert-DevDriveFeatureAvailable + Assert-DevDriveFormatOnReFsFileSystemOnly -FSFormat $FSFormat + Assert-DevDriveSizeMeetsMinimumRequirement -Size $Size + } + Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " $($script:localizedData.CheckDiskInitializedMessage -f $DiskIdType, $DiskId) @@ -976,13 +984,6 @@ function Test-TargetResource } # if } # if - if ($PSBoundParameters.ContainsKey('DevDrive')) - { - Assert-DevDriveFeatureAvailable - Assert-DevDriveFormatOnReFsFileSystemOnly -FSFormat $FSFormat - Assert-DevDriveSizeMeetsMinimumRequirement -Size $Size - } - return $true } # Test-TargetResource From 662baa4469431a2373aaa668d19c75b0328c5212 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Wed, 20 Sep 2023 19:37:21 -0700 Subject: [PATCH 05/21] update comment --- source/DSCResources/DSC_Disk/DSC_Disk.psm1 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 index 33060016..27fa7c9a 100644 --- a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 +++ b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 @@ -428,8 +428,10 @@ function Set-TargetResource } # if <# - Create a new partition when there are no partitions matching user input or if the user has - advised us that they want to create a new partition and volume on the disks unallocated space. + There are two instances when we attempt to create a new partition: + 1. When there are no partitions matching the drive letter the user entered. + 2. When the user has advised us that they want to create a new partition on the disk's + unallocated space, regardless of whether there is one that matches the $size parameter already. #> if (-not $partition -bor $UseUnallocatedSpace) { From 5f5e8d2778322f262e9404e8d02236a7fe479633 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Fri, 22 Sep 2023 00:48:58 -0700 Subject: [PATCH 06/21] add tests --- .../StorageDsc.Common/StorageDsc.Common.psm1 | 125 ++++++++--- .../en-US/StorageDsc.Common.strings.psd1 | 1 + tests/Unit/DSC_Disk.Tests.ps1 | 206 +++++++++++++++++- tests/Unit/StorageDsc.Common.Tests.ps1 | 180 +++++++++++++++ 4 files changed, 480 insertions(+), 32 deletions(-) diff --git a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 index 45b0cd2c..22c0a7f3 100644 --- a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 +++ b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 @@ -224,62 +224,123 @@ function Test-AccessPathAssignedToLocal <# .SYNOPSIS - Validates whether the Dev Drive feature is available and enabled on the system. + Returns C# code that will be used to call Dev Drive related Win32 apis #> -function Assert-DevDriveFeatureAvailable +function Get-DevDriveWin32HelperScript { + [OutputType([System.Type])] [CmdletBinding()] - [OutputType([System.Void])] param () - $DevDriveDefinitions = @' - using System.Runtime.InteropServices; - namespace DevDrive + $DevDriveHelperDefinitions = @' + + public enum DEVELOPER_DRIVE_ENABLEMENT_STATE { - public enum DEVELOPER_DRIVE_ENABLEMENT_STATE - { - DeveloperDriveEnablementStateError = 0, - DeveloperDriveEnabled = 1, - DeveloperDriveDisabledBySystemPolicy = 2, - DeveloperDriveDisabledByGroupPolicy = 3, - } + DeveloperDriveEnablementStateError = 0, + DeveloperDriveEnabled = 1, + DeveloperDriveDisabledBySystemPolicy = 2, + DeveloperDriveDisabledByGroupPolicy = 3, + } + + [DllImport("api-ms-win-core-apiquery-l2-1-0.dll", ExactSpelling = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + public static extern bool IsApiSetImplemented(string Contract); + + [DllImport("api-ms-win-core-sysinfo-l1-2-6.dll")] + public static extern DEVELOPER_DRIVE_ENABLEMENT_STATE GetDeveloperDriveEnablementState(); - public class DevDriveHelper - { - [DllImport("api-ms-win-core-apiquery-l2-1-0.dll", ExactSpelling = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - public static extern bool IsApiSetImplemented(string Contract); - [DllImport("api-ms-win-core-sysinfo-l1-2-6.dll")] - public static extern DEVELOPER_DRIVE_ENABLEMENT_STATE GetDeveloperDriveEnablementState(); - } - } '@ + if (([System.Management.Automation.PSTypeName]'DevDrive.DevDriveHelper').Type) + { + $script:DevDriveWin32Helper = ([System.Management.Automation.PSTypeName]'DevDrive.DevDriveHelper').Type + } + else + { + $script:DevDriveWin32Helper = Add-Type ` + -Namespace 'DevDrive' ` + -Name 'DevDriveHelper' ` + -MemberDefinition $DevDriveHelperDefinitions + } + + return $script:DevDriveWin32Helper + +} # end function Get-DevDriveWin32HelperScript + +<# + .SYNOPSIS + Invokes win32 IsApiSetImplemented function + + .PARAMETER AccessPath + Specifies the contract string for the dll that houses the win32 function +#> +Function Get-IsApiSetImplemented { + + [CmdletBinding()] + Param + ( + [Parameter(Mandatory=$True)] + [ValidateNotNullOrEmpty()] + [OutputType([System.Boolean])] + [String] + $Contract + ) + + $helper = Get-DevDriveWin32HelperScript + return $helper::IsApiSetImplemented($Contract) +} # end function Get-IsApiSetImplemented + +<# + .SYNOPSIS + Invokes win32 GetDeveloperDriveEnablementState function +#> +Function Get-DeveloperDriveEnablementState { + + [CmdletBinding()] + [OutputType([System.Enum])] + Param + () + + $helper = Get-DevDriveWin32HelperScript + return $helper::GetDeveloperDriveEnablementState() +} # end function Get-DeveloperDriveEnablementState + +<# + .SYNOPSIS + Validates whether the Dev Drive feature is available and enabled on the system. +#> +function Assert-DevDriveFeatureAvailable +{ + [CmdletBinding()] + [OutputType([System.Void])] + param + () - Add-Type -TypeDefinition $DevDriveDefinitions + $devDriveHelper = Get-DevDriveWin32HelperScript Write-Verbose -Message ($script:localizedData.CheckingDevDriveEnablementMessage) - $IsApiSetImplemented = [DevDrive.DevDriveHelper]::IsApiSetImplemented("api-ms-win-core-sysinfo-l1-2-6") + $IsApiSetImplemented = Get-IsApiSetImplemented("api-ms-win-core-sysinfo-l1-2-6") + $DevDriveEnablementType = [DevDrive.DevDriveHelper+DEVELOPER_DRIVE_ENABLEMENT_STATE] if ($IsApiSetImplemented) { try { - switch ([DevDrive.DevDriveHelper]::GetDeveloperDriveEnablementState()) + switch (Get-DeveloperDriveEnablementState) { - ([DevDrive.DEVELOPER_DRIVE_ENABLEMENT_STATE]::DeveloperDriveEnablementStateError) + ($DevDriveEnablementType::DeveloperDriveEnablementStateError) { throw $script:localizedData.DevDriveEnablementUnknownError } - ([DevDrive.DEVELOPER_DRIVE_ENABLEMENT_STATE]::DeveloperDriveDisabledBySystemPolicy) + ($DevDriveEnablementType::DeveloperDriveDisabledBySystemPolicy) { throw $script:localizedData.DevDriveDisabledBySystemPolicyError } - ([DevDrive.DEVELOPER_DRIVE_ENABLEMENT_STATE]::DeveloperDriveDisabledByGroupPolicy) + ($DevDriveEnablementType::DeveloperDriveDisabledByGroupPolicy) { throw $script:localizedData.DevDriveDisabledByGroupPolicyError } - ([DevDrive.DEVELOPER_DRIVE_ENABLEMENT_STATE]::DeveloperDriveEnabled) + ($DevDriveEnablementType::DeveloperDriveEnabled) { Write-Verbose -Message ($script:localizedData.DevDriveEnabledMessage) return @@ -313,7 +374,6 @@ function Assert-DevDriveFormatOnReFsFileSystemOnly param ( [Parameter(Mandatory = $true)] - [ValidateSet('NTFS', 'ReFS')] [System.String] $FSFormat ) @@ -363,5 +423,8 @@ Export-ModuleMember -Function @( 'Test-AccessPathAssignedToLocal', 'Assert-DevDriveFeatureAvailable', 'Assert-DevDriveFormatOnReFsFileSystemOnly', - 'Assert-DevDriveSizeMeetsMinimumRequirement' + 'Assert-DevDriveSizeMeetsMinimumRequirement', + 'Get-DevDriveWin32HelperScript', + 'Get-IsApiSetImplemented', + 'Get-DeveloperDriveEnablementState' ) diff --git a/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 b/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 index b971ee6e..8578f177 100644 --- a/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 +++ b/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 @@ -11,4 +11,5 @@ ConvertFrom-StringData @' CheckingDevDriveEnablementMessage = Checking if the Dev Drive feature is available and enabled on the system. DevDriveFeatureNotImplementedError = Dev Drive feature is not implemented on this system. DevDriveMinimumSizeError = Dev Drive volumes must be 50Gb or more in size. + DevDriveOnlyAvailableForReFsError = "Only 'ReFS' is supported for Dev Drive volumes." '@ diff --git a/tests/Unit/DSC_Disk.Tests.ps1 b/tests/Unit/DSC_Disk.Tests.ps1 index 00cc64b2..a750f8a6 100644 --- a/tests/Unit/DSC_Disk.Tests.ps1 +++ b/tests/Unit/DSC_Disk.Tests.ps1 @@ -33,6 +33,7 @@ try { InModuleScope $script:dscResourceName { $script:testDriveLetter = 'G' + $script:testDriveLetterH = 'H' $script:testDiskNumber = 1 $script:testDiskUniqueId = 'TESTDISKUNIQUEID' $script:testDiskFriendlyName = 'TESTDISKFRIENDLYNAME' @@ -105,6 +106,18 @@ try PartitionStyle = 'GPT' } + $script:mockedDisk0GptForDevDrive = [pscustomobject] @{ + Number = $script:testDiskNumber + UniqueId = $script:testDiskUniqueId + FriendlyName = $script:testDiskFriendlyName + SerialNumber = $script:testDiskSerialNumber + Guid = $script:testDiskGptGuid + IsOffline = $false + IsReadOnly = $false + PartitionStyle = 'GPT' + Size = 100Gb + } + $script:mockedCim = [pscustomobject] @{ BlockSize = 4096 } @@ -118,6 +131,24 @@ try Type = 'Basic' } + $script:mockedPartitionSize40Gb = 40GB + + $script:mockedPartitionSize50Gb = 50GB + + $script:mockedPartitionWithGDriveletter = [pscustomobject] @{ + DriveLetter = [System.Char] $script:testDriveLetter + Size = $script:mockedPartitionSize50Gb + PartitionNumber = 1 + Type = 'Basic' + } + + $script:mockedPartitionWithHDriveLetter = [pscustomobject] @{ + DriveLetter = [System.Char] $script:testDriveLetterH + Size = $script:mockedPartitionSize50Gb + PartitionNumber = 1 + Type = 'Basic' + } + <# This condition seems to occur in some systems where the same partition is reported twice with the same drive letter. @@ -145,6 +176,14 @@ try IsReadOnly = $false } + $script:mockedPartitionNoDriveLetter50Gb = [pscustomobject] @{ + DriveLetter = [System.Char] $null + Size = $script:mockedPartitionSize50Gb + PartitionNumber = 1 + Type = 'Basic' + IsReadOnly = $false + } + $script:mockedPartitionNoDriveLetterReadOnly = [pscustomobject] @{ DriveLetter = [System.Char] $null Size = $script:mockedPartitionSize @@ -337,7 +376,11 @@ try [Parameter()] [Switch] - $Force + $Force, + + [Parameter()] + [System.Boolean] + $DevDrive ) } @@ -399,6 +442,25 @@ try ) } + Function Get-IsApiSetImplemented { + + [CmdletBinding()] + Param + ( + [OutputType([System.Boolean])] + [String] + $Contract + ) + } + + Function Get-DeveloperDriveEnablementState { + + [CmdletBinding()] + [OutputType([System.Enum])] + Param + () + } + Describe 'DSC_Disk\Get-TargetResource' { Context 'When online GPT disk with a partition/volume and correct Drive Letter assigned using Disk Number' { # verifiable (should be called) mocks @@ -2622,6 +2684,148 @@ try Assert-MockCalled -CommandName Clear-Disk -Exactly -Times 1 } } + + Context 'When Dev Drive flag enabled with online GPT disk with no partitions format volume is called with Dev Drive flag' { + # verifiable (should be called) mocks + Mock ` + -CommandName Get-DiskByIdentifier ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number ` + -MockWith { $script:mockedDisk0GptForDevDrive } ` + -Verifiable + + Mock ` + -CommandName Get-Partition ` + -Verifiable + + Mock ` + -CommandName New-Partition ` + -ParameterFilter { + $DriveLetter -eq $script:testDriveLetter + } ` + -MockWith { $script:mockedPartitionNoDriveLetter } ` + -Verifiable + + Mock ` + -CommandName Get-Volume ` + -MockWith { $script:mockedVolumeUnformatted } ` + -Verifiable + + Mock ` + -CommandName Format-Volume ` + -Verifiable + + Mock ` + -CommandName Set-Partition ` + -Verifiable + + # mocks that should not be called + Mock -CommandName Set-Disk + Mock -CommandName Initialize-Disk + + It 'Should not throw an exception' { + { + Set-TargetResource ` + -DiskId $script:mockedDisk0Gpt.Number ` + -Driveletter $script:testDriveLetter ` + -Size $script:mockedPartitionSize ` + -FSLabel 'NewLabel' ` + -FSFormat 'ReFS' ` + -DevDrive $true ` + -Verbose + } | Should -Not -Throw + } + + It 'Should call the correct mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 1 ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number + Assert-MockCalled -CommandName Set-Disk -Exactly -Times 0 + Assert-MockCalled -CommandName Initialize-Disk -Exactly -Times 0 + Assert-MockCalled -CommandName Get-Partition -Exactly -Times 4 + Assert-MockCalled -CommandName Get-Volume -Exactly -Times 1 + Assert-MockCalled -CommandName New-Partition -Exactly -Times 1 ` + -ParameterFilter { + $DriveLetter -eq $script:testDriveLetter + } + Assert-MockCalled -CommandName Format-Volume -Exactly -Times 1 ` + -ParameterFilter { + $DevDrive -eq $true + } + Assert-MockCalled -CommandName Set-Partition -Exactly -Times 1 + } + } + + Context 'When the user explicitly wants to create a new drive on unallocated space regardless of if there are partitions that match its size. ' { + # verifiable (should be called) mocks + Mock ` + -CommandName Get-DiskByIdentifier ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number ` + -MockWith { $script:mockedDisk0GptForDevDrive } ` + -Verifiable + + Mock ` + -CommandName Get-Partition ` + -MockWith { $script:mockedPartitionWithGDriveletter } ` + -Verifiable + + Mock ` + -CommandName New-Partition ` + -ParameterFilter { + $DriveLetter -eq $script:testDriveLetterH + } ` + -MockWith { $script:mockedPartitionNoDriveLetter50Gb } ` + -Verifiable + + Mock ` + -CommandName Get-Volume ` + -MockWith { $script:mockedVolumeUnformatted } ` + -Verifiable + + Mock ` + -CommandName Format-Volume ` + -Verifiable + + Mock ` + -CommandName Set-Partition ` + -Verifiable + + # mocks that should not be called + Mock -CommandName Set-Disk + Mock -CommandName Initialize-Disk + + It 'Should not throw an exception' { + { + Set-TargetResource ` + -DiskId $script:mockedDisk0Gpt.Number ` + -Driveletter $script:testDriveLetterH ` + -Size $script:mockedPartitionSize50Gb ` + -FSLabel 'NewLabel' ` + -FSFormat 'ReFS' ` + -DevDrive $true ` + -UseUnallocatedSpace $true ` + -Verbose + } | Should -Not -Throw + } + + It 'Should call the correct mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 1 ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number + Assert-MockCalled -CommandName Set-Disk -Exactly -Times 0 + Assert-MockCalled -CommandName Initialize-Disk -Exactly -Times 0 + Assert-MockCalled -CommandName Get-Partition -Exactly -Times 4 + Assert-MockCalled -CommandName Get-Volume -Exactly -Times 1 + Assert-MockCalled -CommandName New-Partition -Exactly -Times 1 ` + -ParameterFilter { + $DriveLetter -eq $script:testDriveLetterH + } + Assert-MockCalled -CommandName Format-Volume -Exactly -Times 1 ` + -ParameterFilter { + $DevDrive -eq $true + } + Assert-MockCalled -CommandName Set-Partition -Exactly -Times 1 + } + } } Describe 'DSC_Disk\Test-TargetResource' { diff --git a/tests/Unit/StorageDsc.Common.Tests.ps1 b/tests/Unit/StorageDsc.Common.Tests.ps1 index a83de198..e8995bcc 100644 --- a/tests/Unit/StorageDsc.Common.Tests.ps1 +++ b/tests/Unit/StorageDsc.Common.Tests.ps1 @@ -511,5 +511,185 @@ InModuleScope $script:subModuleName { -AccessPath @('\\?\Volume{905551f3-33a5-421d-ac24-c993fbfb3184}\', '\\?\Volume{99cf0194-ac45-4a23-b36e-3e458158a63e}\') | Should -Be $false } } + + Context 'Testing Dev Drive enablement when dev drive feature not implemented' { + # verifiable (should be called) mocks + + Mock ` + -CommandName Get-IsApiSetImplemented ` + -MockWith { return $false } ` + -ModuleName StorageDsc.Common ` + -Verifiable + + It 'Should throw with DevDriveFeatureNotImplementedError' { + { + Assert-DevDriveFeatureAvailable -Verbose + } | Should -Throw -ExpectedMessage $LocalizedData.DevDriveFeatureNotImplementedError + } + + It 'Should call the correct mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-IsApiSetImplemented -Exactly -Times 1 + } + } + + Context 'Testing Dev Drive enablement when dev drive feature is disabled by group policy' { + # verifiable (should be called) mocks + Get-DevDriveWin32HelperScript + $DevDriveEnablementType = [DevDrive.DevDriveHelper+DEVELOPER_DRIVE_ENABLEMENT_STATE] + + Mock ` + -CommandName Get-IsApiSetImplemented ` + -MockWith { return $true } ` + -ModuleName StorageDsc.Common ` + -Verifiable + + Mock ` + -CommandName Get-DeveloperDriveEnablementState ` + -MockWith { return $DevDriveEnablementType::DeveloperDriveDisabledByGroupPolicy } ` + -ModuleName StorageDsc.Common ` + -Verifiable + + It 'Should throw with DevDriveDisabledByGroupPolicyError' { + { + Assert-DevDriveFeatureAvailable -Verbose + } | Should -Throw -ExpectedMessage $LocalizedData.DevDriveDisabledByGroupPolicyError + } + + It 'Should call the correct mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-IsApiSetImplemented -Exactly -Times 1 + Assert-MockCalled -CommandName Get-DeveloperDriveEnablementState -Exactly -Times 1 + } + } + + Context 'Testing Dev Drive enablement when dev drive feature is disabled by system policy' { + # verifiable (should be called) mocks + Mock ` + -CommandName Get-IsApiSetImplemented ` + -MockWith { return $true } ` + -ModuleName StorageDsc.Common ` + -Verifiable + + Mock ` + -CommandName Get-DeveloperDriveEnablementState ` + -MockWith { return $DevDriveEnablementType::DeveloperDriveDisabledBySystemPolicy } ` + -ModuleName StorageDsc.Common ` + -Verifiable + + It 'Should throw with DeveloperDriveDisabledBySystemPolicy' { + { + Assert-DevDriveFeatureAvailable -Verbose + } | Should -Throw -ExpectedMessage $LocalizedData.DeveloperDriveDisabledBySystemPolicy + } + + It 'Should call the correct mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-IsApiSetImplemented -Exactly -Times 1 + Assert-MockCalled -CommandName Get-DeveloperDriveEnablementState -Exactly -Times 1 + } + } + + Context 'Testing Dev Drive enablement when enablement state is unknown' { + # verifiable (should be called) mocks + Mock ` + -CommandName Get-IsApiSetImplemented ` + -MockWith { return $true } ` + -ModuleName StorageDsc.Common ` + -Verifiable + + Mock ` + -CommandName Get-DeveloperDriveEnablementState ` + -MockWith { return $DevDriveEnablementType::DeveloperDriveEnablementStateError } ` + -ModuleName StorageDsc.Common ` + -Verifiable + + It 'Should throw with DevDriveEnablementUnknownError' { + { + Assert-DevDriveFeatureAvailable -Verbose + } | Should -Throw -ExpectedMessage $LocalizedData.DevDriveEnablementUnknownError + } + + It 'Should call the correct mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-IsApiSetImplemented -Exactly -Times 1 + Assert-MockCalled -CommandName Get-DeveloperDriveEnablementState -Exactly -Times 1 + } + } + + Context 'Testing Dev Drive enablement when enablement state is set to enabled' { + # verifiable (should be called) mocks + Mock ` + -CommandName Get-IsApiSetImplemented ` + -MockWith { return $true } ` + -ModuleName StorageDsc.Common ` + -Verifiable + + Mock ` + -CommandName Get-DeveloperDriveEnablementState ` + -MockWith { return $DevDriveEnablementType::DeveloperDriveEnabled } ` + -ModuleName StorageDsc.Common ` + -Verifiable + + It 'Should not throw' { + { + Assert-DevDriveFeatureAvailable -Verbose + } | Should -Not -Throw + } + + It 'Should call the correct mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-IsApiSetImplemented -Exactly -Times 1 + Assert-MockCalled -CommandName Get-DeveloperDriveEnablementState -Exactly -Times 1 + } + } + + Context 'Testing that only ReFS file system allowed in Assert-DevDriveFormatOnReFsFileSystemOnly ' { + # verifiable (should be called) mocks + + $errorRecord = Get-InvalidArgumentRecord ` + -Message ($script:localizedData.DevDriveOnlyAvailableForReFsError ) ` + -ArgumentName 'FSFormat' + + It 'Should throw invalid argument error if a filesystem other than ReFS is passed in' { + { + Assert-DevDriveFormatOnReFsFileSystemOnly -FSFormat "test" -Verbose + } | Should -Throw $errorRecord + } + } + + Context 'Testing Exception not thrown in Assert-DevDriveFormatOnReFsFileSystemOnly when ReFS file system passed in' { + # verifiable (should be called) mocks + + It 'Should not throw invalid argument error if ReFS filesystem is passed in' { + { + Assert-DevDriveFormatOnReFsFileSystemOnly -FSFormat "ReFS" -Verbose + } | Should -Not -Throw + } + } + + Context 'Testing Exception thrown in Assert-DevDriveSizeMeetsMinimumRequirement when size does not meet the minimum size for Dev Drives' { + # verifiable (should be called) mocks + + $errorRecord = Get-InvalidArgumentRecord ` + -Message $($script:localizedData.DevDriveMinimumSizeError) ` + -ArgumentName 'Size' + + It 'Should throw invalid argument error if size less than 50Gb' { + { + Assert-DevDriveSizeMeetsMinimumRequirement -Size 40Gb -Verbose + } | Should -Throw $errorRecord + } + } + + Context 'Testing Exception not thrown in Assert-DevDriveSizeMeetsMinimumRequirement when size does meet the minimum size for Dev Drives' { + # verifiable (should be called) mocks + + It 'Should not throw invalid argument error if size greater than or equal to 50 Gb' { + { + Assert-DevDriveSizeMeetsMinimumRequirement -Size 50Gb -Verbose + } | Should -Not -Throw + } + } } } From 5b38fb14e0e938b9651757e643c7ab8419965e87 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Fri, 22 Sep 2023 02:48:07 -0700 Subject: [PATCH 07/21] add more tests --- CHANGELOG.md | 2 +- source/DSCResources/DSC_Disk/DSC_Disk.psm1 | 22 ++++--- .../StorageDsc.Common/StorageDsc.Common.psm1 | 37 ++++++++++-- .../en-US/StorageDsc.Common.strings.psd1 | 1 + tests/Unit/DSC_Disk.Tests.ps1 | 2 +- tests/Unit/StorageDsc.Common.Tests.ps1 | 60 +++++++++++++++---- 6 files changed, 98 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb88bf7c..e191f95e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Updated DSC_Disk to allow volumes to be formatted as Dev Drives +- Updated DSC_Disk to allow volumes to be formatted as Dev Drives: Fixes #276 ## [5.1.0] - 2023-02-22 diff --git a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 index 27fa7c9a..d495f02f 100644 --- a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 +++ b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 @@ -791,14 +791,6 @@ function Test-TargetResource # Validate the DriveLetter parameter $DriveLetter = Assert-DriveLetterValid -DriveLetter $DriveLetter - # Check Dev Drive requirements if DevDrive parameter supplied - if ($PSBoundParameters.ContainsKey('DevDrive')) - { - Assert-DevDriveFeatureAvailable - Assert-DevDriveFormatOnReFsFileSystemOnly -FSFormat $FSFormat - Assert-DevDriveSizeMeetsMinimumRequirement -Size $Size - } - Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " $($script:localizedData.CheckDiskInitializedMessage -f $DiskIdType, $DiskId) @@ -819,6 +811,20 @@ function Test-TargetResource return $false } # if + # Check Dev Drive requirements if DevDrive parameter supplied + if ($PSBoundParameters.ContainsKey('DevDrive')) + { + Assert-DevDriveFeatureAvailable + Assert-DevDriveFormatOnReFsFileSystemOnly -FSFormat $FSFormat + + # Get largest contiguous free space that is possible to create a partition with + $CurrentDiskFreeSpace = [Math]::Round($disk.LargestFreeExtent / 1GB, 2) + Assert-DevDriveSizeMeetsMinimumRequirement ` + -UserDesiredSize $Size ` + -CurrentDiskFreeSpace $CurrentDiskFreeSpace ` + -DiskNumber $Disk.Number + } + if ($disk.IsOffline) { Write-Verbose -Message ( @( diff --git a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 index 22c0a7f3..ca24c80d 100644 --- a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 +++ b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 @@ -392,8 +392,11 @@ function Assert-DevDriveFormatOnReFsFileSystemOnly Validates that the user entered a size greater than the minimum for Dev Drive volumes. (The minimum is 50 Gb) - .PARAMETER FSFormat - Specifies the size the user wants to create the Dev Drive with. + .PARAMETER UserDesiredSize + Specifies the size the user wants to create the Dev Drive volume with. + + .PARAMETER CurrentDiskFreeSpace + Specifies the maximum free space that can be used to create a partition on the disk with #> function Assert-DevDriveSizeMeetsMinimumRequirement { @@ -402,15 +405,37 @@ function Assert-DevDriveSizeMeetsMinimumRequirement ( [Parameter(Mandatory = $true)] [System.UInt64] - $Size + $UserDesiredSize, + + [Parameter(Mandatory = $true)] + [System.String] + $CurrentDiskFreeSpace, + + [Parameter(Mandatory = $true)] + [System.UInt32] + $DiskNumber ) - # 50 Gb is the minimum size for Dev Drive volumes - if ($Size -lt 50Gb) + <# + 50 Gb is the minimum size for Dev Drive volumes. When size is 0 the user wants to use all + the available space on the disk + #> + if ($UserDesiredSize -eq 0 -bor $UserDesiredSize -ge 50Gb) + { + if ($UserDesiredSize -gt $CurrentDiskFreeSpace) + { + New-InvalidArgumentException ` + -Message $($script:localizedData.DevDriveNotEnoughSpaceToCreateDevDriveError -f ` + $DiskNumber, $UserDesiredSize, $CurrentDiskFreeSpace) ` + -ArgumentName 'UserDesiredSize' + } + + } + elseif ($UserDesiredSize -lt 50Gb) { New-InvalidArgumentException ` -Message $($script:localizedData.DevDriveMinimumSizeError) ` - -ArgumentName 'Size' + -ArgumentName 'UserDesiredSize' } } # end function Assert-DevDriveSizeMeetsMinimumRequirement diff --git a/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 b/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 index 8578f177..748288f2 100644 --- a/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 +++ b/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 @@ -11,5 +11,6 @@ ConvertFrom-StringData @' CheckingDevDriveEnablementMessage = Checking if the Dev Drive feature is available and enabled on the system. DevDriveFeatureNotImplementedError = Dev Drive feature is not implemented on this system. DevDriveMinimumSizeError = Dev Drive volumes must be 50Gb or more in size. + DevDriveNotEnoughSpaceToCreateDevDriveError = Unable to create Dev Drive. There is not enough free space '{2}' to create a partition of size '{1}' on disk '{0}'. DevDriveOnlyAvailableForReFsError = "Only 'ReFS' is supported for Dev Drive volumes." '@ diff --git a/tests/Unit/DSC_Disk.Tests.ps1 b/tests/Unit/DSC_Disk.Tests.ps1 index a750f8a6..04214919 100644 --- a/tests/Unit/DSC_Disk.Tests.ps1 +++ b/tests/Unit/DSC_Disk.Tests.ps1 @@ -2755,7 +2755,7 @@ try } } - Context 'When the user explicitly wants to create a new drive on unallocated space regardless of if there are partitions that match its size. ' { + Context 'When the user explicitly wants to create a new drive on unallocated space regardless of whether there is a partition that matches the size parameter or not. ' { # verifiable (should be called) mocks Mock ` -CommandName Get-DiskByIdentifier ` diff --git a/tests/Unit/StorageDsc.Common.Tests.ps1 b/tests/Unit/StorageDsc.Common.Tests.ps1 index e8995bcc..0a8fd78d 100644 --- a/tests/Unit/StorageDsc.Common.Tests.ps1 +++ b/tests/Unit/StorageDsc.Common.Tests.ps1 @@ -553,7 +553,7 @@ InModuleScope $script:subModuleName { It 'Should throw with DevDriveDisabledByGroupPolicyError' { { Assert-DevDriveFeatureAvailable -Verbose - } | Should -Throw -ExpectedMessage $LocalizedData.DevDriveDisabledByGroupPolicyError + } | Should -Throw -ExpectedMessage $LocalizedData.DevDriveDisabledByGroupPolicyError } It 'Should call the correct mocks' { @@ -580,7 +580,7 @@ InModuleScope $script:subModuleName { It 'Should throw with DeveloperDriveDisabledBySystemPolicy' { { Assert-DevDriveFeatureAvailable -Verbose - } | Should -Throw -ExpectedMessage $LocalizedData.DeveloperDriveDisabledBySystemPolicy + } | Should -Throw -ExpectedMessage $LocalizedData.DeveloperDriveDisabledBySystemPolicy } It 'Should call the correct mocks' { @@ -607,7 +607,7 @@ InModuleScope $script:subModuleName { It 'Should throw with DevDriveEnablementUnknownError' { { Assert-DevDriveFeatureAvailable -Verbose - } | Should -Throw -ExpectedMessage $LocalizedData.DevDriveEnablementUnknownError + } | Should -Throw -ExpectedMessage $LocalizedData.DevDriveEnablementUnknownError } It 'Should call the correct mocks' { @@ -668,26 +668,66 @@ InModuleScope $script:subModuleName { } } - Context 'Testing Exception thrown in Assert-DevDriveSizeMeetsMinimumRequirement when size does not meet the minimum size for Dev Drives' { + Context 'Testing Exception thrown in Assert-DevDriveSizeMeetsMinimumRequirement when UserDesiredSize does not meet the minimum size for Dev Drives' { # verifiable (should be called) mocks $errorRecord = Get-InvalidArgumentRecord ` -Message $($script:localizedData.DevDriveMinimumSizeError) ` - -ArgumentName 'Size' + -ArgumentName 'UserDesiredSize' - It 'Should throw invalid argument error if size less than 50Gb' { + It 'Should throw invalid argument error if UserDesiredSize less than 50Gb' { { - Assert-DevDriveSizeMeetsMinimumRequirement -Size 40Gb -Verbose + Assert-DevDriveSizeMeetsMinimumRequirement ` + -UserDesiredSize 10Gb ` + -CurrentDiskFreeSpace 50Gb ` + -DiskNumber 1 ` + -Verbose } | Should -Throw $errorRecord } } - Context 'Testing Exception not thrown in Assert-DevDriveSizeMeetsMinimumRequirement when size does meet the minimum size for Dev Drives' { + Context 'Testing Exception thrown in Assert-DevDriveSizeMeetsMinimumRequirement when disk free space less than users desired size' { # verifiable (should be called) mocks - It 'Should not throw invalid argument error if size greater than or equal to 50 Gb' { + $errorRecord = Get-InvalidArgumentRecord ` + -Message $($script:localizedData.DevDriveNotEnoughSpaceToCreateDevDriveError -f 1, 50Gb, 40Gb) ` + -ArgumentName 'UserDesiredSize' + + It 'Should throw invalid argument error if UserDesiredSize greater than CurrentDiskFreeSpace' { + { + Assert-DevDriveSizeMeetsMinimumRequirement ` + -UserDesiredSize 50Gb ` + -CurrentDiskFreeSpace 40Gb ` + -DiskNumber 1 ` + -Verbose + } | Should -Throw $errorRecord + } + } + + Context 'Testing Exception not thrown in Assert-DevDriveSizeMeetsMinimumRequirement when UserDesiredSize does meet the minimum size for Dev Drives' { + # verifiable (should be called) mocks + + It 'Should not throw invalid argument error if UserDesiredSize greater than or equal to 50 Gb' { + { + Assert-DevDriveSizeMeetsMinimumRequirement ` + -UserDesiredSize 50Gb ` + -CurrentDiskFreeSpace 50Gb ` + -DiskNumber 1 ` + -Verbose + } | Should -Not -Throw + } + } + + Context 'Testing Exception not thrown in Assert-DevDriveSizeMeetsMinimumRequirement when UserDesiredSize is 0' { + # verifiable (should be called) mocks + + It 'Should not throw invalid argument error if CurrentDiskFreeSpace greater than or equal to 50 Gb' { { - Assert-DevDriveSizeMeetsMinimumRequirement -Size 50Gb -Verbose + Assert-DevDriveSizeMeetsMinimumRequirement ` + -UserDesiredSize 0Gb ` + -CurrentDiskFreeSpace 50Gb ` + -DiskNumber 1 ` + -Verbose } | Should -Not -Throw } } From 75ae5100a2fb88ca32ff910fb6a547027ccce6ba Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Fri, 22 Sep 2023 02:48:34 -0700 Subject: [PATCH 08/21] add example --- .../3-Disk_InitializeDiskWithADevDrive.ps1 | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 source/Examples/Resources/Disk/3-Disk_InitializeDiskWithADevDrive.ps1 diff --git a/source/Examples/Resources/Disk/3-Disk_InitializeDiskWithADevDrive.ps1 b/source/Examples/Resources/Disk/3-Disk_InitializeDiskWithADevDrive.ps1 new file mode 100644 index 00000000..d40cb313 --- /dev/null +++ b/source/Examples/Resources/Disk/3-Disk_InitializeDiskWithADevDrive.ps1 @@ -0,0 +1,69 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID 3f629ab7-358f-4d82-8c0a-556e32514e3e +.AUTHOR DSC Community +.COMPANYNAME DSC Community +.COPYRIGHT Copyright the DSC Community contributors. All rights reserved. +.TAGS DSCConfiguration +.LICENSEURI https://github.com/dsccommunity/StorageDsc/blob/main/LICENSE +.PROJECTURI https://github.com/dsccommunity/StorageDsc +.ICONURI +.EXTERNALMODULEDEPENDENCIES +.REQUIREDSCRIPTS +.EXTERNALSCRIPTDEPENDENCIES +.RELEASENOTES First version. +.PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core +#> + +#Requires -module StorageDsc + +<# + .DESCRIPTION + This configuration will wait for disk 2 to become available, and then make the disk available as + two new Dev Drive volumes, 'E' and 'F', with 'F' using all available space after 'E' has been + created. +#> +Configuration Disk_InitializeDiskWithADevDrive +{ + Import-DSCResource -ModuleName StorageDsc + + Node localhost + { + WaitForDisk Disk2 + { + DiskId = '5E1E50A401000000001517FFFF0AEB84' # Disk 2 + DiskIdType = 'UniqueId' + RetryIntervalSec = 60 + RetryCount = 60 + } + + # Will create a Dev Drive of 50Gb requiring the disk to have 50Gb of unallocated space. + Disk DevDriveVolume1 + { + DiskId = ‘5E1E50A401000000001517FFFF0AEB84’ + DiskIdType = 'UniqueId' + DriveLetter = 'E' + FSFormat = 'ReFS' + FSLabel = 'DevDrive' + DevDrive = true + Size = 50Gb + UseUnallocatedSpace = true + DependsOn = '[WaitForDisk]Disk2' + } + + <# + Will attempt to create a Dev Drive volume using the rest of the space on the disk assuming + that the rest of the space is greater than the minimum size for Dev Drive volumes (50Gb). + #> + Disk DevDriveVolume2 + { + DiskId = ‘5E1E50A401000000001517FFFF0AEB84’ + DiskIdType = 'UniqueId' + DriveLetter = 'F' + FSFormat = 'ReFS' + FSLabel = 'DevDrive' + DevDrive = true + DependsOn = '[Disk]DevDriveVolume1' + } + } +} From 322e5e7541ee7ec1b06fefccb0982c9d5616c5e8 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Fri, 22 Sep 2023 13:04:44 -0700 Subject: [PATCH 09/21] add more tests and comments --- source/DSCResources/DSC_Disk/DSC_Disk.psm1 | 23 +++++-- .../StorageDsc.Common/StorageDsc.Common.psm1 | 65 ++++++++++++++----- .../en-US/StorageDsc.Common.strings.psd1 | 2 +- tests/Unit/StorageDsc.Common.Tests.ps1 | 56 +++++++++++----- 4 files changed, 104 insertions(+), 42 deletions(-) diff --git a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 index d495f02f..2fdb5f36 100644 --- a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 +++ b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 @@ -811,18 +811,27 @@ function Test-TargetResource return $false } # if - # Check Dev Drive requirements if DevDrive parameter supplied + # Check Dev Drive feature is enabled and that the user inputted ReFS as the file system. if ($PSBoundParameters.ContainsKey('DevDrive')) { Assert-DevDriveFeatureAvailable Assert-DevDriveFormatOnReFsFileSystemOnly -FSFormat $FSFormat - # Get largest contiguous free space that is possible to create a partition with - $CurrentDiskFreeSpace = [Math]::Round($disk.LargestFreeExtent / 1GB, 2) - Assert-DevDriveSizeMeetsMinimumRequirement ` - -UserDesiredSize $Size ` - -CurrentDiskFreeSpace $CurrentDiskFreeSpace ` - -DiskNumber $Disk.Number + $tempPartition = Get-Partition ` + -DriveLetter $DriveLetter ` + -ErrorAction SilentlyContinue | Select-Object -First 1 + + # User is attempting to create a new Dev Drive volume in a new partition + if ($null -eq $tempPartition) + { + # Get largest contiguous free space that is possible to create a partition with + Assert-DiskHasEnoughSpaceToCreateDevDrive ` + -UserDesiredSize $Size ` + -CurrentDiskFreeSpace $disk.LargestFreeExtent ` + -DiskNumber $Disk.Number + } + + Assert-DevDriveSizeMeetsMinimumRequirement -UserDesiredSize $Size } if ($disk.IsOffline) diff --git a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 index ca24c80d..2b7eb3a1 100644 --- a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 +++ b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 @@ -250,7 +250,6 @@ function Get-DevDriveWin32HelperScript [DllImport("api-ms-win-core-sysinfo-l1-2-6.dll")] public static extern DEVELOPER_DRIVE_ENABLEMENT_STATE GetDeveloperDriveEnablementState(); - '@ if (([System.Management.Automation.PSTypeName]'DevDrive.DevDriveHelper').Type) { @@ -389,16 +388,18 @@ function Assert-DevDriveFormatOnReFsFileSystemOnly <# .SYNOPSIS - Validates that the user entered a size greater than the minimum for Dev Drive volumes. - (The minimum is 50 Gb) + Validates that the user has enough space on the disk to create a Dev Drive volume. .PARAMETER UserDesiredSize Specifies the size the user wants to create the Dev Drive volume with. .PARAMETER CurrentDiskFreeSpace - Specifies the maximum free space that can be used to create a partition on the disk with + Specifies the maximum free space that can be used to create a partition on the disk with. + + .PARAMETER DiskNumber + Specifies the the disk number the user what to create the Dev Drive volume inside. #> -function Assert-DevDriveSizeMeetsMinimumRequirement +function Assert-DiskHasEnoughSpaceToCreateDevDrive { [CmdletBinding()] param @@ -408,7 +409,7 @@ function Assert-DevDriveSizeMeetsMinimumRequirement $UserDesiredSize, [Parameter(Mandatory = $true)] - [System.String] + [System.UInt64] $CurrentDiskFreeSpace, [Parameter(Mandatory = $true)] @@ -418,20 +419,47 @@ function Assert-DevDriveSizeMeetsMinimumRequirement <# 50 Gb is the minimum size for Dev Drive volumes. When size is 0 the user wants to use all - the available space on the disk + the available space on the disk so we will check if they have at least 50 Gb of space available. #> - if ($UserDesiredSize -eq 0 -bor $UserDesiredSize -ge 50Gb) + $minimumSizeForDevDriveVolumes = 50Gb + $UserDesiredSize = ($UserDesiredSize) ? $UserDesiredSize : $minimumSizeForDevDriveVolumes + if ($UserDesiredSize -gt $CurrentDiskFreeSpace) { - if ($UserDesiredSize -gt $CurrentDiskFreeSpace) - { - New-InvalidArgumentException ` - -Message $($script:localizedData.DevDriveNotEnoughSpaceToCreateDevDriveError -f ` - $DiskNumber, $UserDesiredSize, $CurrentDiskFreeSpace) ` - -ArgumentName 'UserDesiredSize' - } - + $DesiredSizeInGb = [Math]::Round($UserDesiredSize / 1GB, 2) + $CurrentDiskFreeSpaceInGb = [Math]::Round($CurrentDiskFreeSpace / 1GB, 2) + New-InvalidArgumentException ` + -Message $($script:localizedData.DevDriveNotEnoughSpaceToCreateDevDriveError -f ` + $DiskNumber, $DesiredSizeInGb, $CurrentDiskFreeSpaceInGb) ` + -ArgumentName 'UserDesiredSize' } - elseif ($UserDesiredSize -lt 50Gb) +} # end function Assert-DiskHasEnoughSpaceToCreateDevDrive + +<# + .SYNOPSIS + Validates that the user entered a size greater than the minimum for Dev Drive volumes. + (The minimum is 50 Gb) + + .PARAMETER UserDesiredSize + Specifies the size the user wants to create the Dev Drive volume with. + +#> +function Assert-DevDriveSizeMeetsMinimumRequirement +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.UInt64] + $UserDesiredSize + + ) + + <# + 50 Gb is the minimum size for Dev Drive volumes. When UserDesiredSize is provided + This means the user wants to create a Dev Drive volume using all the available space + on the disk. We cover this case in Assert-DiskHasEnoughSpaceToCreateDevDrive + #> + if ($UserDesiredSize -and $UserDesiredSize -lt 50Gb) { New-InvalidArgumentException ` -Message $($script:localizedData.DevDriveMinimumSizeError) ` @@ -451,5 +479,6 @@ Export-ModuleMember -Function @( 'Assert-DevDriveSizeMeetsMinimumRequirement', 'Get-DevDriveWin32HelperScript', 'Get-IsApiSetImplemented', - 'Get-DeveloperDriveEnablementState' + 'Get-DeveloperDriveEnablementState', + 'Assert-DiskHasEnoughSpaceToCreateDevDrive' ) diff --git a/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 b/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 index 748288f2..1a3e9214 100644 --- a/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 +++ b/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 @@ -11,6 +11,6 @@ ConvertFrom-StringData @' CheckingDevDriveEnablementMessage = Checking if the Dev Drive feature is available and enabled on the system. DevDriveFeatureNotImplementedError = Dev Drive feature is not implemented on this system. DevDriveMinimumSizeError = Dev Drive volumes must be 50Gb or more in size. - DevDriveNotEnoughSpaceToCreateDevDriveError = Unable to create Dev Drive. There is not enough free space '{2}' to create a partition of size '{1}' on disk '{0}'. + DevDriveNotEnoughSpaceToCreateDevDriveError = There is not enough unallocated space '{2}Gb' to create a Dev Drive volume of size '{1}Gb' on disk '{0}'. DevDriveOnlyAvailableForReFsError = "Only 'ReFS' is supported for Dev Drive volumes." '@ diff --git a/tests/Unit/StorageDsc.Common.Tests.ps1 b/tests/Unit/StorageDsc.Common.Tests.ps1 index 0a8fd78d..6c5e4d00 100644 --- a/tests/Unit/StorageDsc.Common.Tests.ps1 +++ b/tests/Unit/StorageDsc.Common.Tests.ps1 @@ -25,6 +25,17 @@ Import-Module $script:subModuleFile -Force -ErrorAction Stop Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') InModuleScope $script:subModuleName { + + $script:mockedSizesForDevDriveScenario = [pscustomobject] @{ + UserDesired0Gb = 0Gb + UserDesired10Gb = 10Gb + UserDesired50Gb = 50Gb + CurrentDiskFreeSpace40Gb = 40Gb + CurrentDiskFreeSpace50Gb = 50Gb + } + + $script:mockedDiskNumber = 1 + function Get-Disk { [CmdletBinding()] @@ -678,41 +689,56 @@ InModuleScope $script:subModuleName { It 'Should throw invalid argument error if UserDesiredSize less than 50Gb' { { Assert-DevDriveSizeMeetsMinimumRequirement ` - -UserDesiredSize 10Gb ` - -CurrentDiskFreeSpace 50Gb ` - -DiskNumber 1 ` + -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired10Gb ` -Verbose } | Should -Throw $errorRecord } } - Context 'Testing Exception thrown in Assert-DevDriveSizeMeetsMinimumRequirement when disk free space less than users desired size' { + Context 'Testing Exception thrown in Assert-DiskHasEnoughSpaceToCreateDevDrive when disk free space less than users desired size' { # verifiable (should be called) mocks + $userDesiredSizeInGb = [Math]::Round($mockedSizesForDevDriveScenario.UserDesired50Gb / 1GB, 2) + $currentDiskFreeSpaceInGb = [Math]::Round($mockedSizesForDevDriveScenario.CurrentDiskFreeSpace40Gb / 1GB, 2) $errorRecord = Get-InvalidArgumentRecord ` - -Message $($script:localizedData.DevDriveNotEnoughSpaceToCreateDevDriveError -f 1, 50Gb, 40Gb) ` + -Message $($script:localizedData.DevDriveNotEnoughSpaceToCreateDevDriveError -f ` + $mockedDiskNumber, ` + $userDesiredSizeInGb, ` + $currentDiskFreeSpaceInGb) ` -ArgumentName 'UserDesiredSize' It 'Should throw invalid argument error if UserDesiredSize greater than CurrentDiskFreeSpace' { { - Assert-DevDriveSizeMeetsMinimumRequirement ` - -UserDesiredSize 50Gb ` - -CurrentDiskFreeSpace 40Gb ` - -DiskNumber 1 ` + Assert-DiskHasEnoughSpaceToCreateDevDrive ` + -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired50Gb ` + -CurrentDiskFreeSpace $mockedSizesForDevDriveScenario.CurrentDiskFreeSpace40Gb ` + -DiskNumber $mockedDiskNumber ` -Verbose } | Should -Throw $errorRecord } } + Context 'Testing Exception not thrown in Assert-DiskHasEnoughSpaceToCreateDevDrive when disk free space greater than or equal to users desired size' { + # verifiable (should be called) mocks + + It 'Should not throw invalid argument error if CurrentDiskFreeSpace greater than or equal to UserDesiredSize' { + { + Assert-DiskHasEnoughSpaceToCreateDevDrive ` + -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired50Gb ` + -CurrentDiskFreeSpace $mockedSizesForDevDriveScenario.CurrentDiskFreeSpace50Gb ` + -DiskNumber $mockedDiskNumber ` + -Verbose + } | Should -Not -Throw + } + } + Context 'Testing Exception not thrown in Assert-DevDriveSizeMeetsMinimumRequirement when UserDesiredSize does meet the minimum size for Dev Drives' { # verifiable (should be called) mocks It 'Should not throw invalid argument error if UserDesiredSize greater than or equal to 50 Gb' { { Assert-DevDriveSizeMeetsMinimumRequirement ` - -UserDesiredSize 50Gb ` - -CurrentDiskFreeSpace 50Gb ` - -DiskNumber 1 ` + -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired50Gb ` -Verbose } | Should -Not -Throw } @@ -721,12 +747,10 @@ InModuleScope $script:subModuleName { Context 'Testing Exception not thrown in Assert-DevDriveSizeMeetsMinimumRequirement when UserDesiredSize is 0' { # verifiable (should be called) mocks - It 'Should not throw invalid argument error if CurrentDiskFreeSpace greater than or equal to 50 Gb' { + It 'Should not throw invalid argument error if UserDesiredSize is 0' { { Assert-DevDriveSizeMeetsMinimumRequirement ` - -UserDesiredSize 0Gb ` - -CurrentDiskFreeSpace 50Gb ` - -DiskNumber 1 ` + -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired0Gb ` -Verbose } | Should -Not -Throw } From d3221c202672164e5c45f357831d74d50246c1f0 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Fri, 22 Sep 2023 14:23:52 -0700 Subject: [PATCH 10/21] update localized strings --- .../StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 b/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 index 1a3e9214..ea968317 100644 --- a/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 +++ b/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 @@ -10,7 +10,7 @@ ConvertFrom-StringData @' DevDriveEnabledMessage = Dev Drive feature is enabled on this system. CheckingDevDriveEnablementMessage = Checking if the Dev Drive feature is available and enabled on the system. DevDriveFeatureNotImplementedError = Dev Drive feature is not implemented on this system. - DevDriveMinimumSizeError = Dev Drive volumes must be 50Gb or more in size. - DevDriveNotEnoughSpaceToCreateDevDriveError = There is not enough unallocated space '{2}Gb' to create a Dev Drive volume of size '{1}Gb' on disk '{0}'. + DevDriveMinimumSizeError = Dev Drive volumes must be 50 Gb or more in size. + DevDriveNotEnoughSpaceToCreateDevDriveError = There is not enough unallocated space '{2} Gb' to create a Dev Drive volume of size '{1} Gb' on disk '{0}'. DevDriveOnlyAvailableForReFsError = "Only 'ReFS' is supported for Dev Drive volumes." '@ From fff5bcba5857444f4e0bd9afc9bd493141e017d9 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Fri, 22 Sep 2023 14:55:48 -0700 Subject: [PATCH 11/21] update apostrophes in dev drive example --- .../Resources/Disk/3-Disk_InitializeDiskWithADevDrive.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/Examples/Resources/Disk/3-Disk_InitializeDiskWithADevDrive.ps1 b/source/Examples/Resources/Disk/3-Disk_InitializeDiskWithADevDrive.ps1 index d40cb313..52b745f4 100644 --- a/source/Examples/Resources/Disk/3-Disk_InitializeDiskWithADevDrive.ps1 +++ b/source/Examples/Resources/Disk/3-Disk_InitializeDiskWithADevDrive.ps1 @@ -40,7 +40,7 @@ Configuration Disk_InitializeDiskWithADevDrive # Will create a Dev Drive of 50Gb requiring the disk to have 50Gb of unallocated space. Disk DevDriveVolume1 { - DiskId = ‘5E1E50A401000000001517FFFF0AEB84’ + DiskId = '5E1E50A401000000001517FFFF0AEB84' DiskIdType = 'UniqueId' DriveLetter = 'E' FSFormat = 'ReFS' @@ -57,7 +57,7 @@ Configuration Disk_InitializeDiskWithADevDrive #> Disk DevDriveVolume2 { - DiskId = ‘5E1E50A401000000001517FFFF0AEB84’ + DiskId = '5E1E50A401000000001517FFFF0AEB84' DiskIdType = 'UniqueId' DriveLetter = 'F' FSFormat = 'ReFS' From c326dcbac8738cfde105303d00e15f094afee500 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Fri, 22 Sep 2023 15:14:57 -0700 Subject: [PATCH 12/21] remove ternary operator that doesn't work in ps5.1 --- source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 index 2b7eb3a1..22ec2b14 100644 --- a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 +++ b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 @@ -421,8 +421,11 @@ function Assert-DiskHasEnoughSpaceToCreateDevDrive 50 Gb is the minimum size for Dev Drive volumes. When size is 0 the user wants to use all the available space on the disk so we will check if they have at least 50 Gb of space available. #> - $minimumSizeForDevDriveVolumes = 50Gb - $UserDesiredSize = ($UserDesiredSize) ? $UserDesiredSize : $minimumSizeForDevDriveVolumes + if (-not $UserDesiredSize) + { + $UserDesiredSize = 50Gb + } + if ($UserDesiredSize -gt $CurrentDiskFreeSpace) { $DesiredSizeInGb = [Math]::Round($UserDesiredSize / 1GB, 2) From aa38265d5a3919ed2f620f6dbd08e88d3da24ee7 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Mon, 25 Sep 2023 19:42:13 -0700 Subject: [PATCH 13/21] update tests and fix hqrm errors --- source/DSCResources/DSC_Disk/DSC_Disk.psm1 | 25 +-- .../3-Disk_InitializeDiskWithADevDrive.ps1 | 14 +- .../StorageDsc.Common/StorageDsc.Common.psm1 | 53 +++-- tests/Unit/DSC_Disk.Tests.ps1 | 205 +++++++++++++++++- tests/Unit/StorageDsc.Common.Tests.ps1 | 166 ++++++++++---- 5 files changed, 368 insertions(+), 95 deletions(-) diff --git a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 index 2fdb5f36..356a8dfe 100644 --- a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 +++ b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 @@ -51,14 +51,6 @@ $script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' Specifies if the disks partition schema should be removed entirely, even if data and OEM partitions are present. Only possible with AllowDestructive enabled. This parameter is not used in Get-TargetResource. - - .PARAMETER DevDrive - Specifies if the volume should be formatted as a Dev Drive. - This parameter is not used in Get-TargetResource. - - .PARAMETER UseUnallocatedSpace - Specifies that a new partition and volume should be formatted onto unallocated space on the disk. - This parameter is not used in Get-TargetResource. #> function Get-TargetResource { @@ -107,15 +99,7 @@ function Get-TargetResource [Parameter()] [System.Boolean] - $ClearDisk, - - [Parameter()] - [System.Boolean] - $DevDrive, - - [Parameter()] - [System.Boolean] - $UseUnallocatedSpace + $ClearDisk ) Write-Verbose -Message ( @( @@ -152,8 +136,6 @@ function Get-TargetResource FSLabel = $volume.FileSystemLabel AllocationUnitSize = $blockSize FSFormat = $volume.FileSystem - DevDrive = $DevDrive - UseUnallocatedSpace = $UseUnallocatedSpace } } # Get-TargetResource @@ -824,7 +806,10 @@ function Test-TargetResource # User is attempting to create a new Dev Drive volume in a new partition if ($null -eq $tempPartition) { - # Get largest contiguous free space that is possible to create a partition with + <# + Get the largest contiguous free space that is possible to create a partition with + and see if its possible to create a Dev Drive using that space. + #> Assert-DiskHasEnoughSpaceToCreateDevDrive ` -UserDesiredSize $Size ` -CurrentDiskFreeSpace $disk.LargestFreeExtent ` diff --git a/source/Examples/Resources/Disk/3-Disk_InitializeDiskWithADevDrive.ps1 b/source/Examples/Resources/Disk/3-Disk_InitializeDiskWithADevDrive.ps1 index 52b745f4..ebeb5c70 100644 --- a/source/Examples/Resources/Disk/3-Disk_InitializeDiskWithADevDrive.ps1 +++ b/source/Examples/Resources/Disk/3-Disk_InitializeDiskWithADevDrive.ps1 @@ -45,16 +45,16 @@ Configuration Disk_InitializeDiskWithADevDrive DriveLetter = 'E' FSFormat = 'ReFS' FSLabel = 'DevDrive' - DevDrive = true + DevDrive = $true Size = 50Gb - UseUnallocatedSpace = true + UseUnallocatedSpace = $true DependsOn = '[WaitForDisk]Disk2' } - <# - Will attempt to create a Dev Drive volume using the rest of the space on the disk assuming - that the rest of the space is greater than the minimum size for Dev Drive volumes (50Gb). - #> + <# + Will attempt to create a Dev Drive volume using the rest of the space on the disk assuming + that the rest of the space is greater than the minimum size for Dev Drive volumes (50Gb). + #> Disk DevDriveVolume2 { DiskId = '5E1E50A401000000001517FFFF0AEB84' @@ -62,7 +62,7 @@ Configuration Disk_InitializeDiskWithADevDrive DriveLetter = 'F' FSFormat = 'ReFS' FSLabel = 'DevDrive' - DevDrive = true + DevDrive = $true DependsOn = '[Disk]DevDriveVolume1' } } diff --git a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 index 22ec2b14..87d4ebf8 100644 --- a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 +++ b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 @@ -224,7 +224,7 @@ function Test-AccessPathAssignedToLocal <# .SYNOPSIS - Returns C# code that will be used to call Dev Drive related Win32 apis + Returns C# code that will be used to call Dev Drive related Win32 apis. #> function Get-DevDriveWin32HelperScript { @@ -264,7 +264,6 @@ function Get-DevDriveWin32HelperScript } return $script:DevDriveWin32Helper - } # end function Get-DevDriveWin32HelperScript <# @@ -274,15 +273,15 @@ function Get-DevDriveWin32HelperScript .PARAMETER AccessPath Specifies the contract string for the dll that houses the win32 function #> -Function Get-IsApiSetImplemented { - +function Get-IsApiSetImplemented +{ [CmdletBinding()] - Param + [OutputType([System.Boolean])] + param ( - [Parameter(Mandatory=$True)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [OutputType([System.Boolean])] - [String] + [System.String] $Contract ) @@ -294,11 +293,11 @@ Function Get-IsApiSetImplemented { .SYNOPSIS Invokes win32 GetDeveloperDriveEnablementState function #> -Function Get-DeveloperDriveEnablementState { - +function Get-DeveloperDriveEnablementState +{ [CmdletBinding()] [OutputType([System.Enum])] - Param + param () $helper = Get-DevDriveWin32HelperScript @@ -325,6 +324,7 @@ function Assert-DevDriveFeatureAvailable { try { + # Based on the enablement result we will throw an error or return without doing anything. switch (Get-DeveloperDriveEnablementState) { ($DevDriveEnablementType::DeveloperDriveEnablementStateError) @@ -344,19 +344,23 @@ function Assert-DevDriveFeatureAvailable Write-Verbose -Message ($script:localizedData.DevDriveEnabledMessage) return } - Default + default { throw $script:localizedData.DevDriveEnablementUnknownError } } } - catch [System.EntryPointNotFoundException] # function may not exist in some versions of Windows in the above dll + # function may not exist in some versions of Windows in the apiset dll. + catch [System.EntryPointNotFoundException] { - Write-Error $_.Exception.Message + Write-Verbose $_.Exception.Message } } - # If apiset isn't implemented or we get the EntryPointNotFoundException we should throw since the feature isn't available here. + <# + If apiset isn't implemented or we get the EntryPointNotFoundException we should throw + since the feature isn't available here. + #> throw $script:localizedData.DevDriveFeatureNotImplementedError } # end function Assert-DevDriveFeatureAvailable @@ -419,14 +423,20 @@ function Assert-DiskHasEnoughSpaceToCreateDevDrive <# 50 Gb is the minimum size for Dev Drive volumes. When size is 0 the user wants to use all - the available space on the disk so we will check if they have at least 50 Gb of space available. + the available space on the disk. #> + $notEnoughSpace = $false if (-not $UserDesiredSize) { - $UserDesiredSize = 50Gb + <# + The user wants to use all the available space on the disk. We will check if they have at least 50 Gb + of free space available. + #> + $notEnoughSpace = ($CurrentDiskFreeSpace -lt 50Gb) + $DesiredSizeInGb = 50Gb } - if ($UserDesiredSize -gt $CurrentDiskFreeSpace) + if ($notEnoughSpace -or ($UserDesiredSize -gt $CurrentDiskFreeSpace)) { $DesiredSizeInGb = [Math]::Round($UserDesiredSize / 1GB, 2) $CurrentDiskFreeSpaceInGb = [Math]::Round($CurrentDiskFreeSpace / 1GB, 2) @@ -444,7 +454,6 @@ function Assert-DiskHasEnoughSpaceToCreateDevDrive .PARAMETER UserDesiredSize Specifies the size the user wants to create the Dev Drive volume with. - #> function Assert-DevDriveSizeMeetsMinimumRequirement { @@ -454,13 +463,11 @@ function Assert-DevDriveSizeMeetsMinimumRequirement [Parameter(Mandatory = $true)] [System.UInt64] $UserDesiredSize - ) <# - 50 Gb is the minimum size for Dev Drive volumes. When UserDesiredSize is provided - This means the user wants to create a Dev Drive volume using all the available space - on the disk. We cover this case in Assert-DiskHasEnoughSpaceToCreateDevDrive + 50 Gb is the minimum size for Dev Drive volumes. The case where no size is + provided is covered in Assert-DiskHasEnoughSpaceToCreateDevDrive. #> if ($UserDesiredSize -and $UserDesiredSize -lt 50Gb) { diff --git a/tests/Unit/DSC_Disk.Tests.ps1 b/tests/Unit/DSC_Disk.Tests.ps1 index 04214919..5c8aa7cf 100644 --- a/tests/Unit/DSC_Disk.Tests.ps1 +++ b/tests/Unit/DSC_Disk.Tests.ps1 @@ -220,6 +220,18 @@ try $DiskId -eq $script:mockedDisk0Gpt.Number -and $DiskIdType -eq 'Number' } + $script:userDesiredSize60Gb = 60Gb + + $script:userDesiredSize50Gb = 50Gb + + $script:userDesiredSize40Gb = 40Gb + + $script:userDesiredSizeZeroGb = 0Gb + + $script:currentFreeSpaceSize60Gb = 60Gb + + $script:currentFreeSpaceSize50Gb = 50Gb + <# These functions are required to be able to mock functions where values are passed in via the pipeline. @@ -442,8 +454,8 @@ try ) } - Function Get-IsApiSetImplemented { - + function Get-IsApiSetImplemented + { [CmdletBinding()] Param ( @@ -453,14 +465,55 @@ try ) } - Function Get-DeveloperDriveEnablementState { - + function Get-DeveloperDriveEnablementState + { [CmdletBinding()] [OutputType([System.Enum])] Param () } + function Assert-DevDriveFormatOnReFsFileSystemOnly + { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $FSFormat + ) + } + + function Assert-DiskHasEnoughSpaceToCreateDevDrive + { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.UInt64] + $UserDesiredSize, + + [Parameter(Mandatory = $true)] + [System.UInt64] + $CurrentDiskFreeSpace, + + [Parameter(Mandatory = $true)] + [System.UInt32] + $DiskNumber + ) + } + + function Assert-DevDriveSizeMeetsMinimumRequirement + { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.UInt64] + $UserDesiredSize + ) + } + Describe 'DSC_Disk\Get-TargetResource' { Context 'When online GPT disk with a partition/volume and correct Drive Letter assigned using Disk Number' { # verifiable (should be called) mocks @@ -2826,6 +2879,61 @@ try Assert-MockCalled -CommandName Set-Partition -Exactly -Times 1 } } + + Context 'When the user wants to create a dev drive volume by overwriting a volume that already exists with AllowDestructive set to true' { + # verifiable (should be called) mocks + Mock ` + -CommandName Get-DiskByIdentifier ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number ` + -MockWith { $script:mockedDisk0GptForDevDrive } ` + -Verifiable + + Mock ` + -CommandName Get-Partition ` + -MockWith { $script:mockedPartitionWithGDriveletter } ` + -Verifiable + + Mock ` + -CommandName Get-Volume ` + -MockWith { $script:mockedVolumeReFS } ` + -Verifiable + + Mock ` + -CommandName Format-Volume ` + -Verifiable + + # mocks that should not be called + Mock -CommandName Set-Disk + Mock -CommandName Initialize-Disk + + It 'Should not throw an exception' { + { + Set-TargetResource ` + -DiskId $script:mockedDisk0Gpt.Number ` + -Driveletter $script:testDriveLetterH ` + -Size $script:mockedPartitionSize50Gb ` + -FSLabel 'NewLabel' ` + -FSFormat 'NTFS' ` + -DevDrive $true ` + -AllowDestructive $true ` + -Verbose + } | Should -Not -Throw + } + + It 'Should call the correct mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 1 ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number + Assert-MockCalled -CommandName Set-Disk -Exactly -Times 0 + Assert-MockCalled -CommandName Initialize-Disk -Exactly -Times 0 + Assert-MockCalled -CommandName Get-Partition -Exactly -Times 1 + Assert-MockCalled -CommandName Get-Volume -Exactly -Times 1 + Assert-MockCalled -CommandName Format-Volume -Exactly -Times 1 ` + -ParameterFilter { + $DevDrive -eq $true + } + } + } } Describe 'DSC_Disk\Test-TargetResource' { @@ -3885,6 +3993,95 @@ try Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 1 } } + + Context 'When the Dev Drive flag is enabled, and the user is attempting to put Dev Drive volume in an existing partition' { + # verifiable (should be called) mocks + Mock ` + -CommandName Get-DiskByIdentifier ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number ` + -MockWith { $script:mockedDisk0Gpt } ` + -Verifiable + + Mock ` + -CommandName Assert-DevDriveFeatureAvailable ` + -Verifiable + + Mock ` + -CommandName Get-Partition ` + -MockWith { $script:mockedPartition } ` + -Verifiable + + It 'Should not throw an exception' { + { + $script:result = Test-TargetResource ` + -DiskId $script:mockedDisk0Gpt.Number ` + -DriveLetter $script:testDriveLetter ` + -AllocationUnitSize 4096 ` + -Size $script:userDesiredSize60Gb ` + -FSLabel $script:mockedVolume.FileSystemLabel ` + -FSFormat $script:mockedVolumeReFS.FileSystem ` + -DevDrive $true ` + -Verbose + } | Should -Not -Throw + } + + It 'Should call the correct mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 1 ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number + Assert-MockCalled -CommandName Assert-DevDriveFeatureAvailable -Exactly -Times 1 + Assert-MockCalled -CommandName Get-Partition -Exactly -Times 2 + } + } + + Context 'When the Dev Drive flag is enabled, and the user is attempting to put a Dev Drive volume in a new partition' { + # verifiable (should be called) mocks + Mock ` + -CommandName Get-DiskByIdentifier ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number ` + -MockWith { $script:mockedDisk0Gpt } ` + -Verifiable + + Mock ` + -CommandName Assert-DevDriveFeatureAvailable ` + -Verifiable + + Mock ` + -CommandName Assert-DiskHasEnoughSpaceToCreateDevDrive ` + -Verifiable + + Mock ` + -CommandName Assert-DevDriveFormatOnReFsFileSystemOnly ` + -Verifiable + + Mock ` + -CommandName Get-Partition ` + -Verifiable + + It 'Should not throw an exception' { + { + $script:result = Test-TargetResource ` + -DiskId $script:mockedDisk0Gpt.Number ` + -DriveLetter $script:testDriveLetter ` + -AllocationUnitSize 4096 ` + -Size $script:userDesiredSize60Gb ` + -FSLabel $script:mockedVolume.FileSystemLabel ` + -FSFormat $script:mockedVolumeReFS.FileSystem ` + -DevDrive $true ` + -Verbose + } | Should -Not -Throw + } + + It 'Should call the correct mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 1 ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number + Assert-MockCalled -CommandName Assert-DevDriveFeatureAvailable -Exactly -Times 1 + Assert-MockCalled -CommandName Assert-DiskHasEnoughSpaceToCreateDevDrive -Exactly -Times 1 + Assert-MockCalled -CommandName Assert-DevDriveFormatOnReFsFileSystemOnly -Exactly -Times 1 + Assert-MockCalled -CommandName Get-Partition -Exactly -Times 2 + } + } } } } diff --git a/tests/Unit/StorageDsc.Common.Tests.ps1 b/tests/Unit/StorageDsc.Common.Tests.ps1 index 6c5e4d00..8d5cce20 100644 --- a/tests/Unit/StorageDsc.Common.Tests.ps1 +++ b/tests/Unit/StorageDsc.Common.Tests.ps1 @@ -30,8 +30,10 @@ InModuleScope $script:subModuleName { UserDesired0Gb = 0Gb UserDesired10Gb = 10Gb UserDesired50Gb = 50Gb + UserDesired60Gb = 60Gb CurrentDiskFreeSpace40Gb = 40Gb CurrentDiskFreeSpace50Gb = 50Gb + CurrentDiskFreeSpace60Gb = 60Gb } $script:mockedDiskNumber = 1 @@ -522,10 +524,10 @@ InModuleScope $script:subModuleName { -AccessPath @('\\?\Volume{905551f3-33a5-421d-ac24-c993fbfb3184}\', '\\?\Volume{99cf0194-ac45-4a23-b36e-3e458158a63e}\') | Should -Be $false } } + } - Context 'Testing Dev Drive enablement when dev drive feature not implemented' { - # verifiable (should be called) mocks - + Describe 'StorageDsc.Common\Assert-DevDriveFeatureAvailable' -Tag 'Assert-DevDriveFeatureAvailable' { + Context 'When testing the Dev Drive enablement state and the dev drive feature not implemented' { Mock ` -CommandName Get-IsApiSetImplemented ` -MockWith { return $false } ` @@ -544,20 +546,45 @@ InModuleScope $script:subModuleName { } } - Context 'Testing Dev Drive enablement when dev drive feature is disabled by group policy' { - # verifiable (should be called) mocks + Context 'When testing the Dev Drive enablement state returns an enablement state not defined in the enum' { + Mock ` + -CommandName Get-IsApiSetImplemented ` + -MockWith { $true } ` + -ModuleName StorageDsc.Common ` + -Verifiable + + Mock ` + -CommandName Get-DeveloperDriveEnablementState ` + -MockWith { $null } ` + -ModuleName StorageDsc.Common ` + -Verifiable + + It 'Should throw with DevDriveEnablementUnknownError' { + { + Assert-DevDriveFeatureAvailable -Verbose + } | Should -Throw -ExpectedMessage $LocalizedData.DevDriveEnablementUnknownError + } + + It 'Should call the correct mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-IsApiSetImplemented -Exactly -Times 1 + Assert-MockCalled -CommandName Get-DeveloperDriveEnablementState -Exactly -Times 1 + } + } + + Context 'When testing the Dev Drive enablement state and the dev drive feature is disabled by group policy' { Get-DevDriveWin32HelperScript $DevDriveEnablementType = [DevDrive.DevDriveHelper+DEVELOPER_DRIVE_ENABLEMENT_STATE] Mock ` -CommandName Get-IsApiSetImplemented ` - -MockWith { return $true } ` + -MockWith { $true } ` -ModuleName StorageDsc.Common ` -Verifiable Mock ` -CommandName Get-DeveloperDriveEnablementState ` - -MockWith { return $DevDriveEnablementType::DeveloperDriveDisabledByGroupPolicy } ` + -MockWith { $DevDriveEnablementType::DeveloperDriveDisabledByGroupPolicy } ` -ModuleName StorageDsc.Common ` -Verifiable @@ -574,17 +601,16 @@ InModuleScope $script:subModuleName { } } - Context 'Testing Dev Drive enablement when dev drive feature is disabled by system policy' { - # verifiable (should be called) mocks + Context 'When testing the Dev Drive enablement state and the dev drive feature is disabled by system policy' { Mock ` -CommandName Get-IsApiSetImplemented ` - -MockWith { return $true } ` + -MockWith { $true } ` -ModuleName StorageDsc.Common ` -Verifiable Mock ` -CommandName Get-DeveloperDriveEnablementState ` - -MockWith { return $DevDriveEnablementType::DeveloperDriveDisabledBySystemPolicy } ` + -MockWith { $DevDriveEnablementType::DeveloperDriveDisabledBySystemPolicy } ` -ModuleName StorageDsc.Common ` -Verifiable @@ -601,17 +627,16 @@ InModuleScope $script:subModuleName { } } - Context 'Testing Dev Drive enablement when enablement state is unknown' { - # verifiable (should be called) mocks + Context 'When testing the Dev Drive enablement state and the enablement state is unknown' { Mock ` -CommandName Get-IsApiSetImplemented ` - -MockWith { return $true } ` + -MockWith { $true } ` -ModuleName StorageDsc.Common ` -Verifiable Mock ` -CommandName Get-DeveloperDriveEnablementState ` - -MockWith { return $DevDriveEnablementType::DeveloperDriveEnablementStateError } ` + -MockWith { $DevDriveEnablementType::DeveloperDriveEnablementStateError } ` -ModuleName StorageDsc.Common ` -Verifiable @@ -628,17 +653,16 @@ InModuleScope $script:subModuleName { } } - Context 'Testing Dev Drive enablement when enablement state is set to enabled' { - # verifiable (should be called) mocks + Context 'When testing Dev Drive enablement state and the enablement state is set to enabled' { Mock ` -CommandName Get-IsApiSetImplemented ` - -MockWith { return $true } ` + -MockWith { $true } ` -ModuleName StorageDsc.Common ` -Verifiable Mock ` -CommandName Get-DeveloperDriveEnablementState ` - -MockWith { return $DevDriveEnablementType::DeveloperDriveEnabled } ` + -MockWith { $DevDriveEnablementType::DeveloperDriveEnabled } ` -ModuleName StorageDsc.Common ` -Verifiable @@ -654,9 +678,10 @@ InModuleScope $script:subModuleName { Assert-MockCalled -CommandName Get-DeveloperDriveEnablementState -Exactly -Times 1 } } + } - Context 'Testing that only ReFS file system allowed in Assert-DevDriveFormatOnReFsFileSystemOnly ' { - # verifiable (should be called) mocks + Describe 'StorageDsc.Common\Assert-DevDriveFormatOnReFsFileSystemOnly' -Tag 'Assert-DevDriveFormatOnReFsFileSystemOnly' { + Context 'When testing that only the ReFS file system is allowed' { $errorRecord = Get-InvalidArgumentRecord ` -Message ($script:localizedData.DevDriveOnlyAvailableForReFsError ) ` @@ -669,8 +694,7 @@ InModuleScope $script:subModuleName { } } - Context 'Testing Exception not thrown in Assert-DevDriveFormatOnReFsFileSystemOnly when ReFS file system passed in' { - # verifiable (should be called) mocks + Context 'When testing Exception not thrown in Assert-DevDriveFormatOnReFsFileSystemOnly when ReFS file system passed in' { It 'Should not throw invalid argument error if ReFS filesystem is passed in' { { @@ -678,15 +702,16 @@ InModuleScope $script:subModuleName { } | Should -Not -Throw } } + } - Context 'Testing Exception thrown in Assert-DevDriveSizeMeetsMinimumRequirement when UserDesiredSize does not meet the minimum size for Dev Drives' { - # verifiable (should be called) mocks + Describe 'StorageDsc.Common\Assert-DevDriveSizeMeetsMinimumRequirement' -Tag 'Assert-DevDriveSizeMeetsMinimumRequirement' { + Context 'When UserDesiredSize does not meet the minimum size for Dev Drive volumes' { $errorRecord = Get-InvalidArgumentRecord ` -Message $($script:localizedData.DevDriveMinimumSizeError) ` -ArgumentName 'UserDesiredSize' - It 'Should throw invalid argument error if UserDesiredSize less than 50Gb' { + It 'Should throw invalid argument error' { { Assert-DevDriveSizeMeetsMinimumRequirement ` -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired10Gb ` @@ -695,8 +720,31 @@ InModuleScope $script:subModuleName { } } - Context 'Testing Exception thrown in Assert-DiskHasEnoughSpaceToCreateDevDrive when disk free space less than users desired size' { - # verifiable (should be called) mocks + Context 'When UserDesiredSize meets the minimum size for Dev Drive volumes' { + + It 'Should not throw invalid argument error' { + { + Assert-DevDriveSizeMeetsMinimumRequirement ` + -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired50Gb ` + -Verbose + } | Should -Not -Throw + } + } + + Context 'When UserDesiredSize is 0' { + + It 'Should not throw invalid argument error' { + { + Assert-DevDriveSizeMeetsMinimumRequirement ` + -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired0Gb ` + -Verbose + } | Should -Not -Throw + } + } + } + + Describe 'StorageDsc.Common\Assert-DiskHasEnoughSpaceToCreateDevDrive' -Tag 'Assert-DiskHasEnoughSpaceToCreateDevDrive' { + Context 'When disk free space less than users desired size' { $userDesiredSizeInGb = [Math]::Round($mockedSizesForDevDriveScenario.UserDesired50Gb / 1GB, 2) $currentDiskFreeSpaceInGb = [Math]::Round($mockedSizesForDevDriveScenario.CurrentDiskFreeSpace40Gb / 1GB, 2) @@ -707,7 +755,7 @@ InModuleScope $script:subModuleName { $currentDiskFreeSpaceInGb) ` -ArgumentName 'UserDesiredSize' - It 'Should throw invalid argument error if UserDesiredSize greater than CurrentDiskFreeSpace' { + It 'Should throw invalid argument error' { { Assert-DiskHasEnoughSpaceToCreateDevDrive ` -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired50Gb ` @@ -718,39 +766,75 @@ InModuleScope $script:subModuleName { } } - Context 'Testing Exception not thrown in Assert-DiskHasEnoughSpaceToCreateDevDrive when disk free space greater than or equal to users desired size' { - # verifiable (should be called) mocks + Context 'When no size entered and disk free space less than minimum size for dev drive volumes (50Gb)' { + + $userDesiredSizeInGb = $mockedSizesForDevDriveScenario.UserDesired0Gb + $currentDiskFreeSpaceInGb = [Math]::Round($mockedSizesForDevDriveScenario.CurrentDiskFreeSpace40Gb / 1GB, 2) + $errorRecord = Get-InvalidArgumentRecord ` + -Message $($script:localizedData.DevDriveNotEnoughSpaceToCreateDevDriveError -f ` + $mockedDiskNumber, ` + $userDesiredSizeInGb, ` + $currentDiskFreeSpaceInGb) ` + -ArgumentName 'UserDesiredSize' + + It 'Should throw invalid argument error' { + { + Assert-DiskHasEnoughSpaceToCreateDevDrive ` + -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired0Gb ` + -CurrentDiskFreeSpace $mockedSizesForDevDriveScenario.CurrentDiskFreeSpace40Gb ` + -DiskNumber $mockedDiskNumber ` + -Verbose + } | Should -Throw $errorRecord + } + } + + Context 'When disk free space greater than users desired size' { - It 'Should not throw invalid argument error if CurrentDiskFreeSpace greater than or equal to UserDesiredSize' { + It 'Should not throw invalid argument error if CurrentDiskFreeSpace greater than UserDesiredSize' { { Assert-DiskHasEnoughSpaceToCreateDevDrive ` -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired50Gb ` - -CurrentDiskFreeSpace $mockedSizesForDevDriveScenario.CurrentDiskFreeSpace50Gb ` + -CurrentDiskFreeSpace $mockedSizesForDevDriveScenario.CurrentDiskFreeSpace60Gb ` -DiskNumber $mockedDiskNumber ` -Verbose } | Should -Not -Throw } } - Context 'Testing Exception not thrown in Assert-DevDriveSizeMeetsMinimumRequirement when UserDesiredSize does meet the minimum size for Dev Drives' { - # verifiable (should be called) mocks + Context 'When disk free space equal to users desired size' { - It 'Should not throw invalid argument error if UserDesiredSize greater than or equal to 50 Gb' { + It 'Should not throw invalid argument error if CurrentDiskFreeSpace is equal to UserDesiredSize' { { - Assert-DevDriveSizeMeetsMinimumRequirement ` + Assert-DiskHasEnoughSpaceToCreateDevDrive ` -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired50Gb ` + -CurrentDiskFreeSpace $mockedSizesForDevDriveScenario.CurrentDiskFreeSpace50Gb ` + -DiskNumber $mockedDiskNumber ` -Verbose } | Should -Not -Throw } } - Context 'Testing Exception not thrown in Assert-DevDriveSizeMeetsMinimumRequirement when UserDesiredSize is 0' { - # verifiable (should be called) mocks + Context 'When no size entered and disk free space greater than the minimum size for dev drive volumes (50Gb)' { - It 'Should not throw invalid argument error if UserDesiredSize is 0' { + It 'Should not throw invalid argument error if CurrentDiskFreeSpace is greater than 50Gb' { { - Assert-DevDriveSizeMeetsMinimumRequirement ` + Assert-DiskHasEnoughSpaceToCreateDevDrive ` -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired0Gb ` + -CurrentDiskFreeSpace $mockedSizesForDevDriveScenario.CurrentDiskFreeSpace60Gb ` + -DiskNumber $mockedDiskNumber ` + -Verbose + } | Should -Not -Throw + } + } + + Context 'When no size entered and disk free space equal to the minimum size for dev drive volumes (50Gb)' { + + It 'Should not throw invalid argument error if CurrentDiskFreeSpace is equal to 50Gb' { + { + Assert-DiskHasEnoughSpaceToCreateDevDrive ` + -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired0Gb ` + -CurrentDiskFreeSpace $mockedSizesForDevDriveScenario.CurrentDiskFreeSpace50Gb ` + -DiskNumber $mockedDiskNumber ` -Verbose } | Should -Not -Throw } From 19a146f597c68c87bb84d8c2617ed92765f02675 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Thu, 28 Sep 2023 00:40:55 -0700 Subject: [PATCH 14/21] update check for unallocated space to take into account uninitialized disks and add tests --- source/DSCResources/DSC_Disk/DSC_Disk.psm1 | 32 +++-- .../StorageDsc.Common/StorageDsc.Common.psm1 | 2 +- tests/Unit/DSC_Disk.Tests.ps1 | 109 +++++++++++++++--- tests/Unit/StorageDsc.Common.Tests.ps1 | 2 +- 4 files changed, 118 insertions(+), 27 deletions(-) diff --git a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 index 356a8dfe..494c32df 100644 --- a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 +++ b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 @@ -128,14 +128,14 @@ function Get-TargetResource -ErrorAction SilentlyContinue).BlockSize return @{ - DiskId = $DiskId - DiskIdType = $DiskIdType - DriveLetter = $partition.DriveLetter - PartitionStyle = $disk.PartitionStyle - Size = $partition.Size - FSLabel = $volume.FileSystemLabel - AllocationUnitSize = $blockSize - FSFormat = $volume.FileSystem + DiskId = $DiskId + DiskIdType = $DiskIdType + DriveLetter = $partition.DriveLetter + PartitionStyle = $disk.PartitionStyle + Size = $partition.Size + FSLabel = $volume.FileSystemLabel + AllocationUnitSize = $blockSize + FSFormat = $volume.FileSystem } } # Get-TargetResource @@ -803,16 +803,26 @@ function Test-TargetResource -DriveLetter $DriveLetter ` -ErrorAction SilentlyContinue | Select-Object -First 1 + # For unintialized disks, the largest free extent is the size of the disk. + if ($disk.PartitionStyle -ne 'RAW' ) + { + $currentDiskFreeSpace = $disk.LargestFreeExtent + } + else + { + $currentDiskFreeSpace = $disk.Size + } + # User is attempting to create a new Dev Drive volume in a new partition if ($null -eq $tempPartition) { <# - Get the largest contiguous free space that is possible to create a partition with - and see if its possible to create a Dev Drive using that space. + The Dev Drive will be created within the largest block of continguous unallocated space + available. So we need to check that the space is big enough to create the Dev Drive in. #> Assert-DiskHasEnoughSpaceToCreateDevDrive ` -UserDesiredSize $Size ` - -CurrentDiskFreeSpace $disk.LargestFreeExtent ` + -CurrentDiskFreeSpace $currentDiskFreeSpace ` -DiskNumber $Disk.Number } diff --git a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 index 87d4ebf8..7ff1e242 100644 --- a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 +++ b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 @@ -433,7 +433,7 @@ function Assert-DiskHasEnoughSpaceToCreateDevDrive of free space available. #> $notEnoughSpace = ($CurrentDiskFreeSpace -lt 50Gb) - $DesiredSizeInGb = 50Gb + $UserDesiredSize = 50Gb } if ($notEnoughSpace -or ($UserDesiredSize -gt $CurrentDiskFreeSpace)) diff --git a/tests/Unit/DSC_Disk.Tests.ps1 b/tests/Unit/DSC_Disk.Tests.ps1 index 5c8aa7cf..e8ed495e 100644 --- a/tests/Unit/DSC_Disk.Tests.ps1 +++ b/tests/Unit/DSC_Disk.Tests.ps1 @@ -107,15 +107,29 @@ try } $script:mockedDisk0GptForDevDrive = [pscustomobject] @{ - Number = $script:testDiskNumber - UniqueId = $script:testDiskUniqueId - FriendlyName = $script:testDiskFriendlyName - SerialNumber = $script:testDiskSerialNumber - Guid = $script:testDiskGptGuid - IsOffline = $false - IsReadOnly = $false - PartitionStyle = 'GPT' - Size = 100Gb + Number = $script:testDiskNumber + UniqueId = $script:testDiskUniqueId + FriendlyName = $script:testDiskFriendlyName + SerialNumber = $script:testDiskSerialNumber + Guid = $script:testDiskGptGuid + IsOffline = $false + IsReadOnly = $false + PartitionStyle = 'GPT' + Size = 100Gb + LargestFreeExtent = 90Gb + } + + $script:mockedDisk0RawForDevDrive = [pscustomobject] @{ + Number = $script:testDiskNumber + UniqueId = $script:testDiskUniqueId + FriendlyName = $script:testDiskFriendlyName + SerialNumber = $script:testDiskSerialNumber + Guid = '' + IsOffline = $false + IsReadOnly = $false + PartitionStyle = 'RAW' + Size = 80Gb + LargestFreeExtent = 0 } $script:mockedCim = [pscustomobject] @{ @@ -220,6 +234,18 @@ try $DiskId -eq $script:mockedDisk0Gpt.Number -and $DiskIdType -eq 'Number' } + $script:UninitializeDiskSizeParamsForDevDrive = { + $UserDesiredSize -eq $script:userDesiredSize60Gb -and + $CurrentDiskFreeSpace -eq $script:mockedDisk0RawForDevDrive.size -and + $DiskNumber -eq $script:mockedDisk0RawForDevDrive.Number + } + + $script:InitializeDiskSizeParamsForDevDrive = { + $UserDesiredSize -eq $script:userDesiredSize60Gb -and + $CurrentDiskFreeSpace -eq $script:mockedDisk0GptForDevDrive.LargestFreeExtent -and + $DiskNumber -eq $script:mockedDisk0GptForDevDrive.Number + } + $script:userDesiredSize60Gb = 60Gb $script:userDesiredSize50Gb = 50Gb @@ -2738,7 +2764,7 @@ try } } - Context 'When Dev Drive flag enabled with online GPT disk with no partitions format volume is called with Dev Drive flag' { + Context 'When Dev Drive flag is enabled and format volume is called with the Dev Drive flag' { # verifiable (should be called) mocks Mock ` -CommandName Get-DiskByIdentifier ` @@ -4008,7 +4034,7 @@ try Mock ` -CommandName Get-Partition ` - -MockWith { $script:mockedPartition } ` + -MockWith { $null } ` -Verifiable It 'Should not throw an exception' { @@ -4034,12 +4060,12 @@ try } } - Context 'When the Dev Drive flag is enabled, and the user is attempting to put a Dev Drive volume in a new partition' { + Context 'When the Dev Drive flag is enabled, disk is initialized, and the user is attempting to put a Dev Drive volume in a new partition' { # verifiable (should be called) mocks Mock ` -CommandName Get-DiskByIdentifier ` -ParameterFilter $script:parameterFilter_MockedDisk0Number ` - -MockWith { $script:mockedDisk0Gpt } ` + -MockWith { $script:mockedDisk0GptForDevDrive } ` -Verifiable Mock ` @@ -4048,6 +4074,7 @@ try Mock ` -CommandName Assert-DiskHasEnoughSpaceToCreateDevDrive ` + -ParameterFilter $script:InitializeDiskSizeParamsForDevDrive ` -Verifiable Mock ` @@ -4056,6 +4083,7 @@ try Mock ` -CommandName Get-Partition ` + -MockWith { $null } ` -Verifiable It 'Should not throw an exception' { @@ -4077,12 +4105,65 @@ try Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 1 ` -ParameterFilter $script:parameterFilter_MockedDisk0Number Assert-MockCalled -CommandName Assert-DevDriveFeatureAvailable -Exactly -Times 1 - Assert-MockCalled -CommandName Assert-DiskHasEnoughSpaceToCreateDevDrive -Exactly -Times 1 + Assert-MockCalled -CommandName Assert-DiskHasEnoughSpaceToCreateDevDrive -Exactly -Times 1 ` + -ParameterFilter $script:InitializeDiskSizeParamsForDevDrive Assert-MockCalled -CommandName Assert-DevDriveFormatOnReFsFileSystemOnly -Exactly -Times 1 Assert-MockCalled -CommandName Get-Partition -Exactly -Times 2 } } } + + Context 'When the Dev Drive flag is enabled, disk is not initialized, and the user is attempting to put a Dev Drive volume in a new partition' { + # verifiable (should be called) mocks + Mock ` + -CommandName Get-DiskByIdentifier ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number ` + -MockWith { $script:mockedDisk0RawForDevDrive } ` + -Verifiable + + Mock ` + -CommandName Assert-DevDriveFeatureAvailable ` + -Verifiable + + Mock ` + -CommandName Assert-DiskHasEnoughSpaceToCreateDevDrive ` + -ParameterFilter $script:UninitializeDiskSizeParamsForDevDrive ` + -Verifiable + + Mock ` + -CommandName Assert-DevDriveFormatOnReFsFileSystemOnly ` + -Verifiable + + Mock ` + -CommandName Get-Partition ` + -MockWith { $null } ` + -Verifiable + + It 'Should not throw an exception' { + { + $script:result = Test-TargetResource ` + -DiskId $script:mockedDisk0Gpt.Number ` + -DriveLetter $script:testDriveLetter ` + -AllocationUnitSize 4096 ` + -Size $script:userDesiredSize60Gb ` + -FSLabel $script:mockedVolume.FileSystemLabel ` + -FSFormat $script:mockedVolumeReFS.FileSystem ` + -DevDrive $true ` + -Verbose + } | Should -Not -Throw + } + + It 'Should call the correct mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 1 ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number + Assert-MockCalled -CommandName Assert-DevDriveFeatureAvailable -Exactly -Times 1 + Assert-MockCalled -CommandName Assert-DiskHasEnoughSpaceToCreateDevDrive -Exactly -Times 1 ` + -ParameterFilter $script:UninitializeDiskSizeParamsForDevDrive + Assert-MockCalled -CommandName Assert-DevDriveFormatOnReFsFileSystemOnly -Exactly -Times 1 + Assert-MockCalled -CommandName Get-Partition -Exactly -Times 1 + } + } } } finally diff --git a/tests/Unit/StorageDsc.Common.Tests.ps1 b/tests/Unit/StorageDsc.Common.Tests.ps1 index 8d5cce20..3b32e302 100644 --- a/tests/Unit/StorageDsc.Common.Tests.ps1 +++ b/tests/Unit/StorageDsc.Common.Tests.ps1 @@ -768,7 +768,7 @@ InModuleScope $script:subModuleName { Context 'When no size entered and disk free space less than minimum size for dev drive volumes (50Gb)' { - $userDesiredSizeInGb = $mockedSizesForDevDriveScenario.UserDesired0Gb + $userDesiredSizeInGb = [Math]::Round($mockedSizesForDevDriveScenario.UserDesired50Gb / 1GB, 2) $currentDiskFreeSpaceInGb = [Math]::Round($mockedSizesForDevDriveScenario.CurrentDiskFreeSpace40Gb / 1GB, 2) $errorRecord = Get-InvalidArgumentRecord ` -Message $($script:localizedData.DevDriveNotEnoughSpaceToCreateDevDriveError -f ` From d419000a2c1d4590b4cb18dd20575e565641a2d2 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Tue, 10 Oct 2023 20:14:40 -0700 Subject: [PATCH 15/21] Address Pr comments and remove the 'UseUnallocatedSpace' variable. Now we perform a resize automatically if there is not enough space. --- CHANGELOG.md | 2 +- source/DSCResources/DSC_Disk/DSC_Disk.psm1 | 334 +++++++-- .../DSCResources/DSC_Disk/DSC_Disk.schema.mof | 1 - source/DSCResources/DSC_Disk/README.md | 33 + .../DSC_Disk/en-US/DSC_Disk.strings.psd1 | 10 + ...teDevDriveOnDiskWithExistingPartitions.ps1 | 86 +++ .../3-Disk_InitializeDiskWithADevDrive.ps1 | 69 -- ...skWithMultipleDrivesIncludingDevDrives.ps1 | 101 +++ .../StorageDsc.Common/StorageDsc.Common.psm1 | 267 +++++-- .../en-US/StorageDsc.Common.strings.psd1 | 5 +- tests/Unit/DSC_Disk.Tests.ps1 | 705 +++++++++++++----- tests/Unit/StorageDsc.Common.Tests.ps1 | 187 ++--- 12 files changed, 1279 insertions(+), 521 deletions(-) create mode 100644 source/Examples/Resources/Disk/3-Disk_CreateDevDriveOnDiskWithExistingPartitions.ps1 delete mode 100644 source/Examples/Resources/Disk/3-Disk_InitializeDiskWithADevDrive.ps1 create mode 100644 source/Examples/Resources/Disk/4-Disk_InitializeDiskWithMultipleDrivesIncludingDevDrives.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index e191f95e..4f13d763 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Updated DSC_Disk to allow volumes to be formatted as Dev Drives: Fixes #276 +- Updated DSC_Disk to allow volumes to be formatted as Dev Drives - Fixes [Issue #276](https://github.com/dsccommunity/StorageDsc/issues/276) ## [5.1.0] - 2023-02-22 diff --git a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 index 494c32df..36debb93 100644 --- a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 +++ b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 @@ -51,6 +51,9 @@ $script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' Specifies if the disks partition schema should be removed entirely, even if data and OEM partitions are present. Only possible with AllowDestructive enabled. This parameter is not used in Get-TargetResource. + + .PARAMETER DevDrive + Specifies if the volume is formatted as a Dev Drive. #> function Get-TargetResource { @@ -99,7 +102,11 @@ function Get-TargetResource [Parameter()] [System.Boolean] - $ClearDisk + $ClearDisk, + + [Parameter()] + [System.Boolean] + $DevDrive ) Write-Verbose -Message ( @( @@ -127,6 +134,18 @@ function Get-TargetResource -Query "SELECT BlockSize from Win32_Volume WHERE DriveLetter = '$($DriveLetter):'" ` -ErrorAction SilentlyContinue).BlockSize + if ($volume.UniqueId) + { + $DevDrive = Test-DevDriveVolume ` + -VolumeGuidPath $volume.UniqueId ` + -ErrorAction SilentlyContinue + } + else + { + $DevDrive = $false + } + + return @{ DiskId = $DiskId DiskIdType = $DiskIdType @@ -136,6 +155,7 @@ function Get-TargetResource FSLabel = $volume.FileSystemLabel AllocationUnitSize = $blockSize FSFormat = $volume.FileSystem + DevDrive = $DevDrive } } # Get-TargetResource @@ -176,9 +196,6 @@ function Get-TargetResource .PARAMETER DevDrive Specifies if the volume should be formatted as a Dev Drive. - - .PARAMETER UseUnallocatedSpace - Specifies that a new partition and volume should be formatted onto unallocated space on the disk. #> function Set-TargetResource { @@ -232,11 +249,7 @@ function Set-TargetResource [Parameter()] [System.Boolean] - $DevDrive, - - [Parameter()] - [System.Boolean] - $UseUnallocatedSpace + $DevDrive ) Write-Verbose -Message ( @( @@ -324,6 +337,21 @@ function Set-TargetResource } } + <# + Check Dev Drive assertions. + #> + if ($DevDrive) + { + Assert-DevDriveFeatureAvailable + Assert-FSFormatIsReFsWhenDevDriveFlagSetToTrue -FSFormat $FSFormat + + # we validate the case where the user does not specify a size later on, should we need to create a new partition. + if ($Size) + { + Assert-SizeMeetsMinimumDevDriveRequirement -UserDesiredSize $Size + } + } + # Get the partitions on the disk $partition = $disk | Get-Partition -ErrorAction SilentlyContinue @@ -347,10 +375,85 @@ function Set-TargetResource # There are partitions defined - identify if one matches the size required if ($Size) { - # Find the first basic partition matching the size - $partition = $partition | - Where-Object -FilterScript { $_.Type -eq 'Basic' -and $_.Size -eq $Size } | - Select-Object -First 1 + if ($DevDrive) + { + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.AttemptingToFindAPartitionToResizeForDevDrive) + ) -join '' ) + + <# + Find the first basic partition whose max - min supported partition size is greater than or equal + to the size the user wants so we can resize it later. The max size also includes any unallocated + space next to the partition. + #> + $partitionToResizeForDevDriveScenario = $null + $amountToDecreasePartitionBy = 0 + $isResizeNeeded = $true + foreach ($tempPartiton in $partition) + { + if (-not $tempPartiton.DriveLetter -or $tempPartiton.DriveLetter -eq '') + { + continue + } + + $supportedSize = Get-PartitionSupportedSize -DriveLetter $tempPartiton.DriveLetter + + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.CheckingIfPartitionCanBeResizedForDevDrive -F $tempPartiton.DriveLetter) + ) -join '' ) + + if ($tempPartiton.Type -eq 'Basic' -and (($supportedSize.SizeMax - $supportedSize.SizeMin) -ge $Size)) + { + $unallocatedSpaceNextToPartition = $supportedSize.SizeMax - $tempPartiton.Size + + if ($unallocatedSpaceNextToPartition -ge $Size) + { + <# + The size of the unallocated space next to the partition is already big enough to create a Dev Drive volume on, + so we don't need to resize any partitions. + #> + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.NoPartitionResizeNeededForDevDrive) + ) -join '' ) + + $isResizeNeeded = $false + break + } + + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.PartitionFoundThatCanBeResizedForDevDrive -F $tempPartiton.DriveLetter) + ) -join '' ) + + $partitionToResizeForDevDriveScenario = $tempPartiton + $amountToDecreasePartitionBy = $Size - $unallocatedSpaceNextToPartition + break + } + + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.PartitionCantBeResizedForDevDrive -F $tempPartiton.DriveLetter) + ) -join '' ) + } + + $partition = $partitionToResizeForDevDriveScenario + + if ($isResizeNeeded -and (-not $partition)) + { + $SizeInGb = [Math]::Round($Size / 1GB, 2) + throw ($script:localizedData.FoundNoPartitionsThatCanResizedForDevDrive -F $SizeInGb) + } + } + else + { + # Find the first basic partition matching the size + $partition = $partition | + Where-Object -FilterScript { $_.Type -eq 'Basic' -and $_.Size -eq $Size } | + Select-Object -First 1 + } if ($partition) { @@ -410,12 +513,51 @@ function Set-TargetResource } # if <# - There are two instances when we attempt to create a new partition: - 1. When there are no partitions matching the drive letter the user entered. - 2. When the user has advised us that they want to create a new partition on the disk's - unallocated space, regardless of whether there is one that matches the $size parameter already. + We can't find a partition with the required drive letter, so we may need to make a new one. + First we need to check if there is already enough unallocated space on the disk. + #> + $enoughUnallocatedSpace = ($disk.LargestFreeExtent -ge $Size) + + if ($DevDrive -and $Size -and $partition -and (-not $enoughUnallocatedSpace)) + { + <# + Resize the partition that has the largest max - min supported size. This will create + enough new unallocated space for the Dev Drive volume to be created on. + #> + if ($AllowDestructive) + { + $newPartitionSize = ($partition.Size - $amountToDecreasePartitionBy) + $newPartitionSizeInGb = [Math]::Round($newPartitionSize / 1GB, 2) + $SizeInGb = [Math]::Round($Size / 1GB, 2) + + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.ResizingPartitionToMakeSpaceForDevDriveVolume ` + -f $partition.DriveLetter, $newPartitionSizeInGb, $SizeInGb) + ) -join '' ) + + $partition | Resize-Partition -Size $newPartitionSize + + # Requery the disk since we resized a partition + $disk = Get-DiskByIdentifier ` + -DiskId $DiskId ` + -DiskIdType $DiskIdType + } + else + { + # Allow Destructive is not set to true, so throw an exception. + throw ($script:localizedData.AllowDestructiveNeededForDevDriveOperation -F $partition.DriveLetter) + } + + # We no longer need to use the resized partition as we now have enough unallocated space. + $partition = $null + } + + <# + We enter here if there are no partitions on the disk or when we need to create a new partition + using unallocated space. #> - if (-not $partition -bor $UseUnallocatedSpace) + if (-not $partition) { # Attempt to create a new partition $partitionParams = @{ @@ -431,6 +573,23 @@ function Set-TargetResource -f $DiskIdType, $DiskId, $DriveLetter, "$($Size/1KB) KB") ) -join '' ) + if ($DevDrive) + { + <# + If size is slightly larger in bytes due to low level rounding differences than the max free extent there + won't be any capacity to create the new partition. So if the values are the same in GB after rounding, + we'll update size to be the max free extent + #> + $largestFreeExtentInGb = [Math]::Round($disk.LargestFreeExtent / 1GB, 2) + $SizeInGb = [Math]::Round($Size / 1GB, 2) + if ($SizeInGb -eq $largestFreeExtentInGb) + { + $Size = $disk.LargestFreeExtent + } + + Assert-SizeMeetsMinimumDevDriveRequirement -UserDesiredSize $Size + } + $partitionParams['Size'] = $Size } else @@ -442,6 +601,11 @@ function Set-TargetResource -f $DiskIdType, $DiskId, $DriveLetter, 'all free space') ) -join '' ) + if ($DevDrive) + { + Assert-SizeMeetsMinimumDevDriveRequirement -UserDesiredSize $disk.LargestFreeExtent + } + $partitionParams['UseMaximumSize'] = $true } # if @@ -496,7 +660,7 @@ function Set-TargetResource $supportedSize = $assignedPartition | Get-PartitionSupportedSize <# - If the parition size was not specified then try and make the partition + If the partition size was not specified then try and make the partition use all possible space on the disk. #> if (-not ($PSBoundParameters.ContainsKey('Size'))) @@ -506,7 +670,7 @@ function Set-TargetResource if ($assignedPartition.Size -ne $Size) { - # A patition resize is required + # A partition resize is required if ($AllowDestructive) { if ($FSFormat -eq 'ReFS') @@ -550,6 +714,16 @@ function Set-TargetResource } } + <# + If the Set-TargetResource function is run as a standalone function, and $assignedPartition is not null and there are multiple partitions in $partition + then '$partition | Get-Volume', will give $volume back the first volume on the first partition. If $assignedPartition is after that one, then we could + potentially format a different volume. So we need to make sure that $partition is equal to $assignedPartition before we call Get-Volume. + #> + if ($assignedPartition) + { + $partition = $assignedPartition + } + # Get the Volume on the partition $volume = $partition | Get-Volume @@ -581,6 +755,9 @@ function Set-TargetResource if ($DevDrive) { + # Confirm that the partition size meets the minimum requirements for a Dev Drive volume. + Assert-SizeMeetsMinimumDevDriveRequirement -UserDesiredSize $partition.Size + $formatVolumeParameters['DevDrive'] = $DevDrive } @@ -621,12 +798,16 @@ function Set-TargetResource $formatParam.Add('AllocationUnitSize', $AllocationUnitSize) } - if ($PSBoundParameters.ContainsKey('DevDrive')) + if ($DevDrive) { + # Confirm that the volume size meets the minimum requirements for a Dev Drive volume. + Assert-SizeMeetsMinimumDevDriveRequirement -UserDesiredSize $volume.Size + $formatParam.Add('DevDrive', $DevDrive) } - $Volume | Format-Volume @formatParam + # Update the volume with the new format information. + $volume = $volume | Format-Volume @formatParam } } # if } # if @@ -664,6 +845,25 @@ function Set-TargetResource $($script:localizedData.SuccessfullyInitializedMessage -f $DriveLetter) ) -join '' ) } # if + + # Confirm that the volume is now actually formatted as a Dev Drive volume. + if ($DevDrive) + { + if ($volume.UniqueId -and (Test-DevDriveVolume -VolumeGuidPath $volume.UniqueId)) + { + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.SuccessfullyConfiguredDevDriveVolume -f $DriveLetter) + ) -join '' ) + } + else + { + throw ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.FailedToConfigureDevDriveVolume -f $DriveLetter) + ) -join '' ) + } + } } # Set-TargetResource <# @@ -703,9 +903,6 @@ function Set-TargetResource .PARAMETER DevDrive Specifies if the volume should be formatted as a Dev Drive. - - .PARAMETER UseUnallocatedSpace - Specifies that a new partition and volume should be formatted onto unallocated space on the disk. #> function Test-TargetResource { @@ -758,11 +955,7 @@ function Test-TargetResource [Parameter()] [System.Boolean] - $DevDrive, - - [Parameter()] - [System.Boolean] - $UseUnallocatedSpace + $DevDrive ) Write-Verbose -Message ( @( @@ -793,42 +986,6 @@ function Test-TargetResource return $false } # if - # Check Dev Drive feature is enabled and that the user inputted ReFS as the file system. - if ($PSBoundParameters.ContainsKey('DevDrive')) - { - Assert-DevDriveFeatureAvailable - Assert-DevDriveFormatOnReFsFileSystemOnly -FSFormat $FSFormat - - $tempPartition = Get-Partition ` - -DriveLetter $DriveLetter ` - -ErrorAction SilentlyContinue | Select-Object -First 1 - - # For unintialized disks, the largest free extent is the size of the disk. - if ($disk.PartitionStyle -ne 'RAW' ) - { - $currentDiskFreeSpace = $disk.LargestFreeExtent - } - else - { - $currentDiskFreeSpace = $disk.Size - } - - # User is attempting to create a new Dev Drive volume in a new partition - if ($null -eq $tempPartition) - { - <# - The Dev Drive will be created within the largest block of continguous unallocated space - available. So we need to check that the space is big enough to create the Dev Drive in. - #> - Assert-DiskHasEnoughSpaceToCreateDevDrive ` - -UserDesiredSize $Size ` - -CurrentDiskFreeSpace $currentDiskFreeSpace ` - -DiskNumber $Disk.Number - } - - Assert-DevDriveSizeMeetsMinimumRequirement -UserDesiredSize $Size - } - if ($disk.IsOffline) { Write-Verbose -Message ( @( @@ -920,7 +1077,33 @@ function Test-TargetResource -f $DriveLetter, $Partition.Size, $Size) ) -join '' ) - return $false + if ($DevDrive) + { + <# + In the Dev Drive scenario we may resize a partition to create new unallocated space, so that we can create + a new Dev Drive. When this is done we create a new partition on the largest free extent on the disk. However, + Though the value is equivalent they aren't always the same. E.g 150Gb in powershell cmdline is 161061273600 bytes. + But $disk.LargestFreeExtent could be 161060225024 bytes. Which is different but they are both 150Gb when you convert them. + + See the 'Size' parameter in https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/createpartition-msft-disk + for more information. But to some it up, what the user enters and what the New-Partition cmdlet is able to allocate + can be different in bytes. So to Keep idempotence, we only return false when they arent the same in GB. Also if volume + already ReFs, there is no point in returning false for size mismatches since they can't be resized with resize-partition. + #> + + $partitionInGb = [Math]::Round($partition.Size / 1GB, 2) + $sizeInGb = [Math]::Round($Size / 1GB, 2) + $volume = $partition | Get-Volume + + if (($sizeInGb -ne $partitionInGb) -and $volume.FileSystem -ne 'ReFS') + { + return $false + } + } + else + { + return $false + } } else { @@ -931,6 +1114,11 @@ function Test-TargetResource ) -join '' ) } } # if + + if ($DevDrive) + { + Assert-SizeMeetsMinimumDevDriveRequirement -UserDesiredSize $Size + } } # if $blockSize = (Get-CimInstance ` @@ -996,6 +1184,18 @@ function Test-TargetResource } # if } # if + if ($DevDrive) + { + Assert-DevDriveFeatureAvailable + Assert-FSFormatIsReFsWhenDevDriveFlagSetToTrue -FSFormat $FSFormat + + if ($volume.UniqueId -and (-not (Test-DevDriveVolume -VolumeGuidPath $volume.UniqueId))) + { + # The volume is not configured as a Dev Drive volume. + return $false + } + } + return $true } # Test-TargetResource diff --git a/source/DSCResources/DSC_Disk/DSC_Disk.schema.mof b/source/DSCResources/DSC_Disk/DSC_Disk.schema.mof index d9c00b12..7d06a357 100644 --- a/source/DSCResources/DSC_Disk/DSC_Disk.schema.mof +++ b/source/DSCResources/DSC_Disk/DSC_Disk.schema.mof @@ -13,5 +13,4 @@ class DSC_Disk : OMI_BaseResource [Write, Description("Specifies if potentially destructive operations may occur.")] Boolean AllowDestructive; [Write, Description("Specifies if the disks partition schema should be removed entirely, even if data and OEM partitions are present. Only possible with AllowDestructive enabled.")] Boolean ClearDisk; [Write, Description("Specifies if the volume should be formatted as a Dev Drive.")] Boolean DevDrive; - [Write, Description("Specifies that a new partition and volume should be formatted onto unallocated space in the disk.")] Boolean UseUnallocatedSpace; }; diff --git a/source/DSCResources/DSC_Disk/README.md b/source/DSCResources/DSC_Disk/README.md index 4f6d1ed4..32407f1b 100644 --- a/source/DSCResources/DSC_Disk/README.md +++ b/source/DSCResources/DSC_Disk/README.md @@ -23,10 +23,43 @@ identifier for disks with `GPT` partition table format. If the disk is `RAW` be used. This is because the _Guid_ for a disk is only assigned once the partition table for the disk has been created. +## Dev Drive + +The Dev Drive feature is currently available on Windows 11 in builds 10.0.22621.2338 or later. See [the Dev Drive documentation for the latest in formation](https://learn.microsoft.com/en-us/windows/dev-drive/). + +### What is a Dev Drive volume and how is it different from regular volumes? + +Dev Drive volumes from a storage perspective are just like regular ReFS volumes on a Windows machine. The difference However, is that most of the filter drivers except the antivirus filter will not attach to the volume at boot time by default. This is a low-level concept that most users will never need to interact with but for further reading, see the documentation [here](https://learn.microsoft.com/en-us/windows/dev-drive/#how-do-i-configure-additional-filters-on-dev-drive) for further reading. + +### What is the default state of the Dev Drive flag in this resource? + +By default, the Dev Drive flag is set to **false**. This means that a Dev Drive volume will not be created with the inputted parameters. This is used to create/reconfigure non Dev Drive volumes. Setting the flag to **true** will attempt to create/reconfigure a volume as a Dev Drive volume using the users' inputted parameters. + +### Can more than one Dev Drive be mounted at a time? + +Yes, more than one Dev Drive volume can be mounted at a time. You can have as many Dev Drive volumes as the physical storage amount on the disk permits. Though, it should be noted, that the `minimum size` for a single Dev Drive volume is `50 Gb`. + +### If I have a non Dev Drive volume that is 50 Gb or more can it be reformatted as a Dev Drive volume? + +Yes, since the Dev Drive volume is just like any other volume storage wise to the Windows operating system, a non Dev Drive ReFS volume can be reformatted as a Dev Drive volume. An NTFS volume can also be reformatted as a Dev Drive volume. Note, the Disk resource will throw an exception, should you also attempt to resize a ReFS volume while attempting to reformat it as a Dev Drive volume since ReFS volumes cannot be resized. As Dev Drive volumes are also ReFS volumes, they carry the same restrictions, see: [Resilient File System (ReFS) overview | Microsoft Learn](https://learn.microsoft.com/en-us/windows-server/storage/refs/refs-overview) + +### Dev Drive requirements for this resource + +There are only five requirements: + +1. The Dev Drive feature must be available on the machine. We assert that this is true in order to format a Dev Drive volume onto a partition. +2. The Dev Drive feature is enabled on the machine. Note: the feature could be disabled by either a group or system policy, so if ran in an enterprise environment this should be checked. Note, once a Dev Drive volume is created, its functionality will not change and will not be affected should the feature become disabled afterwards. Disablement would only prevent new Dev Drive volumes from being created. However, this could affect the `idempotence` for the Drive. For example, changes to this drive after disablement (e.g., reformatting the volume as an NTFS volume) would not be corrected by rerunning the configuration. Since the feature is disabled, attempting reformat the volume as a Dev Drive volume will throw an error advising you that it is not possible due to the feature being disabled. +3. If the `size` parameter is entered, the value must be greater than or equal to 50 Gb in size. We assert that this is true in order to format a Dev Drive volume onto a partition. +4. Currently today, if the `size` parameter is not entered then the Disk resource will use the maximum space available on the Disk. When the `DevDrive` flag is set to `$true`, then we assert that the maximum available free unallocated space on the Disk should be `50 Gb or more in size`. This assertion only comes into play if the volume doesn't already exist. +5. The `FSformat` parameter must be set to 'ReFS', when the `DevDrive` flag is set to true. We assert that this is true and throw an exception if it is not. + # Testing Note: Integration tests are not run for the Disk resource when SerialNumber is used since the virtual disk that is created does not have a serial number. +There are no Dev Drive integration tests as the feature is not available in Server +2019 and 2022. + ## Known Issues ### Defragsvc Conflict diff --git a/source/DSCResources/DSC_Disk/en-US/DSC_Disk.strings.psd1 b/source/DSCResources/DSC_Disk/en-US/DSC_Disk.strings.psd1 index 257c42da..42a209c0 100644 --- a/source/DSCResources/DSC_Disk/en-US/DSC_Disk.strings.psd1 +++ b/source/DSCResources/DSC_Disk/en-US/DSC_Disk.strings.psd1 @@ -39,4 +39,14 @@ FreeSpaceViolationError = Attempted to resize partition {0} from {1} to {2} while maximum allowed size was {3}. ResizeRefsNotPossibleMessage = Skipping resize of {0} from {1} to {2}. Resizing ReFS partitions is currently not possible. ResizeNotAllowedMessage = Skipping resize of {0} from {1} to {2}. AllowDestructive is not set to $true. + AllowDestructiveNeededForDevDriveOperation = The partition with drive letter '{0}' was found to be big enough to be resized to create enough space for a new Dev Drive volume. However the 'AllowDestructive' flag was not set to $true. + ResizingPartitionToMakeSpaceForDevDriveVolume = Resizing partition with drive letter '{0}' to size '{1} Gb', so we can make enough unallocated space for a Dev Drive volume of size '{2} Gb'. + SuccessfullyConfiguredDevDriveVolume = The volume on drive '{0}:\\' has been successfully configured as a Dev Drive volume. + FailedToConfigureDevDriveVolume = Failed to configure volume on drive '{0}:\\' as a Dev Drive volume. + AttemptingToFindAPartitionToResizeForDevDrive = Attempting to find a partition that we can resize, in order to create enough unallocated space for a new Dev Drive volume. + CheckingIfPartitionCanBeResizedForDevDrive = Checking if partition with drive letter '{0}' can be resized to create enough unallocated space for the new Dev Drive volume. + PartitionCantBeResizedForDevDrive = Partition with drive letter '{0}' cannot be resized to create the new Dev Drive volume. Continuing search... + PartitionFoundThatCanBeResizedForDevDrive = Found partition with drive letter '{0}' which can be resized to create a new Dev Drive volume. + FoundNoPartitionsThatCanResizedForDevDrive = There is no unallocated space available to create the Dev Drive volume. We found no partitions that could be resized to create '{0} Gb' of unallocated space. + NoPartitionResizeNeededForDevDrive = We found enough unallocated space available on the disk. No partition resize is needed to create the Dev Drive volume. '@ diff --git a/source/Examples/Resources/Disk/3-Disk_CreateDevDriveOnDiskWithExistingPartitions.ps1 b/source/Examples/Resources/Disk/3-Disk_CreateDevDriveOnDiskWithExistingPartitions.ps1 new file mode 100644 index 00000000..ca66ba9e --- /dev/null +++ b/source/Examples/Resources/Disk/3-Disk_CreateDevDriveOnDiskWithExistingPartitions.ps1 @@ -0,0 +1,86 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID 3f629ab7-358f-4d82-8c0a-556e32514e3e +.AUTHOR DSC Community +.COMPANYNAME DSC Community +.COPYRIGHT Copyright the DSC Community contributors. All rights reserved. +.TAGS DSCConfiguration +.LICENSEURI https://github.com/dsccommunity/StorageDsc/blob/main/LICENSE +.PROJECTURI https://github.com/dsccommunity/StorageDsc +.ICONURI +.EXTERNALMODULEDEPENDENCIES +.REQUIREDSCRIPTS +.EXTERNALSCRIPTDEPENDENCIES +.RELEASENOTES First version. +.PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core +#> + +#Requires -module StorageDsc + +<# + .DESCRIPTION + For this scenario we want to create two 60 Gb Dev Drive volumes. We know that disk 2 has 3 existing volumes + NTFS volumes and we prefer not to remove them. At most we only wantvthe disk DSC resource to resize any + of them should there not be enough space for any Dev Drive volume to be created. We also know that the + the 3 existing volumes are 100Gb, 200Gb and 300Gb in size and disk 2 is 600 Gb in size. Since all the space + is being used by the existing volumes, The Disk Dsc resource will resize the existing volumes to create + space for our new Dev Drive volumes. An example of what could happen is the Disk resource could resize the + 300Gb volume to 240Gb for the first Dev Drive volume and then resize the 240Gb volume again to 180Gb for the second. + Thats just one combination, the disk Dsc resource uses the Get-PartitionSupportedSize cmdlet to know which volume + can be be resized to a safe size to create enough unallocated space for the Dev Drive volume to be created. Note: + ReFS volumes cannot be resized, so if the existing volumes were all ReFS volumes, the Disk Dsc resource would not be able + to resize any volumes and would instead throw an exception. + + This configuration below will wait for disk 2 to become available, and then create two new 60 Gb Dev Drive volumes, + 'E' and 'F'. The volumes will be formatted as ReFS volumes and labeled 'Dev Drive 1' and 'Dev Drive 2' respectively. + Note: setting 'AllowDestructive' to $true will not cause the disk to be cleared, as the flag is only used when there + is a need to resize an existing partition. It is used as confirmation that you agree to the resizing which will + create the necessary space for the Dev Drive volume. This flag is **NOT** needed if you already know there is enough + unallocated space on the disk to create the Dev Drive volume. If this flag is not used and there is not enough space + to create the Dev Drive volume an error will be thrown and the Dev Drive will not be created. Its important to be very + careful not to add the 'ClearDisk' flag while using the 'AllowDestructive' flag, as this will cause the disk to be cleared, + and all data lost on the disk (even existing volumes). +#> +Configuration Disk_CreateDevDriveOnDiskWithExistingPartitions +{ + Import-DSCResource -ModuleName StorageDsc + + Node localhost + { + WaitForDisk Disk2 + { + DiskId = '5E1E50A401000000001517FFFF0AEB84' # Disk 2 + DiskIdType = 'UniqueId' + RetryIntervalSec = 60 + RetryCount = 60 + } + + # Will create a Dev Drive volume of 60 Gb called Dev Drive 1. + Disk DevDrive1 + { + DiskId = '5E1E50A401000000001517FFFF0AEB84' + DiskIdType = 'UniqueId' + DriveLetter = 'E' + FSFormat = 'ReFS' + FSLabel = 'DevDrive 1' + DevDrive = $true + AllowDestructive = $true + Size = 60Gb + DependsOn = '[WaitForDisk]Disk2' + } + + # Will create a Dev Drive volume of 60 Gb called Dev Drive 2. + Disk DevDrive2 + { + DiskId = '5E1E50A401000000001517FFFF0AEB84' + DiskIdType = 'UniqueId' + DriveLetter = 'F' + FSFormat = 'ReFS' + FSLabel = 'DevDrive 2' + DevDrive = $true + AllowDestructive = $true + Size = 60Gb + DependsOn = '[Disk]DevDrive1' + } + } +} diff --git a/source/Examples/Resources/Disk/3-Disk_InitializeDiskWithADevDrive.ps1 b/source/Examples/Resources/Disk/3-Disk_InitializeDiskWithADevDrive.ps1 deleted file mode 100644 index ebeb5c70..00000000 --- a/source/Examples/Resources/Disk/3-Disk_InitializeDiskWithADevDrive.ps1 +++ /dev/null @@ -1,69 +0,0 @@ -<#PSScriptInfo -.VERSION 1.0.0 -.GUID 3f629ab7-358f-4d82-8c0a-556e32514e3e -.AUTHOR DSC Community -.COMPANYNAME DSC Community -.COPYRIGHT Copyright the DSC Community contributors. All rights reserved. -.TAGS DSCConfiguration -.LICENSEURI https://github.com/dsccommunity/StorageDsc/blob/main/LICENSE -.PROJECTURI https://github.com/dsccommunity/StorageDsc -.ICONURI -.EXTERNALMODULEDEPENDENCIES -.REQUIREDSCRIPTS -.EXTERNALSCRIPTDEPENDENCIES -.RELEASENOTES First version. -.PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core -#> - -#Requires -module StorageDsc - -<# - .DESCRIPTION - This configuration will wait for disk 2 to become available, and then make the disk available as - two new Dev Drive volumes, 'E' and 'F', with 'F' using all available space after 'E' has been - created. -#> -Configuration Disk_InitializeDiskWithADevDrive -{ - Import-DSCResource -ModuleName StorageDsc - - Node localhost - { - WaitForDisk Disk2 - { - DiskId = '5E1E50A401000000001517FFFF0AEB84' # Disk 2 - DiskIdType = 'UniqueId' - RetryIntervalSec = 60 - RetryCount = 60 - } - - # Will create a Dev Drive of 50Gb requiring the disk to have 50Gb of unallocated space. - Disk DevDriveVolume1 - { - DiskId = '5E1E50A401000000001517FFFF0AEB84' - DiskIdType = 'UniqueId' - DriveLetter = 'E' - FSFormat = 'ReFS' - FSLabel = 'DevDrive' - DevDrive = $true - Size = 50Gb - UseUnallocatedSpace = $true - DependsOn = '[WaitForDisk]Disk2' - } - - <# - Will attempt to create a Dev Drive volume using the rest of the space on the disk assuming - that the rest of the space is greater than the minimum size for Dev Drive volumes (50Gb). - #> - Disk DevDriveVolume2 - { - DiskId = '5E1E50A401000000001517FFFF0AEB84' - DiskIdType = 'UniqueId' - DriveLetter = 'F' - FSFormat = 'ReFS' - FSLabel = 'DevDrive' - DevDrive = $true - DependsOn = '[Disk]DevDriveVolume1' - } - } -} diff --git a/source/Examples/Resources/Disk/4-Disk_InitializeDiskWithMultipleDrivesIncludingDevDrives.ps1 b/source/Examples/Resources/Disk/4-Disk_InitializeDiskWithMultipleDrivesIncludingDevDrives.ps1 new file mode 100644 index 00000000..53e72892 --- /dev/null +++ b/source/Examples/Resources/Disk/4-Disk_InitializeDiskWithMultipleDrivesIncludingDevDrives.ps1 @@ -0,0 +1,101 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID 3f629ab7-358f-4d82-8c0a-556e32514e3e +.AUTHOR DSC Community +.COMPANYNAME DSC Community +.COPYRIGHT Copyright the DSC Community contributors. All rights reserved. +.TAGS DSCConfiguration +.LICENSEURI https://github.com/dsccommunity/StorageDsc/blob/main/LICENSE +.PROJECTURI https://github.com/dsccommunity/StorageDsc +.ICONURI +.EXTERNALMODULEDEPENDENCIES +.REQUIREDSCRIPTS +.EXTERNALSCRIPTDEPENDENCIES +.RELEASENOTES First version. +.PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core +#> + +#Requires -module StorageDsc + +<# + .DESCRIPTION + For this scenario we want to create 2 Non Dev Drive volumes and 2 Dev Drive volumes on a new 1 Tb disk + (disk 1) with no partitions. The first non Dev Drive volume will be an NTFS volume of 100 Gb called 'Data'. + The second non Dev Drive volume will be a ReFS volume of 200 Gb called 'Logs'. The first Dev Drive volume + will be a ReFS volume of 300 Gb called 'Dev Drive 1'. The second Dev Drive volume will be a ReFS volume of + 400 Gb called 'Dev Drive 2'. Note: The Dev Drive volumes will be created after the non Dev Drive volumes are + created but the order does not matter, we could have created the Dev Drive volumes first and then the non Dev + Drive volumes or even interleave them. Since this is a new disk and we know there are no existing partitions, + we do not need to set the 'AllowDestructive' flag for the Dev Drive volumes like in + 3-Disk_CreateDevDriveOnDiskWithExistingPartitions.ps1. + + This configuration below will wait for disk 1 to become available, and then create two new non Dev Drive volumes + called Data and Logs with Drive letters G and J respectively. The D drive is an NTFS drive and the J drive is an + ReFS drive. It also create two new Dev Drive volumes which are assigned drive letters K and L respectively. + The Dev Drive volumes are formatted as ReFS volumes and labeled 'Dev Drive 1' and 'Dev Drive 2' respectively. +#> +Configuration Disk_InitializeDiskWithMultipleDrivesIncludingDevDrives +{ + Import-DSCResource -ModuleName StorageDsc + + Node localhost + { + WaitForDisk Disk1 + { + DiskId = '5E1E50A401000000001517FFFF0AEB81' # Disk 1 + DiskIdType = 'UniqueId' + RetryIntervalSec = 60 + RetryCount = 60 + } + + # Will create a NTFS volume of 100 Gb called Data. + Disk DataVolume + { + DiskId = '5E1E50A401000000001517FFFF0AEB81' + DiskIdType = 'UniqueId' + DriveLetter = 'G' + FSFormat = 'NTFS' + FSLabel = 'Data' + Size = 100Gb + DependsOn = '[WaitForDisk]Disk1' + } + + # Will create a ReFS volume of 200 Gb called Logs. + Disk LogsVolume + { + DiskId = '5E1E50A401000000001517FFFF0AEB81' + DiskIdType = 'UniqueId' + DriveLetter = 'J' + FSFormat = 'ReFS' + FSLabel = 'Logs' + Size = 200Gb + DependsOn = '[Disk]DataVolume' + } + + # Will create a Dev Drive volume of 300 Gb called Dev Drive 1. + Disk DevDrive1 + { + DiskId = '5E1E50A401000000001517FFFF0AEB81' + DiskIdType = 'UniqueId' + DriveLetter = 'K' + FSFormat = 'ReFS' + FSLabel = 'DevDrive 1' + DevDrive = $true + Size = 300Gb + DependsOn = '[Disk]LogsVolume' + } + + # Will create a Dev Drive volume of 400 Gb called Dev Drive 2. + Disk DevDrive2 + { + DiskId = '5E1E50A401000000001517FFFF0AEB81' + DiskIdType = 'UniqueId' + DriveLetter = 'L' + FSFormat = 'ReFS' + FSLabel = 'DevDrive 2' + DevDrive = $true + Size = 400Gb + DependsOn = '[Disk]DevDrive1' + } + } +} diff --git a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 index 7ff1e242..e5faf381 100644 --- a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 +++ b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 @@ -1,3 +1,8 @@ + +using namespace System; +using namespace System.Runtime.InteropServices; +using namespace Microsoft.Win32.SafeHandles; + $modulePath = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'Modules' Import-Module -Name (Join-Path -Path $modulePath -ChildPath 'DscResource.Common') @@ -235,6 +240,8 @@ function Get-DevDriveWin32HelperScript $DevDriveHelperDefinitions = @' + + // https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ne-sysinfoapi-developer_drive_enablement_state public enum DEVELOPER_DRIVE_ENABLEMENT_STATE { DeveloperDriveEnablementStateError = 0, @@ -243,13 +250,133 @@ function Get-DevDriveWin32HelperScript DeveloperDriveDisabledByGroupPolicy = 3, } + // https://learn.microsoft.com/en-us/windows/win32/api/apiquery2/nf-apiquery2-isapisetimplemented [DllImport("api-ms-win-core-apiquery-l2-1-0.dll", ExactSpelling = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] public static extern bool IsApiSetImplemented(string Contract); + // https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getdeveloperdriveenablementstate [DllImport("api-ms-win-core-sysinfo-l1-2-6.dll")] public static extern DEVELOPER_DRIVE_ENABLEMENT_STATE GetDeveloperDriveEnablementState(); + + // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern Microsoft.Win32.SafeHandles.SafeFileHandle CreateFile( + string lpFileName, + uint dwDesiredAccess, + uint dwShareMode, + IntPtr lpSecurityAttributes, + uint dwCreationDisposition, + uint dwFlagsAndAttributes, + IntPtr hTemplateFile); + + // https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-deviceiocontrol + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool DeviceIoControl( + Microsoft.Win32.SafeHandles.SafeFileHandle hDevice, + uint dwIoControlCode, + IntPtr lpInBuffer, + uint nInBufferSize, + IntPtr lpOutBuffer, + uint nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + + // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_file_fs_persistent_volume_information + [StructLayout(LayoutKind.Sequential)] + public struct FILE_FS_PERSISTENT_VOLUME_INFORMATION + { + public uint VolumeFlags; + public uint FlagMask; + public uint Version; + public uint Reserved; + } + + // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_file_fs_persistent_volume_information + public const uint FSCTL_QUERY_PERSISTENT_VOLUME_STATE = 590396U; + public const uint PERSISTENT_VOLUME_STATE_DEV_VOLUME = 0x00002000; + + // https://learn.microsoft.com/en-us/windows/win32/fileio/creating-and-opening-files + public const uint FILE_READ_ATTRIBUTES = 0x0080; + public const uint FILE_WRITE_ATTRIBUTES = 0x0100; + public const uint FILE_SHARE_READ = 0x00000001; + public const uint FILE_SHARE_WRITE = 0x00000002; + public const uint OPEN_EXISTING = 3; + public const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000; + + // To call the win32 function without having to allocate memory in powershell + public static bool DeviceIoControlWrapperForDevDriveQuery(string volumeGuidPath) + { + uint notUsedSize = 0; + var outputVolumeInfo = new FILE_FS_PERSISTENT_VOLUME_INFORMATION { }; + var inputVolumeInfo = new FILE_FS_PERSISTENT_VOLUME_INFORMATION { }; + inputVolumeInfo.FlagMask = PERSISTENT_VOLUME_STATE_DEV_VOLUME; + inputVolumeInfo.Version = 1; + + var volumeFileHandle = CreateFile( + volumeGuidPath, + FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE, + IntPtr.Zero, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + IntPtr.Zero); + + if (volumeFileHandle.IsInvalid) + { + // Handle is invalid. + throw new Exception("CreateFile unable to get file handle for volume to check if its a Dev Drive volume", + new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error())); + } + + + // We need to allocated memory for the structures so we can marshal and unmarshal them. + IntPtr inputVolptr = Marshal.AllocHGlobal(Marshal.SizeOf(inputVolumeInfo)); + IntPtr outputVolptr = Marshal.AllocHGlobal(Marshal.SizeOf(outputVolumeInfo)); + + try + { + Marshal.StructureToPtr(inputVolumeInfo, inputVolptr, false); + + var result = DeviceIoControl( + volumeFileHandle, + FSCTL_QUERY_PERSISTENT_VOLUME_STATE, + inputVolptr, + (uint)Marshal.SizeOf(inputVolumeInfo), + outputVolptr, + (uint)Marshal.SizeOf(outputVolumeInfo), + out notUsedSize, + IntPtr.Zero); + + if (!result) + { + // Can't query volume. + throw new Exception("DeviceIoControl unable to query if volume is a Dev Drive volume", + new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error())); + } + + // Unmarshal the output structure + outputVolumeInfo = (FILE_FS_PERSISTENT_VOLUME_INFORMATION)Marshal.PtrToStructure(outputVolptr, typeof(FILE_FS_PERSISTENT_VOLUME_INFORMATION)); + + if ((outputVolumeInfo.VolumeFlags & PERSISTENT_VOLUME_STATE_DEV_VOLUME) > 0) + { + // Volume is a Dev Drive volume. + return true; + } + + + return false; + } + finally + { + Marshal.FreeHGlobal(inputVolptr); + Marshal.FreeHGlobal(outputVolptr); + volumeFileHandle.Close(); + } + } + + '@ if (([System.Management.Automation.PSTypeName]'DevDrive.DevDriveHelper').Type) { @@ -268,12 +395,12 @@ function Get-DevDriveWin32HelperScript <# .SYNOPSIS - Invokes win32 IsApiSetImplemented function + Invokes win32 IsApiSetImplemented function. - .PARAMETER AccessPath - Specifies the contract string for the dll that houses the win32 function + .PARAMETER Contract + Specifies the contract string for the dll that houses the win32 function. #> -function Get-IsApiSetImplemented +function Invoke-IsApiSetImplemented { [CmdletBinding()] [OutputType([System.Boolean])] @@ -287,13 +414,13 @@ function Get-IsApiSetImplemented $helper = Get-DevDriveWin32HelperScript return $helper::IsApiSetImplemented($Contract) -} # end function Get-IsApiSetImplemented +} # end function Invoke-IsApiSetImplemented <# .SYNOPSIS - Invokes win32 GetDeveloperDriveEnablementState function + Invokes win32 GetDeveloperDriveEnablementState function. #> -function Get-DeveloperDriveEnablementState +function Get-DevDriveEnablementState { [CmdletBinding()] [OutputType([System.Enum])] @@ -302,7 +429,7 @@ function Get-DeveloperDriveEnablementState $helper = Get-DevDriveWin32HelperScript return $helper::GetDeveloperDriveEnablementState() -} # end function Get-DeveloperDriveEnablementState +} # end function Get-DevDriveEnablementState <# .SYNOPSIS @@ -318,14 +445,15 @@ function Assert-DevDriveFeatureAvailable $devDriveHelper = Get-DevDriveWin32HelperScript Write-Verbose -Message ($script:localizedData.CheckingDevDriveEnablementMessage) - $IsApiSetImplemented = Get-IsApiSetImplemented("api-ms-win-core-sysinfo-l1-2-6") + $IsApiSetImplemented = Invoke-IsApiSetImplemented('api-ms-win-core-sysinfo-l1-2-6') $DevDriveEnablementType = [DevDrive.DevDriveHelper+DEVELOPER_DRIVE_ENABLEMENT_STATE] + if ($IsApiSetImplemented) { try { # Based on the enablement result we will throw an error or return without doing anything. - switch (Get-DeveloperDriveEnablementState) + switch (Get-DevDriveEnablementState) { ($DevDriveEnablementType::DeveloperDriveEnablementStateError) { @@ -371,7 +499,7 @@ function Assert-DevDriveFeatureAvailable .PARAMETER FSFormat Specifies the file system format of the new volume. #> -function Assert-DevDriveFormatOnReFsFileSystemOnly +function Assert-FSFormatIsReFsWhenDevDriveFlagSetToTrue { [CmdletBinding()] param @@ -384,99 +512,89 @@ function Assert-DevDriveFormatOnReFsFileSystemOnly if ($FSFormat -ne 'ReFS') { New-InvalidArgumentException ` - -Message $($script:localizedData.DevDriveOnlyAvailableForReFsError -f 'ReFS', $FSFormat) ` + -Message $($script:localizedData.FSFormatNotReFSWhenDevDriveFlagIsTrueError -f 'ReFS', $FSFormat) ` -ArgumentName 'FSFormat' } -} # end function Assert-DevDriveFormatOnReFsFileSystemOnly +} # end function Assert-FSFormatIsReFsWhenDevDriveFlagSetToTrue <# .SYNOPSIS - Validates that the user has enough space on the disk to create a Dev Drive volume. + Validates that the user entered a size greater than the minimum for Dev Drive volumes. + (The minimum is 50 Gb) .PARAMETER UserDesiredSize Specifies the size the user wants to create the Dev Drive volume with. - - .PARAMETER CurrentDiskFreeSpace - Specifies the maximum free space that can be used to create a partition on the disk with. - - .PARAMETER DiskNumber - Specifies the the disk number the user what to create the Dev Drive volume inside. #> -function Assert-DiskHasEnoughSpaceToCreateDevDrive +function Assert-SizeMeetsMinimumDevDriveRequirement { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.UInt64] - $UserDesiredSize, + $UserDesiredSize + ) - [Parameter(Mandatory = $true)] - [System.UInt64] - $CurrentDiskFreeSpace, + # 50 Gb is the minimum size for Dev Drive volumes. + $UserDesiredSizeInGb = [Math]::Round($UserDesiredSize / 1GB, 2) + $minimumSizeForDevDriveInGb = 50 + + if ($UserDesiredSizeInGb -lt $minimumSizeForDevDriveInGb) + { + throw ($script:localizedData.MinimumSizeNeededToCreateDevDriveVolumeError -F $UserDesiredSizeInGb ) + } +} # end function Assert-SizeMeetsMinimumDevDriveRequirement + +<# +.SYNOPSIS + Invokes the wrapper for the DeviceIoControl Win32 API function. + +.PARAMETER VolumeGuidPath + The guid path of the volume that will be queried. +#> +function Invoke-DeviceIoControlWrapperForDevDriveQuery +{ + [CmdletBinding()] + [OutputType([System.boolean])] + param + ( [Parameter(Mandatory = $true)] - [System.UInt32] - $DiskNumber + [System.String] + $VolumeGuidPath ) - <# - 50 Gb is the minimum size for Dev Drive volumes. When size is 0 the user wants to use all - the available space on the disk. - #> - $notEnoughSpace = $false - if (-not $UserDesiredSize) - { - <# - The user wants to use all the available space on the disk. We will check if they have at least 50 Gb - of free space available. - #> - $notEnoughSpace = ($CurrentDiskFreeSpace -lt 50Gb) - $UserDesiredSize = 50Gb - } + $devDriveHelper = Get-DevDriveWin32HelperScript - if ($notEnoughSpace -or ($UserDesiredSize -gt $CurrentDiskFreeSpace)) - { - $DesiredSizeInGb = [Math]::Round($UserDesiredSize / 1GB, 2) - $CurrentDiskFreeSpaceInGb = [Math]::Round($CurrentDiskFreeSpace / 1GB, 2) - New-InvalidArgumentException ` - -Message $($script:localizedData.DevDriveNotEnoughSpaceToCreateDevDriveError -f ` - $DiskNumber, $DesiredSizeInGb, $CurrentDiskFreeSpaceInGb) ` - -ArgumentName 'UserDesiredSize' - } -} # end function Assert-DiskHasEnoughSpaceToCreateDevDrive + return $devDriveHelper::DeviceIoControlWrapperForDevDriveQuery($VolumeGuidPath) + +}# end function Invoke-DeviceIoControlWrapperForDevDriveQuery <# .SYNOPSIS - Validates that the user entered a size greater than the minimum for Dev Drive volumes. - (The minimum is 50 Gb) + Validates that a volume is a Dev Drive volume. This is temporary until a way to do + this is added to the Storage Powershell library to query whether the volume is a Dev Drive volume + or not. - .PARAMETER UserDesiredSize - Specifies the size the user wants to create the Dev Drive volume with. + .PARAMETER VolumeGuidPath + The guid path of the volume that will be queried. #> -function Assert-DevDriveSizeMeetsMinimumRequirement +function Test-DevDriveVolume { [CmdletBinding()] + [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] - [System.UInt64] - $UserDesiredSize + [System.String] + $VolumeGuidPath ) - <# - 50 Gb is the minimum size for Dev Drive volumes. The case where no size is - provided is covered in Assert-DiskHasEnoughSpaceToCreateDevDrive. - #> - if ($UserDesiredSize -and $UserDesiredSize -lt 50Gb) - { - New-InvalidArgumentException ` - -Message $($script:localizedData.DevDriveMinimumSizeError) ` - -ArgumentName 'UserDesiredSize' - } + $devDriveHelper = Get-DevDriveWin32HelperScript -} # end function Assert-DevDriveSizeMeetsMinimumRequirement + return Invoke-DeviceIoControlWrapperForDevDriveQuery -VolumeGuidPath $VolumeGuidPath +}# end function Test-DevDriveVolume Export-ModuleMember -Function @( 'Restart-ServiceIfExists', @@ -485,10 +603,11 @@ Export-ModuleMember -Function @( 'Get-DiskByIdentifier', 'Test-AccessPathAssignedToLocal', 'Assert-DevDriveFeatureAvailable', - 'Assert-DevDriveFormatOnReFsFileSystemOnly', - 'Assert-DevDriveSizeMeetsMinimumRequirement', + 'Assert-FSFormatIsReFsWhenDevDriveFlagSetToTrue', + 'Assert-SizeMeetsMinimumDevDriveRequirement', 'Get-DevDriveWin32HelperScript', - 'Get-IsApiSetImplemented', - 'Get-DeveloperDriveEnablementState', - 'Assert-DiskHasEnoughSpaceToCreateDevDrive' + 'Invoke-IsApiSetImplemented', + 'Get-DevDriveEnablementState', + 'Test-DevDriveVolume', + 'Invoke-DeviceIoControlWrapperForDevDriveQuery' ) diff --git a/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 b/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 index ea968317..8af13fea 100644 --- a/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 +++ b/source/Modules/StorageDsc.Common/en-US/StorageDsc.Common.strings.psd1 @@ -10,7 +10,6 @@ ConvertFrom-StringData @' DevDriveEnabledMessage = Dev Drive feature is enabled on this system. CheckingDevDriveEnablementMessage = Checking if the Dev Drive feature is available and enabled on the system. DevDriveFeatureNotImplementedError = Dev Drive feature is not implemented on this system. - DevDriveMinimumSizeError = Dev Drive volumes must be 50 Gb or more in size. - DevDriveNotEnoughSpaceToCreateDevDriveError = There is not enough unallocated space '{2} Gb' to create a Dev Drive volume of size '{1} Gb' on disk '{0}'. - DevDriveOnlyAvailableForReFsError = "Only 'ReFS' is supported for Dev Drive volumes." + MinimumSizeNeededToCreateDevDriveVolumeError = To configure a volume as a Dev Drive volume the size parameter must be 50 Gb or more. Size of '{0} Gb' was specified. + FSFormatNotReFSWhenDevDriveFlagIsTrueError = Only the 'ReFS' file system can be used with FSFormat when the Dev Drive flag is set to true. '@ diff --git a/tests/Unit/DSC_Disk.Tests.ps1 b/tests/Unit/DSC_Disk.Tests.ps1 index e8ed495e..3662cddd 100644 --- a/tests/Unit/DSC_Disk.Tests.ps1 +++ b/tests/Unit/DSC_Disk.Tests.ps1 @@ -19,6 +19,7 @@ function Invoke-TestSetup -TestType 'Unit' Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') + $modulePath = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'Modules' } function Invoke-TestCleanup @@ -34,6 +35,8 @@ try InModuleScope $script:dscResourceName { $script:testDriveLetter = 'G' $script:testDriveLetterH = 'H' + $script:testDriveLetterK = 'K' + $script:testDriveLetterT = 'T' $script:testDiskNumber = 1 $script:testDiskUniqueId = 'TESTDISKUNIQUEID' $script:testDiskFriendlyName = 'TESTDISKFRIENDLYNAME' @@ -106,7 +109,11 @@ try PartitionStyle = 'GPT' } - $script:mockedDisk0GptForDevDrive = [pscustomobject] @{ + <# + Used in the scenario where a user wants to create a Dev Drive volume + and there is sufficient unallocated space available. + #> + $script:mockedDisk0GptForDevDriveResizeNotNeededScenario = [pscustomobject] @{ Number = $script:testDiskNumber UniqueId = $script:testDiskUniqueId FriendlyName = $script:testDiskFriendlyName @@ -116,7 +123,52 @@ try IsReadOnly = $false PartitionStyle = 'GPT' Size = 100Gb - LargestFreeExtent = 90Gb + } + + <# + Used in the scenario where a user wants to create a Dev Drive volume but there + is insufficient unallocated space available and a resize of any partition is not possibile. + #> + $script:mockedDisk0GptForDevDriveResizeNotPossibleScenario = [pscustomobject] @{ + Number = $script:testDiskNumber + UniqueId = $script:testDiskUniqueId + FriendlyName = $script:testDiskFriendlyName + SerialNumber = $script:testDiskSerialNumber + Guid = $script:testDiskGptGuid + IsOffline = $false + IsReadOnly = $false + PartitionStyle = 'GPT' + Size = 60Gb + } + + <# + Used in the scenario where a user wants to create a Dev Drive volume but there + is insufficient unallocated space available. However a resize of a partition possibile. + which will create new unallocated space for the new partition. + #> + $script:mockedDisk0GptForDevDriveResizePossibleScenario = [pscustomobject] @{ + Number = $script:testDiskNumber + UniqueId = $script:testDiskUniqueId + FriendlyName = $script:testDiskFriendlyName + SerialNumber = $script:testDiskSerialNumber + Guid = $script:testDiskGptGuid + IsOffline = $false + IsReadOnly = $false + PartitionStyle = 'GPT' + Size = 100Gb + } + + $script:mockedDisk0GptForDevDriveAfterResize = [pscustomobject] @{ + Number = $script:testDiskNumber + UniqueId = $script:testDiskUniqueId + FriendlyName = $script:testDiskFriendlyName + SerialNumber = $script:testDiskSerialNumber + Guid = $script:testDiskGptGuid + IsOffline = $false + IsReadOnly = $false + PartitionStyle = 'GPT' + Size = 100Gb + LargestFreeExtent = 50Gb } $script:mockedDisk0RawForDevDrive = [pscustomobject] @{ @@ -149,6 +201,23 @@ try $script:mockedPartitionSize50Gb = 50GB + $script:mockedPartitionSize70Gb = 70GB + + $script:mockedPartitionSize100Gb = 100GB + + $script:mockedPartitionWithTDriveLetter = [pscustomobject] @{ + DriveLetter = [System.Char] $script:testDriveLetterT + Size = $script:mockedPartitionSize50Gb + PartitionNumber = 1 + Type = 'Basic' + } + + $script:mockedPartitionSupportedSizeForTDriveletter = [pscustomobject] @{ + DriveLetter = [System.Char] $script:testDriveLetterT + SizeMax = $script:mockedPartitionSize100Gb + SizeMin = $script:mockedPartitionSize10Gb + } + $script:mockedPartitionWithGDriveletter = [pscustomobject] @{ DriveLetter = [System.Char] $script:testDriveLetter Size = $script:mockedPartitionSize50Gb @@ -156,6 +225,12 @@ try Type = 'Basic' } + $script:mockedPartitionSupportedSizeForGDriveletter = [pscustomobject] @{ + DriveLetter = [System.Char] $script:testDriveLetter + SizeMax = $script:mockedPartitionSize50Gb + SizeMin = $script:mockedPartitionSize50Gb + } + $script:mockedPartitionWithHDriveLetter = [pscustomobject] @{ DriveLetter = [System.Char] $script:testDriveLetterH Size = $script:mockedPartitionSize50Gb @@ -163,6 +238,39 @@ try Type = 'Basic' } + $script:mockedPartitionSupportedSizeForHDriveletter = [pscustomobject] @{ + DriveLetter = [System.Char] $script:testDriveLetterH + SizeMax = $script:mockedPartitionSize100Gb + SizeMin = $script:mockedPartitionSize10Gb + } + + $script:mockedPartitionWithKDriveLetter = [pscustomobject] @{ + DriveLetter = [System.Char] $script:testDriveLetterK + Size = $script:mockedPartitionSize70Gb + PartitionNumber = 1 + Type = 'Basic' + } + + $script:mockedPartitionSupportedSizeForKDriveletter = [pscustomobject] @{ + DriveLetter = [System.Char] $script:testDriveLetterK + SizeMax = $script:mockedPartitionSize100Gb + SizeMin = $script:mockedPartitionSize + } + + $script:mockedPartitionListForResizeNotPossibleScenario = @( + $script:mockedPartitionWithGDriveletter + ) + + $script:mockedPartitionListForResizeNotNeededScenario = @( + $script:mockedPartitionWithGDriveletter, + $script:mockedPartitionWithHDriveLetter + ) + + $script:mockedPartitionListForResizePossibleScenario = @( + $script:mockedPartitionWithGDriveletter, + $script:mockedPartitionWithKDriveLetter + ) + <# This condition seems to occur in some systems where the same partition is reported twice with the same drive letter. @@ -198,6 +306,22 @@ try IsReadOnly = $false } + $script:mockedPartitionGDriveLetter40Gb = [pscustomobject] @{ + DriveLetter = [System.Char] $testDriveLetter + Size = $script:mockedPartitionSize40Gb + PartitionNumber = 1 + Type = 'Basic' + IsReadOnly = $false + } + + $script:mockedPartitionGDriveLetter50Gb = [pscustomobject] @{ + DriveLetter = [System.Char] $testDriveLetter + Size = $script:mockedPartitionSize50Gb + PartitionNumber = 1 + Type = 'Basic' + IsReadOnly = $false + } + $script:mockedPartitionNoDriveLetterReadOnly = [pscustomobject] @{ DriveLetter = [System.Char] $null Size = $script:mockedPartitionSize @@ -230,33 +354,53 @@ try DriveLetter = $script:testDriveLetter } - $script:parameterFilter_MockedDisk0Number = { - $DiskId -eq $script:mockedDisk0Gpt.Number -and $DiskIdType -eq 'Number' + $script:mockedVolumeDevDrive = [pscustomobject] @{ + FileSystemLabel = 'myLabel' + FileSystem = 'ReFS' + DriveLetter = $script:testDriveLetter + UniqueId = '\\?\Volume{3a244a32-efba-4b7e-9a19-7293fc7c7924}\' } - $script:UninitializeDiskSizeParamsForDevDrive = { - $UserDesiredSize -eq $script:userDesiredSize60Gb -and - $CurrentDiskFreeSpace -eq $script:mockedDisk0RawForDevDrive.size -and - $DiskNumber -eq $script:mockedDisk0RawForDevDrive.Number + $script:mockedVolumeCreatedAfterNewPartiton = [pscustomobject] @{ + FileSystemLabel = '' + FileSystem = '' + DriveLetter = $script:testDriveLetterT + UniqueId = '\\?\Volume{3a244a32-efba-4b7e-9a19-7293fc7c7924}\' } - $script:InitializeDiskSizeParamsForDevDrive = { - $UserDesiredSize -eq $script:userDesiredSize60Gb -and - $CurrentDiskFreeSpace -eq $script:mockedDisk0GptForDevDrive.LargestFreeExtent -and - $DiskNumber -eq $script:mockedDisk0GptForDevDrive.Number + $script:mockedVolumeThatExistPriorToConfiguration = [pscustomobject] @{ + FileSystemLabel = 'myLabel' + FileSystem = 'NTFS' + DriveLetter = $script:testDriveLetterT + UniqueId = '\\?\Volume{3a244a32-efba-4b7e-9a19-7293fc7c7924}\' + Size = $script:mockedPartitionSize50Gb } - $script:userDesiredSize60Gb = 60Gb + $script:parameterFilter_MockedDisk0Number = { + $DiskId -eq $script:mockedDisk0Gpt.Number -and $DiskIdType -eq 'Number' + } $script:userDesiredSize50Gb = 50Gb $script:userDesiredSize40Gb = 40Gb - $script:userDesiredSizeZeroGb = 0Gb + $script:amountOfTimesGetDiskByIdentifierIsCalled = 0 - $script:currentFreeSpaceSize60Gb = 60Gb + function Get-PartitionSupportedSizeForDevDriveScenarios + { + [CmdletBinding()] + param + ( + [Parameter()] + $DriveLetter + ) - $script:currentFreeSpaceSize50Gb = 50Gb + switch ($DriveLetter) { + 'G' { $script:mockedPartitionSupportedSizeForGDriveletter } + 'H' { $script:mockedPartitionSupportedSizeForHDriveletter } + 'K' { $script:mockedPartitionSupportedSizeForKDriveletter } + } + } <# These functions are required to be able to mock functions where @@ -499,44 +643,13 @@ try () } - function Assert-DevDriveFormatOnReFsFileSystemOnly - { - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $FSFormat - ) - } - - function Assert-DiskHasEnoughSpaceToCreateDevDrive + function Test-DevDriveVolume { [CmdletBinding()] param ( - [Parameter(Mandatory = $true)] - [System.UInt64] - $UserDesiredSize, - - [Parameter(Mandatory = $true)] - [System.UInt64] - $CurrentDiskFreeSpace, - - [Parameter(Mandatory = $true)] - [System.UInt32] - $DiskNumber - ) - } - - function Assert-DevDriveSizeMeetsMinimumRequirement - { - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [System.UInt64] - $UserDesiredSize + [string] + $VolumeGuidPath ) } @@ -1196,9 +1309,106 @@ try Assert-MockCalled -CommandName Get-Volume -Exactly 1 } } + + Context 'When volume on partition is a Dev Drive volume' { + # verifiable (should be called) mocks + Mock ` + -CommandName Get-CimInstance ` + -MockWith { $script:mockedCim } ` + -Verifiable + + Mock ` + -CommandName Get-DiskByIdentifier ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number ` + -MockWith { $script:mockedDisk0Gpt } ` + -Verifiable + + Mock ` + -CommandName Get-Partition ` + -MockWith { $script:mockedPartition } ` + -Verifiable + + Mock ` + -CommandName Get-Volume ` + -MockWith { $script:mockedVolumeDevDrive } ` + -Verifiable + + Mock ` + -CommandName Test-DevDriveVolume ` + -MockWith { $true } ` + -Verifiable + + $resource = Get-TargetResource ` + -DiskId $script:mockedDisk0Gpt.Number ` + -DriveLetter $script:testDriveLetter ` + -Verbose + + It "Should return DevDrive as $($true)" { + $resource.DevDrive | Should -Be $true + } + + It 'Should call the correct mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-CimInstance -Exactly 1 + Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly 1 ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number + Assert-MockCalled -CommandName Get-Partition -Exactly 1 + Assert-MockCalled -CommandName Get-Volume -Exactly 1 + Assert-MockCalled -CommandName Test-DevDriveVolume -Exactly 1 + } + } + + Context 'When volume on partition is not a Dev Drive volume' { + # verifiable (should be called) mocks + Mock ` + -CommandName Get-CimInstance ` + -MockWith { $script:mockedCim } ` + -Verifiable + + Mock ` + -CommandName Get-DiskByIdentifier ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number ` + -MockWith { $script:mockedDisk0Gpt } ` + -Verifiable + + Mock ` + -CommandName Get-Partition ` + -MockWith { $script:mockedPartition } ` + -Verifiable + + Mock ` + -CommandName Get-Volume ` + -MockWith { $script:mockedVolume } ` + -Verifiable + + Mock -CommandName Test-DevDriveVolume + + $resource = Get-TargetResource ` + -DiskId $script:mockedDisk0Gpt.Number ` + -DriveLetter $script:testDriveLetter ` + -Verbose + + It "Should return DevDrive as $($false)" { + $resource.DevDrive | Should -Be $false + } + + It 'Should call the correct mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-CimInstance -Exactly 1 + Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly 1 ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number + Assert-MockCalled -CommandName Get-Partition -Exactly 1 + Assert-MockCalled -CommandName Get-Volume -Exactly 1 + Assert-MockCalled -CommandName Test-DevDriveVolume -Exactly 0 + } + } } Describe 'DSC_Disk\Set-TargetResource' { + BeforeAll { + $localizedCommonStrings = Get-LocalizedData -BaseDirectory "$modulePath\StorageDsc.Common" -FileName "StorageDsc.Common.strings.psd1" + } + Context 'When offline GPT disk using Disk Number' { # verifiable (should be called) mocks Mock ` @@ -2575,6 +2785,7 @@ try Mock ` -CommandName Format-Volume ` + -MockWith { $script:mockedVolume } ` -Verifiable # mocks that should not be called @@ -2764,37 +2975,99 @@ try } } - Context 'When Dev Drive flag is enabled and format volume is called with the Dev Drive flag' { + Context 'When the Dev Drive flag is true, the AllowDestructive flag is false and there is not enough space on the disk to create the partition' { # verifiable (should be called) mocks Mock ` -CommandName Get-DiskByIdentifier ` -ParameterFilter $script:parameterFilter_MockedDisk0Number ` - -MockWith { $script:mockedDisk0GptForDevDrive } ` + -MockWith { $script:mockedDisk0GptForDevDriveResizeNotPossibleScenario } ` -Verifiable Mock ` -CommandName Get-Partition ` + -MockWith { $script:mockedPartitionListForResizeNotPossibleScenario } ` -Verifiable Mock ` - -CommandName New-Partition ` - -ParameterFilter { - $DriveLetter -eq $script:testDriveLetter - } ` - -MockWith { $script:mockedPartitionNoDriveLetter } ` + -CommandName Assert-DevDriveFeatureAvailable ` + -MockWith { $null } ` + -Verifiable + + Mock ` + -CommandName Get-PartitionSupportedSize ` + -MockWith { & Get-PartitionSupportedSizeForDevDriveScenarios $DriveLetter } ` + -Verifiable + + # mocks that should not be called + Mock -CommandName Set-Disk + Mock -CommandName Initialize-Disk + + $userDesiredSizeInGb = [Math]::Round($script:mockedPartitionSize50Gb / 1GB, 2) + + It 'Should throw an exception' { + { + Set-TargetResource ` + -DiskId $script:mockedDisk0Gpt.Number ` + -Driveletter $script:testDriveLetterT ` + -Size $script:mockedPartitionSize50Gb ` + -FSLabel 'NewLabel' ` + -FSFormat 'ReFS' ` + -DevDrive $true ` + -Verbose + } | Should -Throw -ExpectedMessage ($script:localizedData.FoundNoPartitionsThatCanResizedForDevDrive -F $userDesiredSizeInGb) + } + + It 'Should call the correct mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 1 ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number + Assert-MockCalled -CommandName Set-Disk -Exactly -Times 0 + Assert-MockCalled -CommandName Initialize-Disk -Exactly -Times 0 + Assert-MockCalled -CommandName Get-Partition -Exactly -Times 1 + } + } + + Context 'When the Dev Drive flag is true, AllowDestructive is false and there is enough space on the disk to create the partition' { + # verifiable (should be called) mocks + Mock ` + -CommandName Get-DiskByIdentifier ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number ` + -MockWith { $script:mockedDisk0GptForDevDriveResizeNotNeededScenario } ` + -Verifiable + + Mock ` + -CommandName Get-Partition ` + -MockWith { $script:mockedPartitionListForResizeNotNeededScenario } ` + -Verifiable + + Mock ` + -CommandName Assert-DevDriveFeatureAvailable ` + -MockWith { $null } ` + -Verifiable + + Mock ` + -CommandName Test-DevDriveVolume ` + -MockWith { $true } ` -Verifiable Mock ` -CommandName Get-Volume ` - -MockWith { $script:mockedVolumeUnformatted } ` + -MockWith { $script:mockedVolumeCreatedAfterNewPartiton } ` -Verifiable Mock ` - -CommandName Format-Volume ` + -CommandName Get-PartitionSupportedSize ` + -MockWith { & Get-PartitionSupportedSizeForDevDriveScenarios $DriveLetter } ` -Verifiable Mock ` - -CommandName Set-Partition ` + -CommandName New-Partition ` + -MockWith { $script:mockedPartitionWithTDriveLetter } ` + -Verifiable + + Mock ` + -CommandName Format-Volume ` + -MockWith { $script:mockedVolumeCreatedAfterNewPartiton } ` -Verifiable # mocks that should not be called @@ -2805,8 +3078,8 @@ try { Set-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -Driveletter $script:testDriveLetter ` - -Size $script:mockedPartitionSize ` + -Driveletter $script:testDriveLetterT ` + -Size $script:mockedPartitionSize50Gb ` -FSLabel 'NewLabel' ` -FSFormat 'ReFS' ` -DevDrive $true ` @@ -2822,50 +3095,125 @@ try Assert-MockCalled -CommandName Initialize-Disk -Exactly -Times 0 Assert-MockCalled -CommandName Get-Partition -Exactly -Times 4 Assert-MockCalled -CommandName Get-Volume -Exactly -Times 1 - Assert-MockCalled -CommandName New-Partition -Exactly -Times 1 ` - -ParameterFilter { - $DriveLetter -eq $script:testDriveLetter - } + Assert-MockCalled -CommandName New-Partition -Exactly -Times 1 Assert-MockCalled -CommandName Format-Volume -Exactly -Times 1 ` -ParameterFilter { $DevDrive -eq $true } - Assert-MockCalled -CommandName Set-Partition -Exactly -Times 1 } } - Context 'When the user explicitly wants to create a new drive on unallocated space regardless of whether there is a partition that matches the size parameter or not. ' { + Context 'When the Dev Drive flag is true, AllowDestructive flag is false and there is not enough unallocated disk space but a resize of a partition is possible to create new space' { # verifiable (should be called) mocks Mock ` -CommandName Get-DiskByIdentifier ` -ParameterFilter $script:parameterFilter_MockedDisk0Number ` - -MockWith { $script:mockedDisk0GptForDevDrive } ` + -MockWith { $script:mockedDisk0GptForDevDriveResizePossibleScenario } ` -Verifiable Mock ` -CommandName Get-Partition ` - -MockWith { $script:mockedPartitionWithGDriveletter } ` + -MockWith { $script:mockedPartitionListForResizePossibleScenario } ` -Verifiable Mock ` - -CommandName New-Partition ` - -ParameterFilter { - $DriveLetter -eq $script:testDriveLetterH - } ` - -MockWith { $script:mockedPartitionNoDriveLetter50Gb } ` + -CommandName Assert-DevDriveFeatureAvailable ` + -MockWith { $null } ` + -Verifiable + + Mock ` + -CommandName Get-PartitionSupportedSize ` + -MockWith { & Get-PartitionSupportedSizeForDevDriveScenarios $DriveLetter } ` + -Verifiable + + # mocks that should not be called + Mock -CommandName Set-Disk + Mock -CommandName Initialize-Disk + + It 'Should throw an exception' { + { + Set-TargetResource ` + -DiskId $script:mockedDisk0Gpt.Number ` + -Driveletter $script:testDriveLetterT ` + -Size $script:mockedPartitionSize50Gb ` + -FSLabel 'NewLabel' ` + -FSFormat 'ReFS' ` + -DevDrive $true ` + -Verbose + } | Should -Throw -ExpectedMessage ($script:localizedData.AllowDestructiveNeededForDevDriveOperation -F $script:testDriveLetterK) + } + + It 'Should call the correct mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 1 ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number + Assert-MockCalled -CommandName Set-Disk -Exactly -Times 0 + Assert-MockCalled -CommandName Initialize-Disk -Exactly -Times 0 + Assert-MockCalled -CommandName Get-Partition -Exactly -Times 1 + } + } + + Context 'When the Dev Drive flag is true, AllowDestructive flag is true and there is not enough unallocated disk space but a resize of a partition is possible to create new space' { + # verifiable (should be called) mocks + + # For resize scenario we need to call Get-DiskByIdentifier twice. After the resize a disk.FreeLargestExtent is updated. + Mock ` + -CommandName Get-DiskByIdentifier ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number ` + -MockWith { + $script:amountOfTimesGetDiskByIdentifierIsCalled++ + + if ($script:amountOfTimesGetDiskByIdentifierIsCalled -eq 1) { + + $script:mockedDisk0GptForDevDriveResizePossibleScenario + } + elseif ($script:amountOfTimesGetDiskByIdentifierIsCalled -eq 2) { + + $script:mockedDisk0GptForDevDriveAfterResize + } + else { + $script:mockedDisk0GptForDevDriveResizePossibleScenario + } + } ` + -Verifiable + + Mock ` + -CommandName Get-Partition ` + -MockWith { $script:mockedPartitionListForResizePossibleScenario } ` + -Verifiable + + Mock ` + -CommandName Assert-DevDriveFeatureAvailable ` + -MockWith { $null } ` + -Verifiable + + Mock ` + -CommandName Get-PartitionSupportedSize ` + -MockWith { & Get-PartitionSupportedSizeForDevDriveScenarios $DriveLetter } ` + -Verifiable + + Mock ` + -CommandName Test-DevDriveVolume ` + -MockWith { $true } ` -Verifiable Mock ` -CommandName Get-Volume ` - -MockWith { $script:mockedVolumeUnformatted } ` + -MockWith { $script:mockedVolumeCreatedAfterNewPartiton } ` -Verifiable Mock ` - -CommandName Format-Volume ` + -CommandName New-Partition ` + -MockWith { $script:mockedPartitionWithTDriveLetter } ` -Verifiable Mock ` - -CommandName Set-Partition ` + -CommandName Resize-Partition ` + -Verifiable + + Mock ` + -CommandName Format-Volume ` + -MockWith { $script:mockedVolumeCreatedAfterNewPartiton } ` -Verifiable # mocks that should not be called @@ -2876,70 +3224,77 @@ try { Set-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -Driveletter $script:testDriveLetterH ` + -Driveletter $script:testDriveLetterT ` -Size $script:mockedPartitionSize50Gb ` -FSLabel 'NewLabel' ` -FSFormat 'ReFS' ` -DevDrive $true ` - -UseUnallocatedSpace $true ` + -AllowDestructive $true ` -Verbose } | Should -Not -Throw } It 'Should call the correct mocks' { Assert-VerifiableMock - Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 1 ` + Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 2 ` -ParameterFilter $script:parameterFilter_MockedDisk0Number Assert-MockCalled -CommandName Set-Disk -Exactly -Times 0 Assert-MockCalled -CommandName Initialize-Disk -Exactly -Times 0 Assert-MockCalled -CommandName Get-Partition -Exactly -Times 4 Assert-MockCalled -CommandName Get-Volume -Exactly -Times 1 - Assert-MockCalled -CommandName New-Partition -Exactly -Times 1 ` - -ParameterFilter { - $DriveLetter -eq $script:testDriveLetterH - } + Assert-MockCalled -CommandName Resize-Partition -Exactly -Times 1 Assert-MockCalled -CommandName Format-Volume -Exactly -Times 1 ` -ParameterFilter { $DevDrive -eq $true } - Assert-MockCalled -CommandName Set-Partition -Exactly -Times 1 + Assert-MockCalled -CommandName New-Partition -Exactly -Times 1 } } - Context 'When the user wants to create a dev drive volume by overwriting a volume that already exists with AllowDestructive set to true' { + Context 'When the Dev Drive flag is true, AllowDestructive is true, and a Partition that matches the users drive letter exists.' { # verifiable (should be called) mocks Mock ` -CommandName Get-DiskByIdentifier ` -ParameterFilter $script:parameterFilter_MockedDisk0Number ` - -MockWith { $script:mockedDisk0GptForDevDrive } ` + -MockWith { $script:mockedDisk0GptForDevDriveResizeNotNeededScenario } ` + -Verifiable + + Mock ` + -CommandName Test-DevDriveVolume ` + -MockWith { $true } ` + -Verifiable + + Mock ` + -CommandName Get-PartitionSupportedSize ` + -MockWith { $script:mockedPartitionSupportedSizeForTDriveletter } ` -Verifiable Mock ` -CommandName Get-Partition ` - -MockWith { $script:mockedPartitionWithGDriveletter } ` + -MockWith { $script:mockedPartitionWithTDriveletter } ` -Verifiable Mock ` -CommandName Get-Volume ` - -MockWith { $script:mockedVolumeReFS } ` + -MockWith { $script:mockedVolumeThatExistPriorToConfiguration } ` -Verifiable Mock ` -CommandName Format-Volume ` + -MockWith { $script:mockedVolumeThatExistPriorToConfiguration } ` -Verifiable # mocks that should not be called Mock -CommandName Set-Disk Mock -CommandName Initialize-Disk - It 'Should not throw an exception' { + It 'Should not throw an exception and overwrite the existing partition' { { Set-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -Driveletter $script:testDriveLetterH ` - -Size $script:mockedPartitionSize50Gb ` + -Driveletter $script:testDriveLetterT ` -FSLabel 'NewLabel' ` - -FSFormat 'NTFS' ` + -FSFormat 'ReFS' ` -DevDrive $true ` -AllowDestructive $true ` -Verbose @@ -2960,6 +3315,66 @@ try } } } + + Context 'When the Dev Drive flag is true, AllowDestructive is false, and a Partition that matches the users drive letter exists.' { + # verifiable (should be called) mocks + Mock ` + -CommandName Get-DiskByIdentifier ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number ` + -MockWith { $script:mockedDisk0GptForDevDriveResizeNotNeededScenario } ` + -Verifiable + + Mock ` + -CommandName Test-DevDriveVolume ` + -MockWith { $false } ` + -Verifiable + + Mock ` + -CommandName Get-PartitionSupportedSize ` + -MockWith { $script:mockedPartitionSupportedSizeForTDriveletter } ` + -Verifiable + + Mock ` + -CommandName Get-Partition ` + -MockWith { $script:mockedPartitionWithTDriveletter } ` + -Verifiable + + Mock ` + -CommandName Get-Volume ` + -MockWith { $script:mockedVolumeThatExistPriorToConfiguration } ` + -Verifiable + + # mocks that should not be called + Mock -CommandName Set-Disk + Mock -CommandName Initialize-Disk + Mock -CommandName Format-Volume + + It 'Should throw an exception advising that the volume was not formatted as a Dev Drive volume' { + { + Set-TargetResource ` + -DiskId $script:mockedDisk0Gpt.Number ` + -Driveletter $script:testDriveLetterT ` + -FSLabel 'NewLabel' ` + -FSFormat 'ReFS' ` + -DevDrive $true ` + -Verbose + } | Should -Throw -ExpectedMessage ($script:localizedData.FailedToConfigureDevDriveVolume -F $script:testDriveLetterT) + } + + It 'Should call the correct mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 1 ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number + Assert-MockCalled -CommandName Set-Disk -Exactly -Times 0 + Assert-MockCalled -CommandName Initialize-Disk -Exactly -Times 0 + Assert-MockCalled -CommandName Get-Partition -Exactly -Times 1 + Assert-MockCalled -CommandName Get-Volume -Exactly -Times 1 + Assert-MockCalled -CommandName Format-Volume -Exactly -Times 0 ` + -ParameterFilter { + $DevDrive -eq $true + } + } + } } Describe 'DSC_Disk\Test-TargetResource' { @@ -4020,7 +4435,7 @@ try } } - Context 'When the Dev Drive flag is enabled, and the user is attempting to put Dev Drive volume in an existing partition' { + Context 'When the Dev Drive flag is true, and Size parameter is less than minimum required size for Dev Drive (50 Gb)' { # verifiable (should be called) mocks Mock ` -CommandName Get-DiskByIdentifier ` @@ -4028,62 +4443,52 @@ try -MockWith { $script:mockedDisk0Gpt } ` -Verifiable - Mock ` - -CommandName Assert-DevDriveFeatureAvailable ` - -Verifiable - Mock ` -CommandName Get-Partition ` - -MockWith { $null } ` + -MockWith { $mockedPartitionGDriveLetter40Gb } ` -Verifiable - It 'Should not throw an exception' { + $userDesiredSizeInGb = [Math]::Round($script:userDesiredSize40Gb / 1GB, 2) + + It 'Should throw an exception as size does not meet minimum requirement' { { $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` -DriveLetter $script:testDriveLetter ` -AllocationUnitSize 4096 ` - -Size $script:userDesiredSize60Gb ` + -Size $script:userDesiredSize40Gb ` -FSLabel $script:mockedVolume.FileSystemLabel ` -FSFormat $script:mockedVolumeReFS.FileSystem ` -DevDrive $true ` + -AllowDestructive $true ` -Verbose - } | Should -Not -Throw + } | Should -Throw -ExpectedMessage ($script:localizedCommonStrings.MinimumSizeNeededToCreateDevDriveVolumeError -F $userDesiredSizeInGb) } It 'Should call the correct mocks' { Assert-VerifiableMock Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 1 ` -ParameterFilter $script:parameterFilter_MockedDisk0Number - Assert-MockCalled -CommandName Assert-DevDriveFeatureAvailable -Exactly -Times 1 - Assert-MockCalled -CommandName Get-Partition -Exactly -Times 2 + Assert-MockCalled -CommandName Get-Partition -Exactly -Times 1 } } - Context 'When the Dev Drive flag is enabled, disk is initialized, and the user is attempting to put a Dev Drive volume in a new partition' { + Context 'When the Dev Drive flag is true, but the partition is not formatted as a Dev Drive volume' { # verifiable (should be called) mocks Mock ` -CommandName Get-DiskByIdentifier ` -ParameterFilter $script:parameterFilter_MockedDisk0Number ` - -MockWith { $script:mockedDisk0GptForDevDrive } ` - -Verifiable - - Mock ` - -CommandName Assert-DevDriveFeatureAvailable ` - -Verifiable - - Mock ` - -CommandName Assert-DiskHasEnoughSpaceToCreateDevDrive ` - -ParameterFilter $script:InitializeDiskSizeParamsForDevDrive ` + -MockWith { $script:mockedDisk0Gpt } ` -Verifiable Mock ` - -CommandName Assert-DevDriveFormatOnReFsFileSystemOnly ` + -CommandName Get-Partition ` + -MockWith { $mockedPartitionGDriveLetter50Gb } ` -Verifiable Mock ` - -CommandName Get-Partition ` - -MockWith { $null } ` + -CommandName Get-Volume ` + -MockWith { $script:mockedVolumeThatExistPriorToConfiguration } ` -Verifiable It 'Should not throw an exception' { @@ -4092,10 +4497,11 @@ try -DiskId $script:mockedDisk0Gpt.Number ` -DriveLetter $script:testDriveLetter ` -AllocationUnitSize 4096 ` - -Size $script:userDesiredSize60Gb ` + -Size $script:userDesiredSize50Gb ` -FSLabel $script:mockedVolume.FileSystemLabel ` -FSFormat $script:mockedVolumeReFS.FileSystem ` -DevDrive $true ` + -AllowDestructive $true ` -Verbose } | Should -Not -Throw } @@ -4104,66 +4510,11 @@ try Assert-VerifiableMock Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 1 ` -ParameterFilter $script:parameterFilter_MockedDisk0Number - Assert-MockCalled -CommandName Assert-DevDriveFeatureAvailable -Exactly -Times 1 - Assert-MockCalled -CommandName Assert-DiskHasEnoughSpaceToCreateDevDrive -Exactly -Times 1 ` - -ParameterFilter $script:InitializeDiskSizeParamsForDevDrive - Assert-MockCalled -CommandName Assert-DevDriveFormatOnReFsFileSystemOnly -Exactly -Times 1 - Assert-MockCalled -CommandName Get-Partition -Exactly -Times 2 + Assert-MockCalled -CommandName Get-Partition -Exactly -Times 1 + Assert-MockCalled -CommandName Get-Volume -Exactly -Times 1 } } } - - Context 'When the Dev Drive flag is enabled, disk is not initialized, and the user is attempting to put a Dev Drive volume in a new partition' { - # verifiable (should be called) mocks - Mock ` - -CommandName Get-DiskByIdentifier ` - -ParameterFilter $script:parameterFilter_MockedDisk0Number ` - -MockWith { $script:mockedDisk0RawForDevDrive } ` - -Verifiable - - Mock ` - -CommandName Assert-DevDriveFeatureAvailable ` - -Verifiable - - Mock ` - -CommandName Assert-DiskHasEnoughSpaceToCreateDevDrive ` - -ParameterFilter $script:UninitializeDiskSizeParamsForDevDrive ` - -Verifiable - - Mock ` - -CommandName Assert-DevDriveFormatOnReFsFileSystemOnly ` - -Verifiable - - Mock ` - -CommandName Get-Partition ` - -MockWith { $null } ` - -Verifiable - - It 'Should not throw an exception' { - { - $script:result = Test-TargetResource ` - -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` - -AllocationUnitSize 4096 ` - -Size $script:userDesiredSize60Gb ` - -FSLabel $script:mockedVolume.FileSystemLabel ` - -FSFormat $script:mockedVolumeReFS.FileSystem ` - -DevDrive $true ` - -Verbose - } | Should -Not -Throw - } - - It 'Should call the correct mocks' { - Assert-VerifiableMock - Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 1 ` - -ParameterFilter $script:parameterFilter_MockedDisk0Number - Assert-MockCalled -CommandName Assert-DevDriveFeatureAvailable -Exactly -Times 1 - Assert-MockCalled -CommandName Assert-DiskHasEnoughSpaceToCreateDevDrive -Exactly -Times 1 ` - -ParameterFilter $script:UninitializeDiskSizeParamsForDevDrive - Assert-MockCalled -CommandName Assert-DevDriveFormatOnReFsFileSystemOnly -Exactly -Times 1 - Assert-MockCalled -CommandName Get-Partition -Exactly -Times 1 - } - } } } finally diff --git a/tests/Unit/StorageDsc.Common.Tests.ps1 b/tests/Unit/StorageDsc.Common.Tests.ps1 index 3b32e302..e377d728 100644 --- a/tests/Unit/StorageDsc.Common.Tests.ps1 +++ b/tests/Unit/StorageDsc.Common.Tests.ps1 @@ -529,8 +529,8 @@ InModuleScope $script:subModuleName { Describe 'StorageDsc.Common\Assert-DevDriveFeatureAvailable' -Tag 'Assert-DevDriveFeatureAvailable' { Context 'When testing the Dev Drive enablement state and the dev drive feature not implemented' { Mock ` - -CommandName Get-IsApiSetImplemented ` - -MockWith { return $false } ` + -CommandName Invoke-IsApiSetImplemented ` + -MockWith { $false } ` -ModuleName StorageDsc.Common ` -Verifiable @@ -542,19 +542,19 @@ InModuleScope $script:subModuleName { It 'Should call the correct mocks' { Assert-VerifiableMock - Assert-MockCalled -CommandName Get-IsApiSetImplemented -Exactly -Times 1 + Assert-MockCalled -CommandName Invoke-IsApiSetImplemented -Exactly -Times 1 } } Context 'When testing the Dev Drive enablement state returns an enablement state not defined in the enum' { Mock ` - -CommandName Get-IsApiSetImplemented ` + -CommandName Invoke-IsApiSetImplemented ` -MockWith { $true } ` -ModuleName StorageDsc.Common ` -Verifiable Mock ` - -CommandName Get-DeveloperDriveEnablementState ` + -CommandName Get-DevDriveEnablementState ` -MockWith { $null } ` -ModuleName StorageDsc.Common ` -Verifiable @@ -567,8 +567,8 @@ InModuleScope $script:subModuleName { It 'Should call the correct mocks' { Assert-VerifiableMock - Assert-MockCalled -CommandName Get-IsApiSetImplemented -Exactly -Times 1 - Assert-MockCalled -CommandName Get-DeveloperDriveEnablementState -Exactly -Times 1 + Assert-MockCalled -CommandName Invoke-IsApiSetImplemented -Exactly -Times 1 + Assert-MockCalled -CommandName Get-DevDriveEnablementState -Exactly -Times 1 } } @@ -577,13 +577,13 @@ InModuleScope $script:subModuleName { $DevDriveEnablementType = [DevDrive.DevDriveHelper+DEVELOPER_DRIVE_ENABLEMENT_STATE] Mock ` - -CommandName Get-IsApiSetImplemented ` + -CommandName Invoke-IsApiSetImplemented ` -MockWith { $true } ` -ModuleName StorageDsc.Common ` -Verifiable Mock ` - -CommandName Get-DeveloperDriveEnablementState ` + -CommandName Get-DevDriveEnablementState ` -MockWith { $DevDriveEnablementType::DeveloperDriveDisabledByGroupPolicy } ` -ModuleName StorageDsc.Common ` -Verifiable @@ -596,20 +596,20 @@ InModuleScope $script:subModuleName { It 'Should call the correct mocks' { Assert-VerifiableMock - Assert-MockCalled -CommandName Get-IsApiSetImplemented -Exactly -Times 1 - Assert-MockCalled -CommandName Get-DeveloperDriveEnablementState -Exactly -Times 1 + Assert-MockCalled -CommandName Invoke-IsApiSetImplemented -Exactly -Times 1 + Assert-MockCalled -CommandName Get-DevDriveEnablementState -Exactly -Times 1 } } Context 'When testing the Dev Drive enablement state and the dev drive feature is disabled by system policy' { Mock ` - -CommandName Get-IsApiSetImplemented ` + -CommandName Invoke-IsApiSetImplemented ` -MockWith { $true } ` -ModuleName StorageDsc.Common ` -Verifiable Mock ` - -CommandName Get-DeveloperDriveEnablementState ` + -CommandName Get-DevDriveEnablementState ` -MockWith { $DevDriveEnablementType::DeveloperDriveDisabledBySystemPolicy } ` -ModuleName StorageDsc.Common ` -Verifiable @@ -622,20 +622,20 @@ InModuleScope $script:subModuleName { It 'Should call the correct mocks' { Assert-VerifiableMock - Assert-MockCalled -CommandName Get-IsApiSetImplemented -Exactly -Times 1 - Assert-MockCalled -CommandName Get-DeveloperDriveEnablementState -Exactly -Times 1 + Assert-MockCalled -CommandName Invoke-IsApiSetImplemented -Exactly -Times 1 + Assert-MockCalled -CommandName Get-DevDriveEnablementState -Exactly -Times 1 } } Context 'When testing the Dev Drive enablement state and the enablement state is unknown' { Mock ` - -CommandName Get-IsApiSetImplemented ` + -CommandName Invoke-IsApiSetImplemented ` -MockWith { $true } ` -ModuleName StorageDsc.Common ` -Verifiable Mock ` - -CommandName Get-DeveloperDriveEnablementState ` + -CommandName Get-DevDriveEnablementState ` -MockWith { $DevDriveEnablementType::DeveloperDriveEnablementStateError } ` -ModuleName StorageDsc.Common ` -Verifiable @@ -648,20 +648,20 @@ InModuleScope $script:subModuleName { It 'Should call the correct mocks' { Assert-VerifiableMock - Assert-MockCalled -CommandName Get-IsApiSetImplemented -Exactly -Times 1 - Assert-MockCalled -CommandName Get-DeveloperDriveEnablementState -Exactly -Times 1 + Assert-MockCalled -CommandName Invoke-IsApiSetImplemented -Exactly -Times 1 + Assert-MockCalled -CommandName Get-DevDriveEnablementState -Exactly -Times 1 } } Context 'When testing Dev Drive enablement state and the enablement state is set to enabled' { Mock ` - -CommandName Get-IsApiSetImplemented ` + -CommandName Invoke-IsApiSetImplemented ` -MockWith { $true } ` -ModuleName StorageDsc.Common ` -Verifiable Mock ` - -CommandName Get-DeveloperDriveEnablementState ` + -CommandName Get-DevDriveEnablementState ` -MockWith { $DevDriveEnablementType::DeveloperDriveEnabled } ` -ModuleName StorageDsc.Common ` -Verifiable @@ -674,49 +674,46 @@ InModuleScope $script:subModuleName { It 'Should call the correct mocks' { Assert-VerifiableMock - Assert-MockCalled -CommandName Get-IsApiSetImplemented -Exactly -Times 1 - Assert-MockCalled -CommandName Get-DeveloperDriveEnablementState -Exactly -Times 1 + Assert-MockCalled -CommandName Invoke-IsApiSetImplemented -Exactly -Times 1 + Assert-MockCalled -CommandName Get-DevDriveEnablementState -Exactly -Times 1 } } } - Describe 'StorageDsc.Common\Assert-DevDriveFormatOnReFsFileSystemOnly' -Tag 'Assert-DevDriveFormatOnReFsFileSystemOnly' { + Describe 'StorageDsc.Common\Assert-FSFormatIsReFsWhenDevDriveFlagSetToTrue' -Tag 'Assert-FSFormatIsReFsWhenDevDriveFlagSetToTrue' { Context 'When testing that only the ReFS file system is allowed' { $errorRecord = Get-InvalidArgumentRecord ` - -Message ($script:localizedData.DevDriveOnlyAvailableForReFsError ) ` + -Message ($script:localizedData.FSFormatNotReFSWhenDevDriveFlagIsTrueError ) ` -ArgumentName 'FSFormat' It 'Should throw invalid argument error if a filesystem other than ReFS is passed in' { { - Assert-DevDriveFormatOnReFsFileSystemOnly -FSFormat "test" -Verbose + Assert-FSFormatIsReFsWhenDevDriveFlagSetToTrue -FSFormat "test" -Verbose } | Should -Throw $errorRecord } } - Context 'When testing Exception not thrown in Assert-DevDriveFormatOnReFsFileSystemOnly when ReFS file system passed in' { + Context 'When testing Exception not thrown in Assert-FSFormatIsReFsWhenDevDriveFlagSetToTrue when ReFS file system passed in' { It 'Should not throw invalid argument error if ReFS filesystem is passed in' { { - Assert-DevDriveFormatOnReFsFileSystemOnly -FSFormat "ReFS" -Verbose + Assert-FSFormatIsReFsWhenDevDriveFlagSetToTrue -FSFormat "ReFS" -Verbose } | Should -Not -Throw } } } - Describe 'StorageDsc.Common\Assert-DevDriveSizeMeetsMinimumRequirement' -Tag 'Assert-DevDriveSizeMeetsMinimumRequirement' { + Describe 'StorageDsc.Common\Assert-SizeMeetsMinimumDevDriveRequirement' -Tag 'Assert-SizeMeetsMinimumDevDriveRequirement' { Context 'When UserDesiredSize does not meet the minimum size for Dev Drive volumes' { - $errorRecord = Get-InvalidArgumentRecord ` - -Message $($script:localizedData.DevDriveMinimumSizeError) ` - -ArgumentName 'UserDesiredSize' - + $UserDesiredSizeInGb = [Math]::Round($mockedSizesForDevDriveScenario.UserDesired10Gb / 1GB, 2) It 'Should throw invalid argument error' { { - Assert-DevDriveSizeMeetsMinimumRequirement ` + Assert-SizeMeetsMinimumDevDriveRequirement ` -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired10Gb ` -Verbose - } | Should -Throw $errorRecord + } | Should -Throw -ExpectedMessage ($script:localizedCommonStrings.MinimumSizeNeededToCreateDevDriveVolumeError -F $UserDesiredSizeInGb) } } @@ -724,119 +721,51 @@ InModuleScope $script:subModuleName { It 'Should not throw invalid argument error' { { - Assert-DevDriveSizeMeetsMinimumRequirement ` + Assert-SizeMeetsMinimumDevDriveRequirement ` -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired50Gb ` -Verbose } | Should -Not -Throw } } - - Context 'When UserDesiredSize is 0' { - - It 'Should not throw invalid argument error' { - { - Assert-DevDriveSizeMeetsMinimumRequirement ` - -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired0Gb ` - -Verbose - } | Should -Not -Throw - } - } } - Describe 'StorageDsc.Common\Assert-DiskHasEnoughSpaceToCreateDevDrive' -Tag 'Assert-DiskHasEnoughSpaceToCreateDevDrive' { - Context 'When disk free space less than users desired size' { + Describe 'StorageDsc.Common\Test-DevDriveVolume' -Tag 'Test-DevDriveVolume' { + Context 'When testing whether a volume is a Dev Drive volume and the volume is a Dev Drive volume' { - $userDesiredSizeInGb = [Math]::Round($mockedSizesForDevDriveScenario.UserDesired50Gb / 1GB, 2) - $currentDiskFreeSpaceInGb = [Math]::Round($mockedSizesForDevDriveScenario.CurrentDiskFreeSpace40Gb / 1GB, 2) - $errorRecord = Get-InvalidArgumentRecord ` - -Message $($script:localizedData.DevDriveNotEnoughSpaceToCreateDevDriveError -f ` - $mockedDiskNumber, ` - $userDesiredSizeInGb, ` - $currentDiskFreeSpaceInGb) ` - -ArgumentName 'UserDesiredSize' + Mock ` + -CommandName Invoke-DeviceIoControlWrapperForDevDriveQuery ` + -MockWith { $true } ` + -ModuleName StorageDsc.Common ` + -Verifiable - It 'Should throw invalid argument error' { - { - Assert-DiskHasEnoughSpaceToCreateDevDrive ` - -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired50Gb ` - -CurrentDiskFreeSpace $mockedSizesForDevDriveScenario.CurrentDiskFreeSpace40Gb ` - -DiskNumber $mockedDiskNumber ` - -Verbose - } | Should -Throw $errorRecord + $result = Test-DevDriveVolume -VolumeGuidPath "\\?\Volume{3a244a32-efba-4b7e-9a19-7293fc7c7924}\" -Verbose + It 'Should return true' { + $result | Should -Be $true } - } - Context 'When no size entered and disk free space less than minimum size for dev drive volumes (50Gb)' { - - $userDesiredSizeInGb = [Math]::Round($mockedSizesForDevDriveScenario.UserDesired50Gb / 1GB, 2) - $currentDiskFreeSpaceInGb = [Math]::Round($mockedSizesForDevDriveScenario.CurrentDiskFreeSpace40Gb / 1GB, 2) - $errorRecord = Get-InvalidArgumentRecord ` - -Message $($script:localizedData.DevDriveNotEnoughSpaceToCreateDevDriveError -f ` - $mockedDiskNumber, ` - $userDesiredSizeInGb, ` - $currentDiskFreeSpaceInGb) ` - -ArgumentName 'UserDesiredSize' - - It 'Should throw invalid argument error' { - { - Assert-DiskHasEnoughSpaceToCreateDevDrive ` - -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired0Gb ` - -CurrentDiskFreeSpace $mockedSizesForDevDriveScenario.CurrentDiskFreeSpace40Gb ` - -DiskNumber $mockedDiskNumber ` - -Verbose - } | Should -Throw $errorRecord + It 'Should call the correct mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Invoke-DeviceIoControlWrapperForDevDriveQuery -Exactly -Times 1 } - } - - Context 'When disk free space greater than users desired size' { - It 'Should not throw invalid argument error if CurrentDiskFreeSpace greater than UserDesiredSize' { - { - Assert-DiskHasEnoughSpaceToCreateDevDrive ` - -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired50Gb ` - -CurrentDiskFreeSpace $mockedSizesForDevDriveScenario.CurrentDiskFreeSpace60Gb ` - -DiskNumber $mockedDiskNumber ` - -Verbose - } | Should -Not -Throw - } } - Context 'When disk free space equal to users desired size' { + Context 'When testing whether a volume is a Dev Drive volume and the volume is not a Dev Drive volume' { - It 'Should not throw invalid argument error if CurrentDiskFreeSpace is equal to UserDesiredSize' { - { - Assert-DiskHasEnoughSpaceToCreateDevDrive ` - -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired50Gb ` - -CurrentDiskFreeSpace $mockedSizesForDevDriveScenario.CurrentDiskFreeSpace50Gb ` - -DiskNumber $mockedDiskNumber ` - -Verbose - } | Should -Not -Throw - } - } - - Context 'When no size entered and disk free space greater than the minimum size for dev drive volumes (50Gb)' { + Mock ` + -CommandName Invoke-DeviceIoControlWrapperForDevDriveQuery ` + -MockWith { $false } ` + -ModuleName StorageDsc.Common ` + -Verifiable - It 'Should not throw invalid argument error if CurrentDiskFreeSpace is greater than 50Gb' { - { - Assert-DiskHasEnoughSpaceToCreateDevDrive ` - -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired0Gb ` - -CurrentDiskFreeSpace $mockedSizesForDevDriveScenario.CurrentDiskFreeSpace60Gb ` - -DiskNumber $mockedDiskNumber ` - -Verbose - } | Should -Not -Throw + $result = Test-DevDriveVolume -VolumeGuidPath "\\?\Volume{3a244a32-efba-4b7e-9a19-7293fc7c7923}\" -Verbose + It 'Should return false' { + $result | Should -Be $false } - } - - Context 'When no size entered and disk free space equal to the minimum size for dev drive volumes (50Gb)' { - It 'Should not throw invalid argument error if CurrentDiskFreeSpace is equal to 50Gb' { - { - Assert-DiskHasEnoughSpaceToCreateDevDrive ` - -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired0Gb ` - -CurrentDiskFreeSpace $mockedSizesForDevDriveScenario.CurrentDiskFreeSpace50Gb ` - -DiskNumber $mockedDiskNumber ` - -Verbose - } | Should -Not -Throw + It 'Should call the correct mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Invoke-DeviceIoControlWrapperForDevDriveQuery -Exactly -Times 1 } } } From 05709ead81af9fde06f86dffa4117c415db6f61f Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Sat, 14 Oct 2023 02:16:27 -0700 Subject: [PATCH 16/21] fix unit test that failed because dev drive feature on in server 2019 and 2022. Had to mock the Assert-DevDriveFeatureAvailable function --- source/DSCResources/DSC_Disk/DSC_Disk.psm1 | 6 +----- tests/Unit/DSC_Disk.Tests.ps1 | 20 +++++++++++++++++--- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 index 36debb93..c1402a50 100644 --- a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 +++ b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 @@ -134,17 +134,13 @@ function Get-TargetResource -Query "SELECT BlockSize from Win32_Volume WHERE DriveLetter = '$($DriveLetter):'" ` -ErrorAction SilentlyContinue).BlockSize + $DevDrive = $false if ($volume.UniqueId) { $DevDrive = Test-DevDriveVolume ` -VolumeGuidPath $volume.UniqueId ` -ErrorAction SilentlyContinue } - else - { - $DevDrive = $false - } - return @{ DiskId = $DiskId diff --git a/tests/Unit/DSC_Disk.Tests.ps1 b/tests/Unit/DSC_Disk.Tests.ps1 index 3662cddd..b87529a4 100644 --- a/tests/Unit/DSC_Disk.Tests.ps1 +++ b/tests/Unit/DSC_Disk.Tests.ps1 @@ -1378,10 +1378,12 @@ try Mock ` -CommandName Get-Volume ` - -MockWith { $script:mockedVolume } ` + -MockWith { $script:mockedVolumeThatExistPriorToConfiguration } ` -Verifiable - Mock -CommandName Test-DevDriveVolume + Mock -CommandName Test-DevDriveVolume ` + -MockWith { $false } ` + -Verifiable $resource = Get-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` @@ -1399,7 +1401,7 @@ try -ParameterFilter $script:parameterFilter_MockedDisk0Number Assert-MockCalled -CommandName Get-Partition -Exactly 1 Assert-MockCalled -CommandName Get-Volume -Exactly 1 - Assert-MockCalled -CommandName Test-DevDriveVolume -Exactly 0 + Assert-MockCalled -CommandName Test-DevDriveVolume -Exactly 1 } } } @@ -3284,6 +3286,11 @@ try -MockWith { $script:mockedVolumeThatExistPriorToConfiguration } ` -Verifiable + Mock ` + -CommandName Assert-DevDriveFeatureAvailable ` + -MockWith { $null } ` + -Verifiable + # mocks that should not be called Mock -CommandName Set-Disk Mock -CommandName Initialize-Disk @@ -3309,6 +3316,7 @@ try Assert-MockCalled -CommandName Initialize-Disk -Exactly -Times 0 Assert-MockCalled -CommandName Get-Partition -Exactly -Times 1 Assert-MockCalled -CommandName Get-Volume -Exactly -Times 1 + Assert-MockCalled -CommandName Assert-DevDriveFeatureAvailable -Exactly -Times 1 Assert-MockCalled -CommandName Format-Volume -Exactly -Times 1 ` -ParameterFilter { $DevDrive -eq $true @@ -3344,6 +3352,11 @@ try -MockWith { $script:mockedVolumeThatExistPriorToConfiguration } ` -Verifiable + Mock ` + -CommandName Assert-DevDriveFeatureAvailable ` + -MockWith { $null } ` + -Verifiable + # mocks that should not be called Mock -CommandName Set-Disk Mock -CommandName Initialize-Disk @@ -3369,6 +3382,7 @@ try Assert-MockCalled -CommandName Initialize-Disk -Exactly -Times 0 Assert-MockCalled -CommandName Get-Partition -Exactly -Times 1 Assert-MockCalled -CommandName Get-Volume -Exactly -Times 1 + Assert-MockCalled -CommandName Assert-DevDriveFeatureAvailable -Exactly -Times 1 Assert-MockCalled -CommandName Format-Volume -Exactly -Times 0 ` -ParameterFilter { $DevDrive -eq $true From b1cab40892167def845be7eefc2813284308662a Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Mon, 16 Oct 2023 11:47:23 -0700 Subject: [PATCH 17/21] fix code coverage error --- source/DSCResources/DSC_Disk/DSC_Disk.psm1 | 37 ++- .../DSC_Disk/en-US/DSC_Disk.strings.psd1 | 7 +- .../StorageDsc.Common/StorageDsc.Common.psm1 | 31 ++- tests/Unit/DSC_Disk.Tests.ps1 | 249 +++++++++++++++++- 4 files changed, 296 insertions(+), 28 deletions(-) diff --git a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 index c1402a50..a6b408b6 100644 --- a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 +++ b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 @@ -711,7 +711,7 @@ function Set-TargetResource } <# - If the Set-TargetResource function is run as a standalone function, and $assignedPartition is not null and there are multiple partitions in $partition + If the Set-TargetResource function is run as a standalone function, and $assignedPartition is not null and there are multiple partitions in $partition, then '$partition | Get-Volume', will give $volume back the first volume on the first partition. If $assignedPartition is after that one, then we could potentially format a different volume. So we need to make sure that $partition is equal to $assignedPartition before we call Get-Volume. #> @@ -845,18 +845,22 @@ function Set-TargetResource # Confirm that the volume is now actually formatted as a Dev Drive volume. if ($DevDrive) { - if ($volume.UniqueId -and (Test-DevDriveVolume -VolumeGuidPath $volume.UniqueId)) + $isDevDriveVolume = Test-DevDriveVolume -VolumeGuidPath $volume.UniqueId -ErrorAction SilentlyContinue + + if ($isDevDriveVolume) { Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.SuccessfullyConfiguredDevDriveVolume -f $DriveLetter) + $($script:localizedData.SuccessfullyConfiguredDevDriveVolume ` + -F $volume.UniqueId, $volume.DriveLetter) ) -join '' ) } else { throw ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.FailedToConfigureDevDriveVolume -f $DriveLetter) + $($script:localizedData.FailedToConfigureDevDriveVolume ` + -F $volume.UniqueId, $volume.DriveLetter) ) -join '' ) } } @@ -1182,12 +1186,33 @@ function Test-TargetResource if ($DevDrive) { + # User requested to configure the volume as a Dev Drive volume. So we check that the assertions are met. + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.CheckingDevDriveAssertions) + ) -join '' ) + Assert-DevDriveFeatureAvailable Assert-FSFormatIsReFsWhenDevDriveFlagSetToTrue -FSFormat $FSFormat - if ($volume.UniqueId -and (-not (Test-DevDriveVolume -VolumeGuidPath $volume.UniqueId))) + $isDevDriveVolume = Test-DevDriveVolume -VolumeGuidPath $volume.UniqueId -ErrorAction SilentlyContinue + + if ($isDevDriveVolume) + { + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.TheVolumeIsCurrentlyConfiguredAsADevDriveVolume ` + -F $volume.UniqueId, $volume.DriveLetter) + ) -join '' ) + } + else { - # The volume is not configured as a Dev Drive volume. + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.TheVolumeIsNotConfiguredAsADevDriveVolume ` + -F $volume.UniqueId, $volume.DriveLetter) + ) -join '' ) + return $false } } diff --git a/source/DSCResources/DSC_Disk/en-US/DSC_Disk.strings.psd1 b/source/DSCResources/DSC_Disk/en-US/DSC_Disk.strings.psd1 index 42a209c0..ed227739 100644 --- a/source/DSCResources/DSC_Disk/en-US/DSC_Disk.strings.psd1 +++ b/source/DSCResources/DSC_Disk/en-US/DSC_Disk.strings.psd1 @@ -41,12 +41,15 @@ ResizeNotAllowedMessage = Skipping resize of {0} from {1} to {2}. AllowDestructive is not set to $true. AllowDestructiveNeededForDevDriveOperation = The partition with drive letter '{0}' was found to be big enough to be resized to create enough space for a new Dev Drive volume. However the 'AllowDestructive' flag was not set to $true. ResizingPartitionToMakeSpaceForDevDriveVolume = Resizing partition with drive letter '{0}' to size '{1} Gb', so we can make enough unallocated space for a Dev Drive volume of size '{2} Gb'. - SuccessfullyConfiguredDevDriveVolume = The volume on drive '{0}:\\' has been successfully configured as a Dev Drive volume. - FailedToConfigureDevDriveVolume = Failed to configure volume on drive '{0}:\\' as a Dev Drive volume. + SuccessfullyConfiguredDevDriveVolume = The volume with path '{0}' on drive '{1}:\\' has been successfully configured as a Dev Drive volume. + FailedToConfigureDevDriveVolume = Failed to configure the volume with path '{0}' on drive '{1}:\\' as a Dev Drive volume. AttemptingToFindAPartitionToResizeForDevDrive = Attempting to find a partition that we can resize, in order to create enough unallocated space for a new Dev Drive volume. CheckingIfPartitionCanBeResizedForDevDrive = Checking if partition with drive letter '{0}' can be resized to create enough unallocated space for the new Dev Drive volume. PartitionCantBeResizedForDevDrive = Partition with drive letter '{0}' cannot be resized to create the new Dev Drive volume. Continuing search... PartitionFoundThatCanBeResizedForDevDrive = Found partition with drive letter '{0}' which can be resized to create a new Dev Drive volume. FoundNoPartitionsThatCanResizedForDevDrive = There is no unallocated space available to create the Dev Drive volume. We found no partitions that could be resized to create '{0} Gb' of unallocated space. NoPartitionResizeNeededForDevDrive = We found enough unallocated space available on the disk. No partition resize is needed to create the Dev Drive volume. + CheckingDevDriveAssertions = Checking system meets requirements for the Dev Drive feature. + TheVolumeIsNotConfiguredAsADevDriveVolume = The volume with path '{0}' and Drive letter '{1}' is not configured as a Dev Drive volume. + TheVolumeIsCurrentlyConfiguredAsADevDriveVolume = The volume with path '{0}' and Drive letter '{1}' is currently configured as a Dev Drive volume. '@ diff --git a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 index e5faf381..106503a8 100644 --- a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 +++ b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 @@ -1,8 +1,3 @@ - -using namespace System; -using namespace System.Runtime.InteropServices; -using namespace Microsoft.Win32.SafeHandles; - $modulePath = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'Modules' Import-Module -Name (Join-Path -Path $modulePath -ChildPath 'DscResource.Common') @@ -240,7 +235,6 @@ function Get-DevDriveWin32HelperScript $DevDriveHelperDefinitions = @' - // https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ne-sysinfoapi-developer_drive_enablement_state public enum DEVELOPER_DRIVE_ENABLEMENT_STATE { @@ -262,7 +256,7 @@ function Get-DevDriveWin32HelperScript // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - public static extern Microsoft.Win32.SafeHandles.SafeFileHandle CreateFile( + public static extern SafeFileHandle CreateFile( string lpFileName, uint dwDesiredAccess, uint dwShareMode, @@ -274,7 +268,7 @@ function Get-DevDriveWin32HelperScript // https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-deviceiocontrol [DllImport("kernel32.dll", SetLastError = true)] public static extern bool DeviceIoControl( - Microsoft.Win32.SafeHandles.SafeFileHandle hDevice, + SafeFileHandle hDevice, uint dwIoControlCode, IntPtr lpInBuffer, uint nInBufferSize, @@ -327,10 +321,9 @@ function Get-DevDriveWin32HelperScript { // Handle is invalid. throw new Exception("CreateFile unable to get file handle for volume to check if its a Dev Drive volume", - new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error())); + new Win32Exception(Marshal.GetLastWin32Error())); } - // We need to allocated memory for the structures so we can marshal and unmarshal them. IntPtr inputVolptr = Marshal.AllocHGlobal(Marshal.SizeOf(inputVolumeInfo)); IntPtr outputVolptr = Marshal.AllocHGlobal(Marshal.SizeOf(outputVolumeInfo)); @@ -353,30 +346,32 @@ function Get-DevDriveWin32HelperScript { // Can't query volume. throw new Exception("DeviceIoControl unable to query if volume is a Dev Drive volume", - new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error())); + new Win32Exception(Marshal.GetLastWin32Error())); } // Unmarshal the output structure - outputVolumeInfo = (FILE_FS_PERSISTENT_VOLUME_INFORMATION)Marshal.PtrToStructure(outputVolptr, typeof(FILE_FS_PERSISTENT_VOLUME_INFORMATION)); + outputVolumeInfo = (FILE_FS_PERSISTENT_VOLUME_INFORMATION) Marshal.PtrToStructure( + outputVolptr, + typeof(FILE_FS_PERSISTENT_VOLUME_INFORMATION) + ); + // Check that the output flag is set to Dev Drive volume. if ((outputVolumeInfo.VolumeFlags & PERSISTENT_VOLUME_STATE_DEV_VOLUME) > 0) { // Volume is a Dev Drive volume. return true; } - return false; } finally { + // Free the memory we allocated. Marshal.FreeHGlobal(inputVolptr); Marshal.FreeHGlobal(outputVolptr); volumeFileHandle.Close(); } } - - '@ if (([System.Management.Automation.PSTypeName]'DevDrive.DevDriveHelper').Type) { @@ -384,10 +379,14 @@ function Get-DevDriveWin32HelperScript } else { + # Note: when recompiling changes to the C# code above you'll need to close the powershell session and reopen a new one. $script:DevDriveWin32Helper = Add-Type ` -Namespace 'DevDrive' ` -Name 'DevDriveHelper' ` - -MemberDefinition $DevDriveHelperDefinitions + -MemberDefinition $DevDriveHelperDefinitions ` + -UsingNamespace ` + 'System.ComponentModel', + 'Microsoft.Win32.SafeHandles' } return $script:DevDriveWin32Helper diff --git a/tests/Unit/DSC_Disk.Tests.ps1 b/tests/Unit/DSC_Disk.Tests.ps1 index b87529a4..1a38000c 100644 --- a/tests/Unit/DSC_Disk.Tests.ps1 +++ b/tests/Unit/DSC_Disk.Tests.ps1 @@ -322,6 +322,22 @@ try IsReadOnly = $false } + $script:mockedPartitionGDriveLetterAlternatePartition150Gb = [pscustomobject] @{ + DriveLetter = [System.Char] $testDriveLetter + Size = $script:partitionFormattedByWindows150Gb + PartitionNumber = 1 + Type = 'Basic' + IsReadOnly = $false + } + + $script:mockedPartitionGDriveLetter150Gb = [pscustomobject] @{ + DriveLetter = [System.Char] $testDriveLetter + Size = $script:userDesiredSize150Gb + PartitionNumber = 1 + Type = 'Basic' + IsReadOnly = $false + } + $script:mockedPartitionNoDriveLetterReadOnly = [pscustomobject] @{ DriveLetter = [System.Char] $null Size = $script:mockedPartitionSize @@ -376,10 +392,39 @@ try Size = $script:mockedPartitionSize50Gb } + $script:mockedVolumeThatExistPriorToConfigurationReFS = [pscustomobject] @{ + FileSystemLabel = 'myLabel' + FileSystem = 'ReFS' + DriveLetter = $script:testDriveLetterT + UniqueId = '\\?\Volume{3a244a32-efba-4b7e-9a19-7293fc7c7924}\' + Size = $script:mockedPartitionSize50Gb + } + + $script:mockedVolumeThatExistPriorToConfigurationNtfs150Gb = [pscustomobject] @{ + FileSystemLabel = 'myLabel' + FileSystem = 'NTFS' + DriveLetter = $script:testDriveLetterT + UniqueId = '\\?\Volume{3a244a32-efba-4b7e-9a19-7293fc7c7924}\' + Size = $script:userDesiredSize150Gb + } + + $script:mockedVolumeThatExistPriorToConfigurationRefs150Gb = [pscustomobject] @{ + FileSystemLabel = 'myLabel' + FileSystem = 'ReFS' + DriveLetter = $script:testDriveLetterT + UniqueId = '\\?\Volume{3a244a32-efba-4b7e-9a19-7293fc7c7924}\' + Size = $script:userDesiredSize150Gb + } + $script:parameterFilter_MockedDisk0Number = { $DiskId -eq $script:mockedDisk0Gpt.Number -and $DiskIdType -eq 'Number' } + $script:userDesiredSize150Gb = 150Gb + + # Alternate value in bytes that can represent a 150 Gb partition in a physical hard disk that has been formatted by Windows. + $script:partitionFormattedByWindows150Gb = 161060225024 + $script:userDesiredSize50Gb = 50Gb $script:userDesiredSize40Gb = 40Gb @@ -653,6 +698,17 @@ try ) } + function Assert-FSFormatIsReFsWhenDevDriveFlagSetToTrue + { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $FSFormat + ) + } + Describe 'DSC_Disk\Get-TargetResource' { Context 'When online GPT disk with a partition/volume and correct Drive Letter assigned using Disk Number' { # verifiable (should be called) mocks @@ -3371,7 +3427,8 @@ try -FSFormat 'ReFS' ` -DevDrive $true ` -Verbose - } | Should -Throw -ExpectedMessage ($script:localizedData.FailedToConfigureDevDriveVolume -F $script:testDriveLetterT) + } | Should -Throw -ExpectedMessage ($script:localizedData.FailedToConfigureDevDriveVolume ` + -F $script:mockedVolumeThatExistPriorToConfiguration.UniqueId, $script:testDriveLetterT) } It 'Should call the correct mocks' { @@ -4487,7 +4544,7 @@ try } } - Context 'When the Dev Drive flag is true, but the partition is not formatted as a Dev Drive volume' { + Context 'When the Dev Drive flag is true, but the partition is relatively the same size as user inputted size and volume is NTFS' { # verifiable (should be called) mocks Mock ` -CommandName Get-DiskByIdentifier ` @@ -4497,14 +4554,16 @@ try Mock ` -CommandName Get-Partition ` - -MockWith { $mockedPartitionGDriveLetter50Gb } ` + -MockWith { $script:mockedPartitionGDriveLetterAlternatePartition150Gb } ` -Verifiable Mock ` -CommandName Get-Volume ` - -MockWith { $script:mockedVolumeThatExistPriorToConfiguration } ` + -MockWith { $script:mockedVolumeThatExistPriorToConfigurationNtfs150Gb } ` -Verifiable + $script:result = $null + It 'Should not throw an exception' { { $script:result = Test-TargetResource ` @@ -4520,6 +4579,10 @@ try } | Should -Not -Throw } + It 'Should be false' { + $script:result | Should -Be $false + } + It 'Should call the correct mocks' { Assert-VerifiableMock Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 1 ` @@ -4528,6 +4591,184 @@ try Assert-MockCalled -CommandName Get-Volume -Exactly -Times 1 } } + + Context 'When the Dev Drive flag is true, but the partition is not the same size as user inputted size, volume is ReFS formatted but not Dev Drive volume' { + # verifiable (should be called) mocks + Mock ` + -CommandName Get-DiskByIdentifier ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number ` + -MockWith { $script:mockedDisk0Gpt } ` + -Verifiable + + Mock ` + -CommandName Get-Partition ` + -MockWith { $script:mockedPartitionGDriveLetterAlternatePartition150Gb } ` + -Verifiable + + Mock ` + -CommandName Get-Volume ` + -MockWith { $script:mockedVolumeThatExistPriorToConfigurationReFS } ` + -Verifiable + + + Mock ` + -CommandName Assert-DevDriveFeatureAvailable ` + -Verifiable + + Mock ` + -CommandName Test-DevDriveVolume ` + -MockWith { $false } ` + -Verifiable + + $script:result = $null + + It 'Should not throw an exception' { + { + $script:result = Test-TargetResource ` + -DiskId $script:mockedDisk0Gpt.Number ` + -DriveLetter $script:testDriveLetter ` + -AllocationUnitSize 4096 ` + -Size $script:userDesiredSize50Gb ` + -FSLabel $script:mockedVolume.FileSystemLabel ` + -FSFormat $script:mockedVolumeReFS.FileSystem ` + -DevDrive $true ` + -AllowDestructive $true ` + -Verbose + } | Should -Not -Throw + } + + It 'Should be false' { + $script:result | Should -Be $false + } + + It 'Should call the correct mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 1 ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number + Assert-MockCalled -CommandName Get-Partition -Exactly -Times 1 + Assert-MockCalled -CommandName Get-Volume -Exactly -Times 2 + Assert-MockCalled -CommandName Test-DevDriveVolume -Exactly -Times 1 + Assert-MockCalled -CommandName Assert-DevDriveFeatureAvailable -Exactly -Times 1 + } + } + + Context 'When the Dev Drive flag is true, but the partition is relatively the same size as user inputted size, volume is ReFS formatted and is Dev Drive volume' { + # verifiable (should be called) mocks + Mock ` + -CommandName Get-DiskByIdentifier ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number ` + -MockWith { $script:mockedDisk0Gpt } ` + -Verifiable + + Mock ` + -CommandName Get-Partition ` + -MockWith { $script:mockedPartitionGDriveLetterAlternatePartition150Gb } ` + -Verifiable + + Mock ` + -CommandName Get-Volume ` + -MockWith { $script:mockedVolumeDevDrive } ` + -Verifiable + + Mock ` + -CommandName Assert-DevDriveFeatureAvailable ` + -Verifiable + + Mock ` + -CommandName Test-DevDriveVolume ` + -MockWith { $true } ` + -Verifiable + + $script:result = $null + + It 'Should not throw an exception' { + { + $script:result = Test-TargetResource ` + -DiskId $script:mockedDisk0Gpt.Number ` + -DriveLetter $script:testDriveLetter ` + -AllocationUnitSize 4096 ` + -Size $script:userDesiredSize50Gb ` + -FSLabel $script:mockedVolume.FileSystemLabel ` + -FSFormat $script:mockedVolumeReFS.FileSystem ` + -DevDrive $true ` + -AllowDestructive $true ` + -Verbose + } | Should -Not -Throw + } + + It 'Should be true' { + $script:result | Should -Be $true + } + + It 'Should call the correct mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 1 ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number + Assert-MockCalled -CommandName Get-Partition -Exactly -Times 1 + Assert-MockCalled -CommandName Get-Volume -Exactly -Times 2 + Assert-MockCalled -CommandName Test-DevDriveVolume -Exactly -Times 1 + Assert-MockCalled -CommandName Assert-DevDriveFeatureAvailable -Exactly -Times 1 + } + } + + Context 'When the Dev Drive flag is true, but the partition is relatively the same size as user inputted size, volume is ReFS formatted and is not Dev Drive volume' { + # verifiable (should be called) mocks + Mock ` + -CommandName Get-DiskByIdentifier ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number ` + -MockWith { $script:mockedDisk0Gpt } ` + -Verifiable + + Mock ` + -CommandName Get-Partition ` + -MockWith { $script:mockedPartitionGDriveLetterAlternatePartition150Gb } ` + -Verifiable + + Mock ` + -CommandName Get-Volume ` + -MockWith { $script:mockedVolumeThatExistPriorToConfigurationRefs150Gb } ` + -Verifiable + + Mock ` + -CommandName Assert-DevDriveFeatureAvailable ` + -Verifiable + + Mock ` + -CommandName Test-DevDriveVolume ` + -MockWith { $false } ` + -Verifiable + + $script:result = $null + + It 'Should not throw an exception' { + { + $script:result = Test-TargetResource ` + -DiskId $script:mockedDisk0Gpt.Number ` + -DriveLetter $script:testDriveLetter ` + -AllocationUnitSize 4096 ` + -Size $script:userDesiredSize50Gb ` + -FSLabel $script:mockedVolume.FileSystemLabel ` + -FSFormat $script:mockedVolumeReFS.FileSystem ` + -DevDrive $true ` + -AllowDestructive $true ` + -Verbose + } | Should -Not -Throw + } + + It 'Should be false' { + $script:result | Should -Be $false + } + + It 'Should call the correct mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 1 ` + -ParameterFilter $script:parameterFilter_MockedDisk0Number + Assert-MockCalled -CommandName Get-Partition -Exactly -Times 1 + Assert-MockCalled -CommandName Get-Volume -Exactly -Times 2 + Assert-MockCalled -CommandName Test-DevDriveVolume -Exactly -Times 1 + Assert-MockCalled -CommandName Assert-DevDriveFeatureAvailable -Exactly -Times 1 + } + } } } } From 4fab5467d6439c0fe2a1cebb22063d12d05529b0 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Wed, 18 Oct 2023 17:31:27 -0700 Subject: [PATCH 18/21] update for mbr case and fix spelling errors --- source/DSCResources/DSC_Disk/DSC_Disk.psm1 | 33 ++++++++++++------- source/DSCResources/DSC_Disk/README.md | 8 +++++ ...teDevDriveOnDiskWithExistingPartitions.ps1 | 6 ++-- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 index a6b408b6..2b69959f 100644 --- a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 +++ b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 @@ -379,30 +379,38 @@ function Set-TargetResource ) -join '' ) <# - Find the first basic partition whose max - min supported partition size is greater than or equal + Find the first partition whose max - min supported partition size is greater than or equal to the size the user wants so we can resize it later. The max size also includes any unallocated space next to the partition. #> $partitionToResizeForDevDriveScenario = $null $amountToDecreasePartitionBy = 0 $isResizeNeeded = $true - foreach ($tempPartiton in $partition) + foreach ($tempPartition in $partition) { - if (-not $tempPartiton.DriveLetter -or $tempPartiton.DriveLetter -eq '') + $shouldNotBeResized = ($tempPartition.Type -eq 'System' -or ` + $tempPartition.Type -eq 'Reserved' -or ` + $tempPartition.Type -eq 'Recovery' ` + ) + + $doesNotHaveDriveLetter = (-not $tempPartition.DriveLetter -or ` + $tempPartition.DriveLetter -eq '') + + if ($shouldNotBeResized -or $doesNotHaveDriveLetter) { continue } - $supportedSize = Get-PartitionSupportedSize -DriveLetter $tempPartiton.DriveLetter + $supportedSize = Get-PartitionSupportedSize -DriveLetter $tempPartition.DriveLetter Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.CheckingIfPartitionCanBeResizedForDevDrive -F $tempPartiton.DriveLetter) + $($script:localizedData.CheckingIfPartitionCanBeResizedForDevDrive -F $tempPartition.DriveLetter) ) -join '' ) - if ($tempPartiton.Type -eq 'Basic' -and (($supportedSize.SizeMax - $supportedSize.SizeMin) -ge $Size)) + if (($supportedSize.SizeMax - $supportedSize.SizeMin) -ge $Size) { - $unallocatedSpaceNextToPartition = $supportedSize.SizeMax - $tempPartiton.Size + $unallocatedSpaceNextToPartition = $supportedSize.SizeMax - $tempPartition.Size if ($unallocatedSpaceNextToPartition -ge $Size) { @@ -421,17 +429,17 @@ function Set-TargetResource Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.PartitionFoundThatCanBeResizedForDevDrive -F $tempPartiton.DriveLetter) + $($script:localizedData.PartitionFoundThatCanBeResizedForDevDrive -F $tempPartition.DriveLetter) ) -join '' ) - $partitionToResizeForDevDriveScenario = $tempPartiton + $partitionToResizeForDevDriveScenario = $tempPartition $amountToDecreasePartitionBy = $Size - $unallocatedSpaceNextToPartition break } Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.PartitionCantBeResizedForDevDrive -F $tempPartiton.DriveLetter) + $($script:localizedData.PartitionCantBeResizedForDevDrive -F $tempPartition.DriveLetter) ) -join '' ) } @@ -1087,8 +1095,9 @@ function Test-TargetResource See the 'Size' parameter in https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/createpartition-msft-disk for more information. But to some it up, what the user enters and what the New-Partition cmdlet is able to allocate - can be different in bytes. So to Keep idempotence, we only return false when they arent the same in GB. Also if volume - already ReFs, there is no point in returning false for size mismatches since they can't be resized with resize-partition. + can be different in bytes. So to keep idempotence, we only return false when they arent the same in GB. Also if the volume + already is formatted as a ReFS, there is no point in returning false for size mismatches since they can't be resized with + resize-partition. #> $partitionInGb = [Math]::Round($partition.Size / 1GB, 2) diff --git a/source/DSCResources/DSC_Disk/README.md b/source/DSCResources/DSC_Disk/README.md index 32407f1b..696b05d6 100644 --- a/source/DSCResources/DSC_Disk/README.md +++ b/source/DSCResources/DSC_Disk/README.md @@ -43,6 +43,14 @@ Yes, more than one Dev Drive volume can be mounted at a time. You can have as ma Yes, since the Dev Drive volume is just like any other volume storage wise to the Windows operating system, a non Dev Drive ReFS volume can be reformatted as a Dev Drive volume. An NTFS volume can also be reformatted as a Dev Drive volume. Note, the Disk resource will throw an exception, should you also attempt to resize a ReFS volume while attempting to reformat it as a Dev Drive volume since ReFS volumes cannot be resized. As Dev Drive volumes are also ReFS volumes, they carry the same restrictions, see: [Resilient File System (ReFS) overview | Microsoft Learn](https://learn.microsoft.com/en-us/windows-server/storage/refs/refs-overview) +### If I don't have any unallocated space available to create a Dev Drive volume, what will happen? + +The Disk resource uses the Get-PartitionSupportedSize cmdlet to know which volume can be be resized to a safe size to create enough unallocated space for the Dev Drive volume to be created. As long as the size parameter is used, the Disk resource will shrink the first non ReFS Drive whose (MaxSize - MinSize) is greater than or equal to the size entered in the size parameter. + +If unallocated space exists but isn't enough to create a Dev Drive volume with, The Disk Resource will only shrink the volume noted above by the minimum size needed, to add to the existing unallocated space so it can be equal to the size parameter. For example, if you wanted to create a new 50 Gb Dev Drive volume on disk 0, and let's say on disk 0 there was only a 'C' drive that was 800 Gb in size. Next to the 'C' drive there was only 40 Gb of free contiguous unallocated space. The Disk resource would shrink the 'C' drive by 10 Gb, creating an addition 10 Gb of unallocated space. Now the unallocated space would be 50 Gb in size. The disk resource would then create a new partition and create the Dev Drive volume into this new partition. + +**Note: if no size is entered the disk resource will throw an error stating that size is 0 gb, so no partitions can be resized.** + ### Dev Drive requirements for this resource There are only five requirements: diff --git a/source/Examples/Resources/Disk/3-Disk_CreateDevDriveOnDiskWithExistingPartitions.ps1 b/source/Examples/Resources/Disk/3-Disk_CreateDevDriveOnDiskWithExistingPartitions.ps1 index ca66ba9e..d2258537 100644 --- a/source/Examples/Resources/Disk/3-Disk_CreateDevDriveOnDiskWithExistingPartitions.ps1 +++ b/source/Examples/Resources/Disk/3-Disk_CreateDevDriveOnDiskWithExistingPartitions.ps1 @@ -19,9 +19,9 @@ <# .DESCRIPTION - For this scenario we want to create two 60 Gb Dev Drive volumes. We know that disk 2 has 3 existing volumes - NTFS volumes and we prefer not to remove them. At most we only wantvthe disk DSC resource to resize any - of them should there not be enough space for any Dev Drive volume to be created. We also know that the + For this scenario we want to create two 60 Gb Dev Drive volumes. We know that disk 2 has 3 existing + NTFS volumes and we prefer not to remove them. At most we only want the disk DSC resource to shrink any + of them should there not be enough space for any of the Dev Drive volumes to be created. We also know that the the 3 existing volumes are 100Gb, 200Gb and 300Gb in size and disk 2 is 600 Gb in size. Since all the space is being used by the existing volumes, The Disk Dsc resource will resize the existing volumes to create space for our new Dev Drive volumes. An example of what could happen is the Disk resource could resize the From 3182494ccf9396715c0946794362cba090071d83 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Wed, 18 Oct 2023 21:18:07 -0700 Subject: [PATCH 19/21] update to allow uninitialized disks to be initialized and Dev Drive added after this is done --- source/DSCResources/DSC_Disk/DSC_Disk.psm1 | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 index 2b69959f..6cdf5c43 100644 --- a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 +++ b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 @@ -311,6 +311,11 @@ function Set-TargetResource ) -join '' ) $disk | Initialize-Disk -PartitionStyle $PartitionStyle + + # Requery the disk + $disk = Get-DiskByIdentifier ` + -DiskId $DiskId ` + -DiskIdType $DiskIdType } else { @@ -355,6 +360,9 @@ function Set-TargetResource $assignedPartition = $partition | Where-Object -Property DriveLetter -eq $DriveLetter + # Get the current max unallocated space in bytes. Round up to nearest Gb so we can do better comparisons with the Size parameter. + $currentMaxUnallocatedSpaceInBytes = [Math]::Round($disk.LargestFreeExtent / 1GB, 2) * 1GB + # Check if existing partition already has file system on it if ($null -eq $assignedPartition) { @@ -432,6 +440,7 @@ function Set-TargetResource $($script:localizedData.PartitionFoundThatCanBeResizedForDevDrive -F $tempPartition.DriveLetter) ) -join '' ) + $partitionToResizeForDevDriveScenario = $tempPartition $amountToDecreasePartitionBy = $Size - $unallocatedSpaceNextToPartition break @@ -445,7 +454,7 @@ function Set-TargetResource $partition = $partitionToResizeForDevDriveScenario - if ($isResizeNeeded -and (-not $partition)) + if ($isResizeNeeded -and (-not $partition) -and $currentMaxUnallocatedSpaceInBytes -lt $Size) { $SizeInGb = [Math]::Round($Size / 1GB, 2) throw ($script:localizedData.FoundNoPartitionsThatCanResizedForDevDrive -F $SizeInGb) @@ -520,7 +529,7 @@ function Set-TargetResource We can't find a partition with the required drive letter, so we may need to make a new one. First we need to check if there is already enough unallocated space on the disk. #> - $enoughUnallocatedSpace = ($disk.LargestFreeExtent -ge $Size) + $enoughUnallocatedSpace = ($currentMaxUnallocatedSpaceInBytes -ge $Size) if ($DevDrive -and $Size -and $partition -and (-not $enoughUnallocatedSpace)) { @@ -607,7 +616,7 @@ function Set-TargetResource if ($DevDrive) { - Assert-SizeMeetsMinimumDevDriveRequirement -UserDesiredSize $disk.LargestFreeExtent + Assert-SizeMeetsMinimumDevDriveRequirement -UserDesiredSize $currentMaxUnallocatedSpaceInBytes } $partitionParams['UseMaximumSize'] = $true From 72391a900f5a9894a9a0e22b4dbb4067e19aa9b3 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Wed, 18 Oct 2023 22:18:44 -0700 Subject: [PATCH 20/21] update failed test --- tests/Unit/DSC_Disk.Tests.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Unit/DSC_Disk.Tests.ps1 b/tests/Unit/DSC_Disk.Tests.ps1 index 1a38000c..a25f397d 100644 --- a/tests/Unit/DSC_Disk.Tests.ps1 +++ b/tests/Unit/DSC_Disk.Tests.ps1 @@ -1915,7 +1915,7 @@ try It 'Should call the correct mocks' { Assert-VerifiableMock - Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 1 ` + Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 2 ` -ParameterFilter $script:parameterFilter_MockedDisk0Number Assert-MockCalled -CommandName Set-Disk -Exactly -Times 1 Assert-MockCalled -CommandName Initialize-Disk -Exactly -Times 1 @@ -1984,7 +1984,7 @@ try It 'Should call the correct mocks' { Assert-VerifiableMock - Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 1 ` + Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 2 ` -ParameterFilter $script:parameterFilter_MockedDisk0Number Assert-MockCalled -CommandName Set-Disk -Exactly -Times 0 Assert-MockCalled -CommandName Initialize-Disk -Exactly -Times 1 @@ -3019,7 +3019,7 @@ try It 'Should call the correct mocks' { Assert-VerifiableMock - Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 2 ` + Assert-MockCalled -CommandName Get-DiskByIdentifier -Exactly -Times 3 ` -ParameterFilter $script:parameterFilter_MockedDisk0Number Assert-MockCalled -CommandName Set-Disk -Exactly -Times 0 Assert-MockCalled -CommandName Initialize-Disk -Exactly -Times 1 From c491047859d2f8b13cded04ab2ece9c193a96d4b Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Wed, 25 Oct 2023 13:34:39 -0700 Subject: [PATCH 21/21] update PR based on comments --- CHANGELOG.md | 5 +- source/DSCResources/DSC_Disk/DSC_Disk.psm1 | 96 +++--- .../StorageDsc.Common/StorageDsc.Common.psm1 | 38 ++- tests/Unit/DSC_Disk.Tests.ps1 | 287 +++++++++--------- tests/Unit/StorageDsc.Common.Tests.ps1 | 43 ++- 5 files changed, 278 insertions(+), 191 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f13d763..2d6d1b6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Updated DSC_Disk to allow volumes to be formatted as Dev Drives - Fixes [Issue #276](https://github.com/dsccommunity/StorageDsc/issues/276) +### Added + +- Disk: + - BREAKING CHANGE: Added support for volumes to be formatted as Dev Drives - Fixes [Issue #276](https://github.com/dsccommunity/StorageDsc/issues/276) ## [5.1.0] - 2023-02-22 diff --git a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 index 6cdf5c43..da3c73b4 100644 --- a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 +++ b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 @@ -346,7 +346,10 @@ function Set-TargetResource Assert-DevDriveFeatureAvailable Assert-FSFormatIsReFsWhenDevDriveFlagSetToTrue -FSFormat $FSFormat - # we validate the case where the user does not specify a size later on, should we need to create a new partition. + <# + We validate the case where the user does not specify a size later on, should we need to create a new + partition. + #> if ($Size) { Assert-SizeMeetsMinimumDevDriveRequirement -UserDesiredSize $Size @@ -360,7 +363,10 @@ function Set-TargetResource $assignedPartition = $partition | Where-Object -Property DriveLetter -eq $DriveLetter - # Get the current max unallocated space in bytes. Round up to nearest Gb so we can do better comparisons with the Size parameter. + <# + Get the current max unallocated space in bytes. Round up to nearest Gb so we can do better comparisons + with the Size parameter. + #> $currentMaxUnallocatedSpaceInBytes = [Math]::Round($disk.LargestFreeExtent / 1GB, 2) * 1GB # Check if existing partition already has file system on it @@ -388,18 +394,15 @@ function Set-TargetResource <# Find the first partition whose max - min supported partition size is greater than or equal - to the size the user wants so we can resize it later. The max size also includes any unallocated - space next to the partition. + to the size the user wants so we can resize it later. The max size also includes any + unallocated space next to the partition. #> $partitionToResizeForDevDriveScenario = $null $amountToDecreasePartitionBy = 0 $isResizeNeeded = $true foreach ($tempPartition in $partition) { - $shouldNotBeResized = ($tempPartition.Type -eq 'System' -or ` - $tempPartition.Type -eq 'Reserved' -or ` - $tempPartition.Type -eq 'Recovery' ` - ) + $shouldNotBeResized = ($tempPartition.Type -in 'System','Reserved','Recovery') $doesNotHaveDriveLetter = (-not $tempPartition.DriveLetter -or ` $tempPartition.DriveLetter -eq '') @@ -413,7 +416,7 @@ function Set-TargetResource Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.CheckingIfPartitionCanBeResizedForDevDrive -F $tempPartition.DriveLetter) + $($script:localizedData.CheckingIfPartitionCanBeResizedForDevDrive -f $tempPartition.DriveLetter) ) -join '' ) if (($supportedSize.SizeMax - $supportedSize.SizeMin) -ge $Size) @@ -423,8 +426,8 @@ function Set-TargetResource if ($unallocatedSpaceNextToPartition -ge $Size) { <# - The size of the unallocated space next to the partition is already big enough to create a Dev Drive volume on, - so we don't need to resize any partitions. + The size of the unallocated space next to the partition is already big enough + to create a Dev Drive volume on, so we don't need to resize any partitions. #> Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " @@ -437,7 +440,7 @@ function Set-TargetResource Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.PartitionFoundThatCanBeResizedForDevDrive -F $tempPartition.DriveLetter) + $($script:localizedData.PartitionFoundThatCanBeResizedForDevDrive -f $tempPartition.DriveLetter) ) -join '' ) @@ -448,7 +451,7 @@ function Set-TargetResource Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.PartitionCantBeResizedForDevDrive -F $tempPartition.DriveLetter) + $($script:localizedData.PartitionCantBeResizedForDevDrive -f $tempPartition.DriveLetter) ) -join '' ) } @@ -457,12 +460,15 @@ function Set-TargetResource if ($isResizeNeeded -and (-not $partition) -and $currentMaxUnallocatedSpaceInBytes -lt $Size) { $SizeInGb = [Math]::Round($Size / 1GB, 2) - throw ($script:localizedData.FoundNoPartitionsThatCanResizedForDevDrive -F $SizeInGb) + throw ($script:localizedData.FoundNoPartitionsThatCanResizedForDevDrive -f $SizeInGb) } } else { - # Find the first basic partition matching the size + <# + When not in the Dev Drive scenario we attempt to find the first basic partition matching + the size and use that as the partition. + #> $partition = $partition | Where-Object -FilterScript { $_.Type -eq 'Basic' -and $_.Size -eq $Size } | Select-Object -First 1 @@ -559,7 +565,7 @@ function Set-TargetResource else { # Allow Destructive is not set to true, so throw an exception. - throw ($script:localizedData.AllowDestructiveNeededForDevDriveOperation -F $partition.DriveLetter) + throw ($script:localizedData.AllowDestructiveNeededForDevDriveOperation -f $partition.DriveLetter) } # We no longer need to use the resized partition as we now have enough unallocated space. @@ -589,13 +595,11 @@ function Set-TargetResource if ($DevDrive) { <# - If size is slightly larger in bytes due to low level rounding differences than the max free extent there - won't be any capacity to create the new partition. So if the values are the same in GB after rounding, - we'll update size to be the max free extent + If size is slightly larger in bytes due to low level rounding differences than the max + free extent there won't be any capacity to create the new partition. So if the values + are the same in GB after rounding, we'll update size to be the max free extent #> - $largestFreeExtentInGb = [Math]::Round($disk.LargestFreeExtent / 1GB, 2) - $SizeInGb = [Math]::Round($Size / 1GB, 2) - if ($SizeInGb -eq $largestFreeExtentInGb) + if (Compare-SizeUsingGB -SizeAInBytes $Size -SizeBInBytes $disk.LargestFreeExtent) { $Size = $disk.LargestFreeExtent } @@ -728,9 +732,11 @@ function Set-TargetResource } <# - If the Set-TargetResource function is run as a standalone function, and $assignedPartition is not null and there are multiple partitions in $partition, - then '$partition | Get-Volume', will give $volume back the first volume on the first partition. If $assignedPartition is after that one, then we could - potentially format a different volume. So we need to make sure that $partition is equal to $assignedPartition before we call Get-Volume. + If the Set-TargetResource function is run as a standalone function, and $assignedPartition is not null + and there are multiple partitions in $partition, then '$partition | Get-Volume', will give $volume back + the first volume on the first partition. If $assignedPartition is after that one, then we could + potentially format a different volume. So we need to make sure that $partition is equal to + $assignedPartition before we call Get-Volume. #> if ($assignedPartition) { @@ -869,7 +875,7 @@ function Set-TargetResource Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " $($script:localizedData.SuccessfullyConfiguredDevDriveVolume ` - -F $volume.UniqueId, $volume.DriveLetter) + -f $volume.UniqueId, $volume.DriveLetter) ) -join '' ) } else @@ -877,7 +883,7 @@ function Set-TargetResource throw ( @( "$($MyInvocation.MyCommand): " $($script:localizedData.FailedToConfigureDevDriveVolume ` - -F $volume.UniqueId, $volume.DriveLetter) + -f $volume.UniqueId, $volume.DriveLetter) ) -join '' ) } } @@ -1097,23 +1103,29 @@ function Test-TargetResource if ($DevDrive) { <# - In the Dev Drive scenario we may resize a partition to create new unallocated space, so that we can create - a new Dev Drive. When this is done we create a new partition on the largest free extent on the disk. However, - Though the value is equivalent they aren't always the same. E.g 150Gb in powershell cmdline is 161061273600 bytes. - But $disk.LargestFreeExtent could be 161060225024 bytes. Which is different but they are both 150Gb when you convert them. - - See the 'Size' parameter in https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/createpartition-msft-disk - for more information. But to some it up, what the user enters and what the New-Partition cmdlet is able to allocate - can be different in bytes. So to keep idempotence, we only return false when they arent the same in GB. Also if the volume - already is formatted as a ReFS, there is no point in returning false for size mismatches since they can't be resized with - resize-partition. + In the Dev Drive scenario we may resize a partition to create new unallocated space, so + that we can create a new Dev Drive. When this is done we create a new partition on the + largest free extent on the disk. However, Though the value is equivalent they aren't + always the same. E.g 150Gb in powershell cmdline is 161061273600 bytes. + But $disk.LargestFreeExtent could be 161060225024 bytes. Which is different but they are + both 150Gb when you convert them. + + See the 'Size' parameter in: + https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/createpartition-msft-disk + for more information. But to some it up, what the user enters and what the New-Partition + cmdlet is able to allocate can be different in bytes. So to keep idempotence, we only + return false when they arent the same in GB. Also if the volume already is formatted as + a ReFS, there is no point in returning false for size mismatches since they can't be + resized with resize-partition. #> - $partitionInGb = [Math]::Round($partition.Size / 1GB, 2) - $sizeInGb = [Math]::Round($Size / 1GB, 2) + $partitionSizeEqualToSizeParam = Compare-SizeUsingGB ` + -SizeAInBytes $Size ` + -SizeBInBytes $partition.Size + $volume = $partition | Get-Volume - if (($sizeInGb -ne $partitionInGb) -and $volume.FileSystem -ne 'ReFS') + if ((-not $partitionSizeEqualToSizeParam) -and $volume.FileSystem -ne 'ReFS') { return $false } @@ -1220,7 +1232,7 @@ function Test-TargetResource Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " $($script:localizedData.TheVolumeIsCurrentlyConfiguredAsADevDriveVolume ` - -F $volume.UniqueId, $volume.DriveLetter) + -f $volume.UniqueId, $volume.DriveLetter) ) -join '' ) } else @@ -1228,7 +1240,7 @@ function Test-TargetResource Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " $($script:localizedData.TheVolumeIsNotConfiguredAsADevDriveVolume ` - -F $volume.UniqueId, $volume.DriveLetter) + -f $volume.UniqueId, $volume.DriveLetter) ) -join '' ) return $false diff --git a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 index 106503a8..718f353a 100644 --- a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 +++ b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 @@ -541,7 +541,7 @@ function Assert-SizeMeetsMinimumDevDriveRequirement if ($UserDesiredSizeInGb -lt $minimumSizeForDevDriveInGb) { - throw ($script:localizedData.MinimumSizeNeededToCreateDevDriveVolumeError -F $UserDesiredSizeInGb ) + throw ($script:localizedData.MinimumSizeNeededToCreateDevDriveVolumeError -f $UserDesiredSizeInGb ) } } # end function Assert-SizeMeetsMinimumDevDriveRequirement @@ -595,6 +595,39 @@ function Test-DevDriveVolume return Invoke-DeviceIoControlWrapperForDevDriveQuery -VolumeGuidPath $VolumeGuidPath }# end function Test-DevDriveVolume +<# + .SYNOPSIS + Compares two values that are in bytes to see whether they are equal when converted to gigabytes. + We return true if they are equal and false if they are not. + + .PARAMETER SizeAInBytes + The size of the first value in bytes. + + .PARAMETER SizeBInBytes + The size of the second value in bytes. +#> +function Compare-SizeUsingGB +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [System.UInt64] + $SizeAInBytes, + + [Parameter(Mandatory = $true)] + [System.UInt64] + $SizeBInBytes + ) + + $SizeAInGb = [Math]::Round($SizeAInBytes / 1GB, 2) + $SizeBInGb = [Math]::Round($SizeBInBytes / 1GB, 2) + + return $SizeAInGb -eq $SizeBInGb + +}# end function Compare-SizeUsingGB + Export-ModuleMember -Function @( 'Restart-ServiceIfExists', 'Assert-DriveLetterValid', @@ -608,5 +641,6 @@ Export-ModuleMember -Function @( 'Invoke-IsApiSetImplemented', 'Get-DevDriveEnablementState', 'Test-DevDriveVolume', - 'Invoke-DeviceIoControlWrapperForDevDriveQuery' + 'Invoke-DeviceIoControlWrapperForDevDriveQuery', + 'Compare-SizeUsingGB' ) diff --git a/tests/Unit/DSC_Disk.Tests.ps1 b/tests/Unit/DSC_Disk.Tests.ps1 index a25f397d..9ffddcac 100644 --- a/tests/Unit/DSC_Disk.Tests.ps1 +++ b/tests/Unit/DSC_Disk.Tests.ps1 @@ -33,7 +33,7 @@ Invoke-TestSetup try { InModuleScope $script:dscResourceName { - $script:testDriveLetter = 'G' + $script:testDriveLetterG = 'G' $script:testDriveLetterH = 'H' $script:testDriveLetterK = 'K' $script:testDriveLetterT = 'T' @@ -191,7 +191,7 @@ try $script:mockedPartitionSize = 1GB $script:mockedPartition = [pscustomobject] @{ - DriveLetter = [System.Char] $script:testDriveLetter + DriveLetter = [System.Char] $script:testDriveLetterG Size = $script:mockedPartitionSize PartitionNumber = 1 Type = 'Basic' @@ -219,14 +219,14 @@ try } $script:mockedPartitionWithGDriveletter = [pscustomobject] @{ - DriveLetter = [System.Char] $script:testDriveLetter + DriveLetter = [System.Char] $script:testDriveLetterG Size = $script:mockedPartitionSize50Gb PartitionNumber = 1 Type = 'Basic' } $script:mockedPartitionSupportedSizeForGDriveletter = [pscustomobject] @{ - DriveLetter = [System.Char] $script:testDriveLetter + DriveLetter = [System.Char] $script:testDriveLetterG SizeMax = $script:mockedPartitionSize50Gb SizeMin = $script:mockedPartitionSize50Gb } @@ -277,13 +277,13 @@ try #> $script:mockedPartitionMultiple = @( [pscustomobject] @{ - DriveLetter = [System.Char] $script:testDriveLetter + DriveLetter = [System.Char] $script:testDriveLetterG Size = $script:mockedPartitionSize PartitionNumber = 1 Type = 'Basic' }, [pscustomobject] @{ - DriveLetter = [System.Char] $script:testDriveLetter + DriveLetter = [System.Char] $script:testDriveLetterG Size = $script:mockedPartitionSize PartitionNumber = 1 Type = 'Basic' @@ -307,7 +307,7 @@ try } $script:mockedPartitionGDriveLetter40Gb = [pscustomobject] @{ - DriveLetter = [System.Char] $testDriveLetter + DriveLetter = [System.Char] $testDriveLetterG Size = $script:mockedPartitionSize40Gb PartitionNumber = 1 Type = 'Basic' @@ -315,7 +315,7 @@ try } $script:mockedPartitionGDriveLetter50Gb = [pscustomobject] @{ - DriveLetter = [System.Char] $testDriveLetter + DriveLetter = [System.Char] $testDriveLetterG Size = $script:mockedPartitionSize50Gb PartitionNumber = 1 Type = 'Basic' @@ -323,7 +323,7 @@ try } $script:mockedPartitionGDriveLetterAlternatePartition150Gb = [pscustomobject] @{ - DriveLetter = [System.Char] $testDriveLetter + DriveLetter = [System.Char] $testDriveLetterG Size = $script:partitionFormattedByWindows150Gb PartitionNumber = 1 Type = 'Basic' @@ -331,7 +331,7 @@ try } $script:mockedPartitionGDriveLetter150Gb = [pscustomobject] @{ - DriveLetter = [System.Char] $testDriveLetter + DriveLetter = [System.Char] $testDriveLetterG Size = $script:userDesiredSize150Gb PartitionNumber = 1 Type = 'Basic' @@ -349,7 +349,7 @@ try $script:mockedVolume = [pscustomobject] @{ FileSystemLabel = 'myLabel' FileSystem = 'NTFS' - DriveLetter = $script:testDriveLetter + DriveLetter = $script:testDriveLetterG } $script:mockedVolumeUnformatted = [pscustomobject] @{ @@ -367,13 +367,13 @@ try $script:mockedVolumeReFS = [pscustomobject] @{ FileSystemLabel = 'myLabel' FileSystem = 'ReFS' - DriveLetter = $script:testDriveLetter + DriveLetter = $script:testDriveLetterG } $script:mockedVolumeDevDrive = [pscustomobject] @{ FileSystemLabel = 'myLabel' FileSystem = 'ReFS' - DriveLetter = $script:testDriveLetter + DriveLetter = $script:testDriveLetterG UniqueId = '\\?\Volume{3a244a32-efba-4b7e-9a19-7293fc7c7924}\' } @@ -422,7 +422,7 @@ try $script:userDesiredSize150Gb = 150Gb - # Alternate value in bytes that can represent a 150 Gb partition in a physical hard disk that has been formatted by Windows. + # Alternate value in bytes that can represent a 150 Gb partition in a physical hard disk that has been formatted by Windows. $script:partitionFormattedByWindows150Gb = 161060225024 $script:userDesiredSize50Gb = 50Gb @@ -437,6 +437,7 @@ try param ( [Parameter()] + [System.String] $DriveLetter ) @@ -735,7 +736,7 @@ try $resource = Get-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -Verbose It "Should return DiskId $($script:mockedDisk0Gpt.Number)" { @@ -746,8 +747,8 @@ try $resource.PartitionStyle | Should -Be $script:mockedDisk0Gpt.PartitionStyle } - It "Should return DriveLetter $($script:testDriveLetter)" { - $resource.DriveLetter | Should -Be $script:testDriveLetter + It "Should return DriveLetter $($script:testDriveLetterG)" { + $resource.DriveLetter | Should -Be $script:testDriveLetterG } It "Should return size $($script:mockedPartition.Size)" { @@ -801,7 +802,7 @@ try $resource = Get-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -Verbose It "Should return DiskId $($script:mockedDisk0Gpt.Number)" { @@ -812,8 +813,8 @@ try $resource.PartitionStyle | Should -Be $script:mockedDisk0Gpt.PartitionStyle } - It "Should return DriveLetter $($script:testDriveLetter)" { - $resource.DriveLetter | Should -Be $script:testDriveLetter + It "Should return DriveLetter $($script:testDriveLetterG)" { + $resource.DriveLetter | Should -Be $script:testDriveLetterG } It "Should return size $($script:mockedPartition.Size)" { @@ -868,7 +869,7 @@ try $resource = Get-TargetResource ` -DiskId $script:mockedDisk0Gpt.UniqueId ` -DiskIdType 'UniqueId' ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -Verbose It "Should return DiskId $($script:mockedDisk0Gpt.UniqueId)" { @@ -879,8 +880,8 @@ try $resource.PartitionStyle | Should -Be $script:mockedDisk0Gpt.PartitionStyle } - It "Should return DriveLetter $($script:testDriveLetter)" { - $resource.DriveLetter | Should -Be $script:testDriveLetter + It "Should return DriveLetter $($script:testDriveLetterG)" { + $resource.DriveLetter | Should -Be $script:testDriveLetterG } It "Should return size $($script:mockedPartition.Size)" { @@ -935,7 +936,7 @@ try $resource = Get-TargetResource ` -DiskId $script:mockedDisk0Gpt.FriendlyName ` -DiskIdType 'FriendlyName' ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -Verbose It "Should return DiskId $($script:mockedDisk0Gpt.FriendlyName)" { @@ -946,8 +947,8 @@ try $resource.PartitionStyle | Should -Be $script:mockedDisk0Gpt.PartitionStyle } - It "Should return DriveLetter $($script:testDriveLetter)" { - $resource.DriveLetter | Should -Be $script:testDriveLetter + It "Should return DriveLetter $($script:testDriveLetterG)" { + $resource.DriveLetter | Should -Be $script:testDriveLetterG } It "Should return size $($script:mockedPartition.Size)" { @@ -1002,7 +1003,7 @@ try $resource = Get-TargetResource ` -DiskId $script:mockedDisk0Gpt.SerialNumber ` -DiskIdType 'SerialNumber' ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -Verbose It "Should return DiskId $($script:mockedDisk0Gpt.SerialNumber)" { @@ -1013,8 +1014,8 @@ try $resource.PartitionStyle | Should -Be $script:mockedDisk0Gpt.PartitionStyle } - It "Should return DriveLetter $($script:testDriveLetter)" { - $resource.DriveLetter | Should -Be $script:testDriveLetter + It "Should return DriveLetter $($script:testDriveLetterG)" { + $resource.DriveLetter | Should -Be $script:testDriveLetterG } It "Should return size $($script:mockedPartition.Size)" { @@ -1069,7 +1070,7 @@ try $resource = Get-TargetResource ` -DiskId $script:mockedDisk0Gpt.Guid ` -DiskIdType 'Guid' ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -Verbose It "Should return DiskId $($script:mockedDisk0Gpt.Guid)" { @@ -1080,8 +1081,8 @@ try $resource.PartitionStyle | Should -Be $script:mockedDisk0Gpt.PartitionStyle } - It "Should return DriveLetter $($script:testDriveLetter)" { - $resource.DriveLetter | Should -Be $script:testDriveLetter + It "Should return DriveLetter $($script:testDriveLetterG)" { + $resource.DriveLetter | Should -Be $script:testDriveLetterG } It "Should return Size $($script:mockedPartition.Size)" { @@ -1136,7 +1137,7 @@ try $resource = Get-TargetResource ` -DiskId $script:mockedDisk0Gpt.Guid ` -DiskIdType 'Guid' ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -Verbose It "Should return DiskId $($script:mockedDisk0Gpt.Guid)" { @@ -1147,8 +1148,8 @@ try $resource.PartitionStyle | Should -Be $script:mockedDisk0Gpt.PartitionStyle } - It "Should return DriveLetter $($script:testDriveLetter)" { - $resource.DriveLetter | Should -Be $script:testDriveLetter + It "Should return DriveLetter $($script:testDriveLetterG)" { + $resource.DriveLetter | Should -Be $script:testDriveLetterG } It "Should return Size $($script:mockedPartition.Size)" { @@ -1199,7 +1200,7 @@ try $resource = Get-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -Verbose It "Should return DiskId $($script:mockedDisk0Gpt.Number)" { @@ -1262,7 +1263,7 @@ try $resource = Get-TargetResource ` -DiskId $script:mockedDisk0Mbr.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -Verbose It "Should return DiskId $($script:mockedDisk0Mbr.Number)" { @@ -1325,7 +1326,7 @@ try $resource = Get-TargetResource ` -DiskId $script:mockedDisk0Raw.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -Verbose It "Should return DiskId $($script:mockedDisk0Raw.Number)" { @@ -1396,11 +1397,11 @@ try $resource = Get-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -Verbose It "Should return DevDrive as $($true)" { - $resource.DevDrive | Should -Be $true + $resource.DevDrive | Should -BeTrue } It 'Should call the correct mocks' { @@ -1443,11 +1444,11 @@ try $resource = Get-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -Verbose It "Should return DevDrive as $($false)" { - $resource.DevDrive | Should -Be $false + $resource.DevDrive | Should -BeFalse } It 'Should call the correct mocks' { @@ -1485,7 +1486,7 @@ try Mock ` -CommandName New-Partition ` - -ParameterFilter { $DriveLetter -eq $script:testDriveLetter } ` + -ParameterFilter { $DriveLetter -eq $script:testDriveLetterG} ` -MockWith { $script:mockedPartitionNoDriveLetter } ` -Verifiable @@ -1509,7 +1510,7 @@ try { Set-TargetResource ` -DiskId $script:mockedDisk0GptOffline.Number ` - -Driveletter $script:testDriveLetter ` + -Driveletter $script:testDriveLetterG ` -Verbose } | Should -Not -Throw } @@ -1523,7 +1524,7 @@ try Assert-MockCalled -CommandName Get-Partition -Exactly -Times 4 Assert-MockCalled -CommandName Get-Volume -Exactly -Times 1 Assert-MockCalled -CommandName New-Partition -Exactly -Times 1 ` - -ParameterFilter { $DriveLetter -eq $script:testDriveLetter } + -ParameterFilter { $DriveLetter -eq $script:testDriveLetterG} Assert-MockCalled -CommandName Format-Volume -Exactly -Times 1 Assert-MockCalled -CommandName Set-Partition -Exactly -Times 1 } @@ -1548,7 +1549,7 @@ try Mock ` -CommandName New-Partition ` -ParameterFilter { - $DriveLetter -eq $script:testDriveLetter + $DriveLetter -eq $script:testDriveLetterG } ` -MockWith { $script:mockedPartitionNoDriveLetter } ` -Verifiable @@ -1574,7 +1575,7 @@ try Set-TargetResource ` -DiskId $script:mockedDisk0GptOffline.UniqueId ` -DiskIdType 'UniqueId' ` - -Driveletter $script:testDriveLetter ` + -Driveletter $script:testDriveLetterG ` -Verbose } | Should -Not -Throw } @@ -1589,7 +1590,7 @@ try Assert-MockCalled -CommandName Get-Volume -Exactly -Times 1 Assert-MockCalled -CommandName New-Partition -Exactly -Times 1 ` -ParameterFilter { - $DriveLetter -eq $script:testDriveLetter + $DriveLetter -eq $script:testDriveLetterG } Assert-MockCalled -CommandName Format-Volume -Exactly -Times 1 Assert-MockCalled -CommandName Set-Partition -Exactly -Times 1 @@ -1615,7 +1616,7 @@ try Mock ` -CommandName New-Partition ` -ParameterFilter { - $DriveLetter -eq $script:testDriveLetter + $DriveLetter -eq $script:testDriveLetterG } ` -MockWith { $script:mockedPartitionNoDriveLetter } ` -Verifiable @@ -1641,7 +1642,7 @@ try Set-TargetResource ` -DiskId $script:mockedDisk0GptOffline.FriendlyName ` -DiskIdType 'FriendlyName' ` - -Driveletter $script:testDriveLetter ` + -Driveletter $script:testDriveLetterG ` -Verbose } | Should -Not -Throw } @@ -1656,7 +1657,7 @@ try Assert-MockCalled -CommandName Get-Volume -Exactly -Times 1 Assert-MockCalled -CommandName New-Partition -Exactly -Times 1 ` -ParameterFilter { - $DriveLetter -eq $script:testDriveLetter + $DriveLetter -eq $script:testDriveLetterG } Assert-MockCalled -CommandName Format-Volume -Exactly -Times 1 Assert-MockCalled -CommandName Set-Partition -Exactly -Times 1 @@ -1682,7 +1683,7 @@ try Mock ` -CommandName New-Partition ` -ParameterFilter { - $DriveLetter -eq $script:testDriveLetter + $DriveLetter -eq $script:testDriveLetterG } ` -MockWith { $script:mockedPartitionNoDriveLetter } ` -Verifiable @@ -1708,7 +1709,7 @@ try Set-TargetResource ` -DiskId $script:mockedDisk0GptOffline.SerialNumber ` -DiskIdType 'SerialNumber' ` - -Driveletter $script:testDriveLetter ` + -Driveletter $script:testDriveLetterG ` -Verbose } | Should -Not -Throw } @@ -1723,7 +1724,7 @@ try Assert-MockCalled -CommandName Get-Volume -Exactly -Times 1 Assert-MockCalled -CommandName New-Partition -Exactly -Times 1 ` -ParameterFilter { - $DriveLetter -eq $script:testDriveLetter + $DriveLetter -eq $script:testDriveLetterG } Assert-MockCalled -CommandName Format-Volume -Exactly -Times 1 Assert-MockCalled -CommandName Set-Partition -Exactly -Times 1 @@ -1749,7 +1750,7 @@ try Mock ` -CommandName New-Partition ` -ParameterFilter { - $DriveLetter -eq $script:testDriveLetter + $DriveLetter -eq $script:testDriveLetterG } ` -MockWith { $script:mockedPartitionNoDriveLetter } ` -Verifiable @@ -1775,7 +1776,7 @@ try Set-TargetResource ` -DiskId $script:mockedDisk0GptOffline.Guid ` -DiskIdType 'Guid' ` - -Driveletter $script:testDriveLetter ` + -Driveletter $script:testDriveLetterG ` -Verbose } | Should -Not -Throw } @@ -1790,7 +1791,7 @@ try Assert-MockCalled -CommandName Get-Volume -Exactly -Times 1 Assert-MockCalled -CommandName New-Partition -Exactly -Times 1 ` -ParameterFilter { - $DriveLetter -eq $script:testDriveLetter + $DriveLetter -eq $script:testDriveLetterG } Assert-MockCalled -CommandName Format-Volume -Exactly -Times 1 Assert-MockCalled -CommandName Set-Partition -Exactly -Times 1 @@ -1816,7 +1817,7 @@ try Mock ` -CommandName New-Partition ` -ParameterFilter { - $DriveLetter -eq $script:testDriveLetter + $DriveLetter -eq $script:testDriveLetterG } ` -MockWith { $script:mockedPartitionNoDriveLetter } ` -Verifiable @@ -1841,7 +1842,7 @@ try { Set-TargetResource ` -DiskId $script:mockedDisk0GptReadonly.Number ` - -Driveletter $script:testDriveLetter ` + -Driveletter $script:testDriveLetterG ` -Verbose } | Should -Not -Throw } @@ -1856,7 +1857,7 @@ try Assert-MockCalled -CommandName Get-Volume -Exactly -Times 1 Assert-MockCalled -CommandName New-Partition -Exactly -Times 1 ` -ParameterFilter { - $DriveLetter -eq $script:testDriveLetter + $DriveLetter -eq $script:testDriveLetterG } Assert-MockCalled -CommandName Format-Volume -Exactly -Times 1 Assert-MockCalled -CommandName Set-Partition -Exactly -Times 1 @@ -1886,7 +1887,7 @@ try Mock ` -CommandName New-Partition ` -ParameterFilter { - $DriveLetter -eq $script:testDriveLetter + $DriveLetter -eq $script:testDriveLetterG } ` -MockWith { $script:mockedPartitionNoDriveLetter } ` -Verifiable @@ -1908,7 +1909,7 @@ try { Set-TargetResource ` -DiskId $script:mockedDisk0RawOffline.Number ` - -Driveletter $script:testDriveLetter ` + -Driveletter $script:testDriveLetterG ` -Verbose } | Should -Not -Throw } @@ -1923,7 +1924,7 @@ try Assert-MockCalled -CommandName Get-Volume -Exactly -Times 1 Assert-MockCalled -CommandName New-Partition -Exactly -Times 1 ` -ParameterFilter { - $DriveLetter -eq $script:testDriveLetter + $DriveLetter -eq $script:testDriveLetterG } Assert-MockCalled -CommandName Format-Volume -Exactly -Times 1 Assert-MockCalled -CommandName Set-Partition -Exactly -Times 1 @@ -1949,7 +1950,7 @@ try Mock ` -CommandName New-Partition ` -ParameterFilter { - $DriveLetter -eq $script:testDriveLetter + $DriveLetter -eq $script:testDriveLetterG } ` -MockWith { $script:mockedPartitionNoDriveLetter } ` -Verifiable @@ -1974,7 +1975,7 @@ try { Set-TargetResource ` -DiskId $script:mockedDisk0Raw.Number ` - -Driveletter $script:testDriveLetter ` + -Driveletter $script:testDriveLetterG ` -Size $script:mockedPartitionSize ` -AllocationUnitSize 64 ` -FSLabel 'MyDisk' ` @@ -1992,7 +1993,7 @@ try Assert-MockCalled -CommandName Get-Volume -Exactly -Times 1 Assert-MockCalled -CommandName New-Partition -Exactly -Times 1 ` -ParameterFilter { - $DriveLetter -eq $script:testDriveLetter + $DriveLetter -eq $script:testDriveLetterG } Assert-MockCalled -CommandName Format-Volume -Exactly -Times 1 Assert-MockCalled -CommandName Set-Partition -Exactly -Times 1 @@ -2014,7 +2015,7 @@ try Mock ` -CommandName New-Partition ` -ParameterFilter { - $DriveLetter -eq $script:testDriveLetter + $DriveLetter -eq $script:testDriveLetterG } ` -MockWith { $script:mockedPartitionNoDriveLetter } ` -Verifiable @@ -2040,7 +2041,7 @@ try { Set-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -Driveletter $script:testDriveLetter ` + -Driveletter $script:testDriveLetterG ` -Verbose } | Should -Not -Throw } @@ -2055,7 +2056,7 @@ try Assert-MockCalled -CommandName Get-Volume -Exactly -Times 1 Assert-MockCalled -CommandName New-Partition -Exactly -Times 1 ` -ParameterFilter { - $DriveLetter -eq $script:testDriveLetter + $DriveLetter -eq $script:testDriveLetterG } Assert-MockCalled -CommandName Format-Volume -Exactly -Times 1 Assert-MockCalled -CommandName Set-Partition -Exactly -Times 1 @@ -2077,7 +2078,7 @@ try Mock ` -CommandName New-Partition ` - -ParameterFilter {$DriveLetter -eq $script:testDriveLetter} ` + -ParameterFilter {$DriveLetter -eq $script:testDriveLetterG} ` -MockWith { $script:mockedPartitionNoDriveLetterReadOnly } ` -Verifiable @@ -2099,7 +2100,7 @@ try { Set-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -Driveletter $script:testDriveLetter ` + -Driveletter $script:testDriveLetterG ` -Verbose } | Should -Throw $errorRecord } @@ -2127,7 +2128,7 @@ try Assert-MockCalled -CommandName Get-Volume -Exactly -Times 1 Assert-MockCalled -CommandName New-Partition -Exactly -Times 1 ` -ParameterFilter { - $DriveLetter -eq $script:testDriveLetter + $DriveLetter -eq $script:testDriveLetterG } Assert-MockCalled -CommandName Format-Volume -Exactly -Times 0 Assert-MockCalled -CommandName Set-Volume -Exactly -Times 0 @@ -2150,7 +2151,7 @@ try Mock ` -CommandName New-Partition ` - -ParameterFilter {$DriveLetter -eq $script:testDriveLetter} ` + -ParameterFilter {$DriveLetter -eq $script:testDriveLetterG} ` -MockWith { $script:mockedPartitionNoDriveLetter } ` -Verifiable @@ -2168,7 +2169,7 @@ try { Set-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -Driveletter $script:testDriveLetter ` + -Driveletter $script:testDriveLetterG ` -Verbose } | Should -Not -Throw } @@ -2189,7 +2190,7 @@ try Assert-MockCalled -CommandName Get-Volume -Exactly -Times 2 Assert-MockCalled -CommandName New-Partition -Exactly -Times 1 ` -ParameterFilter { - $DriveLetter -eq $script:testDriveLetter + $DriveLetter -eq $script:testDriveLetterG } Assert-MockCalled -CommandName Format-Volume -Exactly -Times 0 Assert-MockCalled -CommandName Set-Volume -Exactly -Times 0 @@ -2222,7 +2223,7 @@ try { Set-TargetResource ` -DiskId $script:mockedDisk0Mbr.Number ` - -Driveletter $script:testDriveLetter ` + -Driveletter $script:testDriveLetterG ` -Verbose } | Should -Throw $errorRecord } @@ -2266,7 +2267,7 @@ try Set-TargetResource ` -DiskId $script:mockedDisk0Mbr.UniqueId ` -DiskIdType 'UniqueId' ` - -Driveletter $script:testDriveLetter ` + -Driveletter $script:testDriveLetterG ` -Verbose } | Should -Throw $errorRecord } @@ -2313,7 +2314,7 @@ try { Set-targetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -Verbose } | Should -Not -Throw } @@ -2364,7 +2365,7 @@ try { Set-targetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -Size $script:mockedPartitionSize ` -Verbose } | Should -Not -Throw @@ -2527,7 +2528,7 @@ try { Set-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -Driveletter $script:testDriveLetter ` + -Driveletter $script:testDriveLetterG ` -FSLabel 'NewLabel' ` -Verbose } | Should -Not -Throw @@ -2564,7 +2565,7 @@ try Mock ` -CommandName New-Partition ` -ParameterFilter { - $DriveLetter -eq $script:testDriveLetter + $DriveLetter -eq $script:testDriveLetterG } ` -MockWith { $script:mockedPartitionNoDriveLetter } ` -Verifiable @@ -2587,7 +2588,7 @@ try { Set-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -Driveletter $script:testDriveLetter ` + -Driveletter $script:testDriveLetterG ` -Size ($script:mockedPartitionSize + 1024) ` -AllowDestructive $true ` -FSLabel 'NewLabel' ` @@ -2652,7 +2653,7 @@ try { Set-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -Driveletter $script:testDriveLetter ` + -Driveletter $script:testDriveLetterG ` -Size ($script:mockedPartitionSize + 1024) ` -AllowDestructive $true ` -FSLabel 'NewLabel' ` @@ -2724,7 +2725,7 @@ try { Set-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -Driveletter $script:testDriveLetter ` + -Driveletter $script:testDriveLetterG ` -AllowDestructive $true ` -FSLabel 'NewLabel' ` -Verbose @@ -2793,7 +2794,7 @@ try { Set-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -Driveletter $script:testDriveLetter ` + -Driveletter $script:testDriveLetterG ` -Size ($script:mockedPartitionSize + 1024) ` -AllowDestructive $true ` -FSLabel 'NewLabel' ` @@ -2856,7 +2857,7 @@ try { Set-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -Driveletter $script:testDriveLetter ` + -Driveletter $script:testDriveLetterG ` -Size $script:mockedPartitionSize ` -FSFormat 'ReFS' ` -FSLabel 'NewLabel' ` @@ -2916,7 +2917,7 @@ try { Set-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -Driveletter $script:testDriveLetter ` + -Driveletter $script:testDriveLetterG ` -Size $script:mockedPartitionSize ` -FSLabel 'NewLabel' ` -AllowDestructive $true ` @@ -3008,7 +3009,7 @@ try { Set-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -Driveletter $script:testDriveLetter ` + -Driveletter $script:testDriveLetterG ` -Size $script:mockedPartitionSize ` -FSLabel 'NewLabel' ` -AllowDestructive $true ` @@ -3033,7 +3034,7 @@ try } } - Context 'When the Dev Drive flag is true, the AllowDestructive flag is false and there is not enough space on the disk to create the partition' { + Context 'When the DevDrive flag is true, the AllowDestructive flag is false and there is not enough space on the disk to create the partition' { # verifiable (should be called) mocks Mock ` -CommandName Get-DiskByIdentifier ` @@ -3048,12 +3049,11 @@ try Mock ` -CommandName Assert-DevDriveFeatureAvailable ` - -MockWith { $null } ` -Verifiable Mock ` -CommandName Get-PartitionSupportedSize ` - -MockWith { & Get-PartitionSupportedSizeForDevDriveScenarios $DriveLetter } ` + -MockWith { & Get-PartitionSupportedSizeForDevDriveScenarios -DriveLetter $DriveLetter } ` -Verifiable # mocks that should not be called @@ -3072,7 +3072,7 @@ try -FSFormat 'ReFS' ` -DevDrive $true ` -Verbose - } | Should -Throw -ExpectedMessage ($script:localizedData.FoundNoPartitionsThatCanResizedForDevDrive -F $userDesiredSizeInGb) + } | Should -Throw -ExpectedMessage ($script:localizedData.FoundNoPartitionsThatCanResizedForDevDrive -f $userDesiredSizeInGb) } It 'Should call the correct mocks' { @@ -3085,7 +3085,7 @@ try } } - Context 'When the Dev Drive flag is true, AllowDestructive is false and there is enough space on the disk to create the partition' { + Context 'When the DevDrive flag is true, AllowDestructive is false and there is enough space on the disk to create the partition' { # verifiable (should be called) mocks Mock ` -CommandName Get-DiskByIdentifier ` @@ -3100,7 +3100,6 @@ try Mock ` -CommandName Assert-DevDriveFeatureAvailable ` - -MockWith { $null } ` -Verifiable Mock ` @@ -3115,7 +3114,7 @@ try Mock ` -CommandName Get-PartitionSupportedSize ` - -MockWith { & Get-PartitionSupportedSizeForDevDriveScenarios $DriveLetter } ` + -MockWith { & Get-PartitionSupportedSizeForDevDriveScenarios -DriveLetter $DriveLetter } ` -Verifiable Mock ` @@ -3161,7 +3160,7 @@ try } } - Context 'When the Dev Drive flag is true, AllowDestructive flag is false and there is not enough unallocated disk space but a resize of a partition is possible to create new space' { + Context 'When the DevDrive flag is true, AllowDestructive flag is false and there is not enough unallocated disk space but a resize of a partition is possible to create new space' { # verifiable (should be called) mocks Mock ` -CommandName Get-DiskByIdentifier ` @@ -3176,19 +3175,18 @@ try Mock ` -CommandName Assert-DevDriveFeatureAvailable ` - -MockWith { $null } ` -Verifiable Mock ` -CommandName Get-PartitionSupportedSize ` - -MockWith { & Get-PartitionSupportedSizeForDevDriveScenarios $DriveLetter } ` + -MockWith { & Get-PartitionSupportedSizeForDevDriveScenarios -DriveLetter $DriveLetter } ` -Verifiable # mocks that should not be called Mock -CommandName Set-Disk Mock -CommandName Initialize-Disk - It 'Should throw an exception' { + It 'Should throw an exception stating that AllowDestructive flag needs to be set to resize existing partition for DevDrive' { { Set-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` @@ -3198,7 +3196,7 @@ try -FSFormat 'ReFS' ` -DevDrive $true ` -Verbose - } | Should -Throw -ExpectedMessage ($script:localizedData.AllowDestructiveNeededForDevDriveOperation -F $script:testDriveLetterK) + } | Should -Throw -ExpectedMessage ($script:localizedData.AllowDestructiveNeededForDevDriveOperation -f $script:testDriveLetterK) } It 'Should call the correct mocks' { @@ -3211,9 +3209,11 @@ try } } - Context 'When the Dev Drive flag is true, AllowDestructive flag is true and there is not enough unallocated disk space but a resize of a partition is possible to create new space' { + Context 'When the DevDrive flag is true, AllowDestructive flag is true and there is not enough unallocated disk space but a resize of a partition is possible to create new space' { # verifiable (should be called) mocks + $script:amountOfTimesGetDiskByIdentifierIsCalled = 0 + # For resize scenario we need to call Get-DiskByIdentifier twice. After the resize a disk.FreeLargestExtent is updated. Mock ` -CommandName Get-DiskByIdentifier ` @@ -3242,12 +3242,11 @@ try Mock ` -CommandName Assert-DevDriveFeatureAvailable ` - -MockWith { $null } ` -Verifiable Mock ` -CommandName Get-PartitionSupportedSize ` - -MockWith { & Get-PartitionSupportedSizeForDevDriveScenarios $DriveLetter } ` + -MockWith { & Get-PartitionSupportedSizeForDevDriveScenarios -DriveLetter $DriveLetter } ` -Verifiable Mock ` @@ -3309,7 +3308,7 @@ try } } - Context 'When the Dev Drive flag is true, AllowDestructive is true, and a Partition that matches the users drive letter exists.' { + Context 'When the DevDrive flag is true, AllowDestructive is true, and a Partition that matches the users drive letter exists' { # verifiable (should be called) mocks Mock ` -CommandName Get-DiskByIdentifier ` @@ -3344,7 +3343,6 @@ try Mock ` -CommandName Assert-DevDriveFeatureAvailable ` - -MockWith { $null } ` -Verifiable # mocks that should not be called @@ -3380,7 +3378,7 @@ try } } - Context 'When the Dev Drive flag is true, AllowDestructive is false, and a Partition that matches the users drive letter exists.' { + Context 'When the DevDrive flag is true, AllowDestructive is false, and a Partition that matches the users drive letter exists' { # verifiable (should be called) mocks Mock ` -CommandName Get-DiskByIdentifier ` @@ -3410,7 +3408,6 @@ try Mock ` -CommandName Assert-DevDriveFeatureAvailable ` - -MockWith { $null } ` -Verifiable # mocks that should not be called @@ -3428,7 +3425,7 @@ try -DevDrive $true ` -Verbose } | Should -Throw -ExpectedMessage ($script:localizedData.FailedToConfigureDevDriveVolume ` - -F $script:mockedVolumeThatExistPriorToConfiguration.UniqueId, $script:testDriveLetterT) + -f $script:mockedVolumeThatExistPriorToConfiguration.UniqueId, $script:testDriveLetterT) } It 'Should call the correct mocks' { @@ -3472,7 +3469,7 @@ try { $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0GptOffline.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4096 ` -Verbose } | Should -Not -Throw @@ -3511,7 +3508,7 @@ try { $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0GptOffline.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4096 ` -Verbose } | Should -Not -Throw @@ -3551,7 +3548,7 @@ try $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0GptOffline.UniqueId ` -DiskIdType 'UniqueId' ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4096 ` -Verbose } | Should -Not -Throw @@ -3591,7 +3588,7 @@ try $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0GptOffline.FriendlyName ` -DiskIdType 'FriendlyName' ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4096 ` -Verbose } | Should -Not -Throw @@ -3631,7 +3628,7 @@ try $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0GptOffline.SerialNumber ` -DiskIdType 'SerialNumber' ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4096 ` -Verbose } | Should -Not -Throw @@ -3671,7 +3668,7 @@ try $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0GptOffline.Guid ` -DiskIdType 'Guid' ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4096 ` -Verbose } | Should -Not -Throw @@ -3710,7 +3707,7 @@ try { $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0GptReadonly.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4096 ` -Verbose } | Should -Not -Throw @@ -3749,7 +3746,7 @@ try { $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0Raw.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4096 ` -Verbose } | Should -Not -Throw @@ -3790,7 +3787,7 @@ try { Test-TargetResource ` -DiskId $script:mockedDisk0Mbr.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4096 ` -Verbose } | Should -Throw $errorRecord @@ -3827,7 +3824,7 @@ try { Test-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4096 ` -PartitionStyle 'MBR' ` -Verbose @@ -3863,7 +3860,7 @@ try { $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4096 ` -PartitionStyle 'MBR' ` -AllowDestructive $true ` @@ -3915,7 +3912,7 @@ try { $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4096 ` -Size ($script:mockedPartitionSize + 1MB) ` -Verbose @@ -3960,7 +3957,7 @@ try { $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4096 ` -Size ($script:mockedPartitionSize + 1MB) ` -AllowDestructive $true ` @@ -4023,7 +4020,7 @@ try { $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4096 ` -Verbose } | Should -Not -Throw @@ -4084,7 +4081,7 @@ try { $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4096 ` -Verbose } | Should -Not -Throw @@ -4139,7 +4136,7 @@ try { $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4096 ` -AllowDestructive $true ` -Verbose @@ -4192,7 +4189,7 @@ try { $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4096 ` -AllowDestructive $true ` -Verbose @@ -4240,7 +4237,7 @@ try { $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4097 ` -AllowDestructive $true ` -Verbose @@ -4289,7 +4286,7 @@ try { $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -FSFormat 'ReFS' ` -Verbose } | Should -Not -Throw @@ -4338,7 +4335,7 @@ try { $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -FSFormat 'ReFS' ` -AllowDestructive $true ` -Verbose @@ -4388,7 +4385,7 @@ try { $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -FSLabel 'NewLabel' ` -Verbose } | Should -Not -Throw @@ -4483,7 +4480,7 @@ try { $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4096 ` -Size $script:mockedPartition.Size ` -FSLabel $script:mockedVolume.FileSystemLabel ` @@ -4506,7 +4503,7 @@ try } } - Context 'When the Dev Drive flag is true, and Size parameter is less than minimum required size for Dev Drive (50 Gb)' { + Context 'When the DevDrive flag is true, and Size parameter is less than minimum required size for Dev Drive (50 Gb)' { # verifiable (should be called) mocks Mock ` -CommandName Get-DiskByIdentifier ` @@ -4525,7 +4522,7 @@ try { $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4096 ` -Size $script:userDesiredSize40Gb ` -FSLabel $script:mockedVolume.FileSystemLabel ` @@ -4533,7 +4530,7 @@ try -DevDrive $true ` -AllowDestructive $true ` -Verbose - } | Should -Throw -ExpectedMessage ($script:localizedCommonStrings.MinimumSizeNeededToCreateDevDriveVolumeError -F $userDesiredSizeInGb) + } | Should -Throw -ExpectedMessage ($script:localizedCommonStrings.MinimumSizeNeededToCreateDevDriveVolumeError -f $userDesiredSizeInGb) } It 'Should call the correct mocks' { @@ -4544,7 +4541,7 @@ try } } - Context 'When the Dev Drive flag is true, but the partition is relatively the same size as user inputted size and volume is NTFS' { + Context 'When the DevDrive flag is true, but the partition is effectively the same size as user inputted size and volume is NTFS' { # verifiable (should be called) mocks Mock ` -CommandName Get-DiskByIdentifier ` @@ -4568,7 +4565,7 @@ try { $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4096 ` -Size $script:userDesiredSize50Gb ` -FSLabel $script:mockedVolume.FileSystemLabel ` @@ -4580,7 +4577,7 @@ try } It 'Should be false' { - $script:result | Should -Be $false + $script:result | Should -BeFalse } It 'Should call the correct mocks' { @@ -4592,7 +4589,7 @@ try } } - Context 'When the Dev Drive flag is true, but the partition is not the same size as user inputted size, volume is ReFS formatted but not Dev Drive volume' { + Context 'When the DevDrive flag is true, but the partition is not the same size as user inputted size, volume is ReFS formatted but not Dev Drive volume' { # verifiable (should be called) mocks Mock ` -CommandName Get-DiskByIdentifier ` @@ -4626,7 +4623,7 @@ try { $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4096 ` -Size $script:userDesiredSize50Gb ` -FSLabel $script:mockedVolume.FileSystemLabel ` @@ -4638,7 +4635,7 @@ try } It 'Should be false' { - $script:result | Should -Be $false + $script:result | Should -BeFalse } It 'Should call the correct mocks' { @@ -4652,7 +4649,7 @@ try } } - Context 'When the Dev Drive flag is true, but the partition is relatively the same size as user inputted size, volume is ReFS formatted and is Dev Drive volume' { + Context 'When the DevDrive flag is true, but the partition is effectively the same size as user inputted size, volume is ReFS formatted and is Dev Drive volume' { # verifiable (should be called) mocks Mock ` -CommandName Get-DiskByIdentifier ` @@ -4685,7 +4682,7 @@ try { $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4096 ` -Size $script:userDesiredSize50Gb ` -FSLabel $script:mockedVolume.FileSystemLabel ` @@ -4697,7 +4694,7 @@ try } It 'Should be true' { - $script:result | Should -Be $true + $script:result | Should -BeTrue } It 'Should call the correct mocks' { @@ -4711,7 +4708,7 @@ try } } - Context 'When the Dev Drive flag is true, but the partition is relatively the same size as user inputted size, volume is ReFS formatted and is not Dev Drive volume' { + Context 'When the DevDrive flag is true, but the partition is effectively the same size as user inputted size, volume is ReFS formatted and is not Dev Drive volume' { # verifiable (should be called) mocks Mock ` -CommandName Get-DiskByIdentifier ` @@ -4744,7 +4741,7 @@ try { $script:result = Test-TargetResource ` -DiskId $script:mockedDisk0Gpt.Number ` - -DriveLetter $script:testDriveLetter ` + -DriveLetter $script:testDriveLetterG ` -AllocationUnitSize 4096 ` -Size $script:userDesiredSize50Gb ` -FSLabel $script:mockedVolume.FileSystemLabel ` @@ -4756,7 +4753,7 @@ try } It 'Should be false' { - $script:result | Should -Be $false + $script:result | Should -BeFalse } It 'Should call the correct mocks' { diff --git a/tests/Unit/StorageDsc.Common.Tests.ps1 b/tests/Unit/StorageDsc.Common.Tests.ps1 index e377d728..bfe959b5 100644 --- a/tests/Unit/StorageDsc.Common.Tests.ps1 +++ b/tests/Unit/StorageDsc.Common.Tests.ps1 @@ -34,6 +34,12 @@ InModuleScope $script:subModuleName { CurrentDiskFreeSpace40Gb = 40Gb CurrentDiskFreeSpace50Gb = 50Gb CurrentDiskFreeSpace60Gb = 60Gb + + # Alternate value in bytes that can represent a 150 Gb partition in a physical hard disk that has been formatted by Windows. + SizeAEffectively150GbInBytes = 161060225024 + + SizeB150GbInBytes = 150Gb + SizeBInBytesFor49Point99Scenario = 49.99Gb } $script:mockedDiskNumber = 1 @@ -713,7 +719,7 @@ InModuleScope $script:subModuleName { Assert-SizeMeetsMinimumDevDriveRequirement ` -UserDesiredSize $mockedSizesForDevDriveScenario.UserDesired10Gb ` -Verbose - } | Should -Throw -ExpectedMessage ($script:localizedCommonStrings.MinimumSizeNeededToCreateDevDriveVolumeError -F $UserDesiredSizeInGb) + } | Should -Throw -ExpectedMessage ($script:localizedCommonStrings.MinimumSizeNeededToCreateDevDriveVolumeError -f $UserDesiredSizeInGb) } } @@ -769,4 +775,39 @@ InModuleScope $script:subModuleName { } } } + + Describe 'StorageDsc.Common\Compare-SizeUsingGB' -Tag 'Compare-SizeUsingGB' { + Context 'When comparing whether SizeAInBytes is equal to SizeBInBytes when SizeAInBytes is effectively equal to SizeBInBytes when both are converted to Gb' { + + $result = Compare-SizeUsingGB ` + -SizeAInBytes $mockedSizesForDevDriveScenario.SizeB150GbInBytes ` + -SizeBInBytes $mockedSizesForDevDriveScenario.SizeAEffectively150GbInBytes + + It 'Should return true' { + $result | Should -BeTrue + } + } + + Context 'When comparing whether SizeAInBytes is equal to SizeBInBytes when SizeAInBytes is equal to SizeBInBytes when both are converted to Gb' { + + $result = Compare-SizeUsingGB ` + -SizeAInBytes $mockedSizesForDevDriveScenario.UserDesired50Gb ` + -SizeBInBytes $mockedSizesForDevDriveScenario.CurrentDiskFreeSpace50Gb + + It 'Should return true' { + $result | Should -BeTrue + } + } + + Context 'When comparing whether SizeAInBytes is equal to SizeBInBytes when SizeAInBytes is not equal to SizeBInBytes when both are converted to Gb' { + + $result = Compare-SizeUsingGB ` + -SizeAInBytes $mockedSizesForDevDriveScenario.UserDesired50Gb ` + -SizeBInBytes $mockedSizesForDevDriveScenario.SizeBInBytesFor49Point99Scenario + + It 'Should return false' { + $result | Should -BeFalse + } + } + } }