From 6362e97b75e1bbf6909281b4f5fccd74010e57d4 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Sun, 17 Sep 2023 20:36:09 -0700 Subject: [PATCH 01/21] Add initial changes to add a Vhd resource and light up DSC_Disk to allow volumes to be formatted as Dev Drives --- CHANGELOG.md | 2 + .../DSC_VirtualHardDisk.psm1 | 261 ++++++++++ .../DSC_VirtualHardDisk.schema.mof | 10 + .../DSC_VirtualHardDisk/README.md | 3 + .../DSC_VirtualHardDisk/Win32Helpers.psm1 | 477 ++++++++++++++++++ .../en-US/DSC_VirtualHardDisk.strings.psd1 | 10 + .../en-US/Win32Helpers.strings.psd1 | 12 + .../StorageDsc.Common/StorageDsc.Common.psm1 | 34 +- source/StorageDsc.psd1 | 5 +- 9 files changed, 811 insertions(+), 3 deletions(-) create mode 100644 source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 create mode 100644 source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof create mode 100644 source/DSCResources/DSC_VirtualHardDisk/README.md create mode 100644 source/DSCResources/DSC_VirtualHardDisk/Win32Helpers.psm1 create mode 100644 source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 create mode 100644 source/DSCResources/DSC_VirtualHardDisk/en-US/Win32Helpers.strings.psd1 diff --git a/CHANGELOG.md b/CHANGELOG.md index be4be481..050d357b 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] +- Added DSC_VirtualHardDisk resource for creating virtual disks and tests. + ## [5.1.0] - 2023-02-22 ### Changed diff --git a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 new file mode 100644 index 00000000..2d957441 --- /dev/null +++ b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 @@ -0,0 +1,261 @@ +$modulePath = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'Modules' + +# Import the Storage Common Module. +Import-Module -Name (Join-Path -Path $modulePath ` + -ChildPath (Join-Path -Path 'StorageDsc.Common' ` + -ChildPath 'StorageDsc.Common.psm1')) + +Import-Module -Name (Join-Path -Path $modulePath -ChildPath 'DscResource.Common') +Import-Module $PSScriptRoot\\Win32Helpers.psm1 + +# Import Localization Strings. +$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' + +<# + .SYNOPSIS + Returns the current state of the virtual disk. + + .PARAMETER FolderPath + Specifies the path to the folder the virtual disk is located in. + + .PARAMETER FileName + Specifies the file name of the virtual disk. + + .PARAMETER DiskSize + Specifies the size of new virtual disk. + + .PARAMETER DiskFormat + Specifies the supported virtual disk format. + + .PARAMETER DiskType + Specifies the supported virtual disk type. + +#> +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [String] + $FolderPath, + + [Parameter(Mandatory = $true)] + [String] + $FileName, + + [Parameter(Mandatory = $true)] + [System.UInt64] + $DiskSize, + + [Parameter()] + [ValidateSet('vhd', 'vhdx')] + [System.String] + $DiskFormat = 'vhdx', + + [Parameter()] + [ValidateSet('fixed', 'dynamic')] + [System.String] + $DiskType = 'dynamic' + ) + + $virtDiskPath = $($FolderPath + $FileName + "." + $DiskFormat) + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.GettingVirtualDiskMessage -f $virtDiskPath) + ) -join '' ) + + # Validate DiskFormat values. Minimum value for GPT is around ~10MB and the maximum value for + # the vhd format in 2040GB. Maximum for vhdx is 64TB + $isVhdxFormat = $DiskFormat -eq 'vhdx' + if (( -not $isVhdxFormat -and ($DiskSize -lt 10MB -bor $DiskSize -gt 2040GB)) -bor + ($IsVhdxFormat -and ($DiskSize -lt 10MB -bor $DiskSize -gt 64TB))) + { + $DiskSizeString = ConvertFrom-Bytes $DiskSize + $InvalidSizeMsg = ($isVhdxFormat) ? + $script:localizedData.VhdxFormatDiskSizeInvalidMessage : + $script:localizedData.VhdFormatDiskSizeInvalidMessage + + New-InvalidOperationException ` + -Message $($InvalidSizeMsg -f $DiskSizeString) + } + + # Get the virtual disk using its location on the system + return @{ + FolderPath = $FolderPath + FileName = $FileName + DiskSize = $DiskSize + DiskFormat = $DiskFormat + DiskType = $DiskType + } +} # function Get-TargetResource + +<# + .SYNOPSIS + Returns the current state of the virtual disk. + + .PARAMETER FolderPath + Specifies the path to the folder the virtual disk is located in. + + .PARAMETER FileName + Specifies the file name of the virtual disk. + + .PARAMETER DiskSize + Specifies the size of new virtual disk. + + .PARAMETER DiskFormat + Specifies the supported virtual disk format. + + .PARAMETER DiskType + Specifies the supported virtual disk type. + +#> +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [String] + $FolderPath, + + [Parameter(Mandatory = $true)] + [String] + $FileName, + + [Parameter(Mandatory = $true)] + [System.UInt64] + $DiskSize, + + [Parameter()] + [ValidateSet('vhd', 'vhdx')] + [System.String] + $DiskFormat = 'vhdx', + + [Parameter()] + [ValidateSet('fixed', 'dynamic')] + [System.String] + $DiskType = 'dynamic' + ) + + # Validate the FolderPath parameter + $FolderPath = Assert-AccessPathValid $FolderPath + $fullPathToVirtualDisk = $FolderPath + $FileName + "." + $DiskFormat + + # Create and attach virtual disk if it doesn't exist + $virtualDiskFileExists = Test-Path -Path $fullPathToVirtualDisk -PathType Leaf + if (-not $virtualDiskFileExists) + { + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.VirtualDiskDoesNotExistCreatingNowMessage -f $fullPathToVirtualDisk) + ) -join '' ) + + New-SimpleVirtualDisk -VirtualDiskPath $fullPathToVirtualDisk -DiskFormat $DiskFormat -DiskType $DiskType -DiskSizeInBytes $DiskSize + } + else + { + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.VirtualDiskNotAttachedMessage -f $fullPathToVirtualDisk) + ) -join '' ) + + # Virtual disk file exists so lets attempt to attach it to the system. + Add-SimpleVirtualDisk -VirtualDiskPath $fullPathToVirtualDisk -DiskFormat $DiskFormat + } + + +} # function Set-TargetResource + +<# + .SYNOPSIS + Returns the current state of the virtual disk. + .PARAMETER FolderPath + Specifies the path to the folder the virtual disk is located in. + + .PARAMETER FileName + Specifies the file name of the virtual disk. + + .PARAMETER DiskSize + Specifies the size of new virtual disk. + + .PARAMETER DiskFormat + Specifies the supported virtual disk format. + + .PARAMETER DiskType + Specifies the supported virtual disk type. + +#> +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [String] + $FolderPath, + + [Parameter(Mandatory = $true)] + [String] + $FileName, + + [Parameter(Mandatory = $true)] + [System.UInt64] + $DiskSize, + + [Parameter()] + [ValidateSet('vhd', 'vhdx')] + [System.String] + $DiskFormat = 'vhdx', + + [Parameter()] + [ValidateSet('fixed', 'dynamic')] + [System.String] + $DiskType = 'dynamic' + ) + # Validate the FolderPath parameter + $FolderPath = Assert-AccessPathValid $FolderPath + $fullPathToVirtualDisk = $FolderPath + $FileName + "." + $DiskFormat + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.CheckingVirtualDiskExistsMessage -f $fullPathToVirtualDisk) + ) -join '' ) + + #Check if virtual file exists + if (-not (Test-Path -Path $fullPathToVirtualDisk -PathType Leaf)) + { + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.VirtualDiskDoesNotExistMessage -f $fullPathToVirtualDisk) + ) -join '' ) + + return $false + } + + $virtdisk = Get-DiskByIdentifier ` + -DiskId $fullPathToVirtualDisk ` + -DiskIdType 'Location' + + # Found the virtual disk and confirmed its attached to the system. + if ($virtdisk) + { + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.VirtualDiskCurrentlyAttachedMessage -f $fullPathToVirtualDisk) + ) -join '' ) + + return $true + } + + # Either the virtual disk is not attached or the file above exists but is corrupted or wasn't created properly. + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.VirtualDiskNotAttachedOrFileCorruptedMessage -f $fullPathToVirtualDisk) + ) -join '' ) + + return $false +} # function Test-TargetResource + +Export-ModuleMember -Function *-TargetResource diff --git a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof new file mode 100644 index 00000000..d4d03f8d --- /dev/null +++ b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof @@ -0,0 +1,10 @@ + +[ClassVersion("1.0.0.0"), FriendlyName("VirtualHardDisk")] +class DSC_VirtualHardDisk : OMI_BaseResource +{ + [Key, Description("Specifies the folder path where the virtual disk will be created or attached")] String FolderPath; + [Required, Description("Specifies the file name of the virtual disk.")] String FileName; + [Write, Description("Specifies the size of virtual disk.")] Uint64 DiskSize; + [Write, Description("Specifies the disk format the virtual disk file should use. Defaults to vhdx"), ValueMap{"vhd","vhdx"}, Values{"vhd","vhdx"}] String DiskFormat; + [Write, Description("Specifies the disk type the virtual disk should use. Defaults to dynamic"), ValueMap{"Fixed","Dynamic"}, Values{"Fixed","Dynamic"}] String DiskType; +}; diff --git a/source/DSCResources/DSC_VirtualHardDisk/README.md b/source/DSCResources/DSC_VirtualHardDisk/README.md new file mode 100644 index 00000000..d523f4fc --- /dev/null +++ b/source/DSCResources/DSC_VirtualHardDisk/README.md @@ -0,0 +1,3 @@ +# Description + +The resource is used to create a virtual disk and attach it to the system. diff --git a/source/DSCResources/DSC_VirtualHardDisk/Win32Helpers.psm1 b/source/DSCResources/DSC_VirtualHardDisk/Win32Helpers.psm1 new file mode 100644 index 00000000..7c50e6fb --- /dev/null +++ b/source/DSCResources/DSC_VirtualHardDisk/Win32Helpers.psm1 @@ -0,0 +1,477 @@ +$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') + +# Import Localization Strings +$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' + +# Import virtdisk.dll and define structures and constants +Add-Type -TypeDefinition @' + using System; + using System.Runtime.InteropServices; + + namespace Win32 + { + namespace VirtDisk + { + // Define structures and constants for creating a virtual disk. + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ne-virtdisk-create_virtual_disk_version + public enum CREATE_VIRTUAL_DISK_VERSION + { + CREATE_VIRTUAL_DISK_VERSION_UNSPECIFIED = 0, + CREATE_VIRTUAL_DISK_VERSION_1 = 1, + CREATE_VIRTUAL_DISK_VERSION_2 = 2, + } + + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ns-virtdisk-virtual_storage_type + [StructLayout(LayoutKind.Sequential)] + public struct VIRTUAL_STORAGE_TYPE + { + public UInt32 DeviceId; + public Guid VendorId; + } + + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ns-virtdisk-create_virtual_disk_parameters + [StructLayout(LayoutKind.Sequential)] + public struct CREATE_VIRTUAL_DISK_PARAMETERS + { + public CREATE_VIRTUAL_DISK_VERSION Version; + public Guid UniqueId; + public UInt64 MaximumSize; + public UInt32 BlockSizeInBytes; + public UInt32 SectorSizeInBytes; + [MarshalAs(UnmanagedType.LPWStr)] + public string ParentPath; + [MarshalAs(UnmanagedType.LPWStr)] + public string SourcePath; + } + + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ne-virtdisk-virtual_disk_access_mask-r1 + public enum VIRTUAL_DISK_ACCESS_MASK + { + VIRTUAL_DISK_ACCESS_NONE = 0, + VIRTUAL_DISK_ACCESS_ATTACH_RO = 0x00010000, + VIRTUAL_DISK_ACCESS_ATTACH_RW = 0x00020000, + VIRTUAL_DISK_ACCESS_DETACH = 0x00040000, + VIRTUAL_DISK_ACCESS_GET_INFO = 0x00080000, + VIRTUAL_DISK_ACCESS_CREATE = 0x00100000, + VIRTUAL_DISK_ACCESS_METAOPS = 0x00200000, + VIRTUAL_DISK_ACCESS_READ = 0x000d0000, + VIRTUAL_DISK_ACCESS_ALL = 0x003f0000, + VIRTUAL_DISK_ACCESS_WRITABLE = 0x00320000 + } + + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ne-virtdisk-create_virtual_disk_flag + public enum CREATE_VIRTUAL_DISK_FLAG + { + CREATE_VIRTUAL_DISK_FLAG_NONE = 0x0, + CREATE_VIRTUAL_DISK_FLAG_FULL_PHYSICAL_ALLOCATION = 0x1, + } + + // Define structures and constants for attaching a virtual disk. + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ne-virtdisk-attach_virtual_disk_flag + public enum ATTACH_VIRTUAL_DISK_FLAG + { + ATTACH_VIRTUAL_DISK_FLAG_NONE = 0x00000000, + ATTACH_VIRTUAL_DISK_FLAG_READ_ONLY = 0x00000001, + ATTACH_VIRTUAL_DISK_FLAG_NO_DRIVE_LETTER = 0x00000002, + ATTACH_VIRTUAL_DISK_FLAG_PERMANENT_LIFETIME = 0x00000004, + ATTACH_VIRTUAL_DISK_FLAG_NO_LOCAL_HOST = 0x00000008, + ATTACH_VIRTUAL_DISK_FLAG_NO_SECURITY_DESCRIPTOR = 0x00000010, + ATTACH_VIRTUAL_DISK_FLAG_BYPASS_DEFAULT_ENCRYPTION_POLICY = 0x00000020, + ATTACH_VIRTUAL_DISK_FLAG_NON_PNP = 0x00000040, + ATTACH_VIRTUAL_DISK_FLAG_RESTRICTED_RANGE = 0x00000080, + ATTACH_VIRTUAL_DISK_FLAG_SINGLE_PARTITION = 0x00000100, + ATTACH_VIRTUAL_DISK_FLAG_REGISTER_VOLUME = 0x00000200, + ATTACH_VIRTUAL_DISK_FLAG_AT_BOOT = 0x00000400, + } + + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ne-virtdisk-attach_virtual_disk_version + public enum ATTACH_VIRTUAL_DISK_VERSION + { + ATTACH_VIRTUAL_DISK_VERSION_UNSPECIFIED = 0, + ATTACH_VIRTUAL_DISK_VERSION_1 = 1, + } + + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ns-virtdisk-attach_virtual_disk_parameters + [StructLayout(LayoutKind.Sequential)] + public struct ATTACH_VIRTUAL_DISK_PARAMETERS + { + public ATTACH_VIRTUAL_DISK_VERSION Version; + public UInt32 Reserved; + } + + // Define structures and constants for opening a virtual disk. + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ne-virtdisk-open_virtual_disk_version + public enum OPEN_VIRTUAL_DISK_VERSION + { + OPEN_VIRTUAL_DISK_VERSION_UNSPECIFIED = 0, + OPEN_VIRTUAL_DISK_VERSION_1 = 1, + OPEN_VIRTUAL_DISK_VERSION_2 = 2, + } + + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ns-virtdisk-open_virtual_disk_parameters + [StructLayout(LayoutKind.Sequential)] + public struct OPEN_VIRTUAL_DISK_PARAMETERS + { + public OPEN_VIRTUAL_DISK_VERSION Version; + public UInt32 RWDepth; + } + + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ne-virtdisk-open_virtual_disk_flag + public enum OPEN_VIRTUAL_DISK_FLAG + { + OPEN_VIRTUAL_DISK_FLAG_NONE = 0x0, + OPEN_VIRTUAL_DISK_FLAG_NO_PARENTS = 0x1, + OPEN_VIRTUAL_DISK_FLAG_BLANK_FILE = 0x2, + OPEN_VIRTUAL_DISK_FLAG_BOOT_DRIVE = 0x4, + } + + public class VirtDiskHelper + { + // Constants found in virtdisk.h + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ns-virtdisk-virtual_storage_type + public static uint VIRTUAL_STORAGE_TYPE_DEVICE_VHD = 2U; + public static uint VIRTUAL_STORAGE_TYPE_DEVICE_VHDX = 3U; + public static Guid VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT = new Guid(0xEC984AEC, 0xA0F9, 0x47E9, 0x90, 0x1F, 0x71, 0x41, 0x5A, 0x66, 0x34, 0x5B); + + // Declare method to create a virtual disk + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/nf-virtdisk-createvirtualdisk + [DllImport("virtdisk.dll", CharSet = CharSet.Unicode)] + public static extern Int32 CreateVirtualDisk( + ref VIRTUAL_STORAGE_TYPE VirtualStorageType, + string Path, + VIRTUAL_DISK_ACCESS_MASK VirtualDiskAccessMask, + IntPtr SecurityDescriptor, + CREATE_VIRTUAL_DISK_FLAG Flags, + UInt32 ProviderSpecificFlags, + ref CREATE_VIRTUAL_DISK_PARAMETERS Parameters, + IntPtr Overlapped, + ref IntPtr Handle + ); + + // Declare method to attach a virtual disk + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/nf-virtdisk-attachvirtualdisk + [DllImport("virtdisk.dll", CharSet = CharSet.Unicode)] + public static extern Int32 AttachVirtualDisk( + IntPtr VirtualDiskHandle, + IntPtr SecurityDescriptor, + ATTACH_VIRTUAL_DISK_FLAG Flags, + UInt32 ProviderSpecificFlags, + ref ATTACH_VIRTUAL_DISK_PARAMETERS Parameters, + IntPtr Overlapped + ); + + // Declare function to open a handle to a virtual disk + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/nf-virtdisk-openvirtualdisk + [DllImport("virtdisk.dll", CharSet = CharSet.Unicode)] + public static extern Int32 OpenVirtualDisk( + ref VIRTUAL_STORAGE_TYPE VirtualStorageType, + string Path, + VIRTUAL_DISK_ACCESS_MASK VirtualDiskAccessMask, + OPEN_VIRTUAL_DISK_FLAG Flags, + ref OPEN_VIRTUAL_DISK_PARAMETERS Parameters, + ref IntPtr Handle + ); + } + } + + public class Kernel32 + { + // https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CloseHandle(IntPtr hObject); + } + } + +'@ + +<# + .SYNOPSIS + Creates and attaches a virtual disk to the system. + + .PARAMETER VirtualDiskPath + Specifies the whole path to the virtual disk including the file name. + + .PARAMETER DiskFormat + Specifies the supported virtual disk format. + + .PARAMETER DiskType + Specifies the supported virtual disk type. + + .PARAMETER DiskSizeInBytes + Specifies the size of new virtual disk in bytes. +#> +function New-SimpleVirtualDisk +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $VirtualDiskPath, + + [Parameter(Mandatory = $true)] + [System.UInt64] + $DiskSizeInBytes, + + [Parameter(Mandatory = $true)] + [ValidateSet('vhd', 'vhdx')] + [System.String] + $DiskFormat, + + [Parameter(Mandatory = $true)] + [ValidateSet('fixed', 'dynamic')] + [System.String] + $DiskType + ) + try + { + Write-Verbose -Message ($script:localizedData.CreatingVirtualDiskMessage -f $VirtualDiskPath) + $vDiskHelper = New-Object Win32.VirtDisk.VirtDiskHelper + + # Get parameters for CreateVirtualDisk function + $virtualStorageType = Get-VirtualStorageType -DiskFormat $DiskFormat + $createVirtualDiskParameters = New-Object Win32.VirtDisk.CREATE_VIRTUAL_DISK_PARAMETERS + $createVirtualDiskParameters.Version = [Win32.VirtDisk.CREATE_VIRTUAL_DISK_VERSION]::CREATE_VIRTUAL_DISK_VERSION_2 + $createVirtualDiskParameters.MaximumSize = $DiskSizeInBytes + $securityDescriptor = [System.IntPtr]::Zero + $accessMask = [Win32.VirtDisk.VIRTUAL_DISK_ACCESS_MASK]::VIRTUAL_DISK_ACCESS_NONE # Access mask + $providerSpecificFlags = 0 # No Provider-specific flags. + $handle = [System.IntPtr]::Zero # Handle to the new virtual disk + + # Virtual disk will be dynamically expanding, up to the size of $DiskSizeInBytes on the parent disk + $flags = [Win32.VirtDisk.CREATE_VIRTUAL_DISK_FLAG]::CREATE_VIRTUAL_DISK_FLAG_NONE + if ($DiskType -eq 'fixed') + { + # Virtual disk will be fixed, and will take up the up the full size of $DiskSizeInBytes on the parent disk after creation + $flags = [Win32.VirtDisk.CREATE_VIRTUAL_DISK_FLAG]::CREATE_VIRTUAL_DISK_FLAG_FULL_PHYSICAL_ALLOCATION + } + + $result = $vDiskHelper::CreateVirtualDisk( + [ref]$virtualStorageType, + $VirtualDiskPath, + $accessMask, + $securityDescriptor, + $flags, + $providerSpecificFlags, + [ref]$createVirtualDiskParameters, + [System.IntPtr]::Zero, + [ref]$handle) + + if ($result -ne 0) + { + Write-Error -Message ($script:localizedData.CreateVirtualDiskError -f $result) + throw [System.ComponentModel.Win32Exception]::new($result); + } + + Write-Verbose -Message ($script:localizedData.VirtualDiskCreatedSuccessfully -f $VirtualDiskPath) + Add-SimpleVirtualDisk -VirtualDiskPath $VirtualDiskPath -DiskFormat $DiskFormat -Handle $handle + } + catch + { + # Remove file if we created it but were unable to attach it. No handles are open when this happens. + if (Test-Path -Path $VirtualDiskPath -PathType Leaf) + { + Write-Verbose -Message ($script:localizedData.VirtualRemovingCreatedFileMessage -f $VirtualDiskPath) + Remove-Item $VirtualDiskPath -verbose + } + + throw + } + finally + { + # Close handle + $null = [Win32.Kernel32]::CloseHandle($handle) + } +} # function New-VirtualDisk + +<# + .SYNOPSIS + Attaches a virtual disk to the system. + + .PARAMETER VirtualDiskPath + Specifies the whole path to the virtual disk including the file name. + + .PARAMETER DiskFormat + Specifies the supported virtual disk format. + + .PARAMETER Handle + Specifies a reference to a win32 handle that points to a virtual disk +#> +function Add-SimpleVirtualDisk +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $VirtualDiskPath, + + [Parameter(Mandatory = $true)] + [ValidateSet('vhd', 'vhdx')] + [System.String] + $DiskFormat, + + [Parameter()] + [System.IntPtr] + $Handle = [System.IntPtr]::Zero + ) + try + { + Write-Verbose -Message ($script:localizedData.AttachingVirtualDiskMessage -f $VirtualDiskPath) + $vDiskHelper = New-Object Win32.VirtDisk.VirtDiskHelper + + # No handle passed in so we need to open the virtual disk first using $virtualDiskPath to get the handle. + if ($Handle -eq [System.IntPtr]::Zero) + { + $Handle = Get-VirtualDiskHandle -VirtualDiskPath $VirtualDiskPath -DiskFormat $DiskFormat + } + + # Build parameters for AttachVirtualDisk function + $attachVirtualDiskParameters = New-Object Win32.VirtDisk.ATTACH_VIRTUAL_DISK_PARAMETERS + $attachVirtualDiskParameters.Version = [Win32.VirtDisk.ATTACH_VIRTUAL_DISK_VERSION]::ATTACH_VIRTUAL_DISK_VERSION_1 + $securityDescriptor = [System.IntPtr]::Zero # Security descriptor + $providerSpecificFlags = 0 # No Provider-specific flag + $result = 0 + + # Some builds of Windows may not have the ATTACH_VIRTUAL_DISK_FLAG_AT_BOOT flag. So we attempt to attach the virtual + # disk with the flag first. If this fails we attach the virtual disk without the flag. The flag allows the + # virtual disk to be attached by the system at boot time. + for ($attempts = 0; $attempts -lt 2; $attempts++) + { + if ($attempts -eq 0) + { + $flags = [Win32.VirtDisk.ATTACH_VIRTUAL_DISK_FLAG]::ATTACH_VIRTUAL_DISK_FLAG_PERMANENT_LIFETIME -bor + [Win32.VirtDisk.ATTACH_VIRTUAL_DISK_FLAG]::ATTACH_VIRTUAL_DISK_FLAG_AT_BOOT + } + else + { + $flags = [Win32.VirtDisk.ATTACH_VIRTUAL_DISK_FLAG]::ATTACH_VIRTUAL_DISK_FLAG_PERMANENT_LIFETIME + } + + $result = $vDiskHelper::AttachVirtualDisk( + $Handle, + $securityDescriptor, + $flags, + $providerSpecificFlags, + [ref]$attachVirtualDiskParameters, + [System.IntPtr]::Zero) + + if ($result -eq 0) + { + break + } + } + + if ($result -ne 0) + { + Write-Error -Message ($script:localizedData.AttachVirtualDiskError -f $result) + throw [System.ComponentModel.Win32Exception]::new($result); + } + + Write-Verbose -Message ($script:localizedData.VirtualDiskAttachedSuccessfully -f $VirtualDiskPath) + } + finally + { + # Close handle + $null = [Win32.Kernel32]::CloseHandle($Handle) + } + +} # function Add-SimpleVirtualDisk + +<# + .SYNOPSIS + Opens a handle to a virtual disk on the system. + + .PARAMETER VirtualDiskPath + Specifies the whole path to the virtual disk including the file name. + + .PARAMETER DiskFormat + Specifies the supported virtual disk format. +#> +function Get-VirtualDiskHandle +{ + [CmdletBinding()] + [OutputType([System.IntPtr])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $VirtualDiskPath, + + [Parameter(Mandatory = $true)] + [ValidateSet('vhd', 'vhdx')] + [System.String] + $DiskFormat + ) + + Write-Verbose -Message ($script:localizedData.OpeningVirtualBeforeAttachingMessage) + $vDiskHelper = New-Object Win32.VirtDisk.VirtDiskHelper + + # Get parameters for OpenVirtualDisk function + $virtualStorageType = Get-VirtualStorageType -DiskFormat $DiskFormat + $openVirtualDiskParameters = New-Object Win32.VirtDisk.OPEN_VIRTUAL_DISK_PARAMETERS + $openVirtualDiskParameters.Version = [Win32.VirtDisk.OPEN_VIRTUAL_DISK_VERSION]::OPEN_VIRTUAL_DISK_VERSION_1 + $accessMask = [Win32.VirtDisk.VIRTUAL_DISK_ACCESS_MASK]::VIRTUAL_DISK_ACCESS_ALL + $flags = [Win32.VirtDisk.OPEN_VIRTUAL_DISK_FLAG]::OPEN_VIRTUAL_DISK_FLAG_NONE + $handle = [System.IntPtr]::Zero + + $result = $vDiskHelper::OpenVirtualDisk( + [ref]$virtualStorageType, + $VirtualDiskPath, + $accessMask, + $flags, + [ref]$openVirtualDiskParameters, + [ref]$handle) + + if ($result -ne 0) + { + Write-Error -Message ($script:localizedData.OpenVirtualDiskError -f $result) + throw [System.ComponentModel.Win32Exception]::new($result); + } + + Write-Verbose -Message ($script:localizedData.VirtualDiskOpenedSuccessfully -f $VirtualDiskPath) + + return $handle +} # function Get-VirtualDiskHandle + +<# + .SYNOPSIS + Gets the storage type based on the disk format. + + .PARAMETER DiskFormat + Specifies the supported virtual disk format. +#> +function Get-VirtualStorageType +{ + [CmdletBinding()] + [OutputType([Win32.VirtDisk.VIRTUAL_STORAGE_TYPE])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateSet('vhd', 'vhdx')] + [System.String] + $DiskFormat + ) + + # Create VIRTUAL_STORAGE_TYPE structure + $virtualStorageType = New-Object Win32.VirtDisk.VIRTUAL_STORAGE_TYPE + + # Default to the vhdx file format. + $virtualStorageType.VendorId = [Win32.VirtDisk.VirtDiskHelper]::VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT + $virtualStorageType.DeviceId = [Win32.VirtDisk.VirtDiskHelper]::VIRTUAL_STORAGE_TYPE_DEVICE_VHDX + if ($DiskFormat -eq 'vhd') + { + $virtualStorageType.VendorId = [Win32.VirtDisk.VirtDiskHelper]::VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT + $virtualStorageType.DeviceId = [Win32.VirtDisk.VirtDiskHelper]::VIRTUAL_STORAGE_TYPE_DEVICE_VHD + } + + return $virtualStorageType +} # function Get-VirtualStorageType + +Export-ModuleMember -Function @( + 'New-SimpleVirtualDisk', + 'Add-SimpleVirtualDisk' +) diff --git a/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 b/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 new file mode 100644 index 00000000..bb93d0b4 --- /dev/null +++ b/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 @@ -0,0 +1,10 @@ +ConvertFrom-StringData @' + CheckingVirtualDiskExistsMessage = Checking virtual disk at location '{0}' exists and is attached. + VirtualDiskDoesNotExistMessage = The virtual disk at location '{0}' does not exist. + VirtualDiskDoesNotExistCreatingNowMessage = The virtual disk at location '{0}' does not exist. Creating virtual disk now. + VirtualDiskNotAttachedOrFileCorruptedMessage = The virtual disk at location '{0}' is not attached to the system. The file exists but maybe corrupted. + VirtualDiskCurrentlyAttachedMessage = The virtual disk at location '{0}' was found and is attached to the system. + VhdFormatDiskSizeInvalidMessage = The virtual disk size '{0}' was invalid for the 'vhd' format. Min supported value is 10Mb and max supported value 2040Gb. + VhdxFormatDiskSizeInvalidMessage = The virtual disk size '{0}' was invalid for the 'vhdx' format. Min supported value is 10Mb and max supported value 64Tb. + VirtualDiskNotAttachedMessage = The virtual disk at location '{0}' is not attached. Attaching virtual disk now. +'@ diff --git a/source/DSCResources/DSC_VirtualHardDisk/en-US/Win32Helpers.strings.psd1 b/source/DSCResources/DSC_VirtualHardDisk/en-US/Win32Helpers.strings.psd1 new file mode 100644 index 00000000..9a351abc --- /dev/null +++ b/source/DSCResources/DSC_VirtualHardDisk/en-US/Win32Helpers.strings.psd1 @@ -0,0 +1,12 @@ +ConvertFrom-StringData @' + CreatingVirtualDiskMessage = Creating virtual disk at location '{0}'. + CreateVirtualDiskError = Unable to create virtual disk due to error '{0}'. + VirtualDiskCreatedSuccessfully = Virtual disk created successfully at location: '{0}'. + AttachingVirtualDiskMessage = Attaching virtual disk at location '{0}'. + AttachVirtualDiskError = Unable to attach virtual disk due to error '{0}'. + VirtualDiskAttachedSuccessfully = Virtual disk attached successfully at location '{0}'. + OpeningVirtualBeforeAttachingMessage = Attempting to open handle to virtual disk at location '{0}. + OpenVirtualDiskError = Unable to open virtual disk handle due to error '{0}'. + VirtualDiskOpenedSuccessfully = Virtual disk handle for location '{0}' opened successfully. + VirtualRemovingCreatedFileMessage = The virtual disk file at location '{0}' is being removed due to an error while attempting to attach it to the system. +'@ diff --git a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 index 87eb8c64..92e54d2e 100644 --- a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 +++ b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 @@ -222,10 +222,42 @@ function Test-AccessPathAssignedToLocal return $accessPathAssigned } # end function Test-AccessPathLocal +<# + .SYNOPSIS + Converts numeric representation of bytes e.g 10737418240 to its string representation + 10gb. Only Mb, Gb and Tb are supported. + + .PARAMETER Size + Size in bytes +#> +function ConvertFrom-Bytes +{ + [CmdletBinding()] + [OutputType([System.String])] + param + ( + [Parameter(Mandatory = $true)] + [System.UInt64] + $Size + ) + + if ($Size -lt 1GB) + { + return ($Size / 1MB).ToString("0.00MB") + } + elseif ($Size -lt 1TB) + { + return ($Size / 1GB).ToString("0.00GB") + } + + return ($Size / 1TB).ToString("0.00TB") +} # end function ConvertFrom-Bytes + Export-ModuleMember -Function @( 'Restart-ServiceIfExists', 'Assert-DriveLetterValid', 'Assert-AccessPathValid', 'Get-DiskByIdentifier', - 'Test-AccessPathAssignedToLocal' + 'Test-AccessPathAssignedToLocal', + 'ConvertFrom-Bytes' ) diff --git a/source/StorageDsc.psd1 b/source/StorageDsc.psd1 index 2b9632f2..3553673d 100644 --- a/source/StorageDsc.psd1 +++ b/source/StorageDsc.psd1 @@ -42,7 +42,8 @@ 'OpticalDiskDriveLetter', 'WaitForDisk', 'WaitForVolume', - 'Disk' + 'Disk', + 'VirtualHardDisk' ) # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. @@ -52,7 +53,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', 'VirtualHardDisk') # A URL to the license for this module. LicenseUri = 'https://github.com/dsccommunity/StorageDsc/blob/main/LICENSE' From 7b40e0cda6459d841278afb7525a36f8e9339f05 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Tue, 19 Sep 2023 17:01:56 -0700 Subject: [PATCH 02/21] update comments and add parameter validation to string parameters --- .../DSC_VirtualHardDisk.psm1 | 22 ++++++++++++++----- .../DSC_VirtualHardDisk/Win32Helpers.psm1 | 8 ++++--- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 index 2d957441..d5e29f6d 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 +++ b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 @@ -38,10 +38,12 @@ function Get-TargetResource param ( [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [String] $FolderPath, [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [String] $FileName, @@ -60,14 +62,17 @@ function Get-TargetResource $DiskType = 'dynamic' ) + $FolderPath = Assert-AccessPathValid -AccessPath $FolderPath -Slash $virtDiskPath = $($FolderPath + $FileName + "." + $DiskFormat) Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " $($script:localizedData.GettingVirtualDiskMessage -f $virtDiskPath) ) -join '' ) - # Validate DiskFormat values. Minimum value for GPT is around ~10MB and the maximum value for - # the vhd format in 2040GB. Maximum for vhdx is 64TB + <# + Validate DiskFormat values. Minimum value for GPT is around ~10MB and the maximum value for + the vhd format in 2040GB. Maximum for vhdx is 64TB + #> $isVhdxFormat = $DiskFormat -eq 'vhdx' if (( -not $isVhdxFormat -and ($DiskSize -lt 10MB -bor $DiskSize -gt 2040GB)) -bor ($IsVhdxFormat -and ($DiskSize -lt 10MB -bor $DiskSize -gt 64TB))) @@ -77,8 +82,9 @@ function Get-TargetResource $script:localizedData.VhdxFormatDiskSizeInvalidMessage : $script:localizedData.VhdFormatDiskSizeInvalidMessage - New-InvalidOperationException ` - -Message $($InvalidSizeMsg -f $DiskSizeString) + New-InvalidArgumentException ` + -Message $($InvalidSizeMsg -f $DiskSizeString) ` + -ArgumentName 'DiskSize' } # Get the virtual disk using its location on the system @@ -117,10 +123,12 @@ function Set-TargetResource param ( [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [String] $FolderPath, [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [String] $FileName, @@ -140,7 +148,7 @@ function Set-TargetResource ) # Validate the FolderPath parameter - $FolderPath = Assert-AccessPathValid $FolderPath + $FolderPath = Assert-AccessPathValid -AccessPath $FolderPath -Slash $fullPathToVirtualDisk = $FolderPath + $FileName + "." + $DiskFormat # Create and attach virtual disk if it doesn't exist @@ -194,10 +202,12 @@ function Test-TargetResource param ( [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [String] $FolderPath, [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [String] $FileName, @@ -216,7 +226,7 @@ function Test-TargetResource $DiskType = 'dynamic' ) # Validate the FolderPath parameter - $FolderPath = Assert-AccessPathValid $FolderPath + $FolderPath = Assert-AccessPathValid -AccessPath $FolderPath -Slash $fullPathToVirtualDisk = $FolderPath + $FileName + "." + $DiskFormat Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " diff --git a/source/DSCResources/DSC_VirtualHardDisk/Win32Helpers.psm1 b/source/DSCResources/DSC_VirtualHardDisk/Win32Helpers.psm1 index 7c50e6fb..2f40e134 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/Win32Helpers.psm1 +++ b/source/DSCResources/DSC_VirtualHardDisk/Win32Helpers.psm1 @@ -336,9 +336,11 @@ function Add-SimpleVirtualDisk $providerSpecificFlags = 0 # No Provider-specific flag $result = 0 - # Some builds of Windows may not have the ATTACH_VIRTUAL_DISK_FLAG_AT_BOOT flag. So we attempt to attach the virtual - # disk with the flag first. If this fails we attach the virtual disk without the flag. The flag allows the - # virtual disk to be attached by the system at boot time. + <# + Some builds of Windows may not have the ATTACH_VIRTUAL_DISK_FLAG_AT_BOOT flag. So we attempt to attach the virtual + disk with the flag first. If this fails we attach the virtual disk without the flag. The flag allows the + virtual disk to be attached by the system at boot time. + #> for ($attempts = 0; $attempts -lt 2; $attempts++) { if ($attempts -eq 0) From 8f0226081282721396ed4417b002f6c38b3f7468 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Sat, 23 Sep 2023 22:50:55 -0700 Subject: [PATCH 03/21] update win32helpers so we can add tests --- .../DSC_VirtualHardDisk.psm1 | 258 ++++--- .../DSC_VirtualHardDisk.schema.mof | 9 +- .../DSC_VirtualHardDisk/Win32Helpers.psm1 | 619 +++++++++------- .../en-US/DSC_VirtualHardDisk.strings.psd1 | 11 +- ...alHardDisk_CreateFixedSizedVirtualDisk.ps1 | 72 ++ ..._CreateDynamicallyExpandingVirtualDisk.ps1 | 55 ++ .../StorageDsc.Common/StorageDsc.Common.psm1 | 34 +- tests/Unit/DSC_VirtualHardDisk.Tests.ps1 | 666 ++++++++++++++++++ 8 files changed, 1341 insertions(+), 383 deletions(-) create mode 100644 source/Examples/Resources/VirtualHardDisk/1-VirtualHardDisk_CreateFixedSizedVirtualDisk.ps1 create mode 100644 source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 create mode 100644 tests/Unit/DSC_VirtualHardDisk.Tests.ps1 diff --git a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 index d5e29f6d..2734626c 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 +++ b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 @@ -15,21 +15,17 @@ $script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' .SYNOPSIS Returns the current state of the virtual disk. - .PARAMETER FolderPath - Specifies the path to the folder the virtual disk is located in. - - .PARAMETER FileName - Specifies the file name of the virtual disk. + .PARAMETER FilePathWithExtension + Specifies the complete path to the virtual disk file. .PARAMETER DiskSize - Specifies the size of new virtual disk. - - .PARAMETER DiskFormat - Specifies the supported virtual disk format. + Specifies the size the new virtual disk. .PARAMETER DiskType Specifies the supported virtual disk type. + .PARAMETER Ensure + Determines whether the setting should be applied or removed. #> function Get-TargetResource { @@ -40,47 +36,69 @@ function Get-TargetResource [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] - $FolderPath, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [String] - $FileName, + $FilePathWithExtension, [Parameter(Mandatory = $true)] [System.UInt64] $DiskSize, [Parameter()] - [ValidateSet('vhd', 'vhdx')] + [ValidateSet('fixed', 'dynamic')] [System.String] - $DiskFormat = 'vhdx', + $DiskType = 'dynamic', [Parameter()] - [ValidateSet('fixed', 'dynamic')] + [ValidateSet('Present','Absent')] [System.String] - $DiskType = 'dynamic' + $Ensure = 'Present' ) - $FolderPath = Assert-AccessPathValid -AccessPath $FolderPath -Slash - $virtDiskPath = $($FolderPath + $FileName + "." + $DiskFormat) + # We'll only support local paths with drive letters. + if ($FilePathWithExtension -notmatch '[a-zA-Z]:\\') + { + # AccessPath is invalid + New-InvalidArgumentException ` + -Message $($script:localizedData.VirtualHardDiskPathError -f $FilePathWithExtension) ` + -ArgumentName 'FilePath' + } + + $extension = [System.IO.Path]::GetExtension($FilePathWithExtension).TrimStart('.') + if (($extension -ne 'vhd') -and ($extension -ne 'vhdx')) + { + New-InvalidArgumentException ` + -Message $($script:localizedData.VirtualHardDiskUnsupportedFileType -f $extension) ` + -ArgumentName 'FilePath' + } + Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.GettingVirtualDiskMessage -f $virtDiskPath) + $($script:localizedData.GettingVirtualDiskMessage -f $FilePathWithExtension) ) -join '' ) <# Validate DiskFormat values. Minimum value for GPT is around ~10MB and the maximum value for the vhd format in 2040GB. Maximum for vhdx is 64TB #> - $isVhdxFormat = $DiskFormat -eq 'vhdx' - if (( -not $isVhdxFormat -and ($DiskSize -lt 10MB -bor $DiskSize -gt 2040GB)) -bor - ($IsVhdxFormat -and ($DiskSize -lt 10MB -bor $DiskSize -gt 64TB))) + $isVhdxFormat = $extension -eq 'vhdx' + $isInValidSizeForVhdFormat = ($DiskSize -lt 10MB -bor $DiskSize -gt 2040GB) + $isInValidSizeForVhdxFormat = ($DiskSize -lt 10MB -bor $DiskSize -gt 64TB) + if ((-not $isVhdxFormat -and $isInValidSizeForVhdFormat) -bor + ($IsVhdxFormat -and $isInValidSizeForVhdxFormat)) { - $DiskSizeString = ConvertFrom-Bytes $DiskSize - $InvalidSizeMsg = ($isVhdxFormat) ? - $script:localizedData.VhdxFormatDiskSizeInvalidMessage : - $script:localizedData.VhdFormatDiskSizeInvalidMessage + if ($DiskSize -lt 1GB) + { + $DiskSizeString = ($DiskSize / 1MB).ToString("0.00MB") + } + else + { + $DiskSizeString = ($DiskSize / 1TB).ToString("0.00TB") + } + + $InvalidSizeMsg = $script:localizedData.VhdFormatDiskSizeInvalidMessage + if ($isVhdxFormat) + { + $InvalidSizeMsg = $script:localizedData.VhdxFormatDiskSizeInvalidMessage + } New-InvalidArgumentException ` -Message $($InvalidSizeMsg -f $DiskSizeString) ` @@ -89,11 +107,11 @@ function Get-TargetResource # Get the virtual disk using its location on the system return @{ - FolderPath = $FolderPath - FileName = $FileName - DiskSize = $DiskSize - DiskFormat = $DiskFormat - DiskType = $DiskType + FilePathWithExtension = $FilePathWithExtension + DiskSize = $DiskSize + DiskFormat = $extension + DiskType = $DiskType + Ensure = $Ensure } } # function Get-TargetResource @@ -101,21 +119,20 @@ function Get-TargetResource .SYNOPSIS Returns the current state of the virtual disk. - .PARAMETER FolderPath - Specifies the path to the folder the virtual disk is located in. - - .PARAMETER FileName - Specifies the file name of the virtual disk. + .PARAMETER FilePathWithExtension + Specifies the complete path to the virtual disk file. .PARAMETER DiskSize Specifies the size of new virtual disk. .PARAMETER DiskFormat - Specifies the supported virtual disk format. + Specifies the supported virtual disk format. Currently only the vhd and vhdx formats are supported. .PARAMETER DiskType Specifies the supported virtual disk type. + .PARAMETER Ensure + Determines whether the setting should be applied or removed. #> function Set-TargetResource { @@ -125,12 +142,7 @@ function Set-TargetResource [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] - $FolderPath, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [String] - $FileName, + $FilePathWithExtension, [Parameter(Mandatory = $true)] [System.UInt64] @@ -139,77 +151,101 @@ function Set-TargetResource [Parameter()] [ValidateSet('vhd', 'vhdx')] [System.String] - $DiskFormat = 'vhdx', + $DiskFormat, [Parameter()] [ValidateSet('fixed', 'dynamic')] [System.String] - $DiskType = 'dynamic' - ) + $DiskType = 'dynamic', - # Validate the FolderPath parameter - $FolderPath = Assert-AccessPathValid -AccessPath $FolderPath -Slash - $fullPathToVirtualDisk = $FolderPath + $FileName + "." + $DiskFormat + [Parameter()] + [ValidateSet('Present','Absent')] + [System.String] + $Ensure = 'Present' + ) - # Create and attach virtual disk if it doesn't exist - $virtualDiskFileExists = Test-Path -Path $fullPathToVirtualDisk -PathType Leaf - if (-not $virtualDiskFileExists) + $diskImage = Get-DiskImage -ImagePath $FilePathWithExtension + if ($Ensure -eq 'Present') { - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualDiskDoesNotExistCreatingNowMessage -f $fullPathToVirtualDisk) - ) -join '' ) + # Disk doesn't exist + if (-not $diskImage) + { + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.VirtualDiskDoesNotExistCreatingNowMessage -f $FilePathWithExtension) + ) -join '' ) - New-SimpleVirtualDisk -VirtualDiskPath $fullPathToVirtualDisk -DiskFormat $DiskFormat -DiskType $DiskType -DiskSizeInBytes $DiskSize + try + { + New-SimpleVirtualDisk -VirtualDiskPath $FilePathWithExtension -DiskFormat $diskFormat -DiskType $DiskType -DiskSizeInBytes $DiskSize + } + catch + { + # Remove file if we created it but were unable to attach it. No handles are open when this happens. + if (Test-Path -Path $FilePathWithExtension -PathType Leaf) + { + Write-Verbose -Message ($script:localizedData.VirtualRemovingCreatedFileMessage -f $VirtualDiskPath) + Remove-Item $FilePathWithExtension -verbose + } + } + + } + elseif (-not $diskImage.Attached) + { + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.VirtualDiskNotAttachedMessage -f $FilePathWithExtension) + ) -join '' ) + + # Virtual disk file exists so lets attempt to attach it to the system. + Add-SimpleVirtualDisk -VirtualDiskPath $FilePathWithExtension -DiskFormat $diskFormat + } } else { - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualDiskNotAttachedMessage -f $fullPathToVirtualDisk) - ) -join '' ) - - # Virtual disk file exists so lets attempt to attach it to the system. - Add-SimpleVirtualDisk -VirtualDiskPath $fullPathToVirtualDisk -DiskFormat $DiskFormat + # Detach the virtual disk if its not suppose to be mounted + if ($diskImage.Attached) + { + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.VirtualDiskDismountingImageMessage ` + -f $FilePathWithExtension) + ) -join '' ) + + Dismount-DiskImage -ImagePath $FilePathWithExtension + } } - - } # function Set-TargetResource <# .SYNOPSIS Returns the current state of the virtual disk. - .PARAMETER FolderPath - Specifies the path to the folder the virtual disk is located in. - .PARAMETER FileName - Specifies the file name of the virtual disk. + .PARAMETER FilePathWithExtension + Specifies the complete path to the virtual disk file. .PARAMETER DiskSize Specifies the size of new virtual disk. .PARAMETER DiskFormat - Specifies the supported virtual disk format. + Specifies the supported virtual disk format. Currently only the vhd and vhdx formats are supported. .PARAMETER DiskType Specifies the supported virtual disk type. + .PARAMETER Ensure + Determines whether the setting should be applied or removed. #> function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param - ( + ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] - $FolderPath, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [String] - $FileName, + $FilePathWithExtension, [Parameter(Mandatory = $true)] [System.UInt64] @@ -218,54 +254,66 @@ function Test-TargetResource [Parameter()] [ValidateSet('vhd', 'vhdx')] [System.String] - $DiskFormat = 'vhdx', + $DiskFormat, [Parameter()] [ValidateSet('fixed', 'dynamic')] [System.String] - $DiskType = 'dynamic' + $DiskType = 'dynamic', + + [Parameter()] + [ValidateSet('Present','Absent')] + [System.String] + $Ensure = 'Present' ) - # Validate the FolderPath parameter - $FolderPath = Assert-AccessPathValid -AccessPath $FolderPath -Slash - $fullPathToVirtualDisk = $FolderPath + $FileName + "." + $DiskFormat + Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.CheckingVirtualDiskExistsMessage -f $fullPathToVirtualDisk) + $($script:localizedData.CheckingVirtualDiskExistsMessage -f $FilePathWithExtension) ) -join '' ) - #Check if virtual file exists - if (-not (Test-Path -Path $fullPathToVirtualDisk -PathType Leaf)) + $diskImage = Get-DiskImage -ImagePath $FilePathWithExtension + + if ($Ensure -eq 'Present') { + # Found the virtual disk and confirmed its attached to the system. + if ($diskImage.Attached) + { + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.VirtualDiskCurrentlyAttachedMessage -f $FilePathWithExtension) + ) -join '' ) + + return $true + } + Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualDiskDoesNotExistMessage -f $fullPathToVirtualDisk) + $($script:localizedData.VirtualDiskDoesNotExistMessage -f $FilePathWithExtension) ) -join '' ) return $false } + else + { + # Found the virtual disk and confirmed its attached to the system but ensure variable set to absent. + if ($diskImage.Attached) + { + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.VirtualDiskCurrentlyAttachedButShouldNotBeMessage -f $FilePathWithExtension) + ) -join '' ) - $virtdisk = Get-DiskByIdentifier ` - -DiskId $fullPathToVirtualDisk ` - -DiskIdType 'Location' + return $false + } - # Found the virtual disk and confirmed its attached to the system. - if ($virtdisk) - { Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualDiskCurrentlyAttachedMessage -f $fullPathToVirtualDisk) + $($script:localizedData.VirtualDiskDoesNotExistMessage -f $FilePathWithExtension) ) -join '' ) return $true } - - # Either the virtual disk is not attached or the file above exists but is corrupted or wasn't created properly. - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualDiskNotAttachedOrFileCorruptedMessage -f $fullPathToVirtualDisk) - ) -join '' ) - - return $false } # function Test-TargetResource Export-ModuleMember -Function *-TargetResource diff --git a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof index d4d03f8d..aef5a4ec 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof +++ b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof @@ -2,9 +2,8 @@ [ClassVersion("1.0.0.0"), FriendlyName("VirtualHardDisk")] class DSC_VirtualHardDisk : OMI_BaseResource { - [Key, Description("Specifies the folder path where the virtual disk will be created or attached")] String FolderPath; - [Required, Description("Specifies the file name of the virtual disk.")] String FileName; - [Write, Description("Specifies the size of virtual disk.")] Uint64 DiskSize; - [Write, Description("Specifies the disk format the virtual disk file should use. Defaults to vhdx"), ValueMap{"vhd","vhdx"}, Values{"vhd","vhdx"}] String DiskFormat; - [Write, Description("Specifies the disk type the virtual disk should use. Defaults to dynamic"), ValueMap{"Fixed","Dynamic"}, Values{"Fixed","Dynamic"}] String DiskType; + [Key, Description("Specifies the full path to the virtual disk file that will be created or attached. This includes the files extension.")] String FilePathWithExtension; + [Required, Description("Specifies the size the of virtual disk.")] Uint64 DiskSize; + [Write, Description("Specifies the disk type the virtual disk should use. Defaults to dynamic."), ValueMap{"Fixed","Dynamic"}, Values{"Fixed","Dynamic"}] String DiskType; + [Write, Description("Determines whether the virtual disk should be created and mounted or not."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; }; diff --git a/source/DSCResources/DSC_VirtualHardDisk/Win32Helpers.psm1 b/source/DSCResources/DSC_VirtualHardDisk/Win32Helpers.psm1 index 2f40e134..bf8c7d03 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/Win32Helpers.psm1 +++ b/source/DSCResources/DSC_VirtualHardDisk/Win32Helpers.psm1 @@ -5,188 +5,339 @@ Import-Module -Name (Join-Path -Path $modulePath -ChildPath 'DscResource.Common' # Import Localization Strings $script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' -# Import virtdisk.dll and define structures and constants -Add-Type -TypeDefinition @' - using System; - using System.Runtime.InteropServices; +<# + .SYNOPSIS + Returns C# code that will be used to call Dev Drive related Win32 apis +#> +function Get-VirtDiskWin32HelperScript +{ + [CmdletBinding()] + [OutputType([System.Void])] + param + () - namespace Win32 - { - namespace VirtDisk + $virtDiskDefinitions = @' + + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ns-virtdisk-virtual_storage_type + [StructLayout(LayoutKind.Sequential)] + public struct VIRTUAL_STORAGE_TYPE { - // Define structures and constants for creating a virtual disk. - // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ne-virtdisk-create_virtual_disk_version - public enum CREATE_VIRTUAL_DISK_VERSION - { - CREATE_VIRTUAL_DISK_VERSION_UNSPECIFIED = 0, - CREATE_VIRTUAL_DISK_VERSION_1 = 1, - CREATE_VIRTUAL_DISK_VERSION_2 = 2, - } + public UInt32 DeviceId; + public Guid VendorId; + } - // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ns-virtdisk-virtual_storage_type - [StructLayout(LayoutKind.Sequential)] - public struct VIRTUAL_STORAGE_TYPE - { - public UInt32 DeviceId; - public Guid VendorId; - } + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ns-virtdisk-create_virtual_disk_parameters + [StructLayout(LayoutKind.Sequential)] + public struct CREATE_VIRTUAL_DISK_PARAMETERS + { + public UInt32 Version; + public Guid UniqueId; + public UInt64 MaximumSize; + public UInt32 BlockSizeInBytes; + public UInt32 SectorSizeInBytes; + [MarshalAs(UnmanagedType.LPWStr)] + public string ParentPath; + [MarshalAs(UnmanagedType.LPWStr)] + public string SourcePath; + } - // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ns-virtdisk-create_virtual_disk_parameters - [StructLayout(LayoutKind.Sequential)] - public struct CREATE_VIRTUAL_DISK_PARAMETERS - { - public CREATE_VIRTUAL_DISK_VERSION Version; - public Guid UniqueId; - public UInt64 MaximumSize; - public UInt32 BlockSizeInBytes; - public UInt32 SectorSizeInBytes; - [MarshalAs(UnmanagedType.LPWStr)] - public string ParentPath; - [MarshalAs(UnmanagedType.LPWStr)] - public string SourcePath; - } + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ns-virtdisk-attach_virtual_disk_parameters + [StructLayout(LayoutKind.Sequential)] + public struct ATTACH_VIRTUAL_DISK_PARAMETERS + { + public UInt32 Version; + public UInt32 Reserved; + } - // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ne-virtdisk-virtual_disk_access_mask-r1 - public enum VIRTUAL_DISK_ACCESS_MASK - { - VIRTUAL_DISK_ACCESS_NONE = 0, - VIRTUAL_DISK_ACCESS_ATTACH_RO = 0x00010000, - VIRTUAL_DISK_ACCESS_ATTACH_RW = 0x00020000, - VIRTUAL_DISK_ACCESS_DETACH = 0x00040000, - VIRTUAL_DISK_ACCESS_GET_INFO = 0x00080000, - VIRTUAL_DISK_ACCESS_CREATE = 0x00100000, - VIRTUAL_DISK_ACCESS_METAOPS = 0x00200000, - VIRTUAL_DISK_ACCESS_READ = 0x000d0000, - VIRTUAL_DISK_ACCESS_ALL = 0x003f0000, - VIRTUAL_DISK_ACCESS_WRITABLE = 0x00320000 - } + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ns-virtdisk-open_virtual_disk_parameters + [StructLayout(LayoutKind.Sequential)] + public struct OPEN_VIRTUAL_DISK_PARAMETERS + { + public UInt32 Version; + public UInt32 RWDepth; + } - // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ne-virtdisk-create_virtual_disk_flag - public enum CREATE_VIRTUAL_DISK_FLAG - { - CREATE_VIRTUAL_DISK_FLAG_NONE = 0x0, - CREATE_VIRTUAL_DISK_FLAG_FULL_PHYSICAL_ALLOCATION = 0x1, - } + // Define structures and constants for creating a virtual disk. + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ne-virtdisk-create_virtual_disk_version + public static uint CREATE_VIRTUAL_DISK_VERSION_2 = 2; + + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ne-virtdisk-virtual_disk_access_mask-r1 + public static uint VIRTUAL_DISK_ACCESS_NONE = 0; + public static uint VIRTUAL_DISK_ACCESS_ALL = 0x003f0000; + + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ne-virtdisk-create_virtual_disk_flag + public static uint CREATE_VIRTUAL_DISK_FLAG_NONE = 0x0; + public static uint CREATE_VIRTUAL_DISK_FLAG_FULL_PHYSICAL_ALLOCATION = 0x1; + + // Define structures and constants for attaching a virtual disk. + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ne-virtdisk-attach_virtual_disk_flag + public static uint ATTACH_VIRTUAL_DISK_FLAG_PERMANENT_LIFETIME = 0x00000004; + public static uint ATTACH_VIRTUAL_DISK_FLAG_AT_BOOT = 0x00000400; + + + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ne-virtdisk-attach_virtual_disk_version + public static uint ATTACH_VIRTUAL_DISK_VERSION_1 = 1; + + // Define structures and constants for opening a virtual disk. + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ne-virtdisk-open_virtual_disk_version + public static uint OPEN_VIRTUAL_DISK_VERSION_1 = 1; + + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ne-virtdisk-open_virtual_disk_flag + public static uint OPEN_VIRTUAL_DISK_FLAG_NONE = 0x0; + + // Constants found in virtdisk.h + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ns-virtdisk-virtual_storage_type + public static uint VIRTUAL_STORAGE_TYPE_DEVICE_VHD = 2U; + public static uint VIRTUAL_STORAGE_TYPE_DEVICE_VHDX = 3U; + public static Guid VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT = new Guid(0xEC984AEC, 0xA0F9, 0x47E9, 0x90, 0x1F, 0x71, 0x41, 0x5A, 0x66, 0x34, 0x5B); + + // Declare method to create a virtual disk + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/nf-virtdisk-createvirtualdisk + [DllImport("virtdisk.dll", CharSet = CharSet.Unicode)] + public static extern Int32 CreateVirtualDisk( + ref VIRTUAL_STORAGE_TYPE VirtualStorageType, + string Path, + UInt32 VirtualDiskAccessMask, + IntPtr SecurityDescriptor, + UInt32 Flags, + UInt32 ProviderSpecificFlags, + ref CREATE_VIRTUAL_DISK_PARAMETERS Parameters, + IntPtr Overlapped, + ref IntPtr Handle + ); + + // Declare method to attach a virtual disk + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/nf-virtdisk-attachvirtualdisk + [DllImport("virtdisk.dll", CharSet = CharSet.Unicode)] + public static extern Int32 AttachVirtualDisk( + IntPtr VirtualDiskHandle, + IntPtr SecurityDescriptor, + UInt32 Flags, + UInt32 ProviderSpecificFlags, + ref ATTACH_VIRTUAL_DISK_PARAMETERS Parameters, + IntPtr Overlapped + ); + + // Declare function to open a handle to a virtual disk + // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/nf-virtdisk-openvirtualdisk + [DllImport("virtdisk.dll", CharSet = CharSet.Unicode)] + public static extern Int32 OpenVirtualDisk( + ref VIRTUAL_STORAGE_TYPE VirtualStorageType, + string Path, + UInt32 VirtualDiskAccessMask, + UInt32 Flags, + ref OPEN_VIRTUAL_DISK_PARAMETERS Parameters, + ref IntPtr Handle + ); + + // https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CloseHandle(IntPtr hObject); - // Define structures and constants for attaching a virtual disk. - // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ne-virtdisk-attach_virtual_disk_flag - public enum ATTACH_VIRTUAL_DISK_FLAG - { - ATTACH_VIRTUAL_DISK_FLAG_NONE = 0x00000000, - ATTACH_VIRTUAL_DISK_FLAG_READ_ONLY = 0x00000001, - ATTACH_VIRTUAL_DISK_FLAG_NO_DRIVE_LETTER = 0x00000002, - ATTACH_VIRTUAL_DISK_FLAG_PERMANENT_LIFETIME = 0x00000004, - ATTACH_VIRTUAL_DISK_FLAG_NO_LOCAL_HOST = 0x00000008, - ATTACH_VIRTUAL_DISK_FLAG_NO_SECURITY_DESCRIPTOR = 0x00000010, - ATTACH_VIRTUAL_DISK_FLAG_BYPASS_DEFAULT_ENCRYPTION_POLICY = 0x00000020, - ATTACH_VIRTUAL_DISK_FLAG_NON_PNP = 0x00000040, - ATTACH_VIRTUAL_DISK_FLAG_RESTRICTED_RANGE = 0x00000080, - ATTACH_VIRTUAL_DISK_FLAG_SINGLE_PARTITION = 0x00000100, - ATTACH_VIRTUAL_DISK_FLAG_REGISTER_VOLUME = 0x00000200, - ATTACH_VIRTUAL_DISK_FLAG_AT_BOOT = 0x00000400, - } +'@ + if (([System.Management.Automation.PSTypeName]'VirtDisk.Helper').Type) + { + $script:VirtDiskHelper = ([System.Management.Automation.PSTypeName]'VirtDisk.Helper').Type + } + else + { + $script:VirtDiskHelper = Add-Type ` + -Namespace 'VirtDisk' ` + -Name 'Helper' ` + -MemberDefinition $virtDiskDefinitions + } - // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ne-virtdisk-attach_virtual_disk_version - public enum ATTACH_VIRTUAL_DISK_VERSION - { - ATTACH_VIRTUAL_DISK_VERSION_UNSPECIFIED = 0, - ATTACH_VIRTUAL_DISK_VERSION_1 = 1, - } + return $script:VirtDiskHelper +} # end function Get-VirtDiskWin32HelperScript - // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ns-virtdisk-attach_virtual_disk_parameters - [StructLayout(LayoutKind.Sequential)] - public struct ATTACH_VIRTUAL_DISK_PARAMETERS - { - public ATTACH_VIRTUAL_DISK_VERSION Version; - public UInt32 Reserved; - } +<# + .SYNOPSIS + Calls Win32 CreateVirtualDisk api. This is used so we can mock this call + easier - // Define structures and constants for opening a virtual disk. - // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ne-virtdisk-open_virtual_disk_version - public enum OPEN_VIRTUAL_DISK_VERSION - { - OPEN_VIRTUAL_DISK_VERSION_UNSPECIFIED = 0, - OPEN_VIRTUAL_DISK_VERSION_1 = 1, - OPEN_VIRTUAL_DISK_VERSION_2 = 2, - } + .PARAMETER VirtualDiskPath + Specifies the whole path to the virtual disk including the file name. +#> +Function New-VirtualDiskUsingWin32 { - // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ns-virtdisk-open_virtual_disk_parameters - [StructLayout(LayoutKind.Sequential)] - public struct OPEN_VIRTUAL_DISK_PARAMETERS - { - public OPEN_VIRTUAL_DISK_VERSION Version; - public UInt32 RWDepth; - } + [CmdletBinding()] + [OutputType([System.Int32])] + Param + ( + [Parameter(Mandatory = $true)] + [ref] + $VirtualStorageType, - // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ne-virtdisk-open_virtual_disk_flag - public enum OPEN_VIRTUAL_DISK_FLAG - { - OPEN_VIRTUAL_DISK_FLAG_NONE = 0x0, - OPEN_VIRTUAL_DISK_FLAG_NO_PARENTS = 0x1, - OPEN_VIRTUAL_DISK_FLAG_BLANK_FILE = 0x2, - OPEN_VIRTUAL_DISK_FLAG_BOOT_DRIVE = 0x4, - } + [Parameter(Mandatory = $true)] + [System.String] + $VirtualDiskPath, - public class VirtDiskHelper - { - // Constants found in virtdisk.h - // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ns-virtdisk-virtual_storage_type - public static uint VIRTUAL_STORAGE_TYPE_DEVICE_VHD = 2U; - public static uint VIRTUAL_STORAGE_TYPE_DEVICE_VHDX = 3U; - public static Guid VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT = new Guid(0xEC984AEC, 0xA0F9, 0x47E9, 0x90, 0x1F, 0x71, 0x41, 0x5A, 0x66, 0x34, 0x5B); - - // Declare method to create a virtual disk - // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/nf-virtdisk-createvirtualdisk - [DllImport("virtdisk.dll", CharSet = CharSet.Unicode)] - public static extern Int32 CreateVirtualDisk( - ref VIRTUAL_STORAGE_TYPE VirtualStorageType, - string Path, - VIRTUAL_DISK_ACCESS_MASK VirtualDiskAccessMask, - IntPtr SecurityDescriptor, - CREATE_VIRTUAL_DISK_FLAG Flags, - UInt32 ProviderSpecificFlags, - ref CREATE_VIRTUAL_DISK_PARAMETERS Parameters, - IntPtr Overlapped, - ref IntPtr Handle - ); - - // Declare method to attach a virtual disk - // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/nf-virtdisk-attachvirtualdisk - [DllImport("virtdisk.dll", CharSet = CharSet.Unicode)] - public static extern Int32 AttachVirtualDisk( - IntPtr VirtualDiskHandle, - IntPtr SecurityDescriptor, - ATTACH_VIRTUAL_DISK_FLAG Flags, - UInt32 ProviderSpecificFlags, - ref ATTACH_VIRTUAL_DISK_PARAMETERS Parameters, - IntPtr Overlapped - ); - - // Declare function to open a handle to a virtual disk - // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/nf-virtdisk-openvirtualdisk - [DllImport("virtdisk.dll", CharSet = CharSet.Unicode)] - public static extern Int32 OpenVirtualDisk( - ref VIRTUAL_STORAGE_TYPE VirtualStorageType, - string Path, - VIRTUAL_DISK_ACCESS_MASK VirtualDiskAccessMask, - OPEN_VIRTUAL_DISK_FLAG Flags, - ref OPEN_VIRTUAL_DISK_PARAMETERS Parameters, - ref IntPtr Handle - ); - } - } + [Parameter(Mandatory = $true)] + [UInt32] + $AccessMask, - public class Kernel32 - { - // https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool CloseHandle(IntPtr hObject); - } + [Parameter(Mandatory = $true)] + [System.IntPtr] + $SecurityDescriptor, + + [Parameter(Mandatory = $true)] + [UInt32] + $Flags, + + [Parameter(Mandatory = $true)] + [System.UInt32] + $ProviderSpecificFlags, + + [Parameter(Mandatory = $true)] + [ref] + $CreateVirtualDiskParameters, + + [Parameter(Mandatory = $true)] + [System.IntPtr] + $Overlapped, + + [Parameter(Mandatory = $true)] + [ref] + $Handle + ) + + $helper = Get-VirtDiskWin32HelperScript + return $helper::CreateVirtualDisk( + $virtualStorageType, + $VirtualDiskPath, + $AccessMask, + $SecurityDescriptor, + $Flags, + $ProviderSpecificFlags, + $CreateVirtualDiskParameters, + $Overlapped, + $Handle) +} # end function New-VirtualDiskUsingWin32 + +<# + .SYNOPSIS + Calls Win32 AttachVirtualDisk api. This is used so we can mock this call + easier + + .PARAMETER VirtualDiskPath + Specifies the whole path to the virtual disk including the file name. +#> +Function Add-VirtualDiskUsingWin32 { + + [CmdletBinding()] + [OutputType([System.Int32])] + Param + ( + [Parameter(Mandatory = $true)] + [ref] + $Handle, + + [Parameter(Mandatory = $true)] + [System.IntPtr] + $SecurityDescriptor, + + [Parameter(Mandatory = $true)] + [System.UInt32] + $Flags, + + [Parameter(Mandatory = $true)] + [System.Int32] + $ProviderSpecificFlags, + + [Parameter(Mandatory = $true)] + [ref] + $AttachVirtualDiskParameters, + + [Parameter(Mandatory = $true)] + [System.IntPtr] + $Overlapped + ) + + $helper = Get-VirtDiskWin32HelperScript + return $helper::AttachVirtualDisk( + $Handle.Value, + $SecurityDescriptor, + $Flags, + $ProviderSpecificFlags, + $AttachVirtualDiskParameters, + $Overlapped) +} # end function Add-VirtualDiskUsingWin32 + +<# + .SYNOPSIS + Calls Win32 CloseHandle api. This is used so we can mock this call + easier + + .PARAMETER VirtualDiskPath + Specifies the file handle to the virtual disk file. +#> +Function Close-Win32Handle { + + [CmdletBinding()] + [OutputType([System.Void])] + Param + ( + [Parameter(Mandatory = $true)] + [ref] + $Handle + ) + + $helper = Get-VirtDiskWin32HelperScript + if ($Handle.Value) + { + $null = $helper::CloseHandle($Handle.value) } +} # end function Close-Win32Handle -'@ +<# + .SYNOPSIS + Calls Win32 OpenVirtualDisk api. This is used so we can mock this call + easier + + .PARAMETER VirtualDiskPath + Specifies the whole path to the virtual disk including the file name. +#> +Function Get-VirtualDiskUsingWin32 { + + [CmdletBinding()] + [OutputType([System.Int32])] + Param + ( + [Parameter(Mandatory = $true)] + [ref] + $VirtualStorageType, + + [Parameter(Mandatory = $true)] + [System.String] + $VirtualDiskPath, + + [Parameter(Mandatory = $true)] + [System.UInt32] + $AccessMask, + + [Parameter(Mandatory = $true)] + [System.UInt32] + $Flags, + + [Parameter(Mandatory = $true)] + [ref] + $OpenVirtualDiskParameters, + [Parameter(Mandatory = $true)] + [ref] + $Handle + ) + + $helper = Get-VirtDiskWin32HelperScript + return $helper::OpenVirtualDisk( + $VirtualStorageType, + $VirtualDiskPath, + $AccessMask, + $Flags, + $OpenVirtualDiskParameters, + $Handle) +} # end function Get-VirtualDiskUsingWin32 <# .SYNOPSIS Creates and attaches a virtual disk to the system. @@ -229,36 +380,36 @@ function New-SimpleVirtualDisk try { Write-Verbose -Message ($script:localizedData.CreatingVirtualDiskMessage -f $VirtualDiskPath) - $vDiskHelper = New-Object Win32.VirtDisk.VirtDiskHelper + $vDiskHelper = Get-VirtDiskWin32HelperScript # Get parameters for CreateVirtualDisk function - $virtualStorageType = Get-VirtualStorageType -DiskFormat $DiskFormat - $createVirtualDiskParameters = New-Object Win32.VirtDisk.CREATE_VIRTUAL_DISK_PARAMETERS - $createVirtualDiskParameters.Version = [Win32.VirtDisk.CREATE_VIRTUAL_DISK_VERSION]::CREATE_VIRTUAL_DISK_VERSION_2 - $createVirtualDiskParameters.MaximumSize = $DiskSizeInBytes + [ref]$virtualStorageType = Get-VirtualStorageType -DiskFormat $DiskFormat + [ref]$createVirtualDiskParameters = New-Object VirtDisk.Helper+CREATE_VIRTUAL_DISK_PARAMETERS + $createVirtualDiskParameters.Value.Version = [VirtDisk.Helper]::CREATE_VIRTUAL_DISK_VERSION_2 + $createVirtualDiskParameters.Value.MaximumSize = $DiskSizeInBytes $securityDescriptor = [System.IntPtr]::Zero - $accessMask = [Win32.VirtDisk.VIRTUAL_DISK_ACCESS_MASK]::VIRTUAL_DISK_ACCESS_NONE # Access mask + $accessMask = [VirtDisk.Helper]::VIRTUAL_DISK_ACCESS_NONE # Access mask $providerSpecificFlags = 0 # No Provider-specific flags. - $handle = [System.IntPtr]::Zero # Handle to the new virtual disk + [ref]$handle = [System.IntPtr]::Zero # Handle to the new virtual disk # Virtual disk will be dynamically expanding, up to the size of $DiskSizeInBytes on the parent disk - $flags = [Win32.VirtDisk.CREATE_VIRTUAL_DISK_FLAG]::CREATE_VIRTUAL_DISK_FLAG_NONE + $flags = [VirtDisk.Helper]::CREATE_VIRTUAL_DISK_FLAG_NONE if ($DiskType -eq 'fixed') { # Virtual disk will be fixed, and will take up the up the full size of $DiskSizeInBytes on the parent disk after creation - $flags = [Win32.VirtDisk.CREATE_VIRTUAL_DISK_FLAG]::CREATE_VIRTUAL_DISK_FLAG_FULL_PHYSICAL_ALLOCATION + $flags = [VirtDisk.Helper]::CREATE_VIRTUAL_DISK_FLAG_FULL_PHYSICAL_ALLOCATION } - $result = $vDiskHelper::CreateVirtualDisk( - [ref]$virtualStorageType, - $VirtualDiskPath, - $accessMask, - $securityDescriptor, - $flags, - $providerSpecificFlags, - [ref]$createVirtualDiskParameters, - [System.IntPtr]::Zero, - [ref]$handle) + $result = New-VirtualDiskUsingWin32 ` + $virtualStorageType ` + $VirtualDiskPath ` + $accessMask ` + $securityDescriptor ` + $flags ` + $providerSpecificFlags ` + $createVirtualDiskParameters ` + ([System.IntPtr]::Zero) ` + $handle if ($result -ne 0) { @@ -269,21 +420,10 @@ function New-SimpleVirtualDisk Write-Verbose -Message ($script:localizedData.VirtualDiskCreatedSuccessfully -f $VirtualDiskPath) Add-SimpleVirtualDisk -VirtualDiskPath $VirtualDiskPath -DiskFormat $DiskFormat -Handle $handle } - catch - { - # Remove file if we created it but were unable to attach it. No handles are open when this happens. - if (Test-Path -Path $VirtualDiskPath -PathType Leaf) - { - Write-Verbose -Message ($script:localizedData.VirtualRemovingCreatedFileMessage -f $VirtualDiskPath) - Remove-Item $VirtualDiskPath -verbose - } - - throw - } finally { # Close handle - $null = [Win32.Kernel32]::CloseHandle($handle) + Close-Win32Handle $Handle } } # function New-VirtualDisk @@ -315,23 +455,24 @@ function Add-SimpleVirtualDisk $DiskFormat, [Parameter()] - [System.IntPtr] - $Handle = [System.IntPtr]::Zero + [ref] + $Handle ) try { Write-Verbose -Message ($script:localizedData.AttachingVirtualDiskMessage -f $VirtualDiskPath) - $vDiskHelper = New-Object Win32.VirtDisk.VirtDiskHelper + + $vDiskHelper = Get-VirtDiskWin32HelperScript # No handle passed in so we need to open the virtual disk first using $virtualDiskPath to get the handle. - if ($Handle -eq [System.IntPtr]::Zero) + if ($null -eq $Handle) { $Handle = Get-VirtualDiskHandle -VirtualDiskPath $VirtualDiskPath -DiskFormat $DiskFormat } # Build parameters for AttachVirtualDisk function - $attachVirtualDiskParameters = New-Object Win32.VirtDisk.ATTACH_VIRTUAL_DISK_PARAMETERS - $attachVirtualDiskParameters.Version = [Win32.VirtDisk.ATTACH_VIRTUAL_DISK_VERSION]::ATTACH_VIRTUAL_DISK_VERSION_1 + [ref]$attachVirtualDiskParameters = New-Object VirtDisk.Helper+ATTACH_VIRTUAL_DISK_PARAMETERS + $attachVirtualDiskParameters.Value.Version = [VirtDisk.Helper]::ATTACH_VIRTUAL_DISK_VERSION_1 $securityDescriptor = [System.IntPtr]::Zero # Security descriptor $providerSpecificFlags = 0 # No Provider-specific flag $result = 0 @@ -345,21 +486,21 @@ function Add-SimpleVirtualDisk { if ($attempts -eq 0) { - $flags = [Win32.VirtDisk.ATTACH_VIRTUAL_DISK_FLAG]::ATTACH_VIRTUAL_DISK_FLAG_PERMANENT_LIFETIME -bor - [Win32.VirtDisk.ATTACH_VIRTUAL_DISK_FLAG]::ATTACH_VIRTUAL_DISK_FLAG_AT_BOOT + $flags = [VirtDisk.Helper]::ATTACH_VIRTUAL_DISK_FLAG_PERMANENT_LIFETIME -bor + [VirtDisk.Helper]::ATTACH_VIRTUAL_DISK_FLAG_AT_BOOT } else { - $flags = [Win32.VirtDisk.ATTACH_VIRTUAL_DISK_FLAG]::ATTACH_VIRTUAL_DISK_FLAG_PERMANENT_LIFETIME + $flags = [VirtDisk.Helper]::ATTACH_VIRTUAL_DISK_FLAG_PERMANENT_LIFETIME } - $result = $vDiskHelper::AttachVirtualDisk( - $Handle, - $securityDescriptor, - $flags, - $providerSpecificFlags, - [ref]$attachVirtualDiskParameters, - [System.IntPtr]::Zero) + $result = Add-VirtualDiskUsingWin32 ` + $Handle ` + $securityDescriptor ` + $flags ` + $providerSpecificFlags ` + $attachVirtualDiskParameters ` + ([System.IntPtr]::Zero) if ($result -eq 0) { @@ -378,7 +519,7 @@ function Add-SimpleVirtualDisk finally { # Close handle - $null = [Win32.Kernel32]::CloseHandle($Handle) + Close-Win32Handle $Handle } } # function Add-SimpleVirtualDisk @@ -396,7 +537,7 @@ function Add-SimpleVirtualDisk function Get-VirtualDiskHandle { [CmdletBinding()] - [OutputType([System.IntPtr])] + param ( [Parameter(Mandatory = $true)] @@ -410,23 +551,23 @@ function Get-VirtualDiskHandle ) Write-Verbose -Message ($script:localizedData.OpeningVirtualBeforeAttachingMessage) - $vDiskHelper = New-Object Win32.VirtDisk.VirtDiskHelper + $vDiskHelper = Get-VirtDiskWin32HelperScript # Get parameters for OpenVirtualDisk function - $virtualStorageType = Get-VirtualStorageType -DiskFormat $DiskFormat - $openVirtualDiskParameters = New-Object Win32.VirtDisk.OPEN_VIRTUAL_DISK_PARAMETERS - $openVirtualDiskParameters.Version = [Win32.VirtDisk.OPEN_VIRTUAL_DISK_VERSION]::OPEN_VIRTUAL_DISK_VERSION_1 - $accessMask = [Win32.VirtDisk.VIRTUAL_DISK_ACCESS_MASK]::VIRTUAL_DISK_ACCESS_ALL - $flags = [Win32.VirtDisk.OPEN_VIRTUAL_DISK_FLAG]::OPEN_VIRTUAL_DISK_FLAG_NONE - $handle = [System.IntPtr]::Zero - - $result = $vDiskHelper::OpenVirtualDisk( - [ref]$virtualStorageType, - $VirtualDiskPath, - $accessMask, - $flags, - [ref]$openVirtualDiskParameters, - [ref]$handle) + [ref]$virtualStorageType = Get-VirtualStorageType -DiskFormat $DiskFormat + [ref]$openVirtualDiskParameters = New-Object VirtDisk.Helper+OPEN_VIRTUAL_DISK_PARAMETERS + $openVirtualDiskParameters.Value.Version = [VirtDisk.Helper]::OPEN_VIRTUAL_DISK_VERSION_1 + $accessMask = [VirtDisk.Helper]::VIRTUAL_DISK_ACCESS_ALL + $flags = [VirtDisk.Helper]::OPEN_VIRTUAL_DISK_FLAG_NONE + [ref]$handle = [System.IntPtr]::Zero + + $result = Get-VirtualDiskUsingWin32 ` + $virtualStorageType ` + $VirtualDiskPath ` + $accessMask ` + $flags ` + $openVirtualDiskParameters ` + $handle if ($result -ne 0) { @@ -449,7 +590,7 @@ function Get-VirtualDiskHandle function Get-VirtualStorageType { [CmdletBinding()] - [OutputType([Win32.VirtDisk.VIRTUAL_STORAGE_TYPE])] + [OutputType([VirtDisk.Helper+VIRTUAL_STORAGE_TYPE])] param ( [Parameter(Mandatory = $true)] @@ -459,15 +600,15 @@ function Get-VirtualStorageType ) # Create VIRTUAL_STORAGE_TYPE structure - $virtualStorageType = New-Object Win32.VirtDisk.VIRTUAL_STORAGE_TYPE + $virtualStorageType = New-Object -TypeName VirtDisk.Helper+VIRTUAL_STORAGE_TYPE # Default to the vhdx file format. - $virtualStorageType.VendorId = [Win32.VirtDisk.VirtDiskHelper]::VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT - $virtualStorageType.DeviceId = [Win32.VirtDisk.VirtDiskHelper]::VIRTUAL_STORAGE_TYPE_DEVICE_VHDX + $virtualStorageType.VendorId = [VirtDisk.Helper]::VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT + $virtualStorageType.DeviceId = [VirtDisk.Helper]::VIRTUAL_STORAGE_TYPE_DEVICE_VHDX if ($DiskFormat -eq 'vhd') { - $virtualStorageType.VendorId = [Win32.VirtDisk.VirtDiskHelper]::VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT - $virtualStorageType.DeviceId = [Win32.VirtDisk.VirtDiskHelper]::VIRTUAL_STORAGE_TYPE_DEVICE_VHD + $virtualStorageType.VendorId = [VirtDisk.Helper]::VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT + $virtualStorageType.DeviceId = [VirtDisk.Helper]::VIRTUAL_STORAGE_TYPE_DEVICE_VHD } return $virtualStorageType @@ -475,5 +616,11 @@ function Get-VirtualStorageType Export-ModuleMember -Function @( 'New-SimpleVirtualDisk', - 'Add-SimpleVirtualDisk' + 'Add-SimpleVirtualDisk', + 'Get-VirtualDiskHandle', + 'Get-VirtualStorageType', + 'Get-VirtDiskWin32HelperScript', + 'New-VirtualDiskUsingWin32', + 'Add-VirtualDiskUsingWin32', + 'Get-VirtualDiskUsingWin32' ) diff --git a/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 b/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 index bb93d0b4..b994f404 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 +++ b/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 @@ -1,10 +1,13 @@ ConvertFrom-StringData @' CheckingVirtualDiskExistsMessage = Checking virtual disk at location '{0}' exists and is attached. - VirtualDiskDoesNotExistMessage = The virtual disk at location '{0}' does not exist. + VirtualDiskDoesNotExistMessage = The virtual disk at location '{0}' does not exist or is not attached. VirtualDiskDoesNotExistCreatingNowMessage = The virtual disk at location '{0}' does not exist. Creating virtual disk now. - VirtualDiskNotAttachedOrFileCorruptedMessage = The virtual disk at location '{0}' is not attached to the system. The file exists but maybe corrupted. VirtualDiskCurrentlyAttachedMessage = The virtual disk at location '{0}' was found and is attached to the system. - VhdFormatDiskSizeInvalidMessage = The virtual disk size '{0}' was invalid for the 'vhd' format. Min supported value is 10Mb and max supported value 2040Gb. - VhdxFormatDiskSizeInvalidMessage = The virtual disk size '{0}' was invalid for the 'vhdx' format. Min supported value is 10Mb and max supported value 64Tb. + VirtualDiskCurrentlyAttachedButShouldNotBeMessage = The virtual disk at location '{0}' was found and is attached to the system but it should not be. + VhdFormatDiskSizeInvalidMessage = The virtual disk size '{0}' was invalid for the 'vhd' format. Min supported value is 10 Mb and max supported value 2040 Gb. + VhdxFormatDiskSizeInvalidMessage = The virtual disk size '{0}' was invalid for the 'vhdx' format. Min supported value is 10 Mb and max supported value 64 Tb. VirtualDiskNotAttachedMessage = The virtual disk at location '{0}' is not attached. Attaching virtual disk now. + VirtualDiskDismountingImageMessage = The virtual disk located at '{0}' is dismounting. + VirtualHardDiskUnsupportedFileType = The file type .{0} is not supported. Only .vhd and .vhdx file types are supported. + VirtualHardDiskPathError = The path '{0}' must be a fully qualified path that starts with a Drive Letter. '@ diff --git a/source/Examples/Resources/VirtualHardDisk/1-VirtualHardDisk_CreateFixedSizedVirtualDisk.ps1 b/source/Examples/Resources/VirtualHardDisk/1-VirtualHardDisk_CreateFixedSizedVirtualDisk.ps1 new file mode 100644 index 00000000..b92fb8d1 --- /dev/null +++ b/source/Examples/Resources/VirtualHardDisk/1-VirtualHardDisk_CreateFixedSizedVirtualDisk.ps1 @@ -0,0 +1,72 @@ +<#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 formatted volumes, 'G' and 'J', with 'J' using all available space after 'G' has been + created. It also creates a new ReFS formated volume on disk 3 attached as drive letter 'S'. +#> +Configuration VirtualHardDisk_CreateFixedSizedVirtualDisk +{ + Import-DSCResource -ModuleName StorageDsc + + Node localhost + { + WaitForDisk Disk2 + { + DiskId = 2 + RetryIntervalSec = 60 + RetryCount = 60 + } + + Disk GVolume + { + DiskId = 2 + DriveLetter = 'G' + Size = 10737418240 + DependsOn = '[WaitForDisk]Disk2' + } + + Disk JVolume + { + DiskId = 2 + DriveLetter = 'J' + FSLabel = 'Data' + DependsOn = '[Disk]GVolume' + } + + WaitForDisk Disk3 + { + DiskId = 3 + RetryIntervalSec = 60 + RetryCount = 60 + } + + Disk SVolume + { + DiskId = 3 + DriveLetter = 'S' + Size = 107374182400 + FSFormat = 'ReFS' + AllocationUnitSize = 64KB + DependsOn = '[WaitForDisk]Disk3' + } + } +} diff --git a/source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 b/source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 new file mode 100644 index 00000000..c7d5107a --- /dev/null +++ b/source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 @@ -0,0 +1,55 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID 56cbc9fc-4168-4662-9dec-12addcfb82da +.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 with Unique Id '5E1E50A401000000001517FFFF0AEB84' to become + available, and then make the disk available as two new formatted volumes, 'G' and 'J', with 'J' + using all available space after 'G' has been created. It also creates a new ReFS formated + volume on disk 3 with Unique Id '5E1E50A4010000000029AB39450AC9A5' attached as drive letter 'S'. +#> +Configuration VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk +{ + Import-DSCResource -ModuleName StorageDsc + + + Node localhost + { + # Create new virtual disk + VirtualHardDisk newVhd + { + FolderPath = C:\myVhds + FileName = myVHD + DiskSize = 40Gb + DiskFormat = vhdx + DiskType = dynamic + } + + # Create new volume onto the new virtual disk + Disk Volume1 + { + DiskId = ‘C:\myVhds\myVHD.vhdx’ + DiskIdType = 'Location' + DriveLetter = 'E' + FSLabel = 'new volume' + Size = 20Gb + DependsOn = '[VirtualHardDisk]newVhd' + } + } +} diff --git a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 index 92e54d2e..87eb8c64 100644 --- a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 +++ b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 @@ -222,42 +222,10 @@ function Test-AccessPathAssignedToLocal return $accessPathAssigned } # end function Test-AccessPathLocal -<# - .SYNOPSIS - Converts numeric representation of bytes e.g 10737418240 to its string representation - 10gb. Only Mb, Gb and Tb are supported. - - .PARAMETER Size - Size in bytes -#> -function ConvertFrom-Bytes -{ - [CmdletBinding()] - [OutputType([System.String])] - param - ( - [Parameter(Mandatory = $true)] - [System.UInt64] - $Size - ) - - if ($Size -lt 1GB) - { - return ($Size / 1MB).ToString("0.00MB") - } - elseif ($Size -lt 1TB) - { - return ($Size / 1GB).ToString("0.00GB") - } - - return ($Size / 1TB).ToString("0.00TB") -} # end function ConvertFrom-Bytes - Export-ModuleMember -Function @( 'Restart-ServiceIfExists', 'Assert-DriveLetterValid', 'Assert-AccessPathValid', 'Get-DiskByIdentifier', - 'Test-AccessPathAssignedToLocal', - 'ConvertFrom-Bytes' + 'Test-AccessPathAssignedToLocal' ) diff --git a/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 b/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 new file mode 100644 index 00000000..9512cfe8 --- /dev/null +++ b/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 @@ -0,0 +1,666 @@ +$script:dscModuleName = 'StorageDsc' +$script:dscResourceName = 'DSC_VirtualHardDisk' + +function Invoke-TestSetup +{ + try + { + Import-Module -Name DscResource.Test -Force -ErrorAction 'Stop' + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -Tasks build" first.' + } + + $script:testEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` + -TestType 'Unit' + + Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') +} + +function Invoke-TestCleanup +{ + Restore-TestEnvironment -TestEnvironment $script:testEnvironment +} + +Invoke-TestSetup + +# Begin Testing +try +{ + InModuleScope $script:dscResourceName { + $script:DiskImageGoodVhdxPath = 'C:\test.vhdx' + $script:DiskImageBadPath = '\\test.vhdx' + $script:DiskImageGoodVhdPath = 'C:\test.vhd' + $script:DiskImageNonVirtDiskPath = 'C:\test.text' + $script:DiskImageSizeBelowVirtDiskMinimum = 9Mb + $script:DiskImageSizeAboveVhdMaximum = 2041Gb + $script:DiskImageSizeAboveVhdxMaximum = 65Tb + $script:DiskImageSize65Gb = 65Gb + + $script:mockedDiskImageAttachedVhdx = [pscustomobject] @{ + Attached = $true + ImagePath = $script:DiskImageGoodVhdxPath + Size = 100GB + } + + $script:mockedDiskImageAttachedVhd = [pscustomobject] @{ + Attached = $true + ImagePath = $script:DiskImageGoodVhdPath + Size = 100GB + } + + $script:mockedDiskImageNotAttachedVhdx = [pscustomobject] @{ + Attached = $false + ImagePath = $script:DiskImageGoodVhdxPath + Size = 100GB + } + + $script:mockedDiskImageNotAttachedVhd = [pscustomobject] @{ + Attached = $false + ImagePath = $script:DiskImageGoodVhdPath + Size = 100GB + } + + $script:mockedDiskImageEmpty = $null + + function Add-SimpleVirtualDisk + { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $VirtualDiskPath, + + [Parameter(Mandatory = $true)] + [ValidateSet('vhd', 'vhdx')] + [System.String] + $DiskFormat, + + [Parameter()] + [ref] + $Handle + ) + } + + function New-SimpleVirtualDisk + { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $VirtualDiskPath, + + [Parameter(Mandatory = $true)] + [System.UInt64] + $DiskSizeInBytes, + + [Parameter(Mandatory = $true)] + [ValidateSet('vhd', 'vhdx')] + [System.String] + $DiskFormat, + + [Parameter(Mandatory = $true)] + [ValidateSet('fixed', 'dynamic')] + [System.String] + $DiskType + ) + } + Function New-VirtualDiskUsingWin32 + { + [CmdletBinding()] + [OutputType([System.Int32])] + Param + ( + [Parameter(Mandatory = $true)] + [ref] + $VirtualStorageType, + + [Parameter(Mandatory = $true)] + [System.String] + $VirtualDiskPath, + + [Parameter(Mandatory = $true)] + [UInt32] + $AccessMask, + + [Parameter(Mandatory = $true)] + [System.IntPtr] + $SecurityDescriptor, + + [Parameter(Mandatory = $true)] + [UInt32] + $Flags, + + [Parameter(Mandatory = $true)] + [System.UInt32] + $ProviderSpecificFlags, + + [Parameter(Mandatory = $true)] + [ref] + $CreateVirtualDiskParameters, + + [Parameter(Mandatory = $true)] + [System.IntPtr] + $Overlapped, + + [Parameter(Mandatory = $true)] + [ref] + $Handle + ) + } + + Function Add-VirtualDiskUsingWin32 + { + [CmdletBinding()] + [OutputType([System.Int32])] + Param + ( + [Parameter(Mandatory = $true)] + [ref] + $Handle, + + [Parameter(Mandatory = $true)] + [System.IntPtr] + $SecurityDescriptor, + + [Parameter(Mandatory = $true)] + [System.UInt32] + $Flags, + + [Parameter(Mandatory = $true)] + [System.Int32] + $ProviderSpecificFlags, + + [Parameter(Mandatory = $true)] + [ref] + $AttachVirtualDiskParameters, + + [Parameter(Mandatory = $true)] + [System.IntPtr] + $Overlapped + ) + } + + Function Close-Win32Handle + { + + [CmdletBinding()] + [OutputType([System.Void])] + Param + ( + [Parameter(Mandatory = $true)] + [ref] + $Handle + ) + } + + Function Get-VirtualDiskUsingWin32 + { + + [CmdletBinding()] + [OutputType([System.Int32])] + Param + ( + [Parameter(Mandatory = $true)] + [ref] + $VirtualStorageType, + + [Parameter(Mandatory = $true)] + [System.String] + $VirtualDiskPath, + + [Parameter(Mandatory = $true)] + [System.UInt32] + $AccessMask, + + [Parameter(Mandatory = $true)] + [System.UInt32] + $Flags, + + [Parameter(Mandatory = $true)] + [ref] + $OpenVirtualDiskParameters, + + [Parameter(Mandatory = $true)] + [ref] + $Handle + ) + } + + Describe 'DSC_VirtualHardDisk\Get-TargetResource' { + Context 'When file path is not fully qualified' { + + $errorRecord = Get-InvalidArgumentRecord ` + -Message ($script:localizedData.VirtualHardDiskPathError -f ` + $DiskImageBadPath) ` + -ArgumentName 'FilePath' + + It 'Should throw invalid argument error when path is not fully qualified' { + { + Get-TargetResource ` + -FilePathWithExtension $DiskImageBadPath ` + -DiskSize $DiskImageSize65Gb ` + -Verbose + } | Should -Throw $errorRecord + } + } + + Context 'When file extension is not .vhd or .vhdx' { + $extension = [System.IO.Path]::GetExtension($DiskImageNonVirtDiskPath).TrimStart('.') + $errorRecord = Get-InvalidArgumentRecord ` + -Message ($script:localizedData.VirtualHardDiskUnsupportedFileType -f ` + $extension) ` + -ArgumentName 'FilePath' + + It 'Should throw invalid argument error when the file type is not supported' { + { + Get-TargetResource ` + -FilePathWithExtension $DiskImageNonVirtDiskPath ` + -DiskSize $DiskImageSize65Gb ` + -Verbose + } | Should -Throw $errorRecord + } + } + + Context 'When size provided is less than the minimum size for the vhd format' { + + $minSizeInMbString = ($DiskImageSizeBelowVirtDiskMinimum / 1MB).ToString("0.00MB") + $errorRecord = Get-InvalidArgumentRecord ` + -Message ($script:localizedData.VhdFormatDiskSizeInvalidMessage -f ` + $minSizeInMbString) ` + -ArgumentName 'DiskSize' + + It 'Should throw invalid argument error when the provided size is below the minimum for the vhd format' { + { + Get-TargetResource ` + -FilePathWithExtension $DiskImageGoodVhdPath ` + -DiskSize $DiskImageSizeBelowVirtDiskMinimum ` + -Verbose + } | Should -Throw $errorRecord + } + } + + Context 'When size provided is less than the minimum size for the vhdx format' { + $minSizeInMbString = ($DiskImageSizeBelowVirtDiskMinimum / 1MB).ToString("0.00MB") + $errorRecord = Get-InvalidArgumentRecord ` + -Message ($script:localizedData.VhdxFormatDiskSizeInvalidMessage -f ` + $minSizeInMbString) ` + -ArgumentName 'DiskSize' + + It 'Should throw invalid argument error when the provided size is below the minimum for the vhdx format' { + { + Get-TargetResource ` + -FilePathWithExtension $DiskImageGoodVhdxPath ` + -DiskSize $DiskImageSizeBelowVirtDiskMinimum ` + -Verbose + } | Should -Throw $errorRecord + } + } + + Context 'When size provided is greater than the maximum size for the vhd format' { + $maxSizeInTbString = ($DiskImageSizeAboveVhdMaximum / 1TB).ToString("0.00TB") + $errorRecord = Get-InvalidArgumentRecord ` + -Message ($script:localizedData.VhdFormatDiskSizeInvalidMessage -f ` + $maxSizeInTbString) ` + -ArgumentName 'DiskSize' + + It 'Should throw invalid argument error when the provided size is above the maximum for the vhd format' { + { + Get-TargetResource ` + -FilePathWithExtension $DiskImageGoodVhdPath ` + -DiskSize $DiskImageSizeAboveVhdMaximum ` + -Verbose + } | Should -Throw $errorRecord + } + } + + Context 'When size provided is greater than the maximum size for the vhdx format' { + $maxSizeInTbString = ($DiskImageSizeAboveVhdxMaximum / 1TB).ToString("0.00TB") + $errorRecord = Get-InvalidArgumentRecord ` + -Message ($script:localizedData.VhdxFormatDiskSizeInvalidMessage -f ` + $maxSizeInTbString) ` + -ArgumentName 'DiskSize' + + It 'Should throw invalid argument error when the provided size is above the maximum for the vhdx format' { + { + Get-TargetResource ` + -FilePathWithExtension $DiskImageGoodVhdxPath ` + -DiskSize $DiskImageSizeAboveVhdxMaximum ` + -Verbose + } | Should -Throw $errorRecord + } + } + + Context 'When file path to vhdx file is fully qualified' { + It 'Should not throw invalid argument error when path is fully qualified' { + { + Get-TargetResource ` + -FilePathWithExtension $DiskImageGoodVhdxPath ` + -DiskSize $DiskImageSize65Gb ` + -Verbose + } | Should -Not -Throw + } + } + + Context 'When file path to vhd is fully qualified' { + It 'Should not throw invalid argument error when path is fully qualified' { + { + Get-TargetResource ` + -FilePathWithExtension $DiskImageGoodVhdPath ` + -DiskSize $DiskImageSize65Gb ` + -Verbose + } | Should -Not -Throw + } + } + } + + Describe 'DSC_VirtualHardDisk\Set-TargetResource' { + + Context 'Virtual disk is mounted and ensure set to present' { + + Mock ` + -CommandName Get-DiskImage ` + -MockWith { $script:mockedDiskImageAttachedVhdx } ` + -Verifiable + + $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') + It 'Should not throw an exception' { + { + Set-TargetResource ` + -FilePathWithExtension $script:mockedDiskImageAttachedVhdx.ImagePath ` + -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` + -DiskFormat $extension ` + -Ensure 'Present' ` + -Verbose + } | Should -Not -Throw + } + + It 'Should only call required mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-DiskImage -Exactly 1 + } + } + + Context 'Virtual disk is mounted and ensure set to absent, so it should be dismounted' { + + Mock ` + -CommandName Get-DiskImage ` + -MockWith { $script:mockedDiskImageAttachedVhdx } ` + -Verifiable + + Mock ` + -CommandName Dismount-DiskImage ` + -Verifiable + + $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') + It 'Should dismount the virtual disk' { + { + Set-TargetResource ` + -FilePathWithExtension $script:mockedDiskImageAttachedVhdx.ImagePath ` + -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` + -DiskFormat $extension ` + -Ensure 'Absent' ` + -Verbose + } | Should -Not -Throw + } + + It 'Should only call required mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-DiskImage -Exactly 1 + Assert-MockCalled -CommandName Dismount-DiskImage -Exactly 1 + } + } + + Context 'Virtual disk is dismounted and ensure set to present, so it should be re-mounted' { + Mock ` + -CommandName Get-DiskImage ` + -MockWith { $script:mockedDiskImageNotAttachedVhdx } ` + -Verifiable + + Mock ` + -CommandName Add-SimpleVirtualDisk ` + -Verifiable + + $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') + It 'Should Not throw exception' { + { + Set-TargetResource ` + -FilePathWithExtension $script:mockedDiskImageAttachedVhdx.ImagePath ` + -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` + -DiskFormat $extension ` + -Ensure 'Present' ` + -Verbose + } | Should -Not -Throw + } + + It 'Should only call required mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-DiskImage -Exactly 1 + Assert-MockCalled -CommandName Add-SimpleVirtualDisk -Exactly 1 + } + } + + Context 'Virtual disk does not exist and ensure set to present, so a new one should be created.' { + Mock ` + -CommandName Get-DiskImage ` + -MockWith { $script:mockedDiskImageEmpty } ` + -Verifiable + + Mock ` + -CommandName New-SimpleVirtualDisk ` + -Verifiable + + $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') + It 'Should not throw an exception' { + { + Set-TargetResource ` + -FilePathWithExtension $script:mockedDiskImageAttachedVhdx.ImagePath ` + -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` + -DiskFormat $extension ` + -Ensure 'Present' ` + -Verbose + } | Should -Not -Throw + } + + It 'Should only call required mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-DiskImage -Exactly 1 + Assert-MockCalled -CommandName New-SimpleVirtualDisk -Exactly 1 + } + } + + Context 'Virtual disk does not exist and ensure set to present But exception happened after virtual disk file was created' { + Mock ` + -CommandName Get-DiskImage ` + -MockWith { $script:mockedDiskImageEmpty } ` + -Verifiable + + Mock ` + -CommandName New-SimpleVirtualDisk ` + -MockWith { throw } ` + -Verifiable + + Mock ` + -CommandName Test-Path ` + -MockWith { $true } ` + -Verifiable + + Mock ` + -CommandName Remove-Item ` + -Verifiable + + $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') + It 'Should not throw an exception and should remove the created virtual disk file.' { + { + Set-TargetResource ` + -FilePathWithExtension $script:mockedDiskImageAttachedVhdx.ImagePath ` + -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` + -DiskFormat $extension ` + -Ensure 'Present' ` + -Verbose + } | Should -Not -Throw + } + + It 'Should only call required mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-DiskImage -Exactly 1 + Assert-MockCalled -CommandName New-SimpleVirtualDisk -Exactly 1 + Assert-MockCalled -CommandName Test-Path -Exactly 1 + Assert-MockCalled -CommandName Remove-Item -Exactly 1 + } + } + } + + Describe 'DSC_VirtualHardDisk\Test-TargetResource' { + Context 'Virtual disk does not exist and ensure set to present' { + Mock ` + -CommandName Get-DiskImage ` + -MockWith { $script:mockedDiskImageEmpty } ` + -Verifiable + + $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') + It 'Should return false.' { + Test-TargetResource ` + -FilePathWithExtension $script:mockedDiskImageAttachedVhdx.ImagePath ` + -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` + -DiskFormat $extension ` + -Ensure 'Present' ` + -Verbose + | Should -Be $false + } + + It 'Should only call required mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-DiskImage -Exactly 1 + } + } + + Context 'Virtual disk exists but is not mounted while ensure set to present' { + Mock ` + -CommandName Get-DiskImage ` + -MockWith { $script:mockedDiskImageNotAttachedVhdx } ` + -Verifiable + + $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') + It 'Should return false.' { + Test-TargetResource ` + -FilePathWithExtension $script:mockedDiskImageAttachedVhdx.ImagePath ` + -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` + -DiskFormat $extension ` + -Ensure 'Present' ` + -Verbose + | Should -Be $false + } + + It 'Should only call required mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-DiskImage -Exactly 1 + } + } + + Context 'Virtual disk does not exist and ensure set to absent' { + Mock ` + -CommandName Get-DiskImage ` + -MockWith { $script:mockedDiskImageEmpty } ` + -Verifiable + + $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') + It 'Should return true' { + Test-TargetResource ` + -FilePathWithExtension $script:mockedDiskImageAttachedVhdx.ImagePath ` + -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` + -DiskFormat $extension ` + -Ensure 'Absent' ` + -Verbose + | Should -Be $true + } + + It 'Should only call required mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-DiskImage -Exactly 1 + } + } + + Context 'Virtual disk exists, is mounted and ensure set to present' { + Mock ` + -CommandName Get-DiskImage ` + -MockWith { $script:mockedDiskImageAttachedVhdx } ` + -Verifiable + + $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') + It 'Should return true' { + Test-TargetResource ` + -FilePathWithExtension $script:mockedDiskImageAttachedVhdx.ImagePath ` + -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` + -DiskFormat $extension ` + -Ensure 'Present' ` + -Verbose + | Should -Be $true + } + + It 'Should only call required mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-DiskImage -Exactly 1 + } + } + + Context 'Virtual disk exists but is mounted while ensure set to absent' { + Mock ` + -CommandName Get-DiskImage ` + -MockWith { $script:mockedDiskImageAttachedVhdx } ` + -Verifiable + + $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') + It 'Should return false.' { + Test-TargetResource ` + -FilePathWithExtension $script:mockedDiskImageAttachedVhdx.ImagePath ` + -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` + -DiskFormat $extension ` + -Ensure 'absent' ` + -Verbose + | Should -Be $false + } + + It 'Should only call required mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-DiskImage -Exactly 1 + } + } + + Context 'Virtual disk exists but is not mounted while ensure set to absent' { + Mock ` + -CommandName Get-DiskImage ` + -MockWith { $script:mockedDiskImageNotAttachedVhdx } ` + -Verifiable + + $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') + It 'Should return true.' { + Test-TargetResource ` + -FilePathWithExtension $script:mockedDiskImageAttachedVhdx.ImagePath ` + -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` + -DiskFormat $extension ` + -Ensure 'absent' ` + -Verbose + | Should -Be $true + } + + It 'Should only call required mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-DiskImage -Exactly 1 + } + } + + } + #endregion + } +} +finally +{ + Invoke-TestCleanup +} From 811589da17b59033a68747db78fef9cefa85985f Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Sun, 24 Sep 2023 03:40:05 -0700 Subject: [PATCH 04/21] add more tests --- .../DSC_VirtualHardDisk.psm1 | 21 +- ...alHardDisk_CreateFixedSizedVirtualDisk.ps1 | 61 ++-- ..._CreateDynamicallyExpandingVirtualDisk.ps1 | 40 ++- .../VirtualHardDisk.Win32Helpers.psm1} | 0 ...VirtualHardDisk.Win32Helpers.strings.psd1} | 0 tests/Unit/DSC_VirtualHardDisk.Tests.ps1 | 30 +- .../VirtualHardDisk.Win32Helpers.Tests.ps1 | 278 ++++++++++++++++++ 7 files changed, 360 insertions(+), 70 deletions(-) rename source/{DSCResources/DSC_VirtualHardDisk/Win32Helpers.psm1 => Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1} (100%) rename source/{DSCResources/DSC_VirtualHardDisk/en-US/Win32Helpers.strings.psd1 => Modules/VirtualHardDisk.Win32Helpers/en-US/VirtualHardDisk.Win32Helpers.strings.psd1} (100%) create mode 100644 tests/Unit/VirtualHardDisk.Win32Helpers.Tests.ps1 diff --git a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 index 2734626c..13971ad4 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 +++ b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 @@ -5,8 +5,12 @@ Import-Module -Name (Join-Path -Path $modulePath ` -ChildPath (Join-Path -Path 'StorageDsc.Common' ` -ChildPath 'StorageDsc.Common.psm1')) +# Import the VirtualHardDisk Win32Helpers Module. +Import-Module -Name (Join-Path -Path $modulePath ` + -ChildPath (Join-Path -Path 'VirtualHardDisk.Win32Helpers' ` + -ChildPath 'VirtualHardDisk.Win32Helpers.psm1')) + Import-Module -Name (Join-Path -Path $modulePath -ChildPath 'DscResource.Common') -Import-Module $PSScriptRoot\\Win32Helpers.psm1 # Import Localization Strings. $script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' @@ -175,8 +179,18 @@ function Set-TargetResource $($script:localizedData.VirtualDiskDoesNotExistCreatingNowMessage -f $FilePathWithExtension) ) -join '' ) + $folderPath = Split-Path -Parent $FilePathWithExtension + $wasLocationCreated = $false + try { + # Create the location if it doesn't exist. + if (-not (Test-Path -PathType Container $folderPath)) + { + New-Item -ItemType Directory -Path $folderPath + $wasLocationCreated = $true + } + New-SimpleVirtualDisk -VirtualDiskPath $FilePathWithExtension -DiskFormat $diskFormat -DiskType $DiskType -DiskSizeInBytes $DiskSize } catch @@ -187,6 +201,11 @@ function Set-TargetResource Write-Verbose -Message ($script:localizedData.VirtualRemovingCreatedFileMessage -f $VirtualDiskPath) Remove-Item $FilePathWithExtension -verbose } + + if ($wasLocationCreated) + { + Remove-Item -LiteralPath $folderPath + } } } diff --git a/source/Examples/Resources/VirtualHardDisk/1-VirtualHardDisk_CreateFixedSizedVirtualDisk.ps1 b/source/Examples/Resources/VirtualHardDisk/1-VirtualHardDisk_CreateFixedSizedVirtualDisk.ps1 index b92fb8d1..db0c8e0d 100644 --- a/source/Examples/Resources/VirtualHardDisk/1-VirtualHardDisk_CreateFixedSizedVirtualDisk.ps1 +++ b/source/Examples/Resources/VirtualHardDisk/1-VirtualHardDisk_CreateFixedSizedVirtualDisk.ps1 @@ -19,9 +19,8 @@ <# .DESCRIPTION - This configuration will wait for disk 2 to become available, and then make the disk available as - two new formatted volumes, 'G' and 'J', with 'J' using all available space after 'G' has been - created. It also creates a new ReFS formated volume on disk 3 attached as drive letter 'S'. + This configuration will create a fixed sized virtual disk that is 40Gb in size and will format a + NTFS volume named 'new volume' that uses the drive letter E. #> Configuration VirtualHardDisk_CreateFixedSizedVirtualDisk { @@ -29,44 +28,24 @@ Configuration VirtualHardDisk_CreateFixedSizedVirtualDisk Node localhost { - WaitForDisk Disk2 - { - DiskId = 2 - RetryIntervalSec = 60 - RetryCount = 60 - } + # Create new virtual disk + VirtualHardDisk newVhd + { + FilePathWithExtension = C:\myVhds\virtDisk1.vhd + DiskSize = 40Gb + DiskType = 'fixed' + Ensure = 'Present' + } - Disk GVolume - { - DiskId = 2 - DriveLetter = 'G' - Size = 10737418240 - DependsOn = '[WaitForDisk]Disk2' - } - - Disk JVolume - { - DiskId = 2 - DriveLetter = 'J' - FSLabel = 'Data' - DependsOn = '[Disk]GVolume' - } - - WaitForDisk Disk3 - { - DiskId = 3 - RetryIntervalSec = 60 - RetryCount = 60 - } - - Disk SVolume - { - DiskId = 3 - DriveLetter = 'S' - Size = 107374182400 - FSFormat = 'ReFS' - AllocationUnitSize = 64KB - DependsOn = '[WaitForDisk]Disk3' - } + # Create new volume onto the new virtual disk + Disk Volume1 + { + DiskId = 'C:\myVhds\myVHD.vhdx' + DiskIdType = 'Location' + DriveLetter = 'E' + FSLabel = 'new volume' + Size = 20Gb + DependsOn = '[VirtualHardDisk]newVhd' + } } } diff --git a/source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 b/source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 index c7d5107a..a4643224 100644 --- a/source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 +++ b/source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 @@ -19,37 +19,35 @@ <# .DESCRIPTION - This configuration will wait for disk 2 with Unique Id '5E1E50A401000000001517FFFF0AEB84' to become - available, and then make the disk available as two new formatted volumes, 'G' and 'J', with 'J' - using all available space after 'G' has been created. It also creates a new ReFS formated - volume on disk 3 with Unique Id '5E1E50A4010000000029AB39450AC9A5' attached as drive letter 'S'. + This configuration will create a dynamic sized virtual disk that is 40Gb in size and will format a + RefS volume named 'new volume 2' that uses the drive letter F. Note: the directory path in the + FilePathWithExtension parameter must exist. It will not be created for you. #> Configuration VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk { Import-DSCResource -ModuleName StorageDsc - Node localhost { - # Create new virtual disk - VirtualHardDisk newVhd - { - FolderPath = C:\myVhds - FileName = myVHD + # Create new virtual disk + VirtualHardDisk newVhd2 + { + FilePathWithExtension = C:\myVhds\virtDisk2.vhdx DiskSize = 40Gb - DiskFormat = vhdx - DiskType = dynamic - } + DiskType = 'dynamic' + Ensure = 'Present' + } - # Create new volume onto the new virtual disk - Disk Volume1 - { - DiskId = ‘C:\myVhds\myVHD.vhdx’ + # Create new volume onto the new virtual disk + Disk Volume1 + { + DiskId = 'C:\myVhds\myVHD2.vhdx' DiskIdType = 'Location' - DriveLetter = 'E' - FSLabel = 'new volume' + DriveLetter = 'F' + FSLabel = 'new volume 2' + FSFormat = 'ReFS' Size = 20Gb - DependsOn = '[VirtualHardDisk]newVhd' - } + DependsOn = '[VirtualHardDisk]newVhd2' + } } } diff --git a/source/DSCResources/DSC_VirtualHardDisk/Win32Helpers.psm1 b/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 similarity index 100% rename from source/DSCResources/DSC_VirtualHardDisk/Win32Helpers.psm1 rename to source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 diff --git a/source/DSCResources/DSC_VirtualHardDisk/en-US/Win32Helpers.strings.psd1 b/source/Modules/VirtualHardDisk.Win32Helpers/en-US/VirtualHardDisk.Win32Helpers.strings.psd1 similarity index 100% rename from source/DSCResources/DSC_VirtualHardDisk/en-US/Win32Helpers.strings.psd1 rename to source/Modules/VirtualHardDisk.Win32Helpers/en-US/VirtualHardDisk.Win32Helpers.strings.psd1 diff --git a/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 b/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 index 9512cfe8..80eb3b3d 100644 --- a/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 +++ b/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 @@ -40,6 +40,7 @@ try $script:DiskImageSizeAboveVhdMaximum = 2041Gb $script:DiskImageSizeAboveVhdxMaximum = 65Tb $script:DiskImageSize65Gb = 65Gb + $script:MockTestPathCount = 0 $script:mockedDiskImageAttachedVhdx = [pscustomobject] @{ Attached = $true @@ -475,28 +476,43 @@ try } } - Context 'Virtual disk does not exist and ensure set to present But exception happened after virtual disk file was created' { + Context 'When folder does not exist in user provided path but an exception occurs after creating the virtual disk' { + Mock ` -CommandName Get-DiskImage ` -MockWith { $script:mockedDiskImageEmpty } ` -Verifiable + # Folder does not exist on system so return false to go into if block that creates the folder Mock ` - -CommandName New-SimpleVirtualDisk ` - -MockWith { throw } ` + -CommandName Test-Path ` + -ParameterFilter { $script:MockTestPathCount -eq 0 } ` + -MockWith { $script:MockTestPathCount++; $false } ` -Verifiable + # File was created and exists on system so return true to go into if block that deletes file Mock ` -CommandName Test-Path ` + -ParameterFilter { $script:MockTestPathCount -eq 1 } ` -MockWith { $true } ` -Verifiable + Mock ` + -CommandName New-SimpleVirtualDisk ` + -MockWith { throw } ` + -Verifiable + + Mock ` + -CommandName New-Item ` + -Verifiable + Mock ` -CommandName Remove-Item ` -Verifiable + $script:MockTestPathCount = 0 $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') - It 'Should not throw an exception and should remove the created virtual disk file.' { + It 'Should not let exception escape and new folder and file should be deleted' { { Set-TargetResource ` -FilePathWithExtension $script:mockedDiskImageAttachedVhdx.ImagePath ` @@ -510,9 +526,9 @@ try It 'Should only call required mocks' { Assert-VerifiableMock Assert-MockCalled -CommandName Get-DiskImage -Exactly 1 - Assert-MockCalled -CommandName New-SimpleVirtualDisk -Exactly 1 - Assert-MockCalled -CommandName Test-Path -Exactly 1 - Assert-MockCalled -CommandName Remove-Item -Exactly 1 + Assert-MockCalled -CommandName New-Item -Exactly 1 + Assert-MockCalled -CommandName Test-Path -Exactly 2 + Assert-MockCalled -CommandName Remove-Item -Exactly 2 } } } diff --git a/tests/Unit/VirtualHardDisk.Win32Helpers.Tests.ps1 b/tests/Unit/VirtualHardDisk.Win32Helpers.Tests.ps1 new file mode 100644 index 00000000..cf212a74 --- /dev/null +++ b/tests/Unit/VirtualHardDisk.Win32Helpers.Tests.ps1 @@ -0,0 +1,278 @@ +#region HEADER, boilerplate used from StorageDSC.Common.Tests +$script:projectPath = "$PSScriptRoot\..\.." | Convert-Path +$script:projectName = (Get-ChildItem -Path "$script:projectPath\*\*.psd1" | Where-Object -FilterScript { + ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and + $(try + { + Test-ModuleManifest -Path $_.FullName -ErrorAction Stop + } + catch + { + $false + }) + }).BaseName + +$script:parentModule = Get-Module -Name $script:projectName -ListAvailable | Select-Object -First 1 +$script:subModulesFolder = Join-Path -Path $script:parentModule.ModuleBase -ChildPath 'Modules' +Remove-Module -Name $script:parentModule -Force -ErrorAction 'SilentlyContinue' + +$script:subModuleName = (Split-Path -Path $PSCommandPath -Leaf) -replace '\.Tests.ps1' +$script:subModuleFile = Join-Path -Path $script:subModulesFolder -ChildPath "$($script:subModuleName)/$($script:subModuleName).psm1" + +if (-not (Get-Module -Name $script:subModuleFile -ListAvailable)) { + Import-Module $script:subModuleFile -Force -ErrorAction Stop +} +#endregion HEADER + +Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') + +# Begin Testing +InModuleScope $script:subModuleName { + Function New-VirtualDiskUsingWin32 + { + [CmdletBinding()] + [OutputType([System.Int32])] + Param + ( + [Parameter(Mandatory = $true)] + [ref] + $VirtualStorageType, + + [Parameter(Mandatory = $true)] + [System.String] + $VirtualDiskPath, + + [Parameter(Mandatory = $true)] + [UInt32] + $AccessMask, + + [Parameter(Mandatory = $true)] + [System.IntPtr] + $SecurityDescriptor, + + [Parameter(Mandatory = $true)] + [UInt32] + $Flags, + + [Parameter(Mandatory = $true)] + [System.UInt32] + $ProviderSpecificFlags, + + [Parameter(Mandatory = $true)] + [ref] + $CreateVirtualDiskParameters, + + [Parameter(Mandatory = $true)] + [System.IntPtr] + $Overlapped, + + [Parameter(Mandatory = $true)] + [ref] + $Handle + ) + } + + Function Add-VirtualDiskUsingWin32 + { + [CmdletBinding()] + [OutputType([System.Int32])] + Param + ( + [Parameter(Mandatory = $true)] + [ref] + $Handle, + + [Parameter(Mandatory = $true)] + [System.IntPtr] + $SecurityDescriptor, + + [Parameter(Mandatory = $true)] + [System.UInt32] + $Flags, + + [Parameter(Mandatory = $true)] + [System.Int32] + $ProviderSpecificFlags, + + [Parameter(Mandatory = $true)] + [ref] + $AttachVirtualDiskParameters, + + [Parameter(Mandatory = $true)] + [System.IntPtr] + $Overlapped + ) + } + + Function Close-Win32Handle + { + + [CmdletBinding()] + [OutputType([System.Void])] + Param + ( + [Parameter(Mandatory = $true)] + [ref] + $Handle + ) + } + + Function Get-VirtualDiskUsingWin32 + { + + [CmdletBinding()] + [OutputType([System.Int32])] + Param + ( + [Parameter(Mandatory = $true)] + [ref] + $VirtualStorageType, + + [Parameter(Mandatory = $true)] + [System.String] + $VirtualDiskPath, + + [Parameter(Mandatory = $true)] + [System.UInt32] + $AccessMask, + + [Parameter(Mandatory = $true)] + [System.UInt32] + $Flags, + + [Parameter(Mandatory = $true)] + [ref] + $OpenVirtualDiskParameters, + + [Parameter(Mandatory = $true)] + [ref] + $Handle + ) + } + + $script:DiskImageGoodVhdxPath = 'C:\test.vhdx' + $script:AccessDeniedWin32Error = 5 + [ref]$script:TestHandle = [System.IntPtr]::Zero + $script:mockedParams = [pscustomobject] @{ + DiskSizeInBytes = 65Gb + VirtualDiskPath = $script:DiskImageGoodVhdxPath + DiskType = 'dynamic' + DiskFormat = 'vhdx' + } + + Describe 'VirtualHardDisk.Win32Helpers\New-SimpleVirtualDisk' -Tag 'New-SimpleVirtualDisk' { + Context 'Creating and attaching a new virtual disk successfully' { + Mock ` + -CommandName New-VirtualDiskUsingWin32 ` + -MockWith { 0 } ` + -Verifiable + + Mock ` + -CommandName Add-VirtualDiskUsingWin32 ` + -MockWith { 0 } ` + -Verifiable + + It 'Should not throw an exception' { + { + New-SimpleVirtualDisk ` + -VirtualDiskPath $script:mockedParams.VirtualDiskPath ` + -DiskSizeInBytes $script:mockedParams.DiskSizeInBytes ` + -DiskFormat $script:mockedParams.DiskFormat ` + -DiskType $script:mockedParams.DiskType` + -Verbose + } | Should -Not -Throw + } + + It 'Should only call required mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName New-VirtualDiskUsingWin32 -Exactly 1 + Assert-MockCalled -CommandName Add-VirtualDiskUsingWin32 -Exactly 1 + } + } + + Context 'Creating a new virtual disk failed due to exception' { + Mock ` + -CommandName New-VirtualDiskUsingWin32 ` + -MockWith { throw [System.ComponentModel.Win32Exception]::new($AccessDeniedWin32Error) } ` + -Verifiable + + It 'Should throw an exception in creation method' { + { + New-SimpleVirtualDisk ` + -VirtualDiskPath $script:mockedParams.VirtualDiskPath ` + -DiskSizeInBytes $script:mockedParams.DiskSizeInBytes ` + -DiskFormat $script:mockedParams.DiskFormat ` + -DiskType $script:mockedParams.DiskType` + -Verbose + } | Should -Throw + } + + It 'Should only call required mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName New-VirtualDiskUsingWin32 -Exactly 1 + } + } + } + + Describe 'VirtualHardDisk.Win32Helpers\Add-SimpleVirtualDisk' -Tag 'Add-SimpleVirtualDisk' { + Context 'Attaching a virtual disk failed due to exception' { + + Mock ` + -CommandName Get-VirtualDiskHandle ` + -MockWith { $script:TestHandle } ` + -Verifiable + + Mock ` + -CommandName Add-VirtualDiskUsingWin32 ` + -MockWith { throw [System.ComponentModel.Win32Exception]::new($AccessDeniedWin32Error) } ` + -Verifiable + + It 'Should throw an exception during attach function' { + { + Add-SimpleVirtualDisk ` + -VirtualDiskPath $script:mockedParams.VirtualDiskPath ` + -DiskFormat $script:mockedParams.DiskFormat ` + -Handle $script:TestHandle ` + -Verbose + } | Should -Throw + } + + It 'Should only call required mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Add-VirtualDiskUsingWin32 -Exactly 1 + Assert-MockCalled -CommandName Get-VirtualDiskHandle -Exactly 1 + } + } + } + + Describe 'VirtualHardDisk.Win32Helpers\Get-VirtualStorageType' -Tag 'Get-VirtualStorageType' { + Context 'When creating Vhd' { + + Mock ` + -CommandName Get-VirtualDiskHandle ` + -MockWith { $script:TestHandle } ` + -Verifiable + + Mock ` + -CommandName Add-VirtualDiskUsingWin32 ` + -MockWith { throw [System.ComponentModel.Win32Exception]::new($AccessDeniedWin32Error) } ` + -Verifiable + + It 'Should throw an exception during attach function' { + { + Add-SimpleVirtualDisk ` + -VirtualDiskPath $script:mockedParams.VirtualDiskPath ` + -DiskFormat $script:mockedParams.DiskFormat ` + -Handle $script:TestHandle ` + -Verbose + } | Should -Throw + } + + It 'Should only call required mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-VirtualDiskUsingWin32 -Exactly 1 + Assert-MockCalled -CommandName Add-VirtualDiskUsingWin32 -Exactly 1 + } + } + } +} From 7e687e9594e5bc380c10bb9391e0b9e0d4126f0f Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Sun, 24 Sep 2023 19:53:45 -0700 Subject: [PATCH 05/21] Add more unit tests --- source/DSCResources/DSC_Disk/DSC_Disk.psm1 | 2 +- .../DSC_VirtualHardDisk.psm1 | 234 +++++++------ .../DSC_VirtualHardDisk.schema.mof | 3 +- .../en-US/DSC_VirtualHardDisk.strings.psd1 | 2 + ...alHardDisk_CreateFixedSizedVirtualDisk.ps1 | 4 +- ..._CreateDynamicallyExpandingVirtualDisk.ps1 | 6 +- .../VirtualHardDisk.Win32Helpers.psm1 | 1 - tests/Unit/DSC_VirtualHardDisk.Tests.ps1 | 308 +++++++++--------- .../VirtualHardDisk.Win32Helpers.Tests.ps1 | 22 +- 9 files changed, 323 insertions(+), 259 deletions(-) diff --git a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 index 6ba3c59c..d6d3ef72 100644 --- a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 +++ b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 @@ -280,7 +280,7 @@ function Set-TargetResource -DiskIdType $DiskIdType } - if ($disk.PartitionStyle -eq 'RAW') + if ($disk.PartitionStyle -eq 'RAW' -bor (-not $disk.PartitionStyle)) { Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " diff --git a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 index 13971ad4..f9fba974 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 +++ b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 @@ -19,17 +19,8 @@ $script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' .SYNOPSIS Returns the current state of the virtual disk. - .PARAMETER FilePathWithExtension + .PARAMETER FilePath Specifies the complete path to the virtual disk file. - - .PARAMETER DiskSize - Specifies the size the new virtual disk. - - .PARAMETER DiskType - Specifies the supported virtual disk type. - - .PARAMETER Ensure - Determines whether the setting should be applied or removed. #> function Get-TargetResource { @@ -40,82 +31,28 @@ function Get-TargetResource [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] - $FilePathWithExtension, - - [Parameter(Mandatory = $true)] - [System.UInt64] - $DiskSize, - - [Parameter()] - [ValidateSet('fixed', 'dynamic')] - [System.String] - $DiskType = 'dynamic', - - [Parameter()] - [ValidateSet('Present','Absent')] - [System.String] - $Ensure = 'Present' + $FilePath ) - # We'll only support local paths with drive letters. - if ($FilePathWithExtension -notmatch '[a-zA-Z]:\\') - { - # AccessPath is invalid - New-InvalidArgumentException ` - -Message $($script:localizedData.VirtualHardDiskPathError -f $FilePathWithExtension) ` - -ArgumentName 'FilePath' - } - - $extension = [System.IO.Path]::GetExtension($FilePathWithExtension).TrimStart('.') - if (($extension -ne 'vhd') -and ($extension -ne 'vhdx')) - { - New-InvalidArgumentException ` - -Message $($script:localizedData.VirtualHardDiskUnsupportedFileType -f $extension) ` - -ArgumentName 'FilePath' - } - Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.GettingVirtualDiskMessage -f $FilePathWithExtension) + $($script:localizedData.GettingVirtualDiskMessage -f $FilePath) ) -join '' ) - <# - Validate DiskFormat values. Minimum value for GPT is around ~10MB and the maximum value for - the vhd format in 2040GB. Maximum for vhdx is 64TB - #> - $isVhdxFormat = $extension -eq 'vhdx' - $isInValidSizeForVhdFormat = ($DiskSize -lt 10MB -bor $DiskSize -gt 2040GB) - $isInValidSizeForVhdxFormat = ($DiskSize -lt 10MB -bor $DiskSize -gt 64TB) - if ((-not $isVhdxFormat -and $isInValidSizeForVhdFormat) -bor - ($IsVhdxFormat -and $isInValidSizeForVhdxFormat)) + $diskImage = Get-DiskImage -ImagePath $FilePath -ErrorAction SilentlyContinue + $Ensure = 'Present' + if (-not $diskImage) { - if ($DiskSize -lt 1GB) - { - $DiskSizeString = ($DiskSize / 1MB).ToString("0.00MB") - } - else - { - $DiskSizeString = ($DiskSize / 1TB).ToString("0.00TB") - } - - $InvalidSizeMsg = $script:localizedData.VhdFormatDiskSizeInvalidMessage - if ($isVhdxFormat) - { - $InvalidSizeMsg = $script:localizedData.VhdxFormatDiskSizeInvalidMessage - } - - New-InvalidArgumentException ` - -Message $($InvalidSizeMsg -f $DiskSizeString) ` - -ArgumentName 'DiskSize' + $Ensure = 'Absent' } - # Get the virtual disk using its location on the system + # Get the virtual disk info using its path on the system return @{ - FilePathWithExtension = $FilePathWithExtension - DiskSize = $DiskSize - DiskFormat = $extension - DiskType = $DiskType - Ensure = $Ensure + FilePath = $diskImage.ImagePath + Attached = $diskImage.Attached + Size = $diskImage.Size + DiskNumber = $diskImage.DiskNumber + Ensure = $Ensure } } # function Get-TargetResource @@ -123,7 +60,7 @@ function Get-TargetResource .SYNOPSIS Returns the current state of the virtual disk. - .PARAMETER FilePathWithExtension + .PARAMETER FilePath Specifies the complete path to the virtual disk file. .PARAMETER DiskSize @@ -146,7 +83,7 @@ function Set-TargetResource [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] - $FilePathWithExtension, + $FilePath, [Parameter(Mandatory = $true)] [System.UInt64] @@ -168,7 +105,9 @@ function Set-TargetResource $Ensure = 'Present' ) - $diskImage = Get-DiskImage -ImagePath $FilePathWithExtension + Assert-ParametersValid -FilePath $FilePath -DiskSize $DiskSize -DiskFormat $DiskFormat + $diskImage = Get-DiskImage -ImagePath $FilePath -ErrorAction SilentlyContinue + if ($Ensure -eq 'Present') { # Disk doesn't exist @@ -176,10 +115,10 @@ function Set-TargetResource { Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualDiskDoesNotExistCreatingNowMessage -f $FilePathWithExtension) + $($script:localizedData.VirtualDiskDoesNotExistCreatingNowMessage -f $FilePath) ) -join '' ) - $folderPath = Split-Path -Parent $FilePathWithExtension + $folderPath = Split-Path -Parent $FilePath $wasLocationCreated = $false try @@ -191,15 +130,15 @@ function Set-TargetResource $wasLocationCreated = $true } - New-SimpleVirtualDisk -VirtualDiskPath $FilePathWithExtension -DiskFormat $diskFormat -DiskType $DiskType -DiskSizeInBytes $DiskSize + New-SimpleVirtualDisk -VirtualDiskPath $FilePath -DiskFormat $DiskFormat -DiskType $DiskType -DiskSizeInBytes $DiskSize } catch { # Remove file if we created it but were unable to attach it. No handles are open when this happens. - if (Test-Path -Path $FilePathWithExtension -PathType Leaf) + if (Test-Path -Path $FilePath -PathType Leaf) { Write-Verbose -Message ($script:localizedData.VirtualRemovingCreatedFileMessage -f $VirtualDiskPath) - Remove-Item $FilePathWithExtension -verbose + Remove-Item $FilePath -verbose } if ($wasLocationCreated) @@ -213,11 +152,11 @@ function Set-TargetResource { Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualDiskNotAttachedMessage -f $FilePathWithExtension) + $($script:localizedData.VirtualDiskNotAttachedMessage -f $FilePath) ) -join '' ) # Virtual disk file exists so lets attempt to attach it to the system. - Add-SimpleVirtualDisk -VirtualDiskPath $FilePathWithExtension -DiskFormat $diskFormat + Add-SimpleVirtualDisk -VirtualDiskPath $FilePath -DiskFormat $DiskFormat } } else @@ -228,10 +167,10 @@ function Set-TargetResource Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " $($script:localizedData.VirtualDiskDismountingImageMessage ` - -f $FilePathWithExtension) + -f $FilePath) ) -join '' ) - Dismount-DiskImage -ImagePath $FilePathWithExtension + Dismount-DiskImage -ImagePath $FilePath } } } # function Set-TargetResource @@ -240,7 +179,7 @@ function Set-TargetResource .SYNOPSIS Returns the current state of the virtual disk. - .PARAMETER FilePathWithExtension + .PARAMETER FilePath Specifies the complete path to the virtual disk file. .PARAMETER DiskSize @@ -264,7 +203,7 @@ function Test-TargetResource [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] - $FilePathWithExtension, + $FilePath, [Parameter(Mandatory = $true)] [System.UInt64] @@ -273,7 +212,7 @@ function Test-TargetResource [Parameter()] [ValidateSet('vhd', 'vhdx')] [System.String] - $DiskFormat, + $DiskFormat = 'vhdx', [Parameter()] [ValidateSet('fixed', 'dynamic')] @@ -286,12 +225,13 @@ function Test-TargetResource $Ensure = 'Present' ) + Assert-ParametersValid -FilePath $FilePath -DiskSize $DiskSize -DiskFormat $DiskFormat Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.CheckingVirtualDiskExistsMessage -f $FilePathWithExtension) - ) -join '' ) + "$($MyInvocation.MyCommand): " + $($script:localizedData.CheckingVirtualDiskExistsMessage -f $FilePath) + ) -join '' ) - $diskImage = Get-DiskImage -ImagePath $FilePathWithExtension + $diskImage = Get-DiskImage -ImagePath $FilePath -ErrorAction SilentlyContinue if ($Ensure -eq 'Present') { @@ -300,7 +240,7 @@ function Test-TargetResource { Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualDiskCurrentlyAttachedMessage -f $FilePathWithExtension) + $($script:localizedData.VirtualDiskCurrentlyAttachedMessage -f $FilePath) ) -join '' ) return $true @@ -308,7 +248,7 @@ function Test-TargetResource Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualDiskDoesNotExistMessage -f $FilePathWithExtension) + $($script:localizedData.VirtualDiskDoesNotExistMessage -f $FilePath) ) -join '' ) return $false @@ -320,7 +260,7 @@ function Test-TargetResource { Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualDiskCurrentlyAttachedButShouldNotBeMessage -f $FilePathWithExtension) + $($script:localizedData.VirtualDiskCurrentlyAttachedButShouldNotBeMessage -f $FilePath) ) -join '' ) return $false @@ -328,11 +268,107 @@ function Test-TargetResource Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualDiskDoesNotExistMessage -f $FilePathWithExtension) + $($script:localizedData.VirtualDiskDoesNotExistMessage -f $FilePath) ) -join '' ) return $true } } # function Test-TargetResource +<# +.SYNOPSIS + Validates parameters for both set and test operations. + + .PARAMETER FilePath + Specifies the complete path to the virtual disk file. + + .PARAMETER DiskSize + Specifies the size of new virtual disk. + + .PARAMETER DiskFormat + Specifies the supported virtual disk format. Currently only the vhd and vhdx formats are supported. +#> +function Assert-ParametersValid +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $FilePath, + + [Parameter(Mandatory = $true)] + [System.UInt64] + $DiskSize, + + [Parameter(Mandatory = $true)] + [ValidateSet('vhd', 'vhdx')] + [System.String] + $DiskFormat + ) + + # We'll only support local paths with drive letters. + if ($FilePath -notmatch '[a-zA-Z]:\\') + { + # AccessPath is invalid + New-InvalidArgumentException ` + -Message $($script:localizedData.VirtualHardDiskPathError -f $FilePath) ` + -ArgumentName 'FilePath' + } + + $extension = [System.IO.Path]::GetExtension($FilePath).TrimStart('.') + if ($extension) + { + if (($extension -ne 'vhd') -and ($extension -ne 'vhdx')) + { + New-InvalidArgumentException ` + -Message $($script:localizedData.VirtualHardDiskUnsupportedFileType -f $extension) ` + -ArgumentName 'FilePath' + } + elseif ($extension -ne $DiskFormat) + { + New-InvalidArgumentException ` + -Message $($script:localizedData.VirtualHardExtensionAndFormatMismatchError -f $FilePath, $extension, $DiskFormat) ` + -ArgumentName 'FilePath' + } + } + else + { + New-InvalidArgumentException ` + -Message $($script:localizedData.VirtualHardNoExtensionError -f $FilePath) ` + -ArgumentName 'FilePath' + } + + <# + Validate DiskFormat values. Minimum value for GPT is around ~10MB and the maximum value for + the vhd format in 2040GB. Maximum for vhdx is 64TB + #> + $isVhdxFormat = $DiskFormat -eq 'vhdx' + $isInValidSizeForVhdFormat = ($DiskSize -lt 10MB -bor $DiskSize -gt 2040GB) + $isInValidSizeForVhdxFormat = ($DiskSize -lt 10MB -bor $DiskSize -gt 64TB) + if ((-not $isVhdxFormat -and $isInValidSizeForVhdFormat) -bor + ($IsVhdxFormat -and $isInValidSizeForVhdxFormat)) + { + if ($DiskSize -lt 1GB) + { + $DiskSizeString = ($DiskSize / 1MB).ToString("0.00MB") + } + else + { + $DiskSizeString = ($DiskSize / 1TB).ToString("0.00TB") + } + + $InvalidSizeMsg = $script:localizedData.VhdFormatDiskSizeInvalidMessage + if ($isVhdxFormat) + { + $InvalidSizeMsg = $script:localizedData.VhdxFormatDiskSizeInvalidMessage + } + + New-InvalidArgumentException ` + -Message $($InvalidSizeMsg -f $DiskSizeString) ` + -ArgumentName 'DiskSize' + } +} # Assert-ParametersValid + Export-ModuleMember -Function *-TargetResource diff --git a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof index aef5a4ec..95344c45 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof +++ b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof @@ -2,8 +2,9 @@ [ClassVersion("1.0.0.0"), FriendlyName("VirtualHardDisk")] class DSC_VirtualHardDisk : OMI_BaseResource { - [Key, Description("Specifies the full path to the virtual disk file that will be created or attached. This includes the files extension.")] String FilePathWithExtension; + [Key, Description("Specifies the full path to the virtual disk file that will be created or attached. This may or may not include the extension, but the extension must match the disk format.")] String FilePath; [Required, Description("Specifies the size the of virtual disk.")] Uint64 DiskSize; [Write, Description("Specifies the disk type the virtual disk should use. Defaults to dynamic."), ValueMap{"Fixed","Dynamic"}, Values{"Fixed","Dynamic"}] String DiskType; + [Write, Description("Specifies the disk format the virtual disk should use. Defaults to vhdx."), ValueMap{"vhd","vhdx"}, Values{"vhd","vhdx"}] String DiskFormat; [Write, Description("Determines whether the virtual disk should be created and mounted or not."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; }; diff --git a/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 b/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 index b994f404..efbde7bf 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 +++ b/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 @@ -10,4 +10,6 @@ ConvertFrom-StringData @' VirtualDiskDismountingImageMessage = The virtual disk located at '{0}' is dismounting. VirtualHardDiskUnsupportedFileType = The file type .{0} is not supported. Only .vhd and .vhdx file types are supported. VirtualHardDiskPathError = The path '{0}' must be a fully qualified path that starts with a Drive Letter. + VirtualHardExtensionAndFormatMismatchError = The path you entered '{0}' has extension '{1}' but the disk format entered is '{2}'. Both the extension and format must match. + VirtualHardNoExtensionError = The path '{0}' does not contain an extension. Supported extension types are '.vhd' and '.vhdx'. '@ diff --git a/source/Examples/Resources/VirtualHardDisk/1-VirtualHardDisk_CreateFixedSizedVirtualDisk.ps1 b/source/Examples/Resources/VirtualHardDisk/1-VirtualHardDisk_CreateFixedSizedVirtualDisk.ps1 index db0c8e0d..15b649c6 100644 --- a/source/Examples/Resources/VirtualHardDisk/1-VirtualHardDisk_CreateFixedSizedVirtualDisk.ps1 +++ b/source/Examples/Resources/VirtualHardDisk/1-VirtualHardDisk_CreateFixedSizedVirtualDisk.ps1 @@ -31,7 +31,7 @@ Configuration VirtualHardDisk_CreateFixedSizedVirtualDisk # Create new virtual disk VirtualHardDisk newVhd { - FilePathWithExtension = C:\myVhds\virtDisk1.vhd + FilePath = C:\myVhds\virtDisk1.vhd DiskSize = 40Gb DiskType = 'fixed' Ensure = 'Present' @@ -40,7 +40,7 @@ Configuration VirtualHardDisk_CreateFixedSizedVirtualDisk # Create new volume onto the new virtual disk Disk Volume1 { - DiskId = 'C:\myVhds\myVHD.vhdx' + DiskId = 'C:\myVhds\virtDisk1.vhd' DiskIdType = 'Location' DriveLetter = 'E' FSLabel = 'new volume' diff --git a/source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 b/source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 index a4643224..190f45e1 100644 --- a/source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 +++ b/source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 @@ -21,7 +21,7 @@ .DESCRIPTION This configuration will create a dynamic sized virtual disk that is 40Gb in size and will format a RefS volume named 'new volume 2' that uses the drive letter F. Note: the directory path in the - FilePathWithExtension parameter must exist. It will not be created for you. + FilePath parameter must exist. It will not be created for you. #> Configuration VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk { @@ -32,7 +32,7 @@ Configuration VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk # Create new virtual disk VirtualHardDisk newVhd2 { - FilePathWithExtension = C:\myVhds\virtDisk2.vhdx + FilePath = C:\myVhds\virtDisk2.vhdx DiskSize = 40Gb DiskType = 'dynamic' Ensure = 'Present' @@ -41,7 +41,7 @@ Configuration VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk # Create new volume onto the new virtual disk Disk Volume1 { - DiskId = 'C:\myVhds\myVHD2.vhdx' + DiskId = 'C:\myVhds\virtDisk2.vhdx' DiskIdType = 'Location' DriveLetter = 'F' FSLabel = 'new volume 2' diff --git a/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 b/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 index bf8c7d03..1fb2cab3 100644 --- a/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 +++ b/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 @@ -537,7 +537,6 @@ function Add-SimpleVirtualDisk function Get-VirtualDiskHandle { [CmdletBinding()] - param ( [Parameter(Mandatory = $true)] diff --git a/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 b/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 index 80eb3b3d..80ad2527 100644 --- a/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 +++ b/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 @@ -36,6 +36,7 @@ try $script:DiskImageBadPath = '\\test.vhdx' $script:DiskImageGoodVhdPath = 'C:\test.vhd' $script:DiskImageNonVirtDiskPath = 'C:\test.text' + $script:DiskImageVirtDiskPathWithoutExtension = 'C:\test' $script:DiskImageSizeBelowVirtDiskMinimum = 9Mb $script:DiskImageSizeAboveVhdMaximum = 2041Gb $script:DiskImageSizeAboveVhdxMaximum = 65Tb @@ -43,27 +44,47 @@ try $script:MockTestPathCount = 0 $script:mockedDiskImageAttachedVhdx = [pscustomobject] @{ - Attached = $true - ImagePath = $script:DiskImageGoodVhdxPath - Size = 100GB + Attached = $true + ImagePath = $script:DiskImageGoodVhdxPath + Size = 100GB + DiskNumber = 2 } $script:mockedDiskImageAttachedVhd = [pscustomobject] @{ - Attached = $true - ImagePath = $script:DiskImageGoodVhdPath - Size = 100GB + Attached = $true + ImagePath = $script:DiskImageGoodVhdPath + Size = 100GB + DiskNumber = 2 } $script:mockedDiskImageNotAttachedVhdx = [pscustomobject] @{ - Attached = $false - ImagePath = $script:DiskImageGoodVhdxPath - Size = 100GB + Attached = $false + ImagePath = $script:DiskImageGoodVhdxPath + Size = 100GB + DiskNumber = 2 } $script:mockedDiskImageNotAttachedVhd = [pscustomobject] @{ - Attached = $false - ImagePath = $script:DiskImageGoodVhdPath - Size = 100GB + Attached = $false + ImagePath = $script:DiskImageGoodVhdPath + Size = 100GB + DiskNumber = 2 + } + + $script:GetTargetOutputWhenBadPath = [pscustomobject] @{ + FilePath = $null + Attached = $null + Size = $null + DiskNumber = $null + Ensure = 'Absent' + } + + $script:GetTargetOutputWhenPathGood = [pscustomobject] @{ + FilePath = $mockedDiskImageAttachedVhdx.ImagePath + Attached = $mockedDiskImageAttachedVhdx.Attached + Size = $mockedDiskImageAttachedVhdx.Size + DiskNumber = $mockedDiskImageAttachedVhdx.DiskNumber + Ensure = 'Present' } $script:mockedDiskImageEmpty = $null @@ -112,129 +133,70 @@ try $DiskType ) } - Function New-VirtualDiskUsingWin32 - { - [CmdletBinding()] - [OutputType([System.Int32])] - Param - ( - [Parameter(Mandatory = $true)] - [ref] - $VirtualStorageType, - [Parameter(Mandatory = $true)] - [System.String] - $VirtualDiskPath, - - [Parameter(Mandatory = $true)] - [UInt32] - $AccessMask, + Describe 'DSC_VirtualHardDisk\Get-TargetResource' { + Context 'When file path does not exist or was never mounted' { - [Parameter(Mandatory = $true)] - [System.IntPtr] - $SecurityDescriptor, + Mock ` + -CommandName Get-DiskImage ` + -MockWith { $script:mockedDiskImageEmpty } ` + -Verifiable - [Parameter(Mandatory = $true)] - [UInt32] - $Flags, + $resource = Get-TargetResource -FilePath $script:DiskImageBadPath -Verbose - [Parameter(Mandatory = $true)] - [System.UInt32] - $ProviderSpecificFlags, + It "Should return DiskNumber $($script:GetTargetOutputWhenBadPath.DiskNumber)" { + $resource.DiskNumber | Should -Be $script:GetTargetOutputWhenBadPath.DiskNumber + } - [Parameter(Mandatory = $true)] - [ref] - $CreateVirtualDiskParameters, + It "Should return FilePath $($script:GetTargetOutputWhenBadPath.FilePath)" { + $resource.FilePath | Should -Be $script:GetTargetOutputWhenBadPath.FilePath + } - [Parameter(Mandatory = $true)] - [System.IntPtr] - $Overlapped, + It "Should return Attached $($script:GetTargetOutputWhenBadPath.Attached)" { + $resource.Attached | Should -Be $script:GetTargetOutputWhenBadPath.Attached + } - [Parameter(Mandatory = $true)] - [ref] - $Handle - ) - } + It "Should return Size $($script:GetTargetOutputWhenBadPath.Size)" { + $resource.Size | Should -Be $script:GetTargetOutputWhenBadPath.Size + } - Function Add-VirtualDiskUsingWin32 - { - [CmdletBinding()] - [OutputType([System.Int32])] - Param - ( - [Parameter(Mandatory = $true)] - [ref] - $Handle, + It "Should return Ensure $($script:GetTargetOutputWhenBadPath.Ensure)" { + $resource.Ensure | Should -Be $script:GetTargetOutputWhenBadPath.Ensure + } + } - [Parameter(Mandatory = $true)] - [System.IntPtr] - $SecurityDescriptor, + Context 'When file path does exist and was mounted at one point' { + Mock ` + -CommandName Get-DiskImage ` + -MockWith { $script:mockedDiskImageAttachedVhdx } ` + -Verifiable - [Parameter(Mandatory = $true)] - [System.UInt32] - $Flags, + $resource = Get-TargetResource -FilePath $script:DiskImageGoodVhdxPath -Verbose - [Parameter(Mandatory = $true)] - [System.Int32] - $ProviderSpecificFlags, + It "Should return DiskNumber $($script:GetTargetOutputWhenPathGood.DiskNumber)" { + $resource.DiskNumber | Should -Be $script:GetTargetOutputWhenPathGood.DiskNumber + } - [Parameter(Mandatory = $true)] - [ref] - $AttachVirtualDiskParameters, + It "Should return FilePath $($script:GetTargetOutputWhenPathGood.FilePath)" { + $resource.FilePath | Should -Be $script:GetTargetOutputWhenPathGood.FilePath + } - [Parameter(Mandatory = $true)] - [System.IntPtr] - $Overlapped - ) - } + It "Should return Attached $($script:GetTargetOutputWhenPathGood.Attached)" { + $resource.Attached | Should -Be $script:GetTargetOutputWhenPathGood.Attached + } - Function Close-Win32Handle - { + It "Should return Size $($script:GetTargetOutputWhenPathGood.Size)" { + $resource.Size | Should -Be $script:GetTargetOutputWhenPathGood.Size + } - [CmdletBinding()] - [OutputType([System.Void])] - Param - ( - [Parameter(Mandatory = $true)] - [ref] - $Handle - ) + It "Should return Ensure $($script:GetTargetOutputWhenPathGood.Ensure)" { + $resource.Ensure | Should -Be $script:GetTargetOutputWhenPathGood.Ensure + } + } } - Function Get-VirtualDiskUsingWin32 - { - - [CmdletBinding()] - [OutputType([System.Int32])] - Param - ( - [Parameter(Mandatory = $true)] - [ref] - $VirtualStorageType, - - [Parameter(Mandatory = $true)] - [System.String] - $VirtualDiskPath, - - [Parameter(Mandatory = $true)] - [System.UInt32] - $AccessMask, - - [Parameter(Mandatory = $true)] - [System.UInt32] - $Flags, - - [Parameter(Mandatory = $true)] - [ref] - $OpenVirtualDiskParameters, - - [Parameter(Mandatory = $true)] - [ref] - $Handle - ) - } + Describe 'DSC_VirtualHardDisk\Set-TargetResource' { - Describe 'DSC_VirtualHardDisk\Get-TargetResource' { Context 'When file path is not fully qualified' { $errorRecord = Get-InvalidArgumentRecord ` @@ -244,9 +206,11 @@ try It 'Should throw invalid argument error when path is not fully qualified' { { - Get-TargetResource ` - -FilePathWithExtension $DiskImageBadPath ` + Set-TargetResource ` + -FilePath $DiskImageBadPath ` -DiskSize $DiskImageSize65Gb ` + -DiskFormat 'vhdx' ` + -Ensure 'Present' ` -Verbose } | Should -Throw $errorRecord } @@ -261,9 +225,48 @@ try It 'Should throw invalid argument error when the file type is not supported' { { - Get-TargetResource ` - -FilePathWithExtension $DiskImageNonVirtDiskPath ` + Set-TargetResource ` + -FilePath $DiskImageNonVirtDiskPath ` -DiskSize $DiskImageSize65Gb ` + -DiskFormat 'vhdx' ` + -Ensure 'Present' ` + -Verbose + } | Should -Throw $errorRecord + } + } + + Context 'When file extension does not match the disk format' { + $extension = [System.IO.Path]::GetExtension($DiskImageGoodVhdPath).TrimStart('.') + $errorRecord = Get-InvalidArgumentRecord ` + -Message ($script:localizedData.VirtualHardExtensionAndFormatMismatchError -f ` + $DiskImageGoodVhdPath, $extension, 'vhdx') ` + -ArgumentName 'FilePath' + + It 'Should throw invalid argument error when the file type and filepath extension do not match' { + { + Set-TargetResource ` + -FilePath $DiskImageGoodVhdPath ` + -DiskSize $DiskImageSize65Gb ` + -DiskFormat 'vhdx' ` + -Ensure 'Present' ` + -Verbose + } | Should -Throw $errorRecord + } + } + + Context 'When file extension is not present in the file path' { + $errorRecord = Get-InvalidArgumentRecord ` + -Message ($script:localizedData.VirtualHardNoExtensionError -f ` + $script:DiskImageVirtDiskPathWithoutExtension) ` + -ArgumentName 'FilePath' + + It 'Should throw invalid argument error when the file type and filepath extension do not match' { + { + Set-TargetResource ` + -FilePath $script:DiskImageVirtDiskPathWithoutExtension ` + -DiskSize $DiskImageSize65Gb ` + -DiskFormat 'vhdx' ` + -Ensure 'Present' ` -Verbose } | Should -Throw $errorRecord } @@ -279,9 +282,11 @@ try It 'Should throw invalid argument error when the provided size is below the minimum for the vhd format' { { - Get-TargetResource ` - -FilePathWithExtension $DiskImageGoodVhdPath ` + Set-TargetResource ` + -FilePath $DiskImageGoodVhdPath ` -DiskSize $DiskImageSizeBelowVirtDiskMinimum ` + -DiskFormat 'vhd' ` + -Ensure 'Present' ` -Verbose } | Should -Throw $errorRecord } @@ -296,9 +301,11 @@ try It 'Should throw invalid argument error when the provided size is below the minimum for the vhdx format' { { - Get-TargetResource ` - -FilePathWithExtension $DiskImageGoodVhdxPath ` + Set-TargetResource ` + -FilePath $DiskImageGoodVhdxPath ` -DiskSize $DiskImageSizeBelowVirtDiskMinimum ` + -DiskFormat 'vhdx' ` + -Ensure 'Present' ` -Verbose } | Should -Throw $errorRecord } @@ -313,9 +320,11 @@ try It 'Should throw invalid argument error when the provided size is above the maximum for the vhd format' { { - Get-TargetResource ` - -FilePathWithExtension $DiskImageGoodVhdPath ` + Set-TargetResource ` + -FilePath $DiskImageGoodVhdPath ` -DiskSize $DiskImageSizeAboveVhdMaximum ` + -DiskFormat 'vhd' ` + -Ensure 'Present' ` -Verbose } | Should -Throw $errorRecord } @@ -330,9 +339,11 @@ try It 'Should throw invalid argument error when the provided size is above the maximum for the vhdx format' { { - Get-TargetResource ` - -FilePathWithExtension $DiskImageGoodVhdxPath ` + Set-TargetResource ` + -FilePath $DiskImageGoodVhdxPath ` -DiskSize $DiskImageSizeAboveVhdxMaximum ` + -DiskFormat 'vhdx' ` + -Ensure 'Present' ` -Verbose } | Should -Throw $errorRecord } @@ -341,9 +352,11 @@ try Context 'When file path to vhdx file is fully qualified' { It 'Should not throw invalid argument error when path is fully qualified' { { - Get-TargetResource ` - -FilePathWithExtension $DiskImageGoodVhdxPath ` + Set-TargetResource ` + -FilePath $DiskImageGoodVhdxPath ` -DiskSize $DiskImageSize65Gb ` + -DiskFormat 'vhdx' ` + -Ensure 'Present' ` -Verbose } | Should -Not -Throw } @@ -352,16 +365,15 @@ try Context 'When file path to vhd is fully qualified' { It 'Should not throw invalid argument error when path is fully qualified' { { - Get-TargetResource ` - -FilePathWithExtension $DiskImageGoodVhdPath ` + Set-TargetResource ` + -FilePath $DiskImageGoodVhdPath ` -DiskSize $DiskImageSize65Gb ` + -DiskFormat 'vhd' ` + -Ensure 'Present' ` -Verbose } | Should -Not -Throw } } - } - - Describe 'DSC_VirtualHardDisk\Set-TargetResource' { Context 'Virtual disk is mounted and ensure set to present' { @@ -374,7 +386,7 @@ try It 'Should not throw an exception' { { Set-TargetResource ` - -FilePathWithExtension $script:mockedDiskImageAttachedVhdx.ImagePath ` + -FilePath $script:mockedDiskImageAttachedVhdx.ImagePath ` -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` -DiskFormat $extension ` -Ensure 'Present' ` @@ -403,7 +415,7 @@ try It 'Should dismount the virtual disk' { { Set-TargetResource ` - -FilePathWithExtension $script:mockedDiskImageAttachedVhdx.ImagePath ` + -FilePath $script:mockedDiskImageAttachedVhdx.ImagePath ` -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` -DiskFormat $extension ` -Ensure 'Absent' ` @@ -432,7 +444,7 @@ try It 'Should Not throw exception' { { Set-TargetResource ` - -FilePathWithExtension $script:mockedDiskImageAttachedVhdx.ImagePath ` + -FilePath $script:mockedDiskImageAttachedVhdx.ImagePath ` -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` -DiskFormat $extension ` -Ensure 'Present' ` @@ -461,7 +473,7 @@ try It 'Should not throw an exception' { { Set-TargetResource ` - -FilePathWithExtension $script:mockedDiskImageAttachedVhdx.ImagePath ` + -FilePath $script:mockedDiskImageAttachedVhdx.ImagePath ` -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` -DiskFormat $extension ` -Ensure 'Present' ` @@ -515,7 +527,7 @@ try It 'Should not let exception escape and new folder and file should be deleted' { { Set-TargetResource ` - -FilePathWithExtension $script:mockedDiskImageAttachedVhdx.ImagePath ` + -FilePath $script:mockedDiskImageAttachedVhdx.ImagePath ` -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` -DiskFormat $extension ` -Ensure 'Present' ` @@ -543,7 +555,7 @@ try $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') It 'Should return false.' { Test-TargetResource ` - -FilePathWithExtension $script:mockedDiskImageAttachedVhdx.ImagePath ` + -FilePath $script:mockedDiskImageAttachedVhdx.ImagePath ` -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` -DiskFormat $extension ` -Ensure 'Present' ` @@ -566,7 +578,7 @@ try $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') It 'Should return false.' { Test-TargetResource ` - -FilePathWithExtension $script:mockedDiskImageAttachedVhdx.ImagePath ` + -FilePath $script:mockedDiskImageAttachedVhdx.ImagePath ` -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` -DiskFormat $extension ` -Ensure 'Present' ` @@ -589,7 +601,7 @@ try $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') It 'Should return true' { Test-TargetResource ` - -FilePathWithExtension $script:mockedDiskImageAttachedVhdx.ImagePath ` + -FilePath $script:mockedDiskImageAttachedVhdx.ImagePath ` -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` -DiskFormat $extension ` -Ensure 'Absent' ` @@ -612,7 +624,7 @@ try $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') It 'Should return true' { Test-TargetResource ` - -FilePathWithExtension $script:mockedDiskImageAttachedVhdx.ImagePath ` + -FilePath $script:mockedDiskImageAttachedVhdx.ImagePath ` -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` -DiskFormat $extension ` -Ensure 'Present' ` @@ -635,7 +647,7 @@ try $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') It 'Should return false.' { Test-TargetResource ` - -FilePathWithExtension $script:mockedDiskImageAttachedVhdx.ImagePath ` + -FilePath $script:mockedDiskImageAttachedVhdx.ImagePath ` -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` -DiskFormat $extension ` -Ensure 'absent' ` @@ -658,7 +670,7 @@ try $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') It 'Should return true.' { Test-TargetResource ` - -FilePathWithExtension $script:mockedDiskImageAttachedVhdx.ImagePath ` + -FilePath $script:mockedDiskImageAttachedVhdx.ImagePath ` -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` -DiskFormat $extension ` -Ensure 'absent' ` @@ -671,9 +683,7 @@ try Assert-MockCalled -CommandName Get-DiskImage -Exactly 1 } } - } - #endregion } } finally diff --git a/tests/Unit/VirtualHardDisk.Win32Helpers.Tests.ps1 b/tests/Unit/VirtualHardDisk.Win32Helpers.Tests.ps1 index cf212a74..527a15ab 100644 --- a/tests/Unit/VirtualHardDisk.Win32Helpers.Tests.ps1 +++ b/tests/Unit/VirtualHardDisk.Win32Helpers.Tests.ps1 @@ -149,6 +149,22 @@ InModuleScope $script:subModuleName { $Handle ) } + function Get-VirtualDiskHandle + { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $VirtualDiskPath, + + [Parameter(Mandatory = $true)] + [ValidateSet('vhd', 'vhdx')] + [System.String] + $DiskFormat + ) + } + $script:DiskImageGoodVhdxPath = 'C:\test.vhdx' $script:AccessDeniedWin32Error = 5 @@ -213,7 +229,7 @@ InModuleScope $script:subModuleName { } } } - +<# Describe 'VirtualHardDisk.Win32Helpers\Add-SimpleVirtualDisk' -Tag 'Add-SimpleVirtualDisk' { Context 'Attaching a virtual disk failed due to exception' { @@ -224,7 +240,7 @@ InModuleScope $script:subModuleName { Mock ` -CommandName Add-VirtualDiskUsingWin32 ` - -MockWith { throw [System.ComponentModel.Win32Exception]::new($AccessDeniedWin32Error) } ` + -MockWith { $script:AccessDeniedWin32Error } ` -Verifiable It 'Should throw an exception during attach function' { @@ -274,5 +290,5 @@ InModuleScope $script:subModuleName { Assert-MockCalled -CommandName Add-VirtualDiskUsingWin32 -Exactly 1 } } - } + }#> } From 262168338ea785ee565d4acd29cc824759e9487a Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Sun, 24 Sep 2023 23:54:29 -0700 Subject: [PATCH 06/21] add more tests for the win32 helper --- .../VirtualHardDisk.Win32Helpers.psm1 | 13 +- .../VirtualHardDisk.Win32Helpers.strings.psd1 | 6 +- .../VirtualHardDisk.Win32Helpers.Tests.ps1 | 189 ++++++++++++------ 3 files changed, 140 insertions(+), 68 deletions(-) diff --git a/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 b/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 index 1fb2cab3..93be08ff 100644 --- a/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 +++ b/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 @@ -413,8 +413,8 @@ function New-SimpleVirtualDisk if ($result -ne 0) { - Write-Error -Message ($script:localizedData.CreateVirtualDiskError -f $result) - throw [System.ComponentModel.Win32Exception]::new($result); + Write-Verbose -Message ($script:localizedData.CreateVirtualDiskError -f $result) + throw [System.ComponentModel.Win32Exception]::new($result) } Write-Verbose -Message ($script:localizedData.VirtualDiskCreatedSuccessfully -f $VirtualDiskPath) @@ -510,8 +510,8 @@ function Add-SimpleVirtualDisk if ($result -ne 0) { - Write-Error -Message ($script:localizedData.AttachVirtualDiskError -f $result) - throw [System.ComponentModel.Win32Exception]::new($result); + Write-Verbose -Message ($script:localizedData.AttachVirtualDiskError -f $result) + throw [System.ComponentModel.Win32Exception]::new($result) } Write-Verbose -Message ($script:localizedData.VirtualDiskAttachedSuccessfully -f $VirtualDiskPath) @@ -570,8 +570,8 @@ function Get-VirtualDiskHandle if ($result -ne 0) { - Write-Error -Message ($script:localizedData.OpenVirtualDiskError -f $result) - throw [System.ComponentModel.Win32Exception]::new($result); + Write-Verbose -Message ($script:localizedData.OpenVirtualDiskError -f $result) + throw [System.ComponentModel.Win32Exception]::new($result) } Write-Verbose -Message ($script:localizedData.VirtualDiskOpenedSuccessfully -f $VirtualDiskPath) @@ -606,7 +606,6 @@ function Get-VirtualStorageType $virtualStorageType.DeviceId = [VirtDisk.Helper]::VIRTUAL_STORAGE_TYPE_DEVICE_VHDX if ($DiskFormat -eq 'vhd') { - $virtualStorageType.VendorId = [VirtDisk.Helper]::VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT $virtualStorageType.DeviceId = [VirtDisk.Helper]::VIRTUAL_STORAGE_TYPE_DEVICE_VHD } diff --git a/source/Modules/VirtualHardDisk.Win32Helpers/en-US/VirtualHardDisk.Win32Helpers.strings.psd1 b/source/Modules/VirtualHardDisk.Win32Helpers/en-US/VirtualHardDisk.Win32Helpers.strings.psd1 index 9a351abc..ccf1de76 100644 --- a/source/Modules/VirtualHardDisk.Win32Helpers/en-US/VirtualHardDisk.Win32Helpers.strings.psd1 +++ b/source/Modules/VirtualHardDisk.Win32Helpers/en-US/VirtualHardDisk.Win32Helpers.strings.psd1 @@ -1,12 +1,12 @@ ConvertFrom-StringData @' CreatingVirtualDiskMessage = Creating virtual disk at location '{0}'. - CreateVirtualDiskError = Unable to create virtual disk due to error '{0}'. + CreateVirtualDiskError = Unable to create virtual disk due to win32 error '{0}'. VirtualDiskCreatedSuccessfully = Virtual disk created successfully at location: '{0}'. AttachingVirtualDiskMessage = Attaching virtual disk at location '{0}'. - AttachVirtualDiskError = Unable to attach virtual disk due to error '{0}'. + AttachVirtualDiskError = Unable to attach virtual disk due to win32 error '{0}'. VirtualDiskAttachedSuccessfully = Virtual disk attached successfully at location '{0}'. OpeningVirtualBeforeAttachingMessage = Attempting to open handle to virtual disk at location '{0}. - OpenVirtualDiskError = Unable to open virtual disk handle due to error '{0}'. + OpenVirtualDiskError = Unable to open virtual disk handle due to win32 error '{0}'. VirtualDiskOpenedSuccessfully = Virtual disk handle for location '{0}' opened successfully. VirtualRemovingCreatedFileMessage = The virtual disk file at location '{0}' is being removed due to an error while attempting to attach it to the system. '@ diff --git a/tests/Unit/VirtualHardDisk.Win32Helpers.Tests.ps1 b/tests/Unit/VirtualHardDisk.Win32Helpers.Tests.ps1 index 527a15ab..6589a7a0 100644 --- a/tests/Unit/VirtualHardDisk.Win32Helpers.Tests.ps1 +++ b/tests/Unit/VirtualHardDisk.Win32Helpers.Tests.ps1 @@ -106,7 +106,6 @@ InModuleScope $script:subModuleName { Function Close-Win32Handle { - [CmdletBinding()] [OutputType([System.Void])] Param @@ -119,7 +118,6 @@ InModuleScope $script:subModuleName { Function Get-VirtualDiskUsingWin32 { - [CmdletBinding()] [OutputType([System.Int32])] Param @@ -149,25 +147,10 @@ InModuleScope $script:subModuleName { $Handle ) } - function Get-VirtualDiskHandle - { - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $VirtualDiskPath, - - [Parameter(Mandatory = $true)] - [ValidateSet('vhd', 'vhdx')] - [System.String] - $DiskFormat - ) - } - $script:DiskImageGoodVhdxPath = 'C:\test.vhdx' $script:AccessDeniedWin32Error = 5 + $script:vhdDiskFormat = 'vhd' [ref]$script:TestHandle = [System.IntPtr]::Zero $script:mockedParams = [pscustomobject] @{ DiskSizeInBytes = 65Gb @@ -209,19 +192,19 @@ InModuleScope $script:subModuleName { Context 'Creating a new virtual disk failed due to exception' { Mock ` -CommandName New-VirtualDiskUsingWin32 ` - -MockWith { throw [System.ComponentModel.Win32Exception]::new($AccessDeniedWin32Error) } ` + -MockWith { $script:AccessDeniedWin32Error } ` -Verifiable - - It 'Should throw an exception in creation method' { - { - New-SimpleVirtualDisk ` - -VirtualDiskPath $script:mockedParams.VirtualDiskPath ` - -DiskSizeInBytes $script:mockedParams.DiskSizeInBytes ` - -DiskFormat $script:mockedParams.DiskFormat ` - -DiskType $script:mockedParams.DiskType` - -Verbose - } | Should -Throw - } + $exception = [System.ComponentModel.Win32Exception]::new($script:AccessDeniedWin32Error) + It 'Should throw an exception in creation method' { + { + New-SimpleVirtualDisk ` + -VirtualDiskPath $script:mockedParams.VirtualDiskPath ` + -DiskSizeInBytes $script:mockedParams.DiskSizeInBytes ` + -DiskFormat $script:mockedParams.DiskFormat ` + -DiskType $script:mockedParams.DiskType` + -Verbose + } | Should -Throw -ExpectedMessage $exception.Message + } It 'Should only call required mocks' { Assert-VerifiableMock @@ -229,7 +212,7 @@ InModuleScope $script:subModuleName { } } } -<# + Describe 'VirtualHardDisk.Win32Helpers\Add-SimpleVirtualDisk' -Tag 'Add-SimpleVirtualDisk' { Context 'Attaching a virtual disk failed due to exception' { @@ -242,53 +225,143 @@ InModuleScope $script:subModuleName { -CommandName Add-VirtualDiskUsingWin32 ` -MockWith { $script:AccessDeniedWin32Error } ` -Verifiable - - It 'Should throw an exception during attach function' { - { - Add-SimpleVirtualDisk ` - -VirtualDiskPath $script:mockedParams.VirtualDiskPath ` - -DiskFormat $script:mockedParams.DiskFormat ` - -Handle $script:TestHandle ` - -Verbose - } | Should -Throw - } + $exception = [System.ComponentModel.Win32Exception]::new($script:AccessDeniedWin32Error) + It 'Should throw an exception during attach function' { + { + Add-SimpleVirtualDisk ` + -VirtualDiskPath $script:mockedParams.VirtualDiskPath ` + -DiskFormat $script:mockedParams.DiskFormat ` + -Verbose + } | Should -Throw -ExpectedMessage $exception.Message + } It 'Should only call required mocks' { Assert-VerifiableMock - Assert-MockCalled -CommandName Add-VirtualDiskUsingWin32 -Exactly 1 + Assert-MockCalled -CommandName Add-VirtualDiskUsingWin32 -Exactly 2 Assert-MockCalled -CommandName Get-VirtualDiskHandle -Exactly 1 } } - } - Describe 'VirtualHardDisk.Win32Helpers\Get-VirtualStorageType' -Tag 'Get-VirtualStorageType' { - Context 'When creating Vhd' { + Context 'Attaching a virtual disk successfully' { + Mock ` + -CommandName Add-VirtualDiskUsingWin32 ` + -MockWith { 0 } ` + -Verifiable Mock ` -CommandName Get-VirtualDiskHandle ` -MockWith { $script:TestHandle } ` -Verifiable + It 'Should not throw an exception' { + { + Add-SimpleVirtualDisk ` + -VirtualDiskPath $script:mockedParams.VirtualDiskPath ` + -DiskFormat $script:mockedParams.DiskFormat ` + -Verbose + } | Should -Not -Throw + } + + It 'Should only call required mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-VirtualDiskHandle -Exactly 1 + Assert-MockCalled -CommandName Add-VirtualDiskUsingWin32 -Exactly 1 + } + } + } + + Describe 'VirtualHardDisk.Win32Helpers\Get-VirtualDiskHandle' -Tag 'Get-VirtualDiskHandle' { + Context 'Opening a virtual disk file failed due to exception' { + + Mock ` + -CommandName Get-VirtualDiskUsingWin32 ` + -MockWith { $script:AccessDeniedWin32Error } ` + -Verifiable + + $exception = [System.ComponentModel.Win32Exception]::new($script:AccessDeniedWin32Error) + It 'Should throw an exception while attempting to open virtual disk file' { + { + Get-VirtualDiskHandle ` + -VirtualDiskPath $script:mockedParams.VirtualDiskPath ` + -DiskFormat $script:mockedParams.DiskFormat ` + -Verbose + } | Should -Throw -ExpectedMessage $exception.Message + } + + It 'Should only call required mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Get-VirtualDiskUsingWin32 -Exactly 1 + } + } + + Context 'Opening a virtual disk file successfully' { Mock ` - -CommandName Add-VirtualDiskUsingWin32 ` - -MockWith { throw [System.ComponentModel.Win32Exception]::new($AccessDeniedWin32Error) } ` + -CommandName Get-VirtualDiskUsingWin32 ` + -MockWith { 0 } ` -Verifiable - It 'Should throw an exception during attach function' { - { - Add-SimpleVirtualDisk ` - -VirtualDiskPath $script:mockedParams.VirtualDiskPath ` - -DiskFormat $script:mockedParams.DiskFormat ` - -Handle $script:TestHandle ` - -Verbose - } | Should -Throw - } + It 'Should not throw an exception' { + { + Get-VirtualDiskHandle ` + -VirtualDiskPath $script:mockedParams.VirtualDiskPath ` + -DiskFormat $script:mockedParams.DiskFormat ` + -Verbose + } | Should -Not -Throw + } It 'Should only call required mocks' { Assert-VerifiableMock Assert-MockCalled -CommandName Get-VirtualDiskUsingWin32 -Exactly 1 - Assert-MockCalled -CommandName Add-VirtualDiskUsingWin32 -Exactly 1 } } - }#> + } + + Describe 'VirtualHardDisk.Win32Helpers\Get-VirtualStorageType' -Tag 'Get-VirtualStorageType' { + Context 'Storage type requested for vhd disk format' { + $result = Get-VirtualStorageType -DiskFormat $script:vhdDiskFormat + It 'Should not throw an exception' { + { + Get-VirtualStorageType ` + -DiskFormat $script:vhdDiskFormat ` + -Verbose + } | Should -Not -Throw + } + Get-VirtDiskWin32HelperScript + $virtualStorageType = New-Object -TypeName VirtDisk.Helper+VIRTUAL_STORAGE_TYPE + $virtualStorageType.VendorId = [VirtDisk.Helper]::VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT + $virtualStorageType.DeviceId = [VirtDisk.Helper]::VIRTUAL_STORAGE_TYPE_DEVICE_VHD + + It "Should return vendorId $($virtualStorageType.VendorId)" { + $result.VendorId | Should -Be $virtualStorageType.VendorId + } + + It "Should return DeviceId $($virtualStorageType.DeviceId)" { + $result.DeviceId | Should -Be $virtualStorageType.DeviceId + } + } + + Context 'Storage type requested for vhdx disk format' { + $result = Get-VirtualStorageType -DiskFormat $script:mockedParams.DiskFormat + It 'Should not throw an exception' { + { + Get-VirtualStorageType ` + -DiskFormat $script:mockedParams.DiskFormat ` + -Verbose + } | Should -Not -Throw + } + Get-VirtDiskWin32HelperScript + $virtualStorageType = New-Object -TypeName VirtDisk.Helper+VIRTUAL_STORAGE_TYPE + $virtualStorageType.VendorId = [VirtDisk.Helper]::VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT + $virtualStorageType.DeviceId = [VirtDisk.Helper]::VIRTUAL_STORAGE_TYPE_DEVICE_VHDX + + It "Should return vendorId $($virtualStorageType.VendorId)" { + $result.VendorId | Should -Be $virtualStorageType.VendorId + } + + It "Should return DeviceId $($virtualStorageType.DeviceId)" { + $result.DeviceId | Should -Be $virtualStorageType.DeviceId + } + } + + } } From 72324b0d05de0da472f4d3dd50f91daf2f69c4f4 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Mon, 25 Sep 2023 00:39:34 -0700 Subject: [PATCH 07/21] update comments --- .../DSC_VirtualHardDisk.psm1 | 3 + .../DSC_VirtualHardDisk.schema.mof | 2 +- ..._CreateDynamicallyExpandingVirtualDisk.ps1 | 3 +- .../VirtualHardDisk.Win32Helpers.psm1 | 92 +++++++++++++++---- 4 files changed, 79 insertions(+), 21 deletions(-) diff --git a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 index f9fba974..7aa2f0f7 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 +++ b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 @@ -145,6 +145,9 @@ function Set-TargetResource { Remove-Item -LiteralPath $folderPath } + + # Rethrow the exception + throw } } diff --git a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof index 95344c45..4f47d126 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof +++ b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof @@ -4,7 +4,7 @@ class DSC_VirtualHardDisk : OMI_BaseResource { [Key, Description("Specifies the full path to the virtual disk file that will be created or attached. This may or may not include the extension, but the extension must match the disk format.")] String FilePath; [Required, Description("Specifies the size the of virtual disk.")] Uint64 DiskSize; - [Write, Description("Specifies the disk type the virtual disk should use. Defaults to dynamic."), ValueMap{"Fixed","Dynamic"}, Values{"Fixed","Dynamic"}] String DiskType; + [Write, Description("Specifies the disk type the virtual disk should use."), ValueMap{"Fixed","Dynamic"}, Values{"Fixed","Dynamic"}] String DiskType; [Write, Description("Specifies the disk format the virtual disk should use. Defaults to vhdx."), ValueMap{"vhd","vhdx"}, Values{"vhd","vhdx"}] String DiskFormat; [Write, Description("Determines whether the virtual disk should be created and mounted or not."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; }; diff --git a/source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 b/source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 index 190f45e1..e7ff676a 100644 --- a/source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 +++ b/source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 @@ -20,8 +20,7 @@ <# .DESCRIPTION This configuration will create a dynamic sized virtual disk that is 40Gb in size and will format a - RefS volume named 'new volume 2' that uses the drive letter F. Note: the directory path in the - FilePath parameter must exist. It will not be created for you. + RefS volume named 'new volume 2' that uses the drive letter F. #> Configuration VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk { diff --git a/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 b/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 index 93be08ff..44ccd937 100644 --- a/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 +++ b/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 @@ -154,10 +154,35 @@ function Get-VirtDiskWin32HelperScript <# .SYNOPSIS Calls Win32 CreateVirtualDisk api. This is used so we can mock this call - easier + easier. + + .PARAMETER VirtualStorageType + Specifies the type and provider (vendor) of the virtual storage device. .PARAMETER VirtualDiskPath - Specifies the whole path to the virtual disk including the file name. + Specifies the whole path to the virtual disk file. + + .PARAMETER AccessMask + Specifies the bitmask for specifying access rights to a virtual hard disk. + + .PARAMETER SecurityDescriptor + Specifies the security information associated with the virtual disk. + + .PARAMETER Flags + Specifies creation flags for the virtual disk. + + .PARAMETER ProviderSpecificFlags + Specifies flags specific to the type of virtual disk being created. + + .PARAMETER CreateVirtualDiskParameters + Specifies the virtual hard disk creation parameters, providing control over, + and information about, the newly created virtual disk. + + .PARAMETER Overlapped + Specifies the reference to an overlapped structure for asynchronous calls. + + .PARAMETER Handle + Specifies the reference to handle object that represents the newly created virtual disk. #> Function New-VirtualDiskUsingWin32 { @@ -218,10 +243,26 @@ Function New-VirtualDiskUsingWin32 { <# .SYNOPSIS Calls Win32 AttachVirtualDisk api. This is used so we can mock this call - easier + easier. + + .PARAMETER Handle + Specifies the reference to a handle to a virtual disk file. + + .PARAMETER SecurityDescriptor + Specifies the security information associated with the virtual disk. + + .PARAMETER Flags + Specifies attachment flags for the virtual disk. + + .PARAMETER ProviderSpecificFlags + Specifies flags specific to the type of virtual disk being created. + + .PARAMETER AttachVirtualDiskParameters + Specifies the virtual hard disk attach request parameters. + + .PARAMETER Overlapped + Specifies the reference to an overlapped structure for asynchronous calls. - .PARAMETER VirtualDiskPath - Specifies the whole path to the virtual disk including the file name. #> Function Add-VirtualDiskUsingWin32 { @@ -267,10 +308,10 @@ Function Add-VirtualDiskUsingWin32 { <# .SYNOPSIS Calls Win32 CloseHandle api. This is used so we can mock this call - easier + easier. - .PARAMETER VirtualDiskPath - Specifies the file handle to the virtual disk file. + .PARAMETER Handle + Specifies a reference to handle for a file. #> Function Close-Win32Handle { @@ -293,10 +334,25 @@ Function Close-Win32Handle { <# .SYNOPSIS Calls Win32 OpenVirtualDisk api. This is used so we can mock this call - easier + easier. + + .PARAMETER VirtualStorageType + Specifies the type and provider (vendor) of the virtual storage device. .PARAMETER VirtualDiskPath - Specifies the whole path to the virtual disk including the file name. + Specifies the whole path to the virtual disk file. + + .PARAMETER AccessMask + Specifies the bitmask for specifying access rights to a virtual hard disk. + + .PARAMETER Flags + Specifies Open virtual disk flags for the virtual disk. + + .PARAMETER CreateVirtualDiskParameters + Specifies the virtual hard disk open request parameters. + + .PARAMETER Handle + Specifies the reference to handle object that represents the a virtual disk file. #> Function Get-VirtualDiskUsingWin32 { @@ -343,16 +399,16 @@ Function Get-VirtualDiskUsingWin32 { Creates and attaches a virtual disk to the system. .PARAMETER VirtualDiskPath - Specifies the whole path to the virtual disk including the file name. + Specifies the whole path to the virtual disk file. + + .PARAMETER DiskSizeInBytes + Specifies the size of new virtual disk in bytes. .PARAMETER DiskFormat Specifies the supported virtual disk format. .PARAMETER DiskType Specifies the supported virtual disk type. - - .PARAMETER DiskSizeInBytes - Specifies the size of new virtual disk in bytes. #> function New-SimpleVirtualDisk { @@ -388,7 +444,7 @@ function New-SimpleVirtualDisk $createVirtualDiskParameters.Value.Version = [VirtDisk.Helper]::CREATE_VIRTUAL_DISK_VERSION_2 $createVirtualDiskParameters.Value.MaximumSize = $DiskSizeInBytes $securityDescriptor = [System.IntPtr]::Zero - $accessMask = [VirtDisk.Helper]::VIRTUAL_DISK_ACCESS_NONE # Access mask + $accessMask = [VirtDisk.Helper]::VIRTUAL_DISK_ACCESS_NONE $providerSpecificFlags = 0 # No Provider-specific flags. [ref]$handle = [System.IntPtr]::Zero # Handle to the new virtual disk @@ -425,14 +481,14 @@ function New-SimpleVirtualDisk # Close handle Close-Win32Handle $Handle } -} # function New-VirtualDisk +} # function New-SimpleVirtualDisk <# .SYNOPSIS Attaches a virtual disk to the system. .PARAMETER VirtualDiskPath - Specifies the whole path to the virtual disk including the file name. + Specifies the whole path to the virtual disk file. .PARAMETER DiskFormat Specifies the supported virtual disk format. @@ -529,7 +585,7 @@ function Add-SimpleVirtualDisk Opens a handle to a virtual disk on the system. .PARAMETER VirtualDiskPath - Specifies the whole path to the virtual disk including the file name. + Specifies the whole path to the virtual disk file. .PARAMETER DiskFormat Specifies the supported virtual disk format. From 0a611c97796cd4e27664e1e255522a51e0c71bf0 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Mon, 25 Sep 2023 00:43:36 -0700 Subject: [PATCH 08/21] update synopsis and fix test --- .../DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 | 2 +- tests/Unit/DSC_VirtualHardDisk.Tests.ps1 | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 index 7aa2f0f7..306cc5f6 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 +++ b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 @@ -279,7 +279,7 @@ function Test-TargetResource } # function Test-TargetResource <# -.SYNOPSIS + .SYNOPSIS Validates parameters for both set and test operations. .PARAMETER FilePath diff --git a/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 b/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 index 80ad2527..65054e5b 100644 --- a/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 +++ b/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 @@ -511,7 +511,7 @@ try Mock ` -CommandName New-SimpleVirtualDisk ` - -MockWith { throw } ` + -MockWith { throw [System.ComponentModel.Win32Exception]::new($script:AccessDeniedWin32Error) } ` -Verifiable Mock ` @@ -524,6 +524,7 @@ try $script:MockTestPathCount = 0 $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') + $exception = [System.ComponentModel.Win32Exception]::new($script:AccessDeniedWin32Error) It 'Should not let exception escape and new folder and file should be deleted' { { Set-TargetResource ` @@ -532,7 +533,7 @@ try -DiskFormat $extension ` -Ensure 'Present' ` -Verbose - } | Should -Not -Throw + } | Should -Throw -ExpectedMessage $exception.Message } It 'Should only call required mocks' { From 834658c964339534921cfea9f02ac16659790a4f Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Mon, 25 Sep 2023 11:47:23 -0700 Subject: [PATCH 09/21] fix failing tests --- .../DSC_VirtualHardDisk.psm1 | 2 +- .../en-US/DSC_VirtualHardDisk.strings.psd1 | 2 ++ ...ualHardDisk_CreateFixedSizedVirtualDisk.ps1 | 3 ++- ...k_CreateDynamicallyExpandingVirtualDisk.ps1 | 3 ++- .../VirtualHardDisk.Win32Helpers.psm1 | 8 ++++---- .../VirtualHardDisk.Win32Helpers.strings.psd1 | 1 - tests/Unit/DSC_VirtualHardDisk.Tests.ps1 | 18 ++++++------------ 7 files changed, 17 insertions(+), 20 deletions(-) diff --git a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 index 306cc5f6..080cb3df 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 +++ b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 @@ -137,7 +137,7 @@ function Set-TargetResource # Remove file if we created it but were unable to attach it. No handles are open when this happens. if (Test-Path -Path $FilePath -PathType Leaf) { - Write-Verbose -Message ($script:localizedData.VirtualRemovingCreatedFileMessage -f $VirtualDiskPath) + Write-Verbose -Message ($script:localizedData.VirtualRemovingCreatedFileMessage -f $folderPath) Remove-Item $FilePath -verbose } diff --git a/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 b/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 index efbde7bf..93b29517 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 +++ b/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 @@ -12,4 +12,6 @@ ConvertFrom-StringData @' VirtualHardDiskPathError = The path '{0}' must be a fully qualified path that starts with a Drive Letter. VirtualHardExtensionAndFormatMismatchError = The path you entered '{0}' has extension '{1}' but the disk format entered is '{2}'. Both the extension and format must match. VirtualHardNoExtensionError = The path '{0}' does not contain an extension. Supported extension types are '.vhd' and '.vhdx'. + GettingVirtualDiskMessage = Getting virtual disk information for virtual disk located at '{0}. + VirtualRemovingCreatedFileMessage = The virtual disk file at location '{0}' is being removed due to an error while attempting to attach it to the system. '@ diff --git a/source/Examples/Resources/VirtualHardDisk/1-VirtualHardDisk_CreateFixedSizedVirtualDisk.ps1 b/source/Examples/Resources/VirtualHardDisk/1-VirtualHardDisk_CreateFixedSizedVirtualDisk.ps1 index 15b649c6..14bb0276 100644 --- a/source/Examples/Resources/VirtualHardDisk/1-VirtualHardDisk_CreateFixedSizedVirtualDisk.ps1 +++ b/source/Examples/Resources/VirtualHardDisk/1-VirtualHardDisk_CreateFixedSizedVirtualDisk.ps1 @@ -31,8 +31,9 @@ Configuration VirtualHardDisk_CreateFixedSizedVirtualDisk # Create new virtual disk VirtualHardDisk newVhd { - FilePath = C:\myVhds\virtDisk1.vhd + FilePath = 'C:\myVhds\virtDisk1.vhd' DiskSize = 40Gb + DiskFormat = 'vhd' DiskType = 'fixed' Ensure = 'Present' } diff --git a/source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 b/source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 index e7ff676a..1e2ba9d1 100644 --- a/source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 +++ b/source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 @@ -31,8 +31,9 @@ Configuration VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk # Create new virtual disk VirtualHardDisk newVhd2 { - FilePath = C:\myVhds\virtDisk2.vhdx + FilePath = 'C:\myVhds\virtDisk2.vhdx' DiskSize = 40Gb + DiskFormat = 'vhdx' DiskType = 'dynamic' Ensure = 'Present' } diff --git a/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 b/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 index 44ccd937..96c0f17e 100644 --- a/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 +++ b/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 @@ -184,7 +184,7 @@ function Get-VirtDiskWin32HelperScript .PARAMETER Handle Specifies the reference to handle object that represents the newly created virtual disk. #> -Function New-VirtualDiskUsingWin32 { +function New-VirtualDiskUsingWin32 { [CmdletBinding()] [OutputType([System.Int32])] @@ -264,7 +264,7 @@ Function New-VirtualDiskUsingWin32 { Specifies the reference to an overlapped structure for asynchronous calls. #> -Function Add-VirtualDiskUsingWin32 { +function Add-VirtualDiskUsingWin32 { [CmdletBinding()] [OutputType([System.Int32])] @@ -313,7 +313,7 @@ Function Add-VirtualDiskUsingWin32 { .PARAMETER Handle Specifies a reference to handle for a file. #> -Function Close-Win32Handle { +function Close-Win32Handle { [CmdletBinding()] [OutputType([System.Void])] @@ -354,7 +354,7 @@ Function Close-Win32Handle { .PARAMETER Handle Specifies the reference to handle object that represents the a virtual disk file. #> -Function Get-VirtualDiskUsingWin32 { +function Get-VirtualDiskUsingWin32 { [CmdletBinding()] [OutputType([System.Int32])] diff --git a/source/Modules/VirtualHardDisk.Win32Helpers/en-US/VirtualHardDisk.Win32Helpers.strings.psd1 b/source/Modules/VirtualHardDisk.Win32Helpers/en-US/VirtualHardDisk.Win32Helpers.strings.psd1 index ccf1de76..8e3340a7 100644 --- a/source/Modules/VirtualHardDisk.Win32Helpers/en-US/VirtualHardDisk.Win32Helpers.strings.psd1 +++ b/source/Modules/VirtualHardDisk.Win32Helpers/en-US/VirtualHardDisk.Win32Helpers.strings.psd1 @@ -8,5 +8,4 @@ ConvertFrom-StringData @' OpeningVirtualBeforeAttachingMessage = Attempting to open handle to virtual disk at location '{0}. OpenVirtualDiskError = Unable to open virtual disk handle due to win32 error '{0}'. VirtualDiskOpenedSuccessfully = Virtual disk handle for location '{0}' opened successfully. - VirtualRemovingCreatedFileMessage = The virtual disk file at location '{0}' is being removed due to an error while attempting to attach it to the system. '@ diff --git a/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 b/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 index 65054e5b..f0e63feb 100644 --- a/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 +++ b/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 @@ -560,8 +560,7 @@ try -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` -DiskFormat $extension ` -Ensure 'Present' ` - -Verbose - | Should -Be $false + -Verbose | Should -Be $false } It 'Should only call required mocks' { @@ -583,8 +582,7 @@ try -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` -DiskFormat $extension ` -Ensure 'Present' ` - -Verbose - | Should -Be $false + -Verbose | Should -Be $false } It 'Should only call required mocks' { @@ -606,8 +604,7 @@ try -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` -DiskFormat $extension ` -Ensure 'Absent' ` - -Verbose - | Should -Be $true + -Verbose | Should -Be $true } It 'Should only call required mocks' { @@ -629,8 +626,7 @@ try -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` -DiskFormat $extension ` -Ensure 'Present' ` - -Verbose - | Should -Be $true + -Verbose | Should -Be $true } It 'Should only call required mocks' { @@ -652,8 +648,7 @@ try -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` -DiskFormat $extension ` -Ensure 'absent' ` - -Verbose - | Should -Be $false + -Verbose | Should -Be $false } It 'Should only call required mocks' { @@ -675,8 +670,7 @@ try -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` -DiskFormat $extension ` -Ensure 'absent' ` - -Verbose - | Should -Be $true + -Verbose | Should -Be $true } It 'Should only call required mocks' { From de44dc229e562eb4c339e03221374f7b5ccb6b02 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Mon, 25 Sep 2023 13:46:44 -0700 Subject: [PATCH 10/21] fix hqrmtests and add test coverage for disk type is fixed scenario --- .../VirtualHardDisk.Win32Helpers.psm1 | 43 +++++++++++-------- .../VirtualHardDisk.Win32Helpers.Tests.ps1 | 38 +++++++++++++++- 2 files changed, 61 insertions(+), 20 deletions(-) diff --git a/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 b/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 index 96c0f17e..4ff0c21f 100644 --- a/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 +++ b/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 @@ -184,11 +184,11 @@ function Get-VirtDiskWin32HelperScript .PARAMETER Handle Specifies the reference to handle object that represents the newly created virtual disk. #> -function New-VirtualDiskUsingWin32 { - +function New-VirtualDiskUsingWin32 +{ [CmdletBinding()] [OutputType([System.Int32])] - Param + param ( [Parameter(Mandatory = $true)] [ref] @@ -264,11 +264,11 @@ function New-VirtualDiskUsingWin32 { Specifies the reference to an overlapped structure for asynchronous calls. #> -function Add-VirtualDiskUsingWin32 { - +function Add-VirtualDiskUsingWin32 +{ [CmdletBinding()] [OutputType([System.Int32])] - Param + param ( [Parameter(Mandatory = $true)] [ref] @@ -313,11 +313,11 @@ function Add-VirtualDiskUsingWin32 { .PARAMETER Handle Specifies a reference to handle for a file. #> -function Close-Win32Handle { - +function Close-Win32Handle +{ [CmdletBinding()] [OutputType([System.Void])] - Param + param ( [Parameter(Mandatory = $true)] [ref] @@ -354,11 +354,11 @@ function Close-Win32Handle { .PARAMETER Handle Specifies the reference to handle object that represents the a virtual disk file. #> -function Get-VirtualDiskUsingWin32 { - +function Get-VirtualDiskUsingWin32 +{ [CmdletBinding()] [OutputType([System.Int32])] - Param + param ( [Parameter(Mandatory = $true)] [ref] @@ -394,6 +394,7 @@ function Get-VirtualDiskUsingWin32 { $OpenVirtualDiskParameters, $Handle) } # end function Get-VirtualDiskUsingWin32 + <# .SYNOPSIS Creates and attaches a virtual disk to the system. @@ -445,8 +446,10 @@ function New-SimpleVirtualDisk $createVirtualDiskParameters.Value.MaximumSize = $DiskSizeInBytes $securityDescriptor = [System.IntPtr]::Zero $accessMask = [VirtDisk.Helper]::VIRTUAL_DISK_ACCESS_NONE - $providerSpecificFlags = 0 # No Provider-specific flags. - [ref]$handle = [System.IntPtr]::Zero # Handle to the new virtual disk + $providerSpecificFlags = 0 + + # Handle to the new virtual disk + [ref]$handle = [System.IntPtr]::Zero # Virtual disk will be dynamically expanding, up to the size of $DiskSizeInBytes on the parent disk $flags = [VirtDisk.Helper]::CREATE_VIRTUAL_DISK_FLAG_NONE @@ -526,11 +529,11 @@ function Add-SimpleVirtualDisk $Handle = Get-VirtualDiskHandle -VirtualDiskPath $VirtualDiskPath -DiskFormat $DiskFormat } - # Build parameters for AttachVirtualDisk function + # Build parameters for AttachVirtualDisk function. [ref]$attachVirtualDiskParameters = New-Object VirtDisk.Helper+ATTACH_VIRTUAL_DISK_PARAMETERS $attachVirtualDiskParameters.Value.Version = [VirtDisk.Helper]::ATTACH_VIRTUAL_DISK_VERSION_1 - $securityDescriptor = [System.IntPtr]::Zero # Security descriptor - $providerSpecificFlags = 0 # No Provider-specific flag + $securityDescriptor = [System.IntPtr]::Zero + $providerSpecificFlags = 0 $result = 0 <# @@ -608,12 +611,14 @@ function Get-VirtualDiskHandle Write-Verbose -Message ($script:localizedData.OpeningVirtualBeforeAttachingMessage) $vDiskHelper = Get-VirtDiskWin32HelperScript - # Get parameters for OpenVirtualDisk function + # Get parameters for OpenVirtualDisk function. [ref]$virtualStorageType = Get-VirtualStorageType -DiskFormat $DiskFormat [ref]$openVirtualDiskParameters = New-Object VirtDisk.Helper+OPEN_VIRTUAL_DISK_PARAMETERS $openVirtualDiskParameters.Value.Version = [VirtDisk.Helper]::OPEN_VIRTUAL_DISK_VERSION_1 $accessMask = [VirtDisk.Helper]::VIRTUAL_DISK_ACCESS_ALL $flags = [VirtDisk.Helper]::OPEN_VIRTUAL_DISK_FLAG_NONE + + # Handle to the virtual disk. [ref]$handle = [System.IntPtr]::Zero $result = Get-VirtualDiskUsingWin32 ` @@ -654,7 +659,7 @@ function Get-VirtualStorageType $DiskFormat ) - # Create VIRTUAL_STORAGE_TYPE structure + # Create VIRTUAL_STORAGE_TYPE structure. $virtualStorageType = New-Object -TypeName VirtDisk.Helper+VIRTUAL_STORAGE_TYPE # Default to the vhdx file format. diff --git a/tests/Unit/VirtualHardDisk.Win32Helpers.Tests.ps1 b/tests/Unit/VirtualHardDisk.Win32Helpers.Tests.ps1 index 6589a7a0..0eadab56 100644 --- a/tests/Unit/VirtualHardDisk.Win32Helpers.Tests.ps1 +++ b/tests/Unit/VirtualHardDisk.Win32Helpers.Tests.ps1 @@ -159,8 +159,15 @@ InModuleScope $script:subModuleName { DiskFormat = 'vhdx' } + $script:mockedVhdParams = [pscustomobject] @{ + DiskSizeInBytes = 65Gb + VirtualDiskPath = $script:DiskImageGoodVhdxPath + DiskType = 'dynamic' + DiskFormat = 'vhd' + } + Describe 'VirtualHardDisk.Win32Helpers\New-SimpleVirtualDisk' -Tag 'New-SimpleVirtualDisk' { - Context 'Creating and attaching a new virtual disk successfully' { + Context 'Creating and attaching a new virtual disk (vhdx) successfully' { Mock ` -CommandName New-VirtualDiskUsingWin32 ` -MockWith { 0 } ` @@ -189,6 +196,35 @@ InModuleScope $script:subModuleName { } } + Context 'Creating and attaching a new virtual disk (vhd) successfully' { + Mock ` + -CommandName New-VirtualDiskUsingWin32 ` + -MockWith { 0 } ` + -Verifiable + + Mock ` + -CommandName Add-VirtualDiskUsingWin32 ` + -MockWith { 0 } ` + -Verifiable + + It 'Should not throw an exception' { + { + New-SimpleVirtualDisk ` + -VirtualDiskPath $script:mockedVhdParams.VirtualDiskPath ` + -DiskSizeInBytes $script:mockedVhdParams.DiskSizeInBytes ` + -DiskFormat $script:mockedVhdParams.DiskFormat ` + -DiskType $script:mockedVhdParams.DiskType` + -Verbose + } | Should -Not -Throw + } + + It 'Should only call required mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName New-VirtualDiskUsingWin32 -Exactly 1 + Assert-MockCalled -CommandName Add-VirtualDiskUsingWin32 -Exactly 1 + } + } + Context 'Creating a new virtual disk failed due to exception' { Mock ` -CommandName New-VirtualDiskUsingWin32 ` From 66c5bcf689694657bddc7e6541d6362f3a604d63 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Mon, 25 Sep 2023 15:56:41 -0700 Subject: [PATCH 11/21] fix more hqrmtest failures that don't show up in ps5.1 but show up in ps 7 --- .../DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 | 8 +++++--- .../DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 index 080cb3df..d3e9ec0a 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 +++ b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 @@ -85,7 +85,8 @@ function Set-TargetResource [String] $FilePath, - [Parameter(Mandatory = $true)] + [Parameter()] + [ValidateScript({$_ -gt 0})] [System.UInt64] $DiskSize, @@ -208,14 +209,15 @@ function Test-TargetResource [String] $FilePath, - [Parameter(Mandatory = $true)] + [Parameter()] + [ValidateScript({$_ -gt 0})] [System.UInt64] $DiskSize, [Parameter()] [ValidateSet('vhd', 'vhdx')] [System.String] - $DiskFormat = 'vhdx', + $DiskFormat, [Parameter()] [ValidateSet('fixed', 'dynamic')] diff --git a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof index 4f47d126..aa4c1587 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof +++ b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof @@ -3,8 +3,8 @@ class DSC_VirtualHardDisk : OMI_BaseResource { [Key, Description("Specifies the full path to the virtual disk file that will be created or attached. This may or may not include the extension, but the extension must match the disk format.")] String FilePath; - [Required, Description("Specifies the size the of virtual disk.")] Uint64 DiskSize; - [Write, Description("Specifies the disk type the virtual disk should use."), ValueMap{"Fixed","Dynamic"}, Values{"Fixed","Dynamic"}] String DiskType; + [Write, Description("Specifies the size the of virtual disk.")] Uint64 DiskSize; + [Write, Description("Specifies the disk type the virtual disk should use."), ValueMap{"fixed","dynamic"}, Values{"fixed","dynamic"}] String DiskType; [Write, Description("Specifies the disk format the virtual disk should use. Defaults to vhdx."), ValueMap{"vhd","vhdx"}, Values{"vhd","vhdx"}] String DiskFormat; [Write, Description("Determines whether the virtual disk should be created and mounted or not."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; }; From 374103eeee08e8dd30e14a5543bf0c44eeb3ce6f Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Mon, 16 Oct 2023 19:02:01 -0700 Subject: [PATCH 12/21] update resource to add more description about usage and limitations and use safefilehandles instead of closehandle function --- CHANGELOG.md | 2 +- .../DSC_VirtualHardDisk.psm1 | 107 +++++++++--------- .../DSC_VirtualHardDisk.schema.mof | 10 +- .../DSC_VirtualHardDisk/README.md | 48 +++++++- .../en-US/DSC_VirtualHardDisk.strings.psd1 | 26 ++--- ...alHardDisk_CreateFixedSizedVirtualDisk.ps1 | 7 +- ..._CreateDynamicallyExpandingVirtualDisk.ps1 | 11 +- .../VirtualHardDisk.Win32Helpers.psm1 | 104 +++++++++-------- tests/Unit/DSC_VirtualHardDisk.Tests.ps1 | 12 +- .../VirtualHardDisk.Win32Helpers.Tests.ps1 | 35 +++--- 10 files changed, 213 insertions(+), 149 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 050d357b..dae80db9 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] -- Added DSC_VirtualHardDisk resource for creating virtual disks and tests. +- Added DSC_VirtualHardDisk resource for creating virtual disks and tests. - Fixes [Issue #277](https://github.com/dsccommunity/StorageDsc/issues/277) ## [5.1.0] - 2023-02-22 diff --git a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 index d3e9ec0a..aea7d90e 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 +++ b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 @@ -17,10 +17,10 @@ $script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' <# .SYNOPSIS - Returns the current state of the virtual disk. + Returns the current state of the virtual hard disk. .PARAMETER FilePath - Specifies the complete path to the virtual disk file. + Specifies the complete path to the virtual hard disk file. #> function Get-TargetResource { @@ -36,17 +36,18 @@ function Get-TargetResource Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.GettingVirtualDiskMessage -f $FilePath) + $($script:localizedData.GettingVirtualHardDisk -f $FilePath) ) -join '' ) $diskImage = Get-DiskImage -ImagePath $FilePath -ErrorAction SilentlyContinue $Ensure = 'Present' + if (-not $diskImage) { $Ensure = 'Absent' } - # Get the virtual disk info using its path on the system + # Get the virtual hard disk info using its path on the system return @{ FilePath = $diskImage.ImagePath Attached = $diskImage.Attached @@ -58,19 +59,19 @@ function Get-TargetResource <# .SYNOPSIS - Returns the current state of the virtual disk. + Returns the current state of the virtual hard disk. .PARAMETER FilePath - Specifies the complete path to the virtual disk file. + Specifies the complete path to the virtual hard disk file. .PARAMETER DiskSize - Specifies the size of new virtual disk. + Specifies the size of new virtual hard disk. .PARAMETER DiskFormat - Specifies the supported virtual disk format. Currently only the vhd and vhdx formats are supported. + Specifies the supported virtual hard disk format. Currently only the vhd and vhdx formats are supported. .PARAMETER DiskType - Specifies the supported virtual disk type. + Specifies the supported virtual hard disk type. .PARAMETER Ensure Determines whether the setting should be applied or removed. @@ -91,14 +92,14 @@ function Set-TargetResource $DiskSize, [Parameter()] - [ValidateSet('vhd', 'vhdx')] + [ValidateSet('Vhd', 'Vhdx')] [System.String] $DiskFormat, [Parameter()] - [ValidateSet('fixed', 'dynamic')] + [ValidateSet('Fixed', 'Dynamic')] [System.String] - $DiskType = 'dynamic', + $DiskType = 'Dynamic', [Parameter()] [ValidateSet('Present','Absent')] @@ -107,16 +108,17 @@ function Set-TargetResource ) Assert-ParametersValid -FilePath $FilePath -DiskSize $DiskSize -DiskFormat $DiskFormat - $diskImage = Get-DiskImage -ImagePath $FilePath -ErrorAction SilentlyContinue + + $resource = Get-TargetResource -FilePath $FilePath if ($Ensure -eq 'Present') { # Disk doesn't exist - if (-not $diskImage) + if (-not $resource.FilePath) { Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualDiskDoesNotExistCreatingNowMessage -f $FilePath) + $($script:localizedData.VirtualHardDiskDoesNotExistCreatingNow -f $FilePath) ) -join '' ) $folderPath = Split-Path -Parent $FilePath @@ -138,13 +140,13 @@ function Set-TargetResource # Remove file if we created it but were unable to attach it. No handles are open when this happens. if (Test-Path -Path $FilePath -PathType Leaf) { - Write-Verbose -Message ($script:localizedData.VirtualRemovingCreatedFileMessage -f $folderPath) + Write-Verbose -Message ($script:localizedData.RemovingCreatedVirtualHardDiskFile -f $FilePath) Remove-Item $FilePath -verbose } if ($wasLocationCreated) { - Remove-Item -LiteralPath $folderPath + Remove-Item -LiteralPath $folderPath -verbose } # Rethrow the exception @@ -152,25 +154,25 @@ function Set-TargetResource } } - elseif (-not $diskImage.Attached) + elseif (-not $resource.Attached) { Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualDiskNotAttachedMessage -f $FilePath) + $($script:localizedData.VirtualDiskNotAttached -f $FilePath) ) -join '' ) - # Virtual disk file exists so lets attempt to attach it to the system. + # Virtual hard disk file exists so lets attempt to attach it to the system. Add-SimpleVirtualDisk -VirtualDiskPath $FilePath -DiskFormat $DiskFormat } } else { - # Detach the virtual disk if its not suppose to be mounted - if ($diskImage.Attached) + # Detach the virtual hard disk if its not suppose to be attached. + if ($resource.Attached) { Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualDiskDismountingImageMessage ` + $($script:localizedData.VirtualHardDiskDetachingImage ` -f $FilePath) ) -join '' ) @@ -181,19 +183,19 @@ function Set-TargetResource <# .SYNOPSIS - Returns the current state of the virtual disk. + Returns the current state of the virtual hard disk. .PARAMETER FilePath - Specifies the complete path to the virtual disk file. + Specifies the complete path to the virtual hard disk file. .PARAMETER DiskSize - Specifies the size of new virtual disk. + Specifies the size of new virtual hard disk. .PARAMETER DiskFormat - Specifies the supported virtual disk format. Currently only the vhd and vhdx formats are supported. + Specifies the supported virtual hard disk format. Currently only the vhd and vhdx formats are supported. .PARAMETER DiskType - Specifies the supported virtual disk type. + Specifies the supported virtual hard disk type. .PARAMETER Ensure Determines whether the setting should be applied or removed. @@ -215,14 +217,14 @@ function Test-TargetResource $DiskSize, [Parameter()] - [ValidateSet('vhd', 'vhdx')] + [ValidateSet('Vhd', 'Vhdx')] [System.String] $DiskFormat, [Parameter()] - [ValidateSet('fixed', 'dynamic')] + [ValidateSet('Fixed', 'Dynamic')] [System.String] - $DiskType = 'dynamic', + $DiskType = 'Dynamic', [Parameter()] [ValidateSet('Present','Absent')] @@ -231,21 +233,22 @@ function Test-TargetResource ) Assert-ParametersValid -FilePath $FilePath -DiskSize $DiskSize -DiskFormat $DiskFormat + + $resource = Get-TargetResource -FilePath $FilePath + Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.CheckingVirtualDiskExistsMessage -f $FilePath) + $($script:localizedData.CheckingVirtualDiskExists -f $FilePath) ) -join '' ) - $diskImage = Get-DiskImage -ImagePath $FilePath -ErrorAction SilentlyContinue - if ($Ensure -eq 'Present') { - # Found the virtual disk and confirmed its attached to the system. - if ($diskImage.Attached) + # Found the virtual hard disk and confirmed its attached to the system. + if ($resource.Attached) { Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualDiskCurrentlyAttachedMessage -f $FilePath) + $($script:localizedData.VirtualHardDiskCurrentlyAttached -f $FilePath) ) -join '' ) return $true @@ -253,19 +256,19 @@ function Test-TargetResource Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualDiskDoesNotExistMessage -f $FilePath) + $($script:localizedData.VirtualHardDiskMayNotExistOrNotAttached -f $FilePath) ) -join '' ) return $false } else { - # Found the virtual disk and confirmed its attached to the system but ensure variable set to absent. - if ($diskImage.Attached) + # Found the virtual hard disk and confirmed its attached to the system but ensure variable set to 'Absent'. + if ($resource.Attached) { Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualDiskCurrentlyAttachedButShouldNotBeMessage -f $FilePath) + $($script:localizedData.VirtualHardDiskCurrentlyAttachedButShouldNotBe -f $FilePath) ) -join '' ) return $false @@ -273,7 +276,7 @@ function Test-TargetResource Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualDiskDoesNotExistMessage -f $FilePath) + $($script:localizedData.VirtualHardDiskMayNotExistOrNotAttached -f $FilePath) ) -join '' ) return $true @@ -285,13 +288,13 @@ function Test-TargetResource Validates parameters for both set and test operations. .PARAMETER FilePath - Specifies the complete path to the virtual disk file. + Specifies the complete path to the virtual hard disk file. .PARAMETER DiskSize - Specifies the size of new virtual disk. + Specifies the size of new virtual hard disk. .PARAMETER DiskFormat - Specifies the supported virtual disk format. Currently only the vhd and vhdx formats are supported. + Specifies the supported virtual hard disk format. Currently only the vhd and vhdx formats are supported. #> function Assert-ParametersValid { @@ -308,7 +311,7 @@ function Assert-ParametersValid $DiskSize, [Parameter(Mandatory = $true)] - [ValidateSet('vhd', 'vhdx')] + [ValidateSet('Vhd', 'Vhdx')] [System.String] $DiskFormat ) @@ -334,22 +337,22 @@ function Assert-ParametersValid elseif ($extension -ne $DiskFormat) { New-InvalidArgumentException ` - -Message $($script:localizedData.VirtualHardExtensionAndFormatMismatchError -f $FilePath, $extension, $DiskFormat) ` + -Message $($script:localizedData.VirtualHardDiskExtensionAndFormatMismatchError -f $FilePath, $extension, $DiskFormat) ` -ArgumentName 'FilePath' } } else { New-InvalidArgumentException ` - -Message $($script:localizedData.VirtualHardNoExtensionError -f $FilePath) ` + -Message $($script:localizedData.VirtualHardDiskNoExtensionError -f $FilePath) ` -ArgumentName 'FilePath' } <# Validate DiskFormat values. Minimum value for GPT is around ~10MB and the maximum value for - the vhd format in 2040GB. Maximum for vhdx is 64TB + the vhd format in 2040GB. Maximum for the vhdx format is 64TB. #> - $isVhdxFormat = $DiskFormat -eq 'vhdx' + $isVhdxFormat = $DiskFormat -eq 'Vhdx' $isInValidSizeForVhdFormat = ($DiskSize -lt 10MB -bor $DiskSize -gt 2040GB) $isInValidSizeForVhdxFormat = ($DiskSize -lt 10MB -bor $DiskSize -gt 64TB) if ((-not $isVhdxFormat -and $isInValidSizeForVhdFormat) -bor @@ -364,10 +367,10 @@ function Assert-ParametersValid $DiskSizeString = ($DiskSize / 1TB).ToString("0.00TB") } - $InvalidSizeMsg = $script:localizedData.VhdFormatDiskSizeInvalidMessage + $InvalidSizeMsg = $script:localizedData.VhdFormatDiskSizeInvalid if ($isVhdxFormat) { - $InvalidSizeMsg = $script:localizedData.VhdxFormatDiskSizeInvalidMessage + $InvalidSizeMsg = $script:localizedData.VhdxFormatDiskSizeInvalid } New-InvalidArgumentException ` diff --git a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof index aa4c1587..6834fe95 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof +++ b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof @@ -2,9 +2,9 @@ [ClassVersion("1.0.0.0"), FriendlyName("VirtualHardDisk")] class DSC_VirtualHardDisk : OMI_BaseResource { - [Key, Description("Specifies the full path to the virtual disk file that will be created or attached. This may or may not include the extension, but the extension must match the disk format.")] String FilePath; - [Write, Description("Specifies the size the of virtual disk.")] Uint64 DiskSize; - [Write, Description("Specifies the disk type the virtual disk should use."), ValueMap{"fixed","dynamic"}, Values{"fixed","dynamic"}] String DiskType; - [Write, Description("Specifies the disk format the virtual disk should use. Defaults to vhdx."), ValueMap{"vhd","vhdx"}, Values{"vhd","vhdx"}] String DiskFormat; - [Write, Description("Determines whether the virtual disk should be created and mounted or not."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Key, Description("Specifies the full path to the virtual hard disk file that will be created or attached. This must include the extension, and the extension must match the disk format.")] String FilePath; + [Write, Description("Specifies the size the of virtual hard disk.")] Uint64 DiskSize; + [Write, Description("Specifies the disk type the virtual hard disk should use."), ValueMap{"Fixed","Dynamic"}, Values{"Fixed","Dynamic"}] String DiskType; + [Write, Description("Specifies the disk format the virtual hard disk should use. Defaults to Vhdx."), ValueMap{"Vhd","Vhdx"}, Values{"Vhd","Vhdx"}] String DiskFormat; + [Write, Description("Determines whether the virtual hard disk should be created and attached or should not be attached."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; }; diff --git a/source/DSCResources/DSC_VirtualHardDisk/README.md b/source/DSCResources/DSC_VirtualHardDisk/README.md index d523f4fc..3a809eb0 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/README.md +++ b/source/DSCResources/DSC_VirtualHardDisk/README.md @@ -1,3 +1,49 @@ # Description -The resource is used to create a virtual disk and attach it to the system. +The resource is used to create a virtual hard disk and attach it to the system. + +There are 2 high level scenarios, one where the user uses the 'Present' flag and the second when the user uses the 'Absent' flag in the resource. + +1. When the 'Present' flag is used the resource checks if the virtual hard disk is on the machine using the file path that is entered. + + a. If the file path is correct and it is confirmed to be the location of a real virtual hard disk file. The resource will do nothing. + + b. If the location is a real virtual hard disk file but is not attached, the resource will attach the virtual hard disk to the system. + + c. If the location is not real the resource will throw an error advising that the location does not exist. + +2. When the 'Absent' flag is used the resource checks if the virtual disk is on the machine using the file path that is entered. + + a. If the file path is correct and it is confirmed to be the location of a real virtual hard disk. The resource will detach the virtual hard disk from the system. + + b. If the location is a real virtual hard disk but is not attached to the system, the resource will do nothing. + + c. If the location is not real, the resource will throw an error advising that the location does not exist. + + d. The resource **does not** delete a virtual hard disk file, that **already** exists on the system prior to the `Set-TargetResource` function being called. It will only detach the virtual hard disk from the system. The file will remain, this is to prevent accidental deletions. + + +## How does this differ from the [DSC_VHD](https://github.com/dsccommunity/HyperVDsc/tree/main/source/DSCResources/DSC_VHD) in the Hyper-V Dsc? + +This DSC_VirtualHardDisk resource does not rely on the Hyper-V Windows feature being enabled to allow users to use the resource. Unlike the DSC_VHD, users can use this resource right out of the box assuming they are running at least `Windows 8.1 / Windows Server 2008 R2 or later`. The resource uses the publicly available Win32 virtual disk apis, to create and attach a virtual hard disk file (`.vhd` and `.vhdx`) to the system. See more information about the apis [here](https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/). + +## Limitations + +1. The resource only supports .vhd and .vhdx files. No other virtual hard disk file extension is supported at this time. +2. The ability to `expand` the max size of the virtual hard disk after its creation is not currently included in this resource. +3. The resource uses default values internally to create the virtual hard disk file that are not provided in the Get\Test\Set Methods. Values such as: + + a. The ability to set the block size of the virtual hard disk in bytes. + + b. The ability to set the sector size of the virtual hard disk in bytes. + + c. The ability to associate the new virtual hard disk with an existing virtual hard disk. + + d. The ability to specify a resiliency guid for the virtual hard disk. + + e. The ability to set a unique Id for the virtual hard disk and not use one generated by the system. + + f. The ability to prepopulate a new virtual disk with data from an existing virtual disk. + +See [virtdisk.h](https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/) to read about what the Win32 api supports. This resource could in theory support all of these, as the parameters just need to be passed to the the [CreateVirtualDisk](https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/nf-virtdisk-createvirtualdisk) Win32 function call in the resource. So additional help from the community if any of these are wanted, is welcomed. + diff --git a/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 b/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 index 93b29517..394b5500 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 +++ b/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 @@ -1,17 +1,17 @@ ConvertFrom-StringData @' - CheckingVirtualDiskExistsMessage = Checking virtual disk at location '{0}' exists and is attached. - VirtualDiskDoesNotExistMessage = The virtual disk at location '{0}' does not exist or is not attached. - VirtualDiskDoesNotExistCreatingNowMessage = The virtual disk at location '{0}' does not exist. Creating virtual disk now. - VirtualDiskCurrentlyAttachedMessage = The virtual disk at location '{0}' was found and is attached to the system. - VirtualDiskCurrentlyAttachedButShouldNotBeMessage = The virtual disk at location '{0}' was found and is attached to the system but it should not be. - VhdFormatDiskSizeInvalidMessage = The virtual disk size '{0}' was invalid for the 'vhd' format. Min supported value is 10 Mb and max supported value 2040 Gb. - VhdxFormatDiskSizeInvalidMessage = The virtual disk size '{0}' was invalid for the 'vhdx' format. Min supported value is 10 Mb and max supported value 64 Tb. - VirtualDiskNotAttachedMessage = The virtual disk at location '{0}' is not attached. Attaching virtual disk now. - VirtualDiskDismountingImageMessage = The virtual disk located at '{0}' is dismounting. + CheckingVirtualDiskExists = Checking virtual hard disk at location '{0}' exists and is attached. + VirtualHardDiskMayNotExistOrNotAttached = The virtual hard disk at location '{0}' does not exist or is not attached. + VirtualHardDiskDoesNotExistCreatingNow = The virtual hard disk at location '{0}' does not exist. Creating virtual hard disk now. + VirtualHardDiskCurrentlyAttached = The virtual hard disk at location '{0}' was found and is attached to the system. + VirtualHardDiskCurrentlyAttachedButShouldNotBe = The virtual hard disk at location '{0}' was found and is attached to the system but it should not be. + VhdFormatDiskSizeInvalid = The virtual hard disk size '{0}' was invalid for the 'vhd' format. Min supported value is 10 Mb and max supported value 2040 Gb. + VhdxFormatDiskSizeInvalid = The virtual hard disk size '{0}' was invalid for the 'vhdx' format. Min supported value is 10 Mb and max supported value 64 Tb. + VirtualDiskNotAttached = The virtual hard disk at location '{0}' is not attached. Attaching virtual hard disk now. + VirtualHardDiskDetachingImage = The virtual hard disk located at '{0}' is detaching. VirtualHardDiskUnsupportedFileType = The file type .{0} is not supported. Only .vhd and .vhdx file types are supported. VirtualHardDiskPathError = The path '{0}' must be a fully qualified path that starts with a Drive Letter. - VirtualHardExtensionAndFormatMismatchError = The path you entered '{0}' has extension '{1}' but the disk format entered is '{2}'. Both the extension and format must match. - VirtualHardNoExtensionError = The path '{0}' does not contain an extension. Supported extension types are '.vhd' and '.vhdx'. - GettingVirtualDiskMessage = Getting virtual disk information for virtual disk located at '{0}. - VirtualRemovingCreatedFileMessage = The virtual disk file at location '{0}' is being removed due to an error while attempting to attach it to the system. + VirtualHardDiskExtensionAndFormatMismatchError = The path you entered '{0}' has extension '{1}' but the disk format entered is '{2}'. Both the extension and format must match. + VirtualHardDiskNoExtensionError = The path '{0}' does not contain an extension. Supported extension types are '.vhd' and '.vhdx'. + GettingVirtualHardDisk = Getting virtual hard disk information for virtual hard disk located at '{0}. + RemovingCreatedVirtualHardDiskFile = The virtual hard disk file at location '{0}' is being removed due to an error while attempting to attach it to the system. '@ diff --git a/source/Examples/Resources/VirtualHardDisk/1-VirtualHardDisk_CreateFixedSizedVirtualDisk.ps1 b/source/Examples/Resources/VirtualHardDisk/1-VirtualHardDisk_CreateFixedSizedVirtualDisk.ps1 index 14bb0276..2535d73f 100644 --- a/source/Examples/Resources/VirtualHardDisk/1-VirtualHardDisk_CreateFixedSizedVirtualDisk.ps1 +++ b/source/Examples/Resources/VirtualHardDisk/1-VirtualHardDisk_CreateFixedSizedVirtualDisk.ps1 @@ -20,7 +20,8 @@ <# .DESCRIPTION This configuration will create a fixed sized virtual disk that is 40Gb in size and will format a - NTFS volume named 'new volume' that uses the drive letter E. + NTFS volume named 'new volume' that uses the drive letter E. If the folder path in the FilePath + property does not exist, it will be created. #> Configuration VirtualHardDisk_CreateFixedSizedVirtualDisk { @@ -33,8 +34,8 @@ Configuration VirtualHardDisk_CreateFixedSizedVirtualDisk { FilePath = 'C:\myVhds\virtDisk1.vhd' DiskSize = 40Gb - DiskFormat = 'vhd' - DiskType = 'fixed' + DiskFormat = 'Vhd' + DiskType = 'Fixed' Ensure = 'Present' } diff --git a/source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 b/source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 index 1e2ba9d1..aa509080 100644 --- a/source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 +++ b/source/Examples/Resources/VirtualHardDisk/2-VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk.ps1 @@ -19,8 +19,9 @@ <# .DESCRIPTION - This configuration will create a dynamic sized virtual disk that is 40Gb in size and will format a - RefS volume named 'new volume 2' that uses the drive letter F. + This configuration will create a dynamically sized virtual hard disk that has a max size of 80Gb and will format a + RefS volume named 'new volume 2' that uses the drive letter F, onto the newly created virtual hard disk. If the + folder path in the FilePath property does not exist, it will be created. #> Configuration VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk { @@ -32,9 +33,9 @@ Configuration VirtualHardDisk_CreateDynamicallyExpandingVirtualDisk VirtualHardDisk newVhd2 { FilePath = 'C:\myVhds\virtDisk2.vhdx' - DiskSize = 40Gb - DiskFormat = 'vhdx' - DiskType = 'dynamic' + DiskSize = 80Gb + DiskFormat = 'Vhdx' + DiskType = 'Dynamic' Ensure = 'Present' } diff --git a/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 b/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 index 4ff0c21f..f6842361 100644 --- a/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 +++ b/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 @@ -103,14 +103,14 @@ function Get-VirtDiskWin32HelperScript UInt32 ProviderSpecificFlags, ref CREATE_VIRTUAL_DISK_PARAMETERS Parameters, IntPtr Overlapped, - ref IntPtr Handle + out SafeFileHandle Handle ); // Declare method to attach a virtual disk // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/nf-virtdisk-attachvirtualdisk [DllImport("virtdisk.dll", CharSet = CharSet.Unicode)] public static extern Int32 AttachVirtualDisk( - IntPtr VirtualDiskHandle, + SafeFileHandle VirtualDiskHandle, IntPtr SecurityDescriptor, UInt32 Flags, UInt32 ProviderSpecificFlags, @@ -127,14 +127,8 @@ function Get-VirtDiskWin32HelperScript UInt32 VirtualDiskAccessMask, UInt32 Flags, ref OPEN_VIRTUAL_DISK_PARAMETERS Parameters, - ref IntPtr Handle + out SafeFileHandle Handle ); - - // https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool CloseHandle(IntPtr hObject); - '@ if (([System.Management.Automation.PSTypeName]'VirtDisk.Helper').Type) { @@ -145,7 +139,10 @@ function Get-VirtDiskWin32HelperScript $script:VirtDiskHelper = Add-Type ` -Namespace 'VirtDisk' ` -Name 'Helper' ` - -MemberDefinition $virtDiskDefinitions + -MemberDefinition $virtDiskDefinitions ` + -UsingNamespace ` + 'System.ComponentModel', + 'Microsoft.Win32.SafeHandles' } return $script:VirtDiskHelper @@ -425,40 +422,41 @@ function New-SimpleVirtualDisk $DiskSizeInBytes, [Parameter(Mandatory = $true)] - [ValidateSet('vhd', 'vhdx')] + [ValidateSet('Vhd', 'Vhdx')] [System.String] $DiskFormat, [Parameter(Mandatory = $true)] - [ValidateSet('fixed', 'dynamic')] + [ValidateSet('Fixed', 'Dynamic')] [System.String] $DiskType ) - try - { - Write-Verbose -Message ($script:localizedData.CreatingVirtualDiskMessage -f $VirtualDiskPath) - $vDiskHelper = Get-VirtDiskWin32HelperScript - # Get parameters for CreateVirtualDisk function - [ref]$virtualStorageType = Get-VirtualStorageType -DiskFormat $DiskFormat - [ref]$createVirtualDiskParameters = New-Object VirtDisk.Helper+CREATE_VIRTUAL_DISK_PARAMETERS - $createVirtualDiskParameters.Value.Version = [VirtDisk.Helper]::CREATE_VIRTUAL_DISK_VERSION_2 - $createVirtualDiskParameters.Value.MaximumSize = $DiskSizeInBytes - $securityDescriptor = [System.IntPtr]::Zero - $accessMask = [VirtDisk.Helper]::VIRTUAL_DISK_ACCESS_NONE - $providerSpecificFlags = 0 - - # Handle to the new virtual disk - [ref]$handle = [System.IntPtr]::Zero + Write-Verbose -Message ($script:localizedData.CreatingVirtualDiskMessage -f $VirtualDiskPath) + $vDiskHelper = Get-VirtDiskWin32HelperScript - # Virtual disk will be dynamically expanding, up to the size of $DiskSizeInBytes on the parent disk - $flags = [VirtDisk.Helper]::CREATE_VIRTUAL_DISK_FLAG_NONE - if ($DiskType -eq 'fixed') - { - # Virtual disk will be fixed, and will take up the up the full size of $DiskSizeInBytes on the parent disk after creation - $flags = [VirtDisk.Helper]::CREATE_VIRTUAL_DISK_FLAG_FULL_PHYSICAL_ALLOCATION - } + # Get parameters for CreateVirtualDisk function + [ref]$virtualStorageType = Get-VirtualStorageType -DiskFormat $DiskFormat + [ref]$createVirtualDiskParameters = New-Object VirtDisk.Helper+CREATE_VIRTUAL_DISK_PARAMETERS + $createVirtualDiskParameters.Value.Version = [VirtDisk.Helper]::CREATE_VIRTUAL_DISK_VERSION_2 + $createVirtualDiskParameters.Value.MaximumSize = $DiskSizeInBytes + $securityDescriptor = [System.IntPtr]::Zero + $accessMask = [VirtDisk.Helper]::VIRTUAL_DISK_ACCESS_NONE + $providerSpecificFlags = 0 + + # Handle to the new virtual disk + [ref]$handle = [Microsoft.Win32.SafeHandles.SafeFileHandle]::Zero + + # Virtual disk will be dynamically expanding, up to the size of $DiskSizeInBytes on the parent disk + $flags = [VirtDisk.Helper]::CREATE_VIRTUAL_DISK_FLAG_NONE + if ($DiskType -eq 'Fixed') + { + # Virtual disk will be fixed, and will take up the up the full size of $DiskSizeInBytes on the parent disk after creation + $flags = [VirtDisk.Helper]::CREATE_VIRTUAL_DISK_FLAG_FULL_PHYSICAL_ALLOCATION + } + try + { $result = New-VirtualDiskUsingWin32 ` $virtualStorageType ` $VirtualDiskPath ` @@ -472,8 +470,10 @@ function New-SimpleVirtualDisk if ($result -ne 0) { - Write-Verbose -Message ($script:localizedData.CreateVirtualDiskError -f $result) - throw [System.ComponentModel.Win32Exception]::new($result) + $win32Error = [System.ComponentModel.Win32Exception]::new($result) + throw [System.Exception]::new( ` + ($script:localizedData.CreateVirtualDiskError -f $win32Error.Message), ` + $win32Error) } Write-Verbose -Message ($script:localizedData.VirtualDiskCreatedSuccessfully -f $VirtualDiskPath) @@ -482,7 +482,10 @@ function New-SimpleVirtualDisk finally { # Close handle - Close-Win32Handle $Handle + if ($handle.Value) + { + $handle.Value.Close() + } } } # function New-SimpleVirtualDisk @@ -509,7 +512,7 @@ function Add-SimpleVirtualDisk $VirtualDiskPath, [Parameter(Mandatory = $true)] - [ValidateSet('vhd', 'vhdx')] + [ValidateSet('Vhd', 'Vhdx')] [System.String] $DiskFormat, @@ -569,8 +572,10 @@ function Add-SimpleVirtualDisk if ($result -ne 0) { - Write-Verbose -Message ($script:localizedData.AttachVirtualDiskError -f $result) - throw [System.ComponentModel.Win32Exception]::new($result) + $win32Error = [System.ComponentModel.Win32Exception]::new($result) + throw [System.Exception]::new( ` + ($script:localizedData.AttachVirtualDiskError -f $win32Error.Message), ` + $win32Error) } Write-Verbose -Message ($script:localizedData.VirtualDiskAttachedSuccessfully -f $VirtualDiskPath) @@ -578,7 +583,10 @@ function Add-SimpleVirtualDisk finally { # Close handle - Close-Win32Handle $Handle + if ($handle.Value) + { + $handle.Value.Close() + } } } # function Add-SimpleVirtualDisk @@ -603,7 +611,7 @@ function Get-VirtualDiskHandle $VirtualDiskPath, [Parameter(Mandatory = $true)] - [ValidateSet('vhd', 'vhdx')] + [ValidateSet('Vhd', 'Vhdx')] [System.String] $DiskFormat ) @@ -619,7 +627,7 @@ function Get-VirtualDiskHandle $flags = [VirtDisk.Helper]::OPEN_VIRTUAL_DISK_FLAG_NONE # Handle to the virtual disk. - [ref]$handle = [System.IntPtr]::Zero + [ref]$handle = [Microsoft.Win32.SafeHandles.SafeFileHandle]::Zero $result = Get-VirtualDiskUsingWin32 ` $virtualStorageType ` @@ -631,8 +639,10 @@ function Get-VirtualDiskHandle if ($result -ne 0) { - Write-Verbose -Message ($script:localizedData.OpenVirtualDiskError -f $result) - throw [System.ComponentModel.Win32Exception]::new($result) + $win32Error = [System.ComponentModel.Win32Exception]::new($result) + throw [System.Exception]::new( ` + ($script:localizedData.OpenVirtualDiskError -f $win32Error.Message), ` + $win32Error) } Write-Verbose -Message ($script:localizedData.VirtualDiskOpenedSuccessfully -f $VirtualDiskPath) @@ -654,7 +664,7 @@ function Get-VirtualStorageType param ( [Parameter(Mandatory = $true)] - [ValidateSet('vhd', 'vhdx')] + [ValidateSet('Vhd', 'Vhdx')] [System.String] $DiskFormat ) @@ -665,7 +675,7 @@ function Get-VirtualStorageType # Default to the vhdx file format. $virtualStorageType.VendorId = [VirtDisk.Helper]::VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT $virtualStorageType.DeviceId = [VirtDisk.Helper]::VIRTUAL_STORAGE_TYPE_DEVICE_VHDX - if ($DiskFormat -eq 'vhd') + if ($DiskFormat -eq 'Vhd') { $virtualStorageType.DeviceId = [VirtDisk.Helper]::VIRTUAL_STORAGE_TYPE_DEVICE_VHD } diff --git a/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 b/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 index f0e63feb..dd1ee788 100644 --- a/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 +++ b/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 @@ -238,7 +238,7 @@ try Context 'When file extension does not match the disk format' { $extension = [System.IO.Path]::GetExtension($DiskImageGoodVhdPath).TrimStart('.') $errorRecord = Get-InvalidArgumentRecord ` - -Message ($script:localizedData.VirtualHardExtensionAndFormatMismatchError -f ` + -Message ($script:localizedData.VirtualHardDiskExtensionAndFormatMismatchError -f ` $DiskImageGoodVhdPath, $extension, 'vhdx') ` -ArgumentName 'FilePath' @@ -256,7 +256,7 @@ try Context 'When file extension is not present in the file path' { $errorRecord = Get-InvalidArgumentRecord ` - -Message ($script:localizedData.VirtualHardNoExtensionError -f ` + -Message ($script:localizedData.VirtualHardDiskNoExtensionError -f ` $script:DiskImageVirtDiskPathWithoutExtension) ` -ArgumentName 'FilePath' @@ -276,7 +276,7 @@ try $minSizeInMbString = ($DiskImageSizeBelowVirtDiskMinimum / 1MB).ToString("0.00MB") $errorRecord = Get-InvalidArgumentRecord ` - -Message ($script:localizedData.VhdFormatDiskSizeInvalidMessage -f ` + -Message ($script:localizedData.VhdFormatDiskSizeInvalid -f ` $minSizeInMbString) ` -ArgumentName 'DiskSize' @@ -295,7 +295,7 @@ try Context 'When size provided is less than the minimum size for the vhdx format' { $minSizeInMbString = ($DiskImageSizeBelowVirtDiskMinimum / 1MB).ToString("0.00MB") $errorRecord = Get-InvalidArgumentRecord ` - -Message ($script:localizedData.VhdxFormatDiskSizeInvalidMessage -f ` + -Message ($script:localizedData.VhdxFormatDiskSizeInvalid -f ` $minSizeInMbString) ` -ArgumentName 'DiskSize' @@ -314,7 +314,7 @@ try Context 'When size provided is greater than the maximum size for the vhd format' { $maxSizeInTbString = ($DiskImageSizeAboveVhdMaximum / 1TB).ToString("0.00TB") $errorRecord = Get-InvalidArgumentRecord ` - -Message ($script:localizedData.VhdFormatDiskSizeInvalidMessage -f ` + -Message ($script:localizedData.VhdFormatDiskSizeInvalid -f ` $maxSizeInTbString) ` -ArgumentName 'DiskSize' @@ -333,7 +333,7 @@ try Context 'When size provided is greater than the maximum size for the vhdx format' { $maxSizeInTbString = ($DiskImageSizeAboveVhdxMaximum / 1TB).ToString("0.00TB") $errorRecord = Get-InvalidArgumentRecord ` - -Message ($script:localizedData.VhdxFormatDiskSizeInvalidMessage -f ` + -Message ($script:localizedData.VhdxFormatDiskSizeInvalid -f ` $maxSizeInTbString) ` -ArgumentName 'DiskSize' diff --git a/tests/Unit/VirtualHardDisk.Win32Helpers.Tests.ps1 b/tests/Unit/VirtualHardDisk.Win32Helpers.Tests.ps1 index 0eadab56..f29f26a2 100644 --- a/tests/Unit/VirtualHardDisk.Win32Helpers.Tests.ps1 +++ b/tests/Unit/VirtualHardDisk.Win32Helpers.Tests.ps1 @@ -104,18 +104,6 @@ InModuleScope $script:subModuleName { ) } - Function Close-Win32Handle - { - [CmdletBinding()] - [OutputType([System.Void])] - Param - ( - [Parameter(Mandatory = $true)] - [ref] - $Handle - ) - } - Function Get-VirtualDiskUsingWin32 { [CmdletBinding()] @@ -151,7 +139,8 @@ InModuleScope $script:subModuleName { $script:DiskImageGoodVhdxPath = 'C:\test.vhdx' $script:AccessDeniedWin32Error = 5 $script:vhdDiskFormat = 'vhd' - [ref]$script:TestHandle = [System.IntPtr]::Zero + [ref]$script:TestHandle = [Microsoft.Win32.SafeHandles.SafeFileHandle]::Zero + $script:mockedParams = [pscustomobject] @{ DiskSizeInBytes = 65Gb VirtualDiskPath = $script:DiskImageGoodVhdxPath @@ -230,7 +219,12 @@ InModuleScope $script:subModuleName { -CommandName New-VirtualDiskUsingWin32 ` -MockWith { $script:AccessDeniedWin32Error } ` -Verifiable - $exception = [System.ComponentModel.Win32Exception]::new($script:AccessDeniedWin32Error) + + $win32Error = [System.ComponentModel.Win32Exception]::new($script:AccessDeniedWin32Error) + $exception = [System.Exception]::new( ` + ($script:localizedData.CreateVirtualDiskError -f $win32Error.Message), ` + $win32Error) + It 'Should throw an exception in creation method' { { New-SimpleVirtualDisk ` @@ -261,7 +255,12 @@ InModuleScope $script:subModuleName { -CommandName Add-VirtualDiskUsingWin32 ` -MockWith { $script:AccessDeniedWin32Error } ` -Verifiable - $exception = [System.ComponentModel.Win32Exception]::new($script:AccessDeniedWin32Error) + + $win32Error = [System.ComponentModel.Win32Exception]::new($script:AccessDeniedWin32Error) + $exception = [System.Exception]::new( ` + ($script:localizedData.AttachVirtualDiskError -f $win32Error.Message), ` + $win32Error) + It 'Should throw an exception during attach function' { { Add-SimpleVirtualDisk ` @@ -314,7 +313,11 @@ InModuleScope $script:subModuleName { -MockWith { $script:AccessDeniedWin32Error } ` -Verifiable - $exception = [System.ComponentModel.Win32Exception]::new($script:AccessDeniedWin32Error) + $win32Error = [System.ComponentModel.Win32Exception]::new($script:AccessDeniedWin32Error) + $exception = [System.Exception]::new( ` + ($script:localizedData.OpenVirtualDiskError -f $win32Error.Message), ` + $win32Error) + It 'Should throw an exception while attempting to open virtual disk file' { { Get-VirtualDiskHandle ` From 937e81fd6a17144227787fa515334d1f2b0af3d1 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Mon, 16 Oct 2023 19:11:14 -0700 Subject: [PATCH 13/21] remove no longer used function --- .../VirtualHardDisk.Win32Helpers.psm1 | 26 ------------------- source/StorageDsc.psd1 | 2 +- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 b/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 index f6842361..545dae34 100644 --- a/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 +++ b/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 @@ -302,32 +302,6 @@ function Add-VirtualDiskUsingWin32 $Overlapped) } # end function Add-VirtualDiskUsingWin32 -<# - .SYNOPSIS - Calls Win32 CloseHandle api. This is used so we can mock this call - easier. - - .PARAMETER Handle - Specifies a reference to handle for a file. -#> -function Close-Win32Handle -{ - [CmdletBinding()] - [OutputType([System.Void])] - param - ( - [Parameter(Mandatory = $true)] - [ref] - $Handle - ) - - $helper = Get-VirtDiskWin32HelperScript - if ($Handle.Value) - { - $null = $helper::CloseHandle($Handle.value) - } -} # end function Close-Win32Handle - <# .SYNOPSIS Calls Win32 OpenVirtualDisk api. This is used so we can mock this call diff --git a/source/StorageDsc.psd1 b/source/StorageDsc.psd1 index 3553673d..315624b8 100644 --- a/source/StorageDsc.psd1 +++ b/source/StorageDsc.psd1 @@ -53,7 +53,7 @@ Prerelease = '' # Tags applied to this module. These help with module discovery in online galleries. - Tags = @('DesiredStateConfiguration', 'DSC', 'DSCResource', 'Disk', 'Storage', 'Partition', 'Volume', 'VirtualHardDisk') + Tags = @('DesiredStateConfiguration', 'DSC', 'DSCResource', 'Disk', 'Storage', 'Partition', 'Volume', 'Virtual hard disk', 'VHD') # A URL to the license for this module. LicenseUri = 'https://github.com/dsccommunity/StorageDsc/blob/main/LICENSE' From 5cd777cd3e23fced627ffeaf8078d636cd37e432 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Fri, 27 Oct 2023 21:11:42 -0700 Subject: [PATCH 14/21] update based on comments, reword md file, and add parameter names to function calls in win32helpers --- CHANGELOG.md | 3 +- source/DSCResources/DSC_Disk/DSC_Disk.psm1 | 2 +- .../DSC_VirtualHardDisk.psm1 | 4 +- .../DSC_VirtualHardDisk.schema.mof | 10 +- .../DSC_VirtualHardDisk/README.md | 109 +++++++++++------- ...rageDsc.VirtualHardDisk.Win32Helpers.psm1} | 59 ++++++---- ....VirtualHardDisk.Win32Helpers.strings.psd1 | 11 ++ .../VirtualHardDisk.Win32Helpers.strings.psd1 | 11 -- ...sc.VirtualHardDisk.Win32Helpers.Tests.ps1} | 18 ++- 9 files changed, 132 insertions(+), 95 deletions(-) rename source/Modules/{VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 => StorageDsc.VirtualHardDisk.Win32Helpers/StorageDsc.VirtualHardDisk.Win32Helpers.psm1} (93%) create mode 100644 source/Modules/StorageDsc.VirtualHardDisk.Win32Helpers/en-US/StorageDsc.VirtualHardDisk.Win32Helpers.strings.psd1 delete mode 100644 source/Modules/VirtualHardDisk.Win32Helpers/en-US/VirtualHardDisk.Win32Helpers.strings.psd1 rename tests/Unit/{VirtualHardDisk.Win32Helpers.Tests.ps1 => StorageDsc.VirtualHardDisk.Win32Helpers.Tests.ps1} (95%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ef14316..d14080a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Disk: - BREAKING CHANGE: Added support for volumes to be formatted as Dev Drives - Fixes [Issue #276](https://github.com/dsccommunity/StorageDsc/issues/276) -- VirtualHardDisk - - Added DSC_VirtualHardDisk resource for creating virtual disks and tests. - Fixes [Issue #277](https://github.com/dsccommunity/StorageDsc/issues/277) +- Added DSC_VirtualHardDisk resource for creating virtual disks and tests - Fixes [Issue #277](https://github.com/dsccommunity/StorageDsc/issues/277) ## [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 55f21e8c..da3c73b4 100644 --- a/source/DSCResources/DSC_Disk/DSC_Disk.psm1 +++ b/source/DSCResources/DSC_Disk/DSC_Disk.psm1 @@ -303,7 +303,7 @@ function Set-TargetResource -DiskIdType $DiskIdType } - if ($disk.PartitionStyle -eq 'RAW' -bor (-not $disk.PartitionStyle)) + if ($disk.PartitionStyle -eq 'RAW') { Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " diff --git a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 index aea7d90e..303cb4e8 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 +++ b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 @@ -7,8 +7,8 @@ Import-Module -Name (Join-Path -Path $modulePath ` # Import the VirtualHardDisk Win32Helpers Module. Import-Module -Name (Join-Path -Path $modulePath ` - -ChildPath (Join-Path -Path 'VirtualHardDisk.Win32Helpers' ` - -ChildPath 'VirtualHardDisk.Win32Helpers.psm1')) + -ChildPath (Join-Path -Path 'StorageDsc.VirtualHardDisk.Win32Helpers' ` + -ChildPath 'StorageDsc.VirtualHardDisk.Win32Helpers.psm1')) Import-Module -Name (Join-Path -Path $modulePath -ChildPath 'DscResource.Common') diff --git a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof index 6834fe95..3c0c6711 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof +++ b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof @@ -2,9 +2,9 @@ [ClassVersion("1.0.0.0"), FriendlyName("VirtualHardDisk")] class DSC_VirtualHardDisk : OMI_BaseResource { - [Key, Description("Specifies the full path to the virtual hard disk file that will be created or attached. This must include the extension, and the extension must match the disk format.")] String FilePath; - [Write, Description("Specifies the size the of virtual hard disk.")] Uint64 DiskSize; - [Write, Description("Specifies the disk type the virtual hard disk should use."), ValueMap{"Fixed","Dynamic"}, Values{"Fixed","Dynamic"}] String DiskType; - [Write, Description("Specifies the disk format the virtual hard disk should use. Defaults to Vhdx."), ValueMap{"Vhd","Vhdx"}, Values{"Vhd","Vhdx"}] String DiskFormat; - [Write, Description("Determines whether the virtual hard disk should be created and attached or should not be attached."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Key, Description("Specifies the full path to the virtual hard disk file that will be created and attached. This must include the extension, and the extension must match the disk format.")] String FilePath; + [Write, Description("Specifies the size of virtual hard disk to create if it doesn't exist and Ensure is present.")] Uint64 DiskSize; + [Write, Description("Specifies the disk type of virtual hard disk to create if it doesn't exist and Ensure is present."), ValueMap{"Fixed","Dynamic"}, Values{"Fixed","Dynamic"}] String DiskType; + [Write, Description("Specifies the disk format the virtual hard disk should use or create if it does not exist and Ensure is present. Defaults to Vhdx."), ValueMap{"Vhd","Vhdx"}, Values{"Vhd","Vhdx"}] String DiskFormat; + [Write, Description("Determines whether the virtual hard disk should be created and attached or should be detached if it exists."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; }; diff --git a/source/DSCResources/DSC_VirtualHardDisk/README.md b/source/DSCResources/DSC_VirtualHardDisk/README.md index 3a809eb0..6359327e 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/README.md +++ b/source/DSCResources/DSC_VirtualHardDisk/README.md @@ -2,48 +2,79 @@ The resource is used to create a virtual hard disk and attach it to the system. -There are 2 high level scenarios, one where the user uses the 'Present' flag and the second when the user uses the 'Absent' flag in the resource. - -1. When the 'Present' flag is used the resource checks if the virtual hard disk is on the machine using the file path that is entered. - - a. If the file path is correct and it is confirmed to be the location of a real virtual hard disk file. The resource will do nothing. - - b. If the location is a real virtual hard disk file but is not attached, the resource will attach the virtual hard disk to the system. - - c. If the location is not real the resource will throw an error advising that the location does not exist. - -2. When the 'Absent' flag is used the resource checks if the virtual disk is on the machine using the file path that is entered. - - a. If the file path is correct and it is confirmed to be the location of a real virtual hard disk. The resource will detach the virtual hard disk from the system. - - b. If the location is a real virtual hard disk but is not attached to the system, the resource will do nothing. - - c. If the location is not real, the resource will throw an error advising that the location does not exist. - - d. The resource **does not** delete a virtual hard disk file, that **already** exists on the system prior to the `Set-TargetResource` function being called. It will only detach the virtual hard disk from the system. The file will remain, this is to prevent accidental deletions. - +There are 2 high level scenarios, one where the user uses the 'Present' flag +and the second when the user uses the 'Absent' flag in the resource. + +1. When the 'Present' flag is used the resource checks if the virtual hard disk +is on the machine using the file path that is entered. + 1. If the parameters are valid but the file path does not exist. The + resource will attempt to create and attach the virtual hard disk to + the system. Note: only paths with drive letters are accepted e.g + `C:\Myfolder\newVirtFile.vhdx`, or `D:\newVirtFile.vhd`, etc. + 1. If the file path is confirmed to be the location of a real virtual hard + disk file, and it is attached to the system the resource will do nothing. + 1. If the file path is confirmed to be the location of a real virtual hard + disk file, but is not attached, the resource will attach the virtual hard + disk to the system. + 1. If the file path does exist but is not a real file path to a virtual + hard disk file, the resource will throw an error. +1. When the 'Absent' flag is used the resource checks if the virtual disk is on +the machine using the file path that is entered. + 1. If the file path is confirmed to be the location of a real virtual hard + disk file. The resource will check if the virtual hard disk is attached. + If it is attached, the resource will detach the virtual hard disk from + the system. + 1. If the location is a real virtual hard disk but the virtual hard disk + is detached from the system, the resource will do nothing. + 1. If the parameters are valid but the file path is a path to a virtual + hard disk file that doesn't exist, the resource will do nothing. + 1. The resource **does not** delete a virtual hard disk file, that + **already** exists on the system prior to the `Set-TargetResource` + function being called. It will only detach the virtual hard disk + from the system. The file will remain, this is to prevent accidental deletions. + +Note: If the file path to the `.vhd` or `.vhdx` file is real and exists but is +not attached. The resource will simply attach it. Even if the `DiskSize` and +`DiskType` parameters are different. These values are currently only useful for +creation. Additional functionality would need to be added to [resize](https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/nf-virtdisk-resizevirtualdisk) +or change +the virtual disk's type after creation. + +See the [Limitations](#limitations) section for more limitations. ## How does this differ from the [DSC_VHD](https://github.com/dsccommunity/HyperVDsc/tree/main/source/DSCResources/DSC_VHD) in the Hyper-V Dsc? -This DSC_VirtualHardDisk resource does not rely on the Hyper-V Windows feature being enabled to allow users to use the resource. Unlike the DSC_VHD, users can use this resource right out of the box assuming they are running at least `Windows 8.1 / Windows Server 2008 R2 or later`. The resource uses the publicly available Win32 virtual disk apis, to create and attach a virtual hard disk file (`.vhd` and `.vhdx`) to the system. See more information about the apis [here](https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/). +This DSC_VirtualHardDisk resource does not rely on the Hyper-V Windows feature +being enabled to allow users to use the resource. Unlike the DSC_VHD, users can +use this resource right out of the box assuming they are running at least +`Windows 8.1 / Windows Server 2008 R2 or later`. The resource uses the publicly +available Win32 virtual disk apis, to create and attach a virtual hard disk file +(`.vhd` and `.vhdx`) to the system. See more information about the apis [here](https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/). -## Limitations - -1. The resource only supports .vhd and .vhdx files. No other virtual hard disk file extension is supported at this time. -2. The ability to `expand` the max size of the virtual hard disk after its creation is not currently included in this resource. -3. The resource uses default values internally to create the virtual hard disk file that are not provided in the Get\Test\Set Methods. Values such as: - - a. The ability to set the block size of the virtual hard disk in bytes. - - b. The ability to set the sector size of the virtual hard disk in bytes. - - c. The ability to associate the new virtual hard disk with an existing virtual hard disk. +Warning: Using both the DSC_VirtualHardDisk and DSC_VHD resources in the same +config/machine could result in an invalid config. - d. The ability to specify a resiliency guid for the virtual hard disk. - - e. The ability to set a unique Id for the virtual hard disk and not use one generated by the system. - - f. The ability to prepopulate a new virtual disk with data from an existing virtual disk. - -See [virtdisk.h](https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/) to read about what the Win32 api supports. This resource could in theory support all of these, as the parameters just need to be passed to the the [CreateVirtualDisk](https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/nf-virtdisk-createvirtualdisk) Win32 function call in the resource. So additional help from the community if any of these are wanted, is welcomed. +## Limitations +1. The resource only supports `.vhd` and `.vhdx` files. No other virtual hard disk +file extension is supported at this time. +1. The ability to `expand` the max size of the virtual hard disk after its creation +1. The ability to `shrink` the max size of the virtual hard disk after its creation +is not currently included in this resource. +1. The resource uses default values internally to create the virtual hard disk +file that are not provided in the Get\Test\Set Methods. Values such as: + 1. The ability to set the block size of the virtual hard disk in bytes. + 1. The ability to set the sector size of the virtual hard disk in bytes. + 1. The ability to associate the new virtual hard disk with an existing virtual + hard disk. + 1. The ability to specify a resiliency guid for the virtual hard disk. + 1. The ability to set a unique Id for the virtual hard disk and not use one + generated by the system. + 1. The ability to prepopulate a new virtual disk with data from an existing + virtual disk. + +See [virtdisk.h](https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/) +to read about what the Win32 api supports. This resource could in theory support +all of these, as the parameters just need to be passed to the the [CreateVirtualDisk](https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/nf-virtdisk-createvirtualdisk) +Win32 function call in the resource. So additional help from the community if any +of these are wanted, is welcomed. diff --git a/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 b/source/Modules/StorageDsc.VirtualHardDisk.Win32Helpers/StorageDsc.VirtualHardDisk.Win32Helpers.psm1 similarity index 93% rename from source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 rename to source/Modules/StorageDsc.VirtualHardDisk.Win32Helpers/StorageDsc.VirtualHardDisk.Win32Helpers.psm1 index 545dae34..49eb14aa 100644 --- a/source/Modules/VirtualHardDisk.Win32Helpers/VirtualHardDisk.Win32Helpers.psm1 +++ b/source/Modules/StorageDsc.VirtualHardDisk.Win32Helpers/StorageDsc.VirtualHardDisk.Win32Helpers.psm1 @@ -225,6 +225,7 @@ function New-VirtualDiskUsingWin32 ) $helper = Get-VirtDiskWin32HelperScript + return $helper::CreateVirtualDisk( $virtualStorageType, $VirtualDiskPath, @@ -293,6 +294,7 @@ function Add-VirtualDiskUsingWin32 ) $helper = Get-VirtDiskWin32HelperScript + return $helper::AttachVirtualDisk( $Handle.Value, $SecurityDescriptor, @@ -357,6 +359,7 @@ function Get-VirtualDiskUsingWin32 ) $helper = Get-VirtDiskWin32HelperScript + return $helper::OpenVirtualDisk( $VirtualStorageType, $VirtualDiskPath, @@ -423,6 +426,7 @@ function New-SimpleVirtualDisk # Virtual disk will be dynamically expanding, up to the size of $DiskSizeInBytes on the parent disk $flags = [VirtDisk.Helper]::CREATE_VIRTUAL_DISK_FLAG_NONE + if ($DiskType -eq 'Fixed') { # Virtual disk will be fixed, and will take up the up the full size of $DiskSizeInBytes on the parent disk after creation @@ -432,26 +436,30 @@ function New-SimpleVirtualDisk try { $result = New-VirtualDiskUsingWin32 ` - $virtualStorageType ` - $VirtualDiskPath ` - $accessMask ` - $securityDescriptor ` - $flags ` - $providerSpecificFlags ` - $createVirtualDiskParameters ` - ([System.IntPtr]::Zero) ` - $handle + -VirtualStorageType $virtualStorageType ` + -VirtualDiskPath $VirtualDiskPath ` + -AccessMask $accessMask ` + -SecurityDescriptor $securityDescriptor ` + -Flags $flags ` + -ProviderSpecificFlags $providerSpecificFlags ` + -CreateVirtualDiskParameters $createVirtualDiskParameters ` + -Overlapped ([System.IntPtr]::Zero) ` + -Handle $handle if ($result -ne 0) { $win32Error = [System.ComponentModel.Win32Exception]::new($result) throw [System.Exception]::new( ` - ($script:localizedData.CreateVirtualDiskError -f $win32Error.Message), ` + ($script:localizedData.CreateVirtualDiskError -f $VirtualDiskPath, $win32Error.Message), ` $win32Error) } Write-Verbose -Message ($script:localizedData.VirtualDiskCreatedSuccessfully -f $VirtualDiskPath) - Add-SimpleVirtualDisk -VirtualDiskPath $VirtualDiskPath -DiskFormat $DiskFormat -Handle $handle + + Add-SimpleVirtualDisk ` + -VirtualDiskPath $VirtualDiskPath ` + -DiskFormat $DiskFormat ` + -Handle $handle } finally { @@ -531,12 +539,12 @@ function Add-SimpleVirtualDisk } $result = Add-VirtualDiskUsingWin32 ` - $Handle ` - $securityDescriptor ` - $flags ` - $providerSpecificFlags ` - $attachVirtualDiskParameters ` - ([System.IntPtr]::Zero) + -Handle $Handle ` + -SecurityDescriptor $securityDescriptor ` + -Flags $flags ` + -ProviderSpecificFlags $providerSpecificFlags ` + -AttachVirtualDiskParameters $attachVirtualDiskParameters ` + -Overlapped ([System.IntPtr]::Zero) if ($result -eq 0) { @@ -548,7 +556,7 @@ function Add-SimpleVirtualDisk { $win32Error = [System.ComponentModel.Win32Exception]::new($result) throw [System.Exception]::new( ` - ($script:localizedData.AttachVirtualDiskError -f $win32Error.Message), ` + ($script:localizedData.AttachVirtualDiskError -f $VirtualDiskPath, $win32Error.Message), ` $win32Error) } @@ -604,18 +612,18 @@ function Get-VirtualDiskHandle [ref]$handle = [Microsoft.Win32.SafeHandles.SafeFileHandle]::Zero $result = Get-VirtualDiskUsingWin32 ` - $virtualStorageType ` - $VirtualDiskPath ` - $accessMask ` - $flags ` - $openVirtualDiskParameters ` - $handle + -VirtualStorageType $virtualStorageType ` + -VirtualDiskPath $VirtualDiskPath ` + -AccessMask $accessMask ` + -Flags $flags ` + -OpenVirtualDiskParameters $openVirtualDiskParameters ` + -Handle $handle if ($result -ne 0) { $win32Error = [System.ComponentModel.Win32Exception]::new($result) throw [System.Exception]::new( ` - ($script:localizedData.OpenVirtualDiskError -f $win32Error.Message), ` + ($script:localizedData.OpenVirtualDiskError -f $VirtualDiskPath, $win32Error.Message), ` $win32Error) } @@ -649,6 +657,7 @@ function Get-VirtualStorageType # Default to the vhdx file format. $virtualStorageType.VendorId = [VirtDisk.Helper]::VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT $virtualStorageType.DeviceId = [VirtDisk.Helper]::VIRTUAL_STORAGE_TYPE_DEVICE_VHDX + if ($DiskFormat -eq 'Vhd') { $virtualStorageType.DeviceId = [VirtDisk.Helper]::VIRTUAL_STORAGE_TYPE_DEVICE_VHD diff --git a/source/Modules/StorageDsc.VirtualHardDisk.Win32Helpers/en-US/StorageDsc.VirtualHardDisk.Win32Helpers.strings.psd1 b/source/Modules/StorageDsc.VirtualHardDisk.Win32Helpers/en-US/StorageDsc.VirtualHardDisk.Win32Helpers.strings.psd1 new file mode 100644 index 00000000..0eee5c89 --- /dev/null +++ b/source/Modules/StorageDsc.VirtualHardDisk.Win32Helpers/en-US/StorageDsc.VirtualHardDisk.Win32Helpers.strings.psd1 @@ -0,0 +1,11 @@ +ConvertFrom-StringData @' + CreatingVirtualDiskMessage = Creating virtual hard disk at location '{0}'. + CreateVirtualDiskError = Unable to create virtual hard disk at location '{0}' due to error '{1}'. + VirtualDiskCreatedSuccessfully = Virtual hard disk created successfully at location: '{0}'. + AttachingVirtualDiskMessage = Attaching virtual hard disk at location '{0}'. + AttachVirtualDiskError = Unable to attach virtual hard disk at location '{0}' due to error '{1}'. + VirtualDiskAttachedSuccessfully = Virtual hard disk attached successfully at location '{0}'. + OpeningVirtualBeforeAttachingMessage = Attempting to open handle to virtual hard disk at location '{0}. + OpenVirtualDiskError = Unable to open virtual hard disk handle for location '{0}' due to error '{1}'. + VirtualDiskOpenedSuccessfully = Virtual hard disk handle for location '{0}' opened successfully. +'@ diff --git a/source/Modules/VirtualHardDisk.Win32Helpers/en-US/VirtualHardDisk.Win32Helpers.strings.psd1 b/source/Modules/VirtualHardDisk.Win32Helpers/en-US/VirtualHardDisk.Win32Helpers.strings.psd1 deleted file mode 100644 index 8e3340a7..00000000 --- a/source/Modules/VirtualHardDisk.Win32Helpers/en-US/VirtualHardDisk.Win32Helpers.strings.psd1 +++ /dev/null @@ -1,11 +0,0 @@ -ConvertFrom-StringData @' - CreatingVirtualDiskMessage = Creating virtual disk at location '{0}'. - CreateVirtualDiskError = Unable to create virtual disk due to win32 error '{0}'. - VirtualDiskCreatedSuccessfully = Virtual disk created successfully at location: '{0}'. - AttachingVirtualDiskMessage = Attaching virtual disk at location '{0}'. - AttachVirtualDiskError = Unable to attach virtual disk due to win32 error '{0}'. - VirtualDiskAttachedSuccessfully = Virtual disk attached successfully at location '{0}'. - OpeningVirtualBeforeAttachingMessage = Attempting to open handle to virtual disk at location '{0}. - OpenVirtualDiskError = Unable to open virtual disk handle due to win32 error '{0}'. - VirtualDiskOpenedSuccessfully = Virtual disk handle for location '{0}' opened successfully. -'@ diff --git a/tests/Unit/VirtualHardDisk.Win32Helpers.Tests.ps1 b/tests/Unit/StorageDsc.VirtualHardDisk.Win32Helpers.Tests.ps1 similarity index 95% rename from tests/Unit/VirtualHardDisk.Win32Helpers.Tests.ps1 rename to tests/Unit/StorageDsc.VirtualHardDisk.Win32Helpers.Tests.ps1 index f29f26a2..da06421e 100644 --- a/tests/Unit/VirtualHardDisk.Win32Helpers.Tests.ps1 +++ b/tests/Unit/StorageDsc.VirtualHardDisk.Win32Helpers.Tests.ps1 @@ -19,9 +19,7 @@ Remove-Module -Name $script:parentModule -Force -ErrorAction 'SilentlyContinue' $script:subModuleName = (Split-Path -Path $PSCommandPath -Leaf) -replace '\.Tests.ps1' $script:subModuleFile = Join-Path -Path $script:subModulesFolder -ChildPath "$($script:subModuleName)/$($script:subModuleName).psm1" -if (-not (Get-Module -Name $script:subModuleFile -ListAvailable)) { - Import-Module $script:subModuleFile -Force -ErrorAction Stop -} +Import-Module $script:subModuleFile -Force -ErrorAction Stop #endregion HEADER Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') @@ -155,7 +153,7 @@ InModuleScope $script:subModuleName { DiskFormat = 'vhd' } - Describe 'VirtualHardDisk.Win32Helpers\New-SimpleVirtualDisk' -Tag 'New-SimpleVirtualDisk' { + Describe 'StorageDsc.VirtualHardDisk.Win32Helpers\New-SimpleVirtualDisk' -Tag 'New-SimpleVirtualDisk' { Context 'Creating and attaching a new virtual disk (vhdx) successfully' { Mock ` -CommandName New-VirtualDiskUsingWin32 ` @@ -222,7 +220,7 @@ InModuleScope $script:subModuleName { $win32Error = [System.ComponentModel.Win32Exception]::new($script:AccessDeniedWin32Error) $exception = [System.Exception]::new( ` - ($script:localizedData.CreateVirtualDiskError -f $win32Error.Message), ` + ($script:localizedData.CreateVirtualDiskError -f $script:mockedParams.VirtualDiskPath, $win32Error.Message), ` $win32Error) It 'Should throw an exception in creation method' { @@ -243,7 +241,7 @@ InModuleScope $script:subModuleName { } } - Describe 'VirtualHardDisk.Win32Helpers\Add-SimpleVirtualDisk' -Tag 'Add-SimpleVirtualDisk' { + Describe 'StorageDsc.VirtualHardDisk.Win32Helpers\Add-SimpleVirtualDisk' -Tag 'Add-SimpleVirtualDisk' { Context 'Attaching a virtual disk failed due to exception' { Mock ` @@ -258,7 +256,7 @@ InModuleScope $script:subModuleName { $win32Error = [System.ComponentModel.Win32Exception]::new($script:AccessDeniedWin32Error) $exception = [System.Exception]::new( ` - ($script:localizedData.AttachVirtualDiskError -f $win32Error.Message), ` + ($script:localizedData.AttachVirtualDiskError -f $script:mockedParams.VirtualDiskPath, $win32Error.Message), ` $win32Error) It 'Should throw an exception during attach function' { @@ -305,7 +303,7 @@ InModuleScope $script:subModuleName { } } - Describe 'VirtualHardDisk.Win32Helpers\Get-VirtualDiskHandle' -Tag 'Get-VirtualDiskHandle' { + Describe 'StorageDsc.VirtualHardDisk.Win32Helpers\Get-VirtualDiskHandle' -Tag 'Get-VirtualDiskHandle' { Context 'Opening a virtual disk file failed due to exception' { Mock ` @@ -315,7 +313,7 @@ InModuleScope $script:subModuleName { $win32Error = [System.ComponentModel.Win32Exception]::new($script:AccessDeniedWin32Error) $exception = [System.Exception]::new( ` - ($script:localizedData.OpenVirtualDiskError -f $win32Error.Message), ` + ($script:localizedData.OpenVirtualDiskError -f $script:mockedParams.VirtualDiskPath, $win32Error.Message), ` $win32Error) It 'Should throw an exception while attempting to open virtual disk file' { @@ -355,7 +353,7 @@ InModuleScope $script:subModuleName { } } - Describe 'VirtualHardDisk.Win32Helpers\Get-VirtualStorageType' -Tag 'Get-VirtualStorageType' { + Describe 'StorageDsc.VirtualHardDisk.Win32Helpers\Get-VirtualStorageType' -Tag 'Get-VirtualStorageType' { Context 'Storage type requested for vhd disk format' { $result = Get-VirtualStorageType -DiskFormat $script:vhdDiskFormat It 'Should not throw an exception' { From a4f1d5c32cb4436486e5548db23fa5e8549a6982 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Fri, 27 Oct 2023 21:14:11 -0700 Subject: [PATCH 15/21] update readme file --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b8d6e2a9..bca9bfe2 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ The **StorageDsc** module contains the following resources: disk drive (e.g. a CDROM or DVD drive). This resource ignores mounted ISOs. - **WaitForDisk** wait for a disk to become available. - **WaitForVolume** wait for a drive to be mounted and become available. +- **VirtualHardDisk** used to create and attach a virtual hard disk. This project has adopted [this code of conduct](CODE_OF_CONDUCT.md). From ecba5e766aef7a5ebaadc153f1b5a81a82a72705 Mon Sep 17 00:00:00 2001 From: bbonaby-MSFT Date: Sat, 28 Oct 2023 09:36:00 -0700 Subject: [PATCH 16/21] remove spaces in psd1 tags --- source/StorageDsc.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/StorageDsc.psd1 b/source/StorageDsc.psd1 index c69627a1..4a0bbfd0 100644 --- a/source/StorageDsc.psd1 +++ b/source/StorageDsc.psd1 @@ -53,7 +53,7 @@ Prerelease = '' # Tags applied to this module. These help with module discovery in online galleries. - Tags = @('DesiredStateConfiguration', 'DSC', 'DSCResource', 'Disk', 'Storage', 'Partition', 'Volume', 'Dev Drive', 'Virtual hard disk', 'VHD') + Tags = @('DesiredStateConfiguration', 'DSC', 'DSCResource', 'Disk', 'Storage', 'Partition', 'Volume', 'DevDrive', 'VirtualHardDisk', 'VHD') # A URL to the license for this module. LicenseUri = 'https://github.com/dsccommunity/StorageDsc/blob/main/LICENSE' From 49ccf98e66f9fe864fde642da062f32d27ea6185 Mon Sep 17 00:00:00 2001 From: "U-BRANX-PC\\brand" Date: Wed, 7 Aug 2024 11:31:55 -0700 Subject: [PATCH 17/21] update based on PR comments and add an IsAdmin check --- .../DSC_VirtualHardDisk.psm1 | 108 ++++---- .../DSC_VirtualHardDisk/README.md | 2 +- .../en-US/DSC_VirtualHardDisk.strings.psd1 | 13 +- .../StorageDsc.Common/StorageDsc.Common.psm1 | 28 +- ...orageDsc.VirtualHardDisk.Win32Helpers.psm1 | 78 +++--- ....VirtualHardDisk.Win32Helpers.strings.psd1 | 8 +- tests/Unit/DSC_VirtualHardDisk.Tests.ps1 | 252 ++++++++++++------ ...Dsc.VirtualHardDisk.Win32Helpers.Tests.ps1 | 70 ++--- 8 files changed, 334 insertions(+), 225 deletions(-) diff --git a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 index 303cb4e8..4480cbd5 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 +++ b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 @@ -30,7 +30,7 @@ function Get-TargetResource ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [String] + [System.String] $FilePath ) @@ -40,11 +40,11 @@ function Get-TargetResource ) -join '' ) $diskImage = Get-DiskImage -ImagePath $FilePath -ErrorAction SilentlyContinue - $Ensure = 'Present' + $ensure = 'Present' if (-not $diskImage) { - $Ensure = 'Absent' + $ensure = 'Absent' } # Get the virtual hard disk info using its path on the system @@ -53,7 +53,7 @@ function Get-TargetResource Attached = $diskImage.Attached Size = $diskImage.Size DiskNumber = $diskImage.DiskNumber - Ensure = $Ensure + Ensure = $ensure } } # function Get-TargetResource @@ -83,11 +83,11 @@ function Set-TargetResource ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [String] + [System.String] $FilePath, [Parameter()] - [ValidateScript({$_ -gt 0})] + [ValidateScript({ $_ -gt 0 })] [System.UInt64] $DiskSize, @@ -102,24 +102,29 @@ function Set-TargetResource $DiskType = 'Dynamic', [Parameter()] - [ValidateSet('Present','Absent')] + [ValidateSet('Present', 'Absent')] [System.String] $Ensure = 'Present' ) Assert-ParametersValid -FilePath $FilePath -DiskSize $DiskSize -DiskFormat $DiskFormat - $resource = Get-TargetResource -FilePath $FilePath + if (-not (Test-RunningAsAdministrator)) + { + throw $script:localizedData.VirtualDiskAdminError + } + + $currentState = Get-TargetResource -FilePath $FilePath if ($Ensure -eq 'Present') { # Disk doesn't exist - if (-not $resource.FilePath) + if (-not $currentState.FilePath) { Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualHardDiskDoesNotExistCreatingNow -f $FilePath) - ) -join '' ) + "$($MyInvocation.MyCommand): " + $($script:localizedData.VirtualHardDiskDoesNotExistCreatingNow -f $FilePath) + ) -join '' ) $folderPath = Split-Path -Parent $FilePath $wasLocationCreated = $false @@ -137,16 +142,16 @@ function Set-TargetResource } catch { - # Remove file if we created it but were unable to attach it. No handles are open when this happens. + # Remove file if we created it but were unable to attach it. No handles are open when this happens. if (Test-Path -Path $FilePath -PathType Leaf) { Write-Verbose -Message ($script:localizedData.RemovingCreatedVirtualHardDiskFile -f $FilePath) - Remove-Item $FilePath -verbose + Remove-Item -LiteralPath $FilePath -Verbose -Force } if ($wasLocationCreated) { - Remove-Item -LiteralPath $folderPath -verbose + Remove-Item -LiteralPath $folderPath -Verbose -Force } # Rethrow the exception @@ -154,11 +159,11 @@ function Set-TargetResource } } - elseif (-not $resource.Attached) + elseif (-not $currentState.Attached) { Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualDiskNotAttached -f $FilePath) + "$($MyInvocation.MyCommand): " + $($script:localizedData.VirtualDiskNotMounted -f $FilePath) ) -join '' ) # Virtual hard disk file exists so lets attempt to attach it to the system. @@ -168,12 +173,12 @@ function Set-TargetResource else { # Detach the virtual hard disk if its not suppose to be attached. - if ($resource.Attached) + if ($currentState.Attached) { Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " $($script:localizedData.VirtualHardDiskDetachingImage ` - -f $FilePath) + -f $FilePath) ) -join '' ) Dismount-DiskImage -ImagePath $FilePath @@ -205,14 +210,14 @@ function Test-TargetResource [CmdletBinding()] [OutputType([System.Boolean])] param - ( + ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [String] + [System.String] $FilePath, [Parameter()] - [ValidateScript({$_ -gt 0})] + [ValidateScript({ $_ -gt 0 })] [System.UInt64] $DiskSize, @@ -227,57 +232,57 @@ function Test-TargetResource $DiskType = 'Dynamic', [Parameter()] - [ValidateSet('Present','Absent')] + [ValidateSet('Present', 'Absent')] [System.String] $Ensure = 'Present' ) Assert-ParametersValid -FilePath $FilePath -DiskSize $DiskSize -DiskFormat $DiskFormat - $resource = Get-TargetResource -FilePath $FilePath + $currentState = Get-TargetResource -FilePath $FilePath Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.CheckingVirtualDiskExists -f $FilePath) - ) -join '' ) + "$($MyInvocation.MyCommand): " + $($script:localizedData.CheckingVirtualDiskExists -f $FilePath) + ) -join '' ) if ($Ensure -eq 'Present') { # Found the virtual hard disk and confirmed its attached to the system. - if ($resource.Attached) + if ($currentState.Attached) { Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualHardDiskCurrentlyAttached -f $FilePath) - ) -join '' ) + "$($MyInvocation.MyCommand): " + $($script:localizedData.VirtualHardDiskCurrentlyMounted -f $FilePath) + ) -join '' ) return $true } Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualHardDiskMayNotExistOrNotAttached -f $FilePath) - ) -join '' ) + "$($MyInvocation.MyCommand): " + $($script:localizedData.VirtualHardDiskMayNotExistOrNotMounted -f $FilePath) + ) -join '' ) return $false } else { # Found the virtual hard disk and confirmed its attached to the system but ensure variable set to 'Absent'. - if ($resource.Attached) + if ($currentState.Attached) { Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualHardDiskCurrentlyAttachedButShouldNotBe -f $FilePath) - ) -join '' ) + "$($MyInvocation.MyCommand): " + $($script:localizedData.VirtualHardDiskCurrentlyMountedButShouldNotBe -f $FilePath) + ) -join '' ) return $false } Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.VirtualHardDiskMayNotExistOrNotAttached -f $FilePath) - ) -join '' ) + "$($MyInvocation.MyCommand): " + $($script:localizedData.VirtualHardDiskMayNotExistOrNotMounted -f $FilePath) + ) -join '' ) return $true } @@ -303,10 +308,11 @@ function Assert-ParametersValid ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [String] + [System.String] $FilePath, [Parameter(Mandatory = $true)] + [ValidateScript({ $_ -gt 0 })] [System.UInt64] $DiskSize, @@ -337,8 +343,8 @@ function Assert-ParametersValid elseif ($extension -ne $DiskFormat) { New-InvalidArgumentException ` - -Message $($script:localizedData.VirtualHardDiskExtensionAndFormatMismatchError -f $FilePath, $extension, $DiskFormat) ` - -ArgumentName 'FilePath' + -Message $($script:localizedData.VirtualHardDiskExtensionAndFormatMismatchError -f $FilePath, $extension, $DiskFormat) ` + -ArgumentName 'FilePath' } } else @@ -356,25 +362,25 @@ function Assert-ParametersValid $isInValidSizeForVhdFormat = ($DiskSize -lt 10MB -bor $DiskSize -gt 2040GB) $isInValidSizeForVhdxFormat = ($DiskSize -lt 10MB -bor $DiskSize -gt 64TB) if ((-not $isVhdxFormat -and $isInValidSizeForVhdFormat) -bor - ($IsVhdxFormat -and $isInValidSizeForVhdxFormat)) + ($isVhdxFormat -and $isInValidSizeForVhdxFormat)) { if ($DiskSize -lt 1GB) { - $DiskSizeString = ($DiskSize / 1MB).ToString("0.00MB") + $diskSizeString = ($DiskSize / 1MB).ToString('0.00MB') } else { - $DiskSizeString = ($DiskSize / 1TB).ToString("0.00TB") + $diskSizeString = ($DiskSize / 1TB).ToString('0.00TB') } - $InvalidSizeMsg = $script:localizedData.VhdFormatDiskSizeInvalid + $invalidSizeMsg = $script:localizedData.VhdFormatDiskSizeInvalid if ($isVhdxFormat) { - $InvalidSizeMsg = $script:localizedData.VhdxFormatDiskSizeInvalid + $invalidSizeMsg = $script:localizedData.VhdxFormatDiskSizeInvalid } New-InvalidArgumentException ` - -Message $($InvalidSizeMsg -f $DiskSizeString) ` + -Message $($invalidSizeMsg -f $diskSizeString) ` -ArgumentName 'DiskSize' } } # Assert-ParametersValid diff --git a/source/DSCResources/DSC_VirtualHardDisk/README.md b/source/DSCResources/DSC_VirtualHardDisk/README.md index 6359327e..c2d9e37d 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/README.md +++ b/source/DSCResources/DSC_VirtualHardDisk/README.md @@ -58,7 +58,7 @@ config/machine could result in an invalid config. 1. The resource only supports `.vhd` and `.vhdx` files. No other virtual hard disk file extension is supported at this time. -1. The ability to `expand` the max size of the virtual hard disk after its creation +1. The ability to `expand` the max size of the virtual hard disk after its creation is not currently included in this resource. 1. The ability to `shrink` the max size of the virtual hard disk after its creation is not currently included in this resource. 1. The resource uses default values internally to create the virtual hard disk diff --git a/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 b/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 index 394b5500..d3fb5281 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 +++ b/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 @@ -1,17 +1,18 @@ ConvertFrom-StringData @' - CheckingVirtualDiskExists = Checking virtual hard disk at location '{0}' exists and is attached. - VirtualHardDiskMayNotExistOrNotAttached = The virtual hard disk at location '{0}' does not exist or is not attached. + CheckingVirtualDiskExists = Checking virtual hard disk at location '{0}' exists and is mounted. + VirtualHardDiskMayNotExistOrNotMounted = The virtual hard disk at location '{0}' does not exist or is not mounted. VirtualHardDiskDoesNotExistCreatingNow = The virtual hard disk at location '{0}' does not exist. Creating virtual hard disk now. - VirtualHardDiskCurrentlyAttached = The virtual hard disk at location '{0}' was found and is attached to the system. - VirtualHardDiskCurrentlyAttachedButShouldNotBe = The virtual hard disk at location '{0}' was found and is attached to the system but it should not be. + VirtualHardDiskCurrentlyMounted = The virtual hard disk at location '{0}' was found and is mounted to the system. + VirtualHardDiskCurrentlyMountedButShouldNotBe = The virtual hard disk at location '{0}' was found and is mounted to the system but it should not be. VhdFormatDiskSizeInvalid = The virtual hard disk size '{0}' was invalid for the 'vhd' format. Min supported value is 10 Mb and max supported value 2040 Gb. VhdxFormatDiskSizeInvalid = The virtual hard disk size '{0}' was invalid for the 'vhdx' format. Min supported value is 10 Mb and max supported value 64 Tb. - VirtualDiskNotAttached = The virtual hard disk at location '{0}' is not attached. Attaching virtual hard disk now. + VirtualDiskNotMounted = The virtual hard disk at location '{0}' is not mounted. Mounting virtual hard disk now. VirtualHardDiskDetachingImage = The virtual hard disk located at '{0}' is detaching. VirtualHardDiskUnsupportedFileType = The file type .{0} is not supported. Only .vhd and .vhdx file types are supported. VirtualHardDiskPathError = The path '{0}' must be a fully qualified path that starts with a Drive Letter. VirtualHardDiskExtensionAndFormatMismatchError = The path you entered '{0}' has extension '{1}' but the disk format entered is '{2}'. Both the extension and format must match. VirtualHardDiskNoExtensionError = The path '{0}' does not contain an extension. Supported extension types are '.vhd' and '.vhdx'. GettingVirtualHardDisk = Getting virtual hard disk information for virtual hard disk located at '{0}. - RemovingCreatedVirtualHardDiskFile = The virtual hard disk file at location '{0}' is being removed due to an error while attempting to attach it to the system. + RemovingCreatedVirtualHardDiskFile = The virtual hard disk file at location '{0}' is being removed due to an error while attempting to mount it to the system. + VirtualDiskAdminError = Creating and mounting a virtual disk requires running PowerShell as an Administrator. Please launch PowerShell as Administrator and try again. '@ diff --git a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 index 718f353a..524e16aa 100644 --- a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 +++ b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 @@ -160,7 +160,7 @@ function Get-DiskByIdentifier $DiskId, [Parameter()] - [ValidateSet('Number','UniqueId','Guid','Location','FriendlyName','SerialNumber')] + [ValidateSet('Number', 'UniqueId', 'Guid', 'Location', 'FriendlyName', 'SerialNumber')] [System.String] $DiskIdType = 'Number' ) @@ -182,7 +182,7 @@ function Get-DiskByIdentifier default # for filters requiring Where-Object { $disk = Get-Disk -ErrorAction SilentlyContinue | - Where-Object -Property $DiskIdType -EQ $DiskId + Where-Object -Property $DiskIdType -EQ $DiskId } } @@ -233,7 +233,7 @@ function Get-DevDriveWin32HelperScript param () - $DevDriveHelperDefinitions = @' + $DevDriveHelperDefinitions = @' // https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ne-sysinfoapi-developer_drive_enablement_state public enum DEVELOPER_DRIVE_ENABLEMENT_STATE @@ -385,8 +385,8 @@ function Get-DevDriveWin32HelperScript -Name 'DevDriveHelper' ` -MemberDefinition $DevDriveHelperDefinitions ` -UsingNamespace ` - 'System.ComponentModel', - 'Microsoft.Win32.SafeHandles' + 'System.ComponentModel', + 'Microsoft.Win32.SafeHandles' } return $script:DevDriveWin32Helper @@ -628,6 +628,21 @@ function Compare-SizeUsingGB }# end function Compare-SizeUsingGB +<# + .SYNOPSIS + Gets a boolean indicating whether the script is running as Administrator or not. +#> +function Test-RunningAsAdministrator +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + () + + $currentPrincipal = New-Object -TypeName Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) + return $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +} # end function Test-RunningAsAdministrator + Export-ModuleMember -Function @( 'Restart-ServiceIfExists', 'Assert-DriveLetterValid', @@ -642,5 +657,6 @@ Export-ModuleMember -Function @( 'Get-DevDriveEnablementState', 'Test-DevDriveVolume', 'Invoke-DeviceIoControlWrapperForDevDriveQuery', - 'Compare-SizeUsingGB' + 'Compare-SizeUsingGB', + 'Test-RunningAsAdministrator' ) diff --git a/source/Modules/StorageDsc.VirtualHardDisk.Win32Helpers/StorageDsc.VirtualHardDisk.Win32Helpers.psm1 b/source/Modules/StorageDsc.VirtualHardDisk.Win32Helpers/StorageDsc.VirtualHardDisk.Win32Helpers.psm1 index 49eb14aa..60c018fc 100644 --- a/source/Modules/StorageDsc.VirtualHardDisk.Win32Helpers/StorageDsc.VirtualHardDisk.Win32Helpers.psm1 +++ b/source/Modules/StorageDsc.VirtualHardDisk.Win32Helpers/StorageDsc.VirtualHardDisk.Win32Helpers.psm1 @@ -16,7 +16,7 @@ function Get-VirtDiskWin32HelperScript param () - $virtDiskDefinitions = @' + $virtDiskDefinitions = @' // https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ns-virtdisk-virtual_storage_type [StructLayout(LayoutKind.Sequential)] @@ -141,17 +141,20 @@ function Get-VirtDiskWin32HelperScript -Name 'Helper' ` -MemberDefinition $virtDiskDefinitions ` -UsingNamespace ` - 'System.ComponentModel', - 'Microsoft.Win32.SafeHandles' + 'System.ComponentModel', + 'Microsoft.Win32.SafeHandles' } return $script:VirtDiskHelper } # end function Get-VirtDiskWin32HelperScript <# + .DESCRIPTION + Calls the CreateVirtualDisk Win32 api to create a new virtual disk. + This is used so we can mock this call easier. + .SYNOPSIS - Calls Win32 CreateVirtualDisk api. This is used so we can mock this call - easier. + Creates a new virtual disk. .PARAMETER VirtualStorageType Specifies the type and provider (vendor) of the virtual storage device. @@ -192,6 +195,7 @@ function New-VirtualDiskUsingWin32 $VirtualStorageType, [Parameter(Mandatory = $true)] + [ValidateScript({ -not (Test-Path $_) })] [System.String] $VirtualDiskPath, @@ -239,9 +243,12 @@ function New-VirtualDiskUsingWin32 } # end function New-VirtualDiskUsingWin32 <# + .DESCRIPTION + Calls the AttachVirtualDisk Win32 api to mount an existing virtual disk + to the system. This is used so we can mock this call easier. + .SYNOPSIS - Calls Win32 AttachVirtualDisk api. This is used so we can mock this call - easier. + Mounts an existing virtual disk to the system. .PARAMETER Handle Specifies the reference to a handle to a virtual disk file. @@ -305,9 +312,12 @@ function Add-VirtualDiskUsingWin32 } # end function Add-VirtualDiskUsingWin32 <# + .DESCRIPTION + Calls the OpenVirtualDisk Win32 api to open an existing virtual disk. + This is used so we can mock this call easier. + .SYNOPSIS - Calls Win32 OpenVirtualDisk api. This is used so we can mock this call - easier. + Opens an existing virtual disk. .PARAMETER VirtualStorageType Specifies the type and provider (vendor) of the virtual storage device. @@ -338,6 +348,7 @@ function Get-VirtualDiskUsingWin32 $VirtualStorageType, [Parameter(Mandatory = $true)] + [ValidateScript({ Test-Path $_ })] [System.String] $VirtualDiskPath, @@ -371,7 +382,7 @@ function Get-VirtualDiskUsingWin32 <# .SYNOPSIS - Creates and attaches a virtual disk to the system. + Creates and mounts a virtual disk to the system. .PARAMETER VirtualDiskPath Specifies the whole path to the virtual disk file. @@ -414,7 +425,7 @@ function New-SimpleVirtualDisk # Get parameters for CreateVirtualDisk function [ref]$virtualStorageType = Get-VirtualStorageType -DiskFormat $DiskFormat - [ref]$createVirtualDiskParameters = New-Object VirtDisk.Helper+CREATE_VIRTUAL_DISK_PARAMETERS + [ref]$createVirtualDiskParameters = New-Object -TypeName VirtDisk.Helper+CREATE_VIRTUAL_DISK_PARAMETERS $createVirtualDiskParameters.Value.Version = [VirtDisk.Helper]::CREATE_VIRTUAL_DISK_VERSION_2 $createVirtualDiskParameters.Value.MaximumSize = $DiskSizeInBytes $securityDescriptor = [System.IntPtr]::Zero @@ -435,6 +446,7 @@ function New-SimpleVirtualDisk try { + # create the virtual disk $result = New-VirtualDiskUsingWin32 ` -VirtualStorageType $virtualStorageType ` -VirtualDiskPath $VirtualDiskPath ` @@ -451,11 +463,12 @@ function New-SimpleVirtualDisk $win32Error = [System.ComponentModel.Win32Exception]::new($result) throw [System.Exception]::new( ` ($script:localizedData.CreateVirtualDiskError -f $VirtualDiskPath, $win32Error.Message), ` - $win32Error) + $win32Error) } Write-Verbose -Message ($script:localizedData.VirtualDiskCreatedSuccessfully -f $VirtualDiskPath) + # Mount the newly created virtual disk Add-SimpleVirtualDisk ` -VirtualDiskPath $VirtualDiskPath ` -DiskFormat $DiskFormat ` @@ -473,7 +486,7 @@ function New-SimpleVirtualDisk <# .SYNOPSIS - Attaches a virtual disk to the system. + Mounts a virtual disk to the system. .PARAMETER VirtualDiskPath Specifies the whole path to the virtual disk file. @@ -504,7 +517,7 @@ function Add-SimpleVirtualDisk ) try { - Write-Verbose -Message ($script:localizedData.AttachingVirtualDiskMessage -f $VirtualDiskPath) + Write-Verbose -Message ($script:localizedData.MountingVirtualDiskMessage -f $VirtualDiskPath) $vDiskHelper = Get-VirtDiskWin32HelperScript @@ -515,29 +528,22 @@ function Add-SimpleVirtualDisk } # Build parameters for AttachVirtualDisk function. - [ref]$attachVirtualDiskParameters = New-Object VirtDisk.Helper+ATTACH_VIRTUAL_DISK_PARAMETERS + [ref]$attachVirtualDiskParameters = New-Object -TypeName VirtDisk.Helper+ATTACH_VIRTUAL_DISK_PARAMETERS $attachVirtualDiskParameters.Value.Version = [VirtDisk.Helper]::ATTACH_VIRTUAL_DISK_VERSION_1 $securityDescriptor = [System.IntPtr]::Zero $providerSpecificFlags = 0 $result = 0 <# - Some builds of Windows may not have the ATTACH_VIRTUAL_DISK_FLAG_AT_BOOT flag. So we attempt to attach the virtual - disk with the flag first. If this fails we attach the virtual disk without the flag. The flag allows the - virtual disk to be attached by the system at boot time. + Some builds of Windows may not have the ATTACH_VIRTUAL_DISK_FLAG_AT_BOOT flag. So we attempt to mount the virtual + disk with the flag first. If this fails we mount the virtual disk without the flag. The flag allows the + virtual disk to be auto-mounted by the system at boot time. #> - for ($attempts = 0; $attempts -lt 2; $attempts++) - { - if ($attempts -eq 0) - { - $flags = [VirtDisk.Helper]::ATTACH_VIRTUAL_DISK_FLAG_PERMANENT_LIFETIME -bor - [VirtDisk.Helper]::ATTACH_VIRTUAL_DISK_FLAG_AT_BOOT - } - else - { - $flags = [VirtDisk.Helper]::ATTACH_VIRTUAL_DISK_FLAG_PERMANENT_LIFETIME - } + $combinedFlags = [VirtDisk.Helper]::ATTACH_VIRTUAL_DISK_FLAG_PERMANENT_LIFETIME -bor [VirtDisk.Helper]::ATTACH_VIRTUAL_DISK_FLAG_AT_BOOT + $attemptFlagValues = @($combinedFlags, [VirtDisk.Helper]::ATTACH_VIRTUAL_DISK_FLAG_PERMANENT_LIFETIME) + foreach ($flags in $attemptFlagValues) + { $result = Add-VirtualDiskUsingWin32 ` -Handle $Handle ` -SecurityDescriptor $securityDescriptor ` @@ -556,11 +562,11 @@ function Add-SimpleVirtualDisk { $win32Error = [System.ComponentModel.Win32Exception]::new($result) throw [System.Exception]::new( ` - ($script:localizedData.AttachVirtualDiskError -f $VirtualDiskPath, $win32Error.Message), ` - $win32Error) + ($script:localizedData.MountVirtualDiskError -f $VirtualDiskPath, $win32Error.Message), ` + $win32Error) } - Write-Verbose -Message ($script:localizedData.VirtualDiskAttachedSuccessfully -f $VirtualDiskPath) + Write-Verbose -Message ($script:localizedData.VirtualDiskMountedSuccessfully -f $VirtualDiskPath) } finally { @@ -598,12 +604,12 @@ function Get-VirtualDiskHandle $DiskFormat ) - Write-Verbose -Message ($script:localizedData.OpeningVirtualBeforeAttachingMessage) + Write-Verbose -Message ($script:localizedData.OpeningVirtualBeforeMountingMessage) $vDiskHelper = Get-VirtDiskWin32HelperScript # Get parameters for OpenVirtualDisk function. - [ref]$virtualStorageType = Get-VirtualStorageType -DiskFormat $DiskFormat - [ref]$openVirtualDiskParameters = New-Object VirtDisk.Helper+OPEN_VIRTUAL_DISK_PARAMETERS + [ref]$virtualStorageType = Get-VirtualStorageType -DiskFormat $DiskFormat + [ref]$openVirtualDiskParameters = New-Object -TypeName VirtDisk.Helper+OPEN_VIRTUAL_DISK_PARAMETERS $openVirtualDiskParameters.Value.Version = [VirtDisk.Helper]::OPEN_VIRTUAL_DISK_VERSION_1 $accessMask = [VirtDisk.Helper]::VIRTUAL_DISK_ACCESS_ALL $flags = [VirtDisk.Helper]::OPEN_VIRTUAL_DISK_FLAG_NONE @@ -624,7 +630,7 @@ function Get-VirtualDiskHandle $win32Error = [System.ComponentModel.Win32Exception]::new($result) throw [System.Exception]::new( ` ($script:localizedData.OpenVirtualDiskError -f $VirtualDiskPath, $win32Error.Message), ` - $win32Error) + $win32Error) } Write-Verbose -Message ($script:localizedData.VirtualDiskOpenedSuccessfully -f $VirtualDiskPath) diff --git a/source/Modules/StorageDsc.VirtualHardDisk.Win32Helpers/en-US/StorageDsc.VirtualHardDisk.Win32Helpers.strings.psd1 b/source/Modules/StorageDsc.VirtualHardDisk.Win32Helpers/en-US/StorageDsc.VirtualHardDisk.Win32Helpers.strings.psd1 index 0eee5c89..5df5109f 100644 --- a/source/Modules/StorageDsc.VirtualHardDisk.Win32Helpers/en-US/StorageDsc.VirtualHardDisk.Win32Helpers.strings.psd1 +++ b/source/Modules/StorageDsc.VirtualHardDisk.Win32Helpers/en-US/StorageDsc.VirtualHardDisk.Win32Helpers.strings.psd1 @@ -2,10 +2,10 @@ ConvertFrom-StringData @' CreatingVirtualDiskMessage = Creating virtual hard disk at location '{0}'. CreateVirtualDiskError = Unable to create virtual hard disk at location '{0}' due to error '{1}'. VirtualDiskCreatedSuccessfully = Virtual hard disk created successfully at location: '{0}'. - AttachingVirtualDiskMessage = Attaching virtual hard disk at location '{0}'. - AttachVirtualDiskError = Unable to attach virtual hard disk at location '{0}' due to error '{1}'. - VirtualDiskAttachedSuccessfully = Virtual hard disk attached successfully at location '{0}'. - OpeningVirtualBeforeAttachingMessage = Attempting to open handle to virtual hard disk at location '{0}. + MountingVirtualDiskMessage = Mounting virtual hard disk at location '{0}'. + MountVirtualDiskError = Unable to mount virtual hard disk at location '{0}' due to error '{1}'. + VirtualDiskMountedSuccessfully = Virtual hard disk mounted successfully at location '{0}'. + OpeningVirtualBeforeMountingMessage = Attempting to open handle to virtual hard disk at location '{0}. OpenVirtualDiskError = Unable to open virtual hard disk handle for location '{0}' due to error '{1}'. VirtualDiskOpenedSuccessfully = Virtual hard disk handle for location '{0}' opened successfully. '@ diff --git a/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 b/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 index dd1ee788..24ccb5b0 100644 --- a/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 +++ b/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 @@ -43,48 +43,48 @@ try $script:DiskImageSize65Gb = 65Gb $script:MockTestPathCount = 0 - $script:mockedDiskImageAttachedVhdx = [pscustomobject] @{ - Attached = $true - ImagePath = $script:DiskImageGoodVhdxPath - Size = 100GB - DiskNumber = 2 + $script:mockedDiskImageMountedVhdx = [pscustomobject] @{ + Attached = $true + ImagePath = $script:DiskImageGoodVhdxPath + Size = 100GB + DiskNumber = 2 } - $script:mockedDiskImageAttachedVhd = [pscustomobject] @{ - Attached = $true - ImagePath = $script:DiskImageGoodVhdPath - Size = 100GB - DiskNumber = 2 + $script:mockedDiskImageMountedVhd = [pscustomobject] @{ + Attached = $true + ImagePath = $script:DiskImageGoodVhdPath + Size = 100GB + DiskNumber = 2 } - $script:mockedDiskImageNotAttachedVhdx = [pscustomobject] @{ - Attached = $false - ImagePath = $script:DiskImageGoodVhdxPath - Size = 100GB - DiskNumber = 2 + $script:mockedDiskImageNotMountedVhdx = [pscustomobject] @{ + Attached = $false + ImagePath = $script:DiskImageGoodVhdxPath + Size = 100GB + DiskNumber = 2 } - $script:mockedDiskImageNotAttachedVhd = [pscustomobject] @{ - Attached = $false - ImagePath = $script:DiskImageGoodVhdPath - Size = 100GB - DiskNumber = 2 + $script:mockedDiskImageNotMountedVhd = [pscustomobject] @{ + Attached = $false + ImagePath = $script:DiskImageGoodVhdPath + Size = 100GB + DiskNumber = 2 } $script:GetTargetOutputWhenBadPath = [pscustomobject] @{ - FilePath = $null + FilePath = $null Attached = $null - Size = $null - DiskNumber = $null - Ensure = 'Absent' + Size = $null + DiskNumber = $null + Ensure = 'Absent' } $script:GetTargetOutputWhenPathGood = [pscustomobject] @{ - FilePath = $mockedDiskImageAttachedVhdx.ImagePath - Attached = $mockedDiskImageAttachedVhdx.Attached - Size = $mockedDiskImageAttachedVhdx.Size - DiskNumber = $mockedDiskImageAttachedVhdx.DiskNumber - Ensure = 'Present' + FilePath = $mockedDiskImageMountedVhdx.ImagePath + Attached = $mockedDiskImageMountedVhdx.Attached + Size = $mockedDiskImageMountedVhdx.Size + DiskNumber = $mockedDiskImageMountedVhdx.DiskNumber + Ensure = 'Present' } $script:mockedDiskImageEmpty = $null @@ -134,9 +134,16 @@ try ) } + function Test-RunningAsAdministrator + { + [CmdletBinding()] + [OutputType([System.Boolean])] + Param + () + } + Describe 'DSC_VirtualHardDisk\Get-TargetResource' { Context 'When file path does not exist or was never mounted' { - Mock ` -CommandName Get-DiskImage ` -MockWith { $script:mockedDiskImageEmpty } ` @@ -152,7 +159,7 @@ try $resource.FilePath | Should -Be $script:GetTargetOutputWhenBadPath.FilePath } - It "Should return Attached $($script:GetTargetOutputWhenBadPath.Attached)" { + It "Should return Mounted $($script:GetTargetOutputWhenBadPath.Attached)" { $resource.Attached | Should -Be $script:GetTargetOutputWhenBadPath.Attached } @@ -168,7 +175,7 @@ try Context 'When file path does exist and was mounted at one point' { Mock ` -CommandName Get-DiskImage ` - -MockWith { $script:mockedDiskImageAttachedVhdx } ` + -MockWith { $script:mockedDiskImageMountedVhdx } ` -Verifiable $resource = Get-TargetResource -FilePath $script:DiskImageGoodVhdxPath -Verbose @@ -181,7 +188,7 @@ try $resource.FilePath | Should -Be $script:GetTargetOutputWhenPathGood.FilePath } - It "Should return Attached $($script:GetTargetOutputWhenPathGood.Attached)" { + It "Should return Mounted $($script:GetTargetOutputWhenPathGood.Attached)" { $resource.Attached | Should -Be $script:GetTargetOutputWhenPathGood.Attached } @@ -196,8 +203,10 @@ try } Describe 'DSC_VirtualHardDisk\Set-TargetResource' { - Context 'When file path is not fully qualified' { + Mock ` + -CommandName Test-RunningAsAdministrator ` + -MockWith { $true } $errorRecord = Get-InvalidArgumentRecord ` -Message ($script:localizedData.VirtualHardDiskPathError -f ` @@ -216,7 +225,30 @@ try } } + Context 'When not running as administrator' { + Mock ` + -CommandName Test-RunningAsAdministrator ` + -MockWith { $false } + + $exception = [System.Exception]::new($script:localizedData.VirtualDiskAdminError) + + It 'Should throw an error message that the user should run resource as admin' { + { + Set-TargetResource ` + -FilePath $DiskImageGoodVhdPath ` + -DiskSize $DiskImageSize65Gb ` + -DiskFormat 'vhd' ` + -Ensure 'Present' ` + -Verbose + } | Should -Throw -ExpectedMessage $exception.Message + } + } + Context 'When file extension is not .vhd or .vhdx' { + Mock ` + -CommandName Test-RunningAsAdministrator ` + -MockWith { $true } + $extension = [System.IO.Path]::GetExtension($DiskImageNonVirtDiskPath).TrimStart('.') $errorRecord = Get-InvalidArgumentRecord ` -Message ($script:localizedData.VirtualHardDiskUnsupportedFileType -f ` @@ -236,6 +268,10 @@ try } Context 'When file extension does not match the disk format' { + Mock ` + -CommandName Test-RunningAsAdministrator ` + -MockWith { $true } + $extension = [System.IO.Path]::GetExtension($DiskImageGoodVhdPath).TrimStart('.') $errorRecord = Get-InvalidArgumentRecord ` -Message ($script:localizedData.VirtualHardDiskExtensionAndFormatMismatchError -f ` @@ -255,9 +291,13 @@ try } Context 'When file extension is not present in the file path' { + Mock ` + -CommandName Test-RunningAsAdministrator ` + -MockWith { $true } + $errorRecord = Get-InvalidArgumentRecord ` -Message ($script:localizedData.VirtualHardDiskNoExtensionError -f ` - $script:DiskImageVirtDiskPathWithoutExtension) ` + $script:DiskImageVirtDiskPathWithoutExtension) ` -ArgumentName 'FilePath' It 'Should throw invalid argument error when the file type and filepath extension do not match' { @@ -273,8 +313,11 @@ try } Context 'When size provided is less than the minimum size for the vhd format' { + Mock ` + -CommandName Test-RunningAsAdministrator ` + -MockWith { $true } - $minSizeInMbString = ($DiskImageSizeBelowVirtDiskMinimum / 1MB).ToString("0.00MB") + $minSizeInMbString = ($DiskImageSizeBelowVirtDiskMinimum / 1MB).ToString('0.00MB') $errorRecord = Get-InvalidArgumentRecord ` -Message ($script:localizedData.VhdFormatDiskSizeInvalid -f ` $minSizeInMbString) ` @@ -293,7 +336,11 @@ try } Context 'When size provided is less than the minimum size for the vhdx format' { - $minSizeInMbString = ($DiskImageSizeBelowVirtDiskMinimum / 1MB).ToString("0.00MB") + Mock ` + -CommandName Test-RunningAsAdministrator ` + -MockWith { $true } + + $minSizeInMbString = ($DiskImageSizeBelowVirtDiskMinimum / 1MB).ToString('0.00MB') $errorRecord = Get-InvalidArgumentRecord ` -Message ($script:localizedData.VhdxFormatDiskSizeInvalid -f ` $minSizeInMbString) ` @@ -312,7 +359,11 @@ try } Context 'When size provided is greater than the maximum size for the vhd format' { - $maxSizeInTbString = ($DiskImageSizeAboveVhdMaximum / 1TB).ToString("0.00TB") + Mock ` + -CommandName Test-RunningAsAdministrator ` + -MockWith { $true } + + $maxSizeInTbString = ($DiskImageSizeAboveVhdMaximum / 1TB).ToString('0.00TB') $errorRecord = Get-InvalidArgumentRecord ` -Message ($script:localizedData.VhdFormatDiskSizeInvalid -f ` $maxSizeInTbString) ` @@ -331,7 +382,11 @@ try } Context 'When size provided is greater than the maximum size for the vhdx format' { - $maxSizeInTbString = ($DiskImageSizeAboveVhdxMaximum / 1TB).ToString("0.00TB") + Mock ` + -CommandName Test-RunningAsAdministrator ` + -MockWith { $true } + + $maxSizeInTbString = ($DiskImageSizeAboveVhdxMaximum / 1TB).ToString('0.00TB') $errorRecord = Get-InvalidArgumentRecord ` -Message ($script:localizedData.VhdxFormatDiskSizeInvalid -f ` $maxSizeInTbString) ` @@ -350,6 +405,10 @@ try } Context 'When file path to vhdx file is fully qualified' { + Mock ` + -CommandName Test-RunningAsAdministrator ` + -MockWith { $true } + It 'Should not throw invalid argument error when path is fully qualified' { { Set-TargetResource ` @@ -363,6 +422,10 @@ try } Context 'When file path to vhd is fully qualified' { + Mock ` + -CommandName Test-RunningAsAdministrator ` + -MockWith { $true } + It 'Should not throw invalid argument error when path is fully qualified' { { Set-TargetResource ` @@ -376,18 +439,21 @@ try } Context 'Virtual disk is mounted and ensure set to present' { + Mock ` + -CommandName Test-RunningAsAdministrator ` + -MockWith { $true } Mock ` -CommandName Get-DiskImage ` - -MockWith { $script:mockedDiskImageAttachedVhdx } ` + -MockWith { $script:mockedDiskImageMountedVhdx } ` -Verifiable - $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') + $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageMountedVhdx.ImagePath).TrimStart('.') It 'Should not throw an exception' { { Set-TargetResource ` - -FilePath $script:mockedDiskImageAttachedVhdx.ImagePath ` - -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` + -FilePath $script:mockedDiskImageMountedVhdx.ImagePath ` + -DiskSize $script:mockedDiskImageMountedVhdx.Size ` -DiskFormat $extension ` -Ensure 'Present' ` -Verbose @@ -401,22 +467,25 @@ try } Context 'Virtual disk is mounted and ensure set to absent, so it should be dismounted' { + Mock ` + -CommandName Test-RunningAsAdministrator ` + -MockWith { $true } Mock ` -CommandName Get-DiskImage ` - -MockWith { $script:mockedDiskImageAttachedVhdx } ` + -MockWith { $script:mockedDiskImageMountedVhdx } ` -Verifiable Mock ` -CommandName Dismount-DiskImage ` -Verifiable - $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') + $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageMountedVhdx.ImagePath).TrimStart('.') It 'Should dismount the virtual disk' { { Set-TargetResource ` - -FilePath $script:mockedDiskImageAttachedVhdx.ImagePath ` - -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` + -FilePath $script:mockedDiskImageMountedVhdx.ImagePath ` + -DiskSize $script:mockedDiskImageMountedVhdx.Size ` -DiskFormat $extension ` -Ensure 'Absent' ` -Verbose @@ -431,21 +500,25 @@ try } Context 'Virtual disk is dismounted and ensure set to present, so it should be re-mounted' { + Mock ` + -CommandName Test-RunningAsAdministrator ` + -MockWith { $true } + Mock ` -CommandName Get-DiskImage ` - -MockWith { $script:mockedDiskImageNotAttachedVhdx } ` + -MockWith { $script:mockedDiskImageNotMountedVhdx } ` -Verifiable Mock ` -CommandName Add-SimpleVirtualDisk ` -Verifiable - $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') + $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageMountedVhdx.ImagePath).TrimStart('.') It 'Should Not throw exception' { { Set-TargetResource ` - -FilePath $script:mockedDiskImageAttachedVhdx.ImagePath ` - -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` + -FilePath $script:mockedDiskImageMountedVhdx.ImagePath ` + -DiskSize $script:mockedDiskImageMountedVhdx.Size ` -DiskFormat $extension ` -Ensure 'Present' ` -Verbose @@ -459,7 +532,11 @@ try } } - Context 'Virtual disk does not exist and ensure set to present, so a new one should be created.' { + Context 'Virtual disk does not exist and ensure set to present, so a new one should be created and mounted' { + Mock ` + -CommandName Test-RunningAsAdministrator ` + -MockWith { $true } + Mock ` -CommandName Get-DiskImage ` -MockWith { $script:mockedDiskImageEmpty } ` @@ -469,12 +546,12 @@ try -CommandName New-SimpleVirtualDisk ` -Verifiable - $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') + $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageMountedVhdx.ImagePath).TrimStart('.') It 'Should not throw an exception' { { Set-TargetResource ` - -FilePath $script:mockedDiskImageAttachedVhdx.ImagePath ` - -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` + -FilePath $script:mockedDiskImageMountedVhdx.ImagePath ` + -DiskSize $script:mockedDiskImageMountedVhdx.Size ` -DiskFormat $extension ` -Ensure 'Present' ` -Verbose @@ -489,6 +566,9 @@ try } Context 'When folder does not exist in user provided path but an exception occurs after creating the virtual disk' { + Mock ` + -CommandName Test-RunningAsAdministrator ` + -MockWith { $true } Mock ` -CommandName Get-DiskImage ` @@ -523,13 +603,13 @@ try -Verifiable $script:MockTestPathCount = 0 - $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') + $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageMountedVhdx.ImagePath).TrimStart('.') $exception = [System.ComponentModel.Win32Exception]::new($script:AccessDeniedWin32Error) It 'Should not let exception escape and new folder and file should be deleted' { { Set-TargetResource ` - -FilePath $script:mockedDiskImageAttachedVhdx.ImagePath ` - -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` + -FilePath $script:mockedDiskImageMountedVhdx.ImagePath ` + -DiskSize $script:mockedDiskImageMountedVhdx.Size ` -DiskFormat $extension ` -Ensure 'Present' ` -Verbose @@ -553,14 +633,14 @@ try -MockWith { $script:mockedDiskImageEmpty } ` -Verifiable - $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') + $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageMountedVhdx.ImagePath).TrimStart('.') It 'Should return false.' { Test-TargetResource ` - -FilePath $script:mockedDiskImageAttachedVhdx.ImagePath ` - -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` + -FilePath $script:mockedDiskImageMountedVhdx.ImagePath ` + -DiskSize $script:mockedDiskImageMountedVhdx.Size ` -DiskFormat $extension ` -Ensure 'Present' ` - -Verbose | Should -Be $false + -Verbose | Should -BeFalse } It 'Should only call required mocks' { @@ -572,17 +652,17 @@ try Context 'Virtual disk exists but is not mounted while ensure set to present' { Mock ` -CommandName Get-DiskImage ` - -MockWith { $script:mockedDiskImageNotAttachedVhdx } ` + -MockWith { $script:mockedDiskImageNotMountedVhdx } ` -Verifiable - $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') + $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageMountedVhdx.ImagePath).TrimStart('.') It 'Should return false.' { Test-TargetResource ` - -FilePath $script:mockedDiskImageAttachedVhdx.ImagePath ` - -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` + -FilePath $script:mockedDiskImageMountedVhdx.ImagePath ` + -DiskSize $script:mockedDiskImageMountedVhdx.Size ` -DiskFormat $extension ` -Ensure 'Present' ` - -Verbose | Should -Be $false + -Verbose | Should -BeFalse } It 'Should only call required mocks' { @@ -597,14 +677,14 @@ try -MockWith { $script:mockedDiskImageEmpty } ` -Verifiable - $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') + $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageMountedVhdx.ImagePath).TrimStart('.') It 'Should return true' { Test-TargetResource ` - -FilePath $script:mockedDiskImageAttachedVhdx.ImagePath ` - -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` + -FilePath $script:mockedDiskImageMountedVhdx.ImagePath ` + -DiskSize $script:mockedDiskImageMountedVhdx.Size ` -DiskFormat $extension ` -Ensure 'Absent' ` - -Verbose | Should -Be $true + -Verbose | Should -BeTrue } It 'Should only call required mocks' { @@ -616,17 +696,17 @@ try Context 'Virtual disk exists, is mounted and ensure set to present' { Mock ` -CommandName Get-DiskImage ` - -MockWith { $script:mockedDiskImageAttachedVhdx } ` + -MockWith { $script:mockedDiskImageMountedVhdx } ` -Verifiable - $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') + $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageMountedVhdx.ImagePath).TrimStart('.') It 'Should return true' { Test-TargetResource ` - -FilePath $script:mockedDiskImageAttachedVhdx.ImagePath ` - -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` + -FilePath $script:mockedDiskImageMountedVhdx.ImagePath ` + -DiskSize $script:mockedDiskImageMountedVhdx.Size ` -DiskFormat $extension ` -Ensure 'Present' ` - -Verbose | Should -Be $true + -Verbose | Should -BeTrue } It 'Should only call required mocks' { @@ -638,17 +718,17 @@ try Context 'Virtual disk exists but is mounted while ensure set to absent' { Mock ` -CommandName Get-DiskImage ` - -MockWith { $script:mockedDiskImageAttachedVhdx } ` + -MockWith { $script:mockedDiskImageMountedVhdx } ` -Verifiable - $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') + $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageMountedVhdx.ImagePath).TrimStart('.') It 'Should return false.' { Test-TargetResource ` - -FilePath $script:mockedDiskImageAttachedVhdx.ImagePath ` - -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` + -FilePath $script:mockedDiskImageMountedVhdx.ImagePath ` + -DiskSize $script:mockedDiskImageMountedVhdx.Size ` -DiskFormat $extension ` -Ensure 'absent' ` - -Verbose | Should -Be $false + -Verbose | Should -BeFalse } It 'Should only call required mocks' { @@ -660,17 +740,17 @@ try Context 'Virtual disk exists but is not mounted while ensure set to absent' { Mock ` -CommandName Get-DiskImage ` - -MockWith { $script:mockedDiskImageNotAttachedVhdx } ` + -MockWith { $script:mockedDiskImageNotMountedVhdx } ` -Verifiable - $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageAttachedVhdx.ImagePath).TrimStart('.') + $extension = [System.IO.Path]::GetExtension($script:mockedDiskImageMountedVhdx.ImagePath).TrimStart('.') It 'Should return true.' { Test-TargetResource ` - -FilePath $script:mockedDiskImageAttachedVhdx.ImagePath ` - -DiskSize $script:mockedDiskImageAttachedVhdx.Size ` + -FilePath $script:mockedDiskImageMountedVhdx.ImagePath ` + -DiskSize $script:mockedDiskImageMountedVhdx.Size ` -DiskFormat $extension ` -Ensure 'absent' ` - -Verbose | Should -Be $true + -Verbose | Should -BeTrue } It 'Should only call required mocks' { diff --git a/tests/Unit/StorageDsc.VirtualHardDisk.Win32Helpers.Tests.ps1 b/tests/Unit/StorageDsc.VirtualHardDisk.Win32Helpers.Tests.ps1 index da06421e..e91c1272 100644 --- a/tests/Unit/StorageDsc.VirtualHardDisk.Win32Helpers.Tests.ps1 +++ b/tests/Unit/StorageDsc.VirtualHardDisk.Win32Helpers.Tests.ps1 @@ -26,7 +26,7 @@ Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\Co # Begin Testing InModuleScope $script:subModuleName { - Function New-VirtualDiskUsingWin32 + function New-VirtualDiskUsingWin32 { [CmdletBinding()] [OutputType([System.Int32])] @@ -70,7 +70,7 @@ InModuleScope $script:subModuleName { ) } - Function Add-VirtualDiskUsingWin32 + function Add-VirtualDiskUsingWin32 { [CmdletBinding()] [OutputType([System.Int32])] @@ -102,7 +102,7 @@ InModuleScope $script:subModuleName { ) } - Function Get-VirtualDiskUsingWin32 + function Get-VirtualDiskUsingWin32 { [CmdletBinding()] [OutputType([System.Int32])] @@ -140,17 +140,17 @@ InModuleScope $script:subModuleName { [ref]$script:TestHandle = [Microsoft.Win32.SafeHandles.SafeFileHandle]::Zero $script:mockedParams = [pscustomobject] @{ - DiskSizeInBytes = 65Gb - VirtualDiskPath = $script:DiskImageGoodVhdxPath - DiskType = 'dynamic' - DiskFormat = 'vhdx' + DiskSizeInBytes = 65Gb + VirtualDiskPath = $script:DiskImageGoodVhdxPath + DiskType = 'dynamic' + DiskFormat = 'vhdx' } $script:mockedVhdParams = [pscustomobject] @{ - DiskSizeInBytes = 65Gb - VirtualDiskPath = $script:DiskImageGoodVhdxPath - DiskType = 'dynamic' - DiskFormat = 'vhd' + DiskSizeInBytes = 65Gb + VirtualDiskPath = $script:DiskImageGoodVhdxPath + DiskType = 'dynamic' + DiskFormat = 'vhd' } Describe 'StorageDsc.VirtualHardDisk.Win32Helpers\New-SimpleVirtualDisk' -Tag 'New-SimpleVirtualDisk' { @@ -165,16 +165,16 @@ InModuleScope $script:subModuleName { -MockWith { 0 } ` -Verifiable - It 'Should not throw an exception' { - { - New-SimpleVirtualDisk ` - -VirtualDiskPath $script:mockedParams.VirtualDiskPath ` - -DiskSizeInBytes $script:mockedParams.DiskSizeInBytes ` - -DiskFormat $script:mockedParams.DiskFormat ` - -DiskType $script:mockedParams.DiskType` - -Verbose - } | Should -Not -Throw - } + It 'Should not throw an exception' { + { + New-SimpleVirtualDisk ` + -VirtualDiskPath $script:mockedParams.VirtualDiskPath ` + -DiskSizeInBytes $script:mockedParams.DiskSizeInBytes ` + -DiskFormat $script:mockedParams.DiskFormat ` + -DiskType $script:mockedParams.DiskType` + -Verbose + } | Should -Not -Throw + } It 'Should only call required mocks' { Assert-VerifiableMock @@ -194,16 +194,16 @@ InModuleScope $script:subModuleName { -MockWith { 0 } ` -Verifiable - It 'Should not throw an exception' { - { - New-SimpleVirtualDisk ` - -VirtualDiskPath $script:mockedVhdParams.VirtualDiskPath ` - -DiskSizeInBytes $script:mockedVhdParams.DiskSizeInBytes ` - -DiskFormat $script:mockedVhdParams.DiskFormat ` - -DiskType $script:mockedVhdParams.DiskType` - -Verbose - } | Should -Not -Throw - } + It 'Should not throw an exception' { + { + New-SimpleVirtualDisk ` + -VirtualDiskPath $script:mockedVhdParams.VirtualDiskPath ` + -DiskSizeInBytes $script:mockedVhdParams.DiskSizeInBytes ` + -DiskFormat $script:mockedVhdParams.DiskFormat ` + -DiskType $script:mockedVhdParams.DiskType` + -Verbose + } | Should -Not -Throw + } It 'Should only call required mocks' { Assert-VerifiableMock @@ -221,7 +221,7 @@ InModuleScope $script:subModuleName { $win32Error = [System.ComponentModel.Win32Exception]::new($script:AccessDeniedWin32Error) $exception = [System.Exception]::new( ` ($script:localizedData.CreateVirtualDiskError -f $script:mockedParams.VirtualDiskPath, $win32Error.Message), ` - $win32Error) + $win32Error) It 'Should throw an exception in creation method' { { @@ -256,8 +256,8 @@ InModuleScope $script:subModuleName { $win32Error = [System.ComponentModel.Win32Exception]::new($script:AccessDeniedWin32Error) $exception = [System.Exception]::new( ` - ($script:localizedData.AttachVirtualDiskError -f $script:mockedParams.VirtualDiskPath, $win32Error.Message), ` - $win32Error) + ($script:localizedData.MountVirtualDiskError -f $script:mockedParams.VirtualDiskPath, $win32Error.Message), ` + $win32Error) It 'Should throw an exception during attach function' { { @@ -314,7 +314,7 @@ InModuleScope $script:subModuleName { $win32Error = [System.ComponentModel.Win32Exception]::new($script:AccessDeniedWin32Error) $exception = [System.Exception]::new( ` ($script:localizedData.OpenVirtualDiskError -f $script:mockedParams.VirtualDiskPath, $win32Error.Message), ` - $win32Error) + $win32Error) It 'Should throw an exception while attempting to open virtual disk file' { { From 9191a914d508e44ed29961a26e499721d3a02c4f Mon Sep 17 00:00:00 2001 From: Branden Bonaby Date: Mon, 19 Aug 2024 21:34:43 -0700 Subject: [PATCH 18/21] update pipeline based on comments --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 312a4239..fbbca69a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -27,7 +27,7 @@ stages: vmImage: 'windows-latest' steps: - pwsh: | - dotnet tool install --global GitVersion.Tool + dotnet tool install --global GitVersion.Tool --version 5.* $gitVersionObject = dotnet-gitversion | ConvertFrom-Json $gitVersionObject.PSObject.Properties.ForEach{ Write-Host -Object "Setting Task Variable '$($_.Name)' with value '$($_.Value)'." From bc94b2aa77bd88922ba94655dd1a098e4c27346f Mon Sep 17 00:00:00 2001 From: Branden Bonaby Date: Tue, 20 Aug 2024 10:49:08 -0700 Subject: [PATCH 19/21] Add virtual hard disk integration tests --- .../DSC_VirtualHardDisk.psm1 | 2 +- .../DSC_VirtualHardDisk.schema.mof | 2 + .../DSC_VirtualHardDisk.Integration.Tests.ps1 | 87 +++++++++++++++++++ .../DSC_VirtualHardDisk.config.ps1 | 40 +++++++++ 4 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 tests/Integration/DSC_VirtualHardDisk.Integration.Tests.ps1 create mode 100644 tests/Integration/DSC_VirtualHardDisk.config.ps1 diff --git a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 index 4480cbd5..a4e8003d 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 +++ b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 @@ -51,7 +51,7 @@ function Get-TargetResource return @{ FilePath = $diskImage.ImagePath Attached = $diskImage.Attached - Size = $diskImage.Size + DiskSize = $diskImage.Size DiskNumber = $diskImage.DiskNumber Ensure = $ensure } diff --git a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof index 3c0c6711..9e442fbe 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof +++ b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.schema.mof @@ -7,4 +7,6 @@ class DSC_VirtualHardDisk : OMI_BaseResource [Write, Description("Specifies the disk type of virtual hard disk to create if it doesn't exist and Ensure is present."), ValueMap{"Fixed","Dynamic"}, Values{"Fixed","Dynamic"}] String DiskType; [Write, Description("Specifies the disk format the virtual hard disk should use or create if it does not exist and Ensure is present. Defaults to Vhdx."), ValueMap{"Vhd","Vhdx"}, Values{"Vhd","Vhdx"}] String DiskFormat; [Write, Description("Determines whether the virtual hard disk should be created and attached or should be detached if it exists."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Read, Description("Returns whether or not the virtual hard disk is mounted to the system.")] Boolean Attached; + [Read, Description("Returns the disk number of the virtual hard disk if it is mounted to the system.")] UInt32 DiskNumber; }; diff --git a/tests/Integration/DSC_VirtualHardDisk.Integration.Tests.ps1 b/tests/Integration/DSC_VirtualHardDisk.Integration.Tests.ps1 new file mode 100644 index 00000000..84c3979b --- /dev/null +++ b/tests/Integration/DSC_VirtualHardDisk.Integration.Tests.ps1 @@ -0,0 +1,87 @@ +$script:dscModuleName = 'StorageDsc' +$script:dscResourceName = 'DSC_VirtualHardDisk' + +try +{ + Import-Module -Name DscResource.Test -Force -ErrorAction 'Stop' +} +catch [System.IO.FileNotFoundException] +{ + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -Tasks build" first.' +} + +$script:testEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` + -TestType 'Integration' + +Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') + +try +{ + $configFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dscResourceName).config.ps1" + . $configFile -Verbose -ErrorAction Stop + + Describe "$($script:dscResourceName)_CreateAndAttachFixedVhd_Integration" { + Context 'Create and attach a fixed virtual disk' { + It 'Should compile and apply the MOF without throwing' { + { + & "$($script:dscResourceName)_CreateAndAttachFixedVhd_Config" -OutputPath $TestDrive + Start-DscConfiguration -Path $TestDrive -ComputerName localhost -Wait -Verbose -Force + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { Get-DscConfiguration -Verbose -ErrorAction Stop } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $currentState = Get-DscConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq "$($script:dscResourceName)_CreateAndAttachFixedVhd_Config" + } + $currentState.FilePath | Should -Be $script:TestFixedVirtualHardDiskVhd.FilePath + $currentState.DiskSize | Should -Be 5Gb + $currentState.Ensure | Should -Be 'Present' + } + + AfterAll { + Dismount-DiskImage -ImagePath $script:TestFixedVirtualHardDiskVhd.FilePath -StorageType VHD + Remove-Item -Path $script:TestFixedVirtualHardDiskVhd.FilePath -Force + } + } + } + + Describe "$($script:dscResourceName)_CreateAndAttachDynamicallyExpandingVhdx_Integration" { + Context 'Create and attach a dynamically expanding virtual disk' { + It 'Should compile and apply the MOF without throwing' { + { + & "$($script:dscResourceName)_CreateAndAttachDynamicallyExpandingVhdx_Config" -OutputPath $TestDrive + Start-DscConfiguration -Path $TestDrive -ComputerName localhost -Wait -Verbose -Force + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { Get-DscConfiguration -Verbose -ErrorAction Stop } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $currentState = Get-DscConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq "$($script:dscResourceName)_CreateAndAttachDynamicallyExpandingVhdx_Config" + } + $currentState.FilePath | Should -Be $script:TestDynamicVirtualHardDiskVhdx.FilePath + $currentState.DiskSize | Should -Be 10Gb + $currentState.Ensure | Should -Be 'Present' + } + + AfterAll { + Dismount-DiskImage -ImagePath $script:TestDynamicVirtualHardDiskVhdx.FilePath -StorageType VHDX + Remove-Item -Path $script:TestDynamicVirtualHardDiskVhdx.FilePath -Force + } + } + } +} +finally +{ + Restore-TestEnvironment -TestEnvironment $script:testEnvironment +} diff --git a/tests/Integration/DSC_VirtualHardDisk.config.ps1 b/tests/Integration/DSC_VirtualHardDisk.config.ps1 new file mode 100644 index 00000000..daa5f5ae --- /dev/null +++ b/tests/Integration/DSC_VirtualHardDisk.config.ps1 @@ -0,0 +1,40 @@ +$TestFixedVirtualHardDiskVhd = @{ + FilePath = "$($pwd.drive.name):\newTestFixedVhd.vhd" + DiskSize = 5GB + DiskFormat = 'Vhd' + DiskType = 'Fixed' +} + +$TestDynamicVirtualHardDiskVhdx = @{ + FilePath = "$($pwd.drive.name):\newTestDynamicVhdx.vhdx" + DiskSize = 10GB + DiskFormat = 'Vhdx' + DiskType = 'Dynamic' +} + +configuration DSC_VirtualHardDisk_CreateAndAttachFixedVhd_Config { + Import-DscResource -ModuleName StorageDsc + node localhost { + VirtualHardDisk Integration_Test { + FilePath = $TestFixedVirtualHardDiskVhd.FilePath + DiskSize = $TestFixedVirtualHardDiskVhd.DiskSize + DiskFormat = $TestFixedVirtualHardDiskVhd.DiskFormat + DiskType = $TestFixedVirtualHardDiskVhd.DiskType + Ensure = 'Present' + } + } +} + +configuration DSC_VirtualHardDisk_CreateAndAttachDynamicallyExpandingVhdx_Config { + Import-DscResource -ModuleName StorageDsc + node localhost { + + VirtualHardDisk Integration_Test { + FilePath = $TestDynamicVirtualHardDiskVhdx.FilePath + DiskSize = $TestDynamicVirtualHardDiskVhdx.DiskSize + DiskFormat = $TestDynamicVirtualHardDiskVhdx.DiskFormat + DiskType = $TestDynamicVirtualHardDiskVhdx.DiskType + Ensure = 'Present' + } + } +} From 3ebf094fc8a9422f19cf13376fd6562074ebfb1f Mon Sep 17 00:00:00 2001 From: Branden Bonaby Date: Tue, 20 Aug 2024 11:53:52 -0700 Subject: [PATCH 20/21] Fix unit test --- tests/Unit/DSC_VirtualHardDisk.Tests.ps1 | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 b/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 index 24ccb5b0..bf81fa02 100644 --- a/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 +++ b/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 @@ -149,26 +149,26 @@ try -MockWith { $script:mockedDiskImageEmpty } ` -Verifiable - $resource = Get-TargetResource -FilePath $script:DiskImageBadPath -Verbose + $currentState = Get-TargetResource -FilePath $script:DiskImageBadPath -Verbose It "Should return DiskNumber $($script:GetTargetOutputWhenBadPath.DiskNumber)" { - $resource.DiskNumber | Should -Be $script:GetTargetOutputWhenBadPath.DiskNumber + $currentState.DiskNumber | Should -Be $script:GetTargetOutputWhenBadPath.DiskNumber } It "Should return FilePath $($script:GetTargetOutputWhenBadPath.FilePath)" { - $resource.FilePath | Should -Be $script:GetTargetOutputWhenBadPath.FilePath + $currentState.FilePath | Should -Be $script:GetTargetOutputWhenBadPath.FilePath } It "Should return Mounted $($script:GetTargetOutputWhenBadPath.Attached)" { - $resource.Attached | Should -Be $script:GetTargetOutputWhenBadPath.Attached + $currentState.Attached | Should -Be $script:GetTargetOutputWhenBadPath.Attached } It "Should return Size $($script:GetTargetOutputWhenBadPath.Size)" { - $resource.Size | Should -Be $script:GetTargetOutputWhenBadPath.Size + $currentState.DiskSize | Should -Be $script:GetTargetOutputWhenBadPath.Size } It "Should return Ensure $($script:GetTargetOutputWhenBadPath.Ensure)" { - $resource.Ensure | Should -Be $script:GetTargetOutputWhenBadPath.Ensure + $currentState.Ensure | Should -Be $script:GetTargetOutputWhenBadPath.Ensure } } @@ -178,26 +178,26 @@ try -MockWith { $script:mockedDiskImageMountedVhdx } ` -Verifiable - $resource = Get-TargetResource -FilePath $script:DiskImageGoodVhdxPath -Verbose + $currentState = Get-TargetResource -FilePath $script:DiskImageGoodVhdxPath -Verbose It "Should return DiskNumber $($script:GetTargetOutputWhenPathGood.DiskNumber)" { - $resource.DiskNumber | Should -Be $script:GetTargetOutputWhenPathGood.DiskNumber + $currentState.DiskNumber | Should -Be $script:GetTargetOutputWhenPathGood.DiskNumber } It "Should return FilePath $($script:GetTargetOutputWhenPathGood.FilePath)" { - $resource.FilePath | Should -Be $script:GetTargetOutputWhenPathGood.FilePath + $currentState.FilePath | Should -Be $script:GetTargetOutputWhenPathGood.FilePath } It "Should return Mounted $($script:GetTargetOutputWhenPathGood.Attached)" { - $resource.Attached | Should -Be $script:GetTargetOutputWhenPathGood.Attached + $currentState.Attached | Should -Be $script:GetTargetOutputWhenPathGood.Attached } It "Should return Size $($script:GetTargetOutputWhenPathGood.Size)" { - $resource.Size | Should -Be $script:GetTargetOutputWhenPathGood.Size + $currentState.DiskSize | Should -Be $script:GetTargetOutputWhenPathGood.Size } It "Should return Ensure $($script:GetTargetOutputWhenPathGood.Ensure)" { - $resource.Ensure | Should -Be $script:GetTargetOutputWhenPathGood.Ensure + $currentState.Ensure | Should -Be $script:GetTargetOutputWhenPathGood.Ensure } } } From 49fb63822ed84e69d561a8170c5c081e93e59c4a Mon Sep 17 00:00:00 2001 From: Branden Bonaby Date: Thu, 12 Sep 2024 17:59:11 -0700 Subject: [PATCH 21/21] update based on comments --- .../DSC_VirtualHardDisk.psm1 | 9 +- .../en-US/DSC_VirtualHardDisk.strings.psd1 | 2 +- .../StorageDsc.Common/StorageDsc.Common.psm1 | 18 +--- ...orageDsc.VirtualHardDisk.Win32Helpers.psm1 | 18 ++-- .../DSC_VirtualHardDisk.Integration.Tests.ps1 | 88 +++++++++++++++---- .../DSC_VirtualHardDisk.config.ps1 | 40 +++------ tests/Unit/DSC_VirtualHardDisk.Tests.ps1 | 60 ++++--------- 7 files changed, 123 insertions(+), 112 deletions(-) diff --git a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 index a4e8003d..551b7638 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 +++ b/source/DSCResources/DSC_VirtualHardDisk/DSC_VirtualHardDisk.psm1 @@ -109,8 +109,13 @@ function Set-TargetResource Assert-ParametersValid -FilePath $FilePath -DiskSize $DiskSize -DiskFormat $DiskFormat - if (-not (Test-RunningAsAdministrator)) + try { + Assert-ElevatedUser + } + catch + { + # Use a user friendly error message specific to the virtual disk dsc resource. throw $script:localizedData.VirtualDiskAdminError } @@ -172,7 +177,7 @@ function Set-TargetResource } else { - # Detach the virtual hard disk if its not suppose to be attached. + # Detach the virtual hard disk if its not supposed to be attached. if ($currentState.Attached) { Write-Verbose -Message ( @( diff --git a/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 b/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 index d3fb5281..77bf0a25 100644 --- a/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 +++ b/source/DSCResources/DSC_VirtualHardDisk/en-US/DSC_VirtualHardDisk.strings.psd1 @@ -14,5 +14,5 @@ ConvertFrom-StringData @' VirtualHardDiskNoExtensionError = The path '{0}' does not contain an extension. Supported extension types are '.vhd' and '.vhdx'. GettingVirtualHardDisk = Getting virtual hard disk information for virtual hard disk located at '{0}. RemovingCreatedVirtualHardDiskFile = The virtual hard disk file at location '{0}' is being removed due to an error while attempting to mount it to the system. - VirtualDiskAdminError = Creating and mounting a virtual disk requires running PowerShell as an Administrator. Please launch PowerShell as Administrator and try again. + VirtualDiskAdminError = Creating and mounting a virtual disk requires running local Administrator permissions. Please ensure this resource is being applied with an account with local Administrator permissions. '@ diff --git a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 index 524e16aa..5ae97c10 100644 --- a/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 +++ b/source/Modules/StorageDsc.Common/StorageDsc.Common.psm1 @@ -628,21 +628,6 @@ function Compare-SizeUsingGB }# end function Compare-SizeUsingGB -<# - .SYNOPSIS - Gets a boolean indicating whether the script is running as Administrator or not. -#> -function Test-RunningAsAdministrator -{ - [CmdletBinding()] - [OutputType([System.Boolean])] - param - () - - $currentPrincipal = New-Object -TypeName Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) - return $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) -} # end function Test-RunningAsAdministrator - Export-ModuleMember -Function @( 'Restart-ServiceIfExists', 'Assert-DriveLetterValid', @@ -657,6 +642,5 @@ Export-ModuleMember -Function @( 'Get-DevDriveEnablementState', 'Test-DevDriveVolume', 'Invoke-DeviceIoControlWrapperForDevDriveQuery', - 'Compare-SizeUsingGB', - 'Test-RunningAsAdministrator' + 'Compare-SizeUsingGB' ) diff --git a/source/Modules/StorageDsc.VirtualHardDisk.Win32Helpers/StorageDsc.VirtualHardDisk.Win32Helpers.psm1 b/source/Modules/StorageDsc.VirtualHardDisk.Win32Helpers/StorageDsc.VirtualHardDisk.Win32Helpers.psm1 index 60c018fc..245d6191 100644 --- a/source/Modules/StorageDsc.VirtualHardDisk.Win32Helpers/StorageDsc.VirtualHardDisk.Win32Helpers.psm1 +++ b/source/Modules/StorageDsc.VirtualHardDisk.Win32Helpers/StorageDsc.VirtualHardDisk.Win32Helpers.psm1 @@ -149,13 +149,13 @@ function Get-VirtDiskWin32HelperScript } # end function Get-VirtDiskWin32HelperScript <# + .SYNOPSIS + Creates a new virtual disk. + .DESCRIPTION Calls the CreateVirtualDisk Win32 api to create a new virtual disk. This is used so we can mock this call easier. - .SYNOPSIS - Creates a new virtual disk. - .PARAMETER VirtualStorageType Specifies the type and provider (vendor) of the virtual storage device. @@ -243,13 +243,13 @@ function New-VirtualDiskUsingWin32 } # end function New-VirtualDiskUsingWin32 <# + .SYNOPSIS + Mounts an existing virtual disk to the system. + .DESCRIPTION Calls the AttachVirtualDisk Win32 api to mount an existing virtual disk to the system. This is used so we can mock this call easier. - .SYNOPSIS - Mounts an existing virtual disk to the system. - .PARAMETER Handle Specifies the reference to a handle to a virtual disk file. @@ -312,13 +312,13 @@ function Add-VirtualDiskUsingWin32 } # end function Add-VirtualDiskUsingWin32 <# + .SYNOPSIS + Opens an existing virtual disk. + .DESCRIPTION Calls the OpenVirtualDisk Win32 api to open an existing virtual disk. This is used so we can mock this call easier. - .SYNOPSIS - Opens an existing virtual disk. - .PARAMETER VirtualStorageType Specifies the type and provider (vendor) of the virtual storage device. diff --git a/tests/Integration/DSC_VirtualHardDisk.Integration.Tests.ps1 b/tests/Integration/DSC_VirtualHardDisk.Integration.Tests.ps1 index 84c3979b..23c7af4f 100644 --- a/tests/Integration/DSC_VirtualHardDisk.Integration.Tests.ps1 +++ b/tests/Integration/DSC_VirtualHardDisk.Integration.Tests.ps1 @@ -25,10 +25,37 @@ try Describe "$($script:dscResourceName)_CreateAndAttachFixedVhd_Integration" { Context 'Create and attach a fixed virtual disk' { - It 'Should compile and apply the MOF without throwing' { + $configData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + FilePath = "$($pwd.drive.name):\newTestFixedVhd.vhd" + Attached = $true + DiskSize = 5GB + DiskFormat = 'Vhd' + DiskType = 'Fixed' + Ensure = 'Present' + } + ) + } + + It 'Should compile the MOF without throwing' { + { + & "$($script:dscResourceName)_CreateAndAttachFixedVhd_Config" ` + -OutputPath $TestDrive ` + -ConfigurationData $configData + } | Should -Not -Throw + } + + It 'Should apply the MOF without throwing' { { - & "$($script:dscResourceName)_CreateAndAttachFixedVhd_Config" -OutputPath $TestDrive - Start-DscConfiguration -Path $TestDrive -ComputerName localhost -Wait -Verbose -Force + Start-DscConfiguration ` + -Path $TestDrive ` + -ComputerName localhost ` + -Wait ` + -Verbose ` + -Force ` + -ErrorAction Stop } | Should -Not -Throw } @@ -40,24 +67,52 @@ try $currentState = Get-DscConfiguration | Where-Object -FilterScript { $_.ConfigurationName -eq "$($script:dscResourceName)_CreateAndAttachFixedVhd_Config" } - $currentState.FilePath | Should -Be $script:TestFixedVirtualHardDiskVhd.FilePath - $currentState.DiskSize | Should -Be 5Gb - $currentState.Ensure | Should -Be 'Present' + $currentState.FilePath | Should -Be $configData.AllNodes.FilePath + $currentState.DiskSize | Should -Be $configData.AllNodes.DiskSize + $currentState.Attached | Should -Be $configData.AllNodes.Attached + $currentState.Ensure | Should -Be $configData.AllNodes.Ensure } AfterAll { - Dismount-DiskImage -ImagePath $script:TestFixedVirtualHardDiskVhd.FilePath -StorageType VHD - Remove-Item -Path $script:TestFixedVirtualHardDiskVhd.FilePath -Force + Dismount-DiskImage -ImagePath $TestFixedVirtualHardDiskVhdPath -StorageType VHD + Remove-Item -Path $TestFixedVirtualHardDiskVhdPath -Force } } } Describe "$($script:dscResourceName)_CreateAndAttachDynamicallyExpandingVhdx_Integration" { Context 'Create and attach a dynamically expanding virtual disk' { - It 'Should compile and apply the MOF without throwing' { + $configData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + FilePath = "$($pwd.drive.name):\newTestDynamicVhdx.vhdx" + Attached = $true + DiskSize = 10GB + DiskFormat = 'Vhdx' + DiskType = 'Dynamic' + Ensure = 'Present' + } + ) + } + + It 'Should compile the MOF without throwing' { + { + & "$($script:dscResourceName)_CreateAndAttachDynamicallyExpandingVhdx_Config" ` + -OutputPath $TestDrive ` + -ConfigurationData $configData + } | Should -Not -Throw + } + + It 'Should apply the MOF without throwing' { { - & "$($script:dscResourceName)_CreateAndAttachDynamicallyExpandingVhdx_Config" -OutputPath $TestDrive - Start-DscConfiguration -Path $TestDrive -ComputerName localhost -Wait -Verbose -Force + Start-DscConfiguration ` + -Path $TestDrive ` + -ComputerName localhost ` + -Wait ` + -Verbose ` + -Force ` + -ErrorAction Stop } | Should -Not -Throw } @@ -69,14 +124,15 @@ try $currentState = Get-DscConfiguration | Where-Object -FilterScript { $_.ConfigurationName -eq "$($script:dscResourceName)_CreateAndAttachDynamicallyExpandingVhdx_Config" } - $currentState.FilePath | Should -Be $script:TestDynamicVirtualHardDiskVhdx.FilePath - $currentState.DiskSize | Should -Be 10Gb - $currentState.Ensure | Should -Be 'Present' + $currentState.FilePath | Should -Be $configData.AllNodes.FilePath + $currentState.DiskSize | Should -Be $configData.AllNodes.DiskSize + $currentState.Attached | Should -Be $configData.AllNodes.Attached + $currentState.Ensure | Should -Be $configData.AllNodes.Ensure } AfterAll { - Dismount-DiskImage -ImagePath $script:TestDynamicVirtualHardDiskVhdx.FilePath -StorageType VHDX - Remove-Item -Path $script:TestDynamicVirtualHardDiskVhdx.FilePath -Force + Dismount-DiskImage -ImagePath $TestDynamicVirtualHardDiskVhdx -StorageType VHDX + Remove-Item -Path $TestDynamicVirtualHardDiskVhdx -Force } } } diff --git a/tests/Integration/DSC_VirtualHardDisk.config.ps1 b/tests/Integration/DSC_VirtualHardDisk.config.ps1 index daa5f5ae..2d323f82 100644 --- a/tests/Integration/DSC_VirtualHardDisk.config.ps1 +++ b/tests/Integration/DSC_VirtualHardDisk.config.ps1 @@ -1,40 +1,28 @@ -$TestFixedVirtualHardDiskVhd = @{ - FilePath = "$($pwd.drive.name):\newTestFixedVhd.vhd" - DiskSize = 5GB - DiskFormat = 'Vhd' - DiskType = 'Fixed' -} - -$TestDynamicVirtualHardDiskVhdx = @{ - FilePath = "$($pwd.drive.name):\newTestDynamicVhdx.vhdx" - DiskSize = 10GB - DiskFormat = 'Vhdx' - DiskType = 'Dynamic' -} +$TestFixedVirtualHardDiskVhdPath = "$($pwd.drive.name):\newTestFixedVhd.vhd" +$TestDynamicVirtualHardDiskVhdx = "$($pwd.drive.name):\newTestDynamicVhdx.vhdx" configuration DSC_VirtualHardDisk_CreateAndAttachFixedVhd_Config { Import-DscResource -ModuleName StorageDsc node localhost { VirtualHardDisk Integration_Test { - FilePath = $TestFixedVirtualHardDiskVhd.FilePath - DiskSize = $TestFixedVirtualHardDiskVhd.DiskSize - DiskFormat = $TestFixedVirtualHardDiskVhd.DiskFormat - DiskType = $TestFixedVirtualHardDiskVhd.DiskType - Ensure = 'Present' - } + FilePath = $TestFixedVirtualHardDiskVhdPath + DiskSize = 5GB + DiskFormat = 'Vhd' + DiskType = 'Fixed' + Ensure = 'Present' + } } } configuration DSC_VirtualHardDisk_CreateAndAttachDynamicallyExpandingVhdx_Config { Import-DscResource -ModuleName StorageDsc node localhost { - VirtualHardDisk Integration_Test { - FilePath = $TestDynamicVirtualHardDiskVhdx.FilePath - DiskSize = $TestDynamicVirtualHardDiskVhdx.DiskSize - DiskFormat = $TestDynamicVirtualHardDiskVhdx.DiskFormat - DiskType = $TestDynamicVirtualHardDiskVhdx.DiskType - Ensure = 'Present' - } + FilePath = $TestDynamicVirtualHardDiskVhdx + DiskSize = 10GB + DiskFormat = 'Vhdx' + DiskType = 'Dynamic' + Ensure = 'Present' + } } } diff --git a/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 b/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 index bf81fa02..238ac731 100644 --- a/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 +++ b/tests/Unit/DSC_VirtualHardDisk.Tests.ps1 @@ -134,14 +134,6 @@ try ) } - function Test-RunningAsAdministrator - { - [CmdletBinding()] - [OutputType([System.Boolean])] - Param - () - } - Describe 'DSC_VirtualHardDisk\Get-TargetResource' { Context 'When file path does not exist or was never mounted' { Mock ` @@ -172,7 +164,7 @@ try } } - Context 'When file path does exist and was mounted at one point' { + Context 'When file path does exist and is currently mounted' { Mock ` -CommandName Get-DiskImage ` -MockWith { $script:mockedDiskImageMountedVhdx } ` @@ -205,8 +197,7 @@ try Describe 'DSC_VirtualHardDisk\Set-TargetResource' { Context 'When file path is not fully qualified' { Mock ` - -CommandName Test-RunningAsAdministrator ` - -MockWith { $true } + -CommandName Assert-ElevatedUser $errorRecord = Get-InvalidArgumentRecord ` -Message ($script:localizedData.VirtualHardDiskPathError -f ` @@ -227,8 +218,9 @@ try Context 'When not running as administrator' { Mock ` - -CommandName Test-RunningAsAdministrator ` - -MockWith { $false } + -CommandName Assert-ElevatedUser ` + -MockWith { throw [System.Exception]::new('User not elevated.')} ` + -Verifiable $exception = [System.Exception]::new($script:localizedData.VirtualDiskAdminError) @@ -246,8 +238,7 @@ try Context 'When file extension is not .vhd or .vhdx' { Mock ` - -CommandName Test-RunningAsAdministrator ` - -MockWith { $true } + -CommandName Assert-ElevatedUser $extension = [System.IO.Path]::GetExtension($DiskImageNonVirtDiskPath).TrimStart('.') $errorRecord = Get-InvalidArgumentRecord ` @@ -269,8 +260,7 @@ try Context 'When file extension does not match the disk format' { Mock ` - -CommandName Test-RunningAsAdministrator ` - -MockWith { $true } + -CommandName Assert-ElevatedUser $extension = [System.IO.Path]::GetExtension($DiskImageGoodVhdPath).TrimStart('.') $errorRecord = Get-InvalidArgumentRecord ` @@ -292,8 +282,7 @@ try Context 'When file extension is not present in the file path' { Mock ` - -CommandName Test-RunningAsAdministrator ` - -MockWith { $true } + -CommandName Assert-ElevatedUser $errorRecord = Get-InvalidArgumentRecord ` -Message ($script:localizedData.VirtualHardDiskNoExtensionError -f ` @@ -314,8 +303,7 @@ try Context 'When size provided is less than the minimum size for the vhd format' { Mock ` - -CommandName Test-RunningAsAdministrator ` - -MockWith { $true } + -CommandName Assert-ElevatedUser $minSizeInMbString = ($DiskImageSizeBelowVirtDiskMinimum / 1MB).ToString('0.00MB') $errorRecord = Get-InvalidArgumentRecord ` @@ -337,8 +325,7 @@ try Context 'When size provided is less than the minimum size for the vhdx format' { Mock ` - -CommandName Test-RunningAsAdministrator ` - -MockWith { $true } + -CommandName Assert-ElevatedUser $minSizeInMbString = ($DiskImageSizeBelowVirtDiskMinimum / 1MB).ToString('0.00MB') $errorRecord = Get-InvalidArgumentRecord ` @@ -360,8 +347,7 @@ try Context 'When size provided is greater than the maximum size for the vhd format' { Mock ` - -CommandName Test-RunningAsAdministrator ` - -MockWith { $true } + -CommandName Assert-ElevatedUser $maxSizeInTbString = ($DiskImageSizeAboveVhdMaximum / 1TB).ToString('0.00TB') $errorRecord = Get-InvalidArgumentRecord ` @@ -383,8 +369,7 @@ try Context 'When size provided is greater than the maximum size for the vhdx format' { Mock ` - -CommandName Test-RunningAsAdministrator ` - -MockWith { $true } + -CommandName Assert-ElevatedUser $maxSizeInTbString = ($DiskImageSizeAboveVhdxMaximum / 1TB).ToString('0.00TB') $errorRecord = Get-InvalidArgumentRecord ` @@ -406,8 +391,7 @@ try Context 'When file path to vhdx file is fully qualified' { Mock ` - -CommandName Test-RunningAsAdministrator ` - -MockWith { $true } + -CommandName Assert-ElevatedUser It 'Should not throw invalid argument error when path is fully qualified' { { @@ -423,8 +407,7 @@ try Context 'When file path to vhd is fully qualified' { Mock ` - -CommandName Test-RunningAsAdministrator ` - -MockWith { $true } + -CommandName Assert-ElevatedUser It 'Should not throw invalid argument error when path is fully qualified' { { @@ -440,8 +423,7 @@ try Context 'Virtual disk is mounted and ensure set to present' { Mock ` - -CommandName Test-RunningAsAdministrator ` - -MockWith { $true } + -CommandName Assert-ElevatedUser Mock ` -CommandName Get-DiskImage ` @@ -468,8 +450,7 @@ try Context 'Virtual disk is mounted and ensure set to absent, so it should be dismounted' { Mock ` - -CommandName Test-RunningAsAdministrator ` - -MockWith { $true } + -CommandName Assert-ElevatedUser Mock ` -CommandName Get-DiskImage ` @@ -501,8 +482,7 @@ try Context 'Virtual disk is dismounted and ensure set to present, so it should be re-mounted' { Mock ` - -CommandName Test-RunningAsAdministrator ` - -MockWith { $true } + -CommandName Assert-ElevatedUser Mock ` -CommandName Get-DiskImage ` @@ -534,8 +514,7 @@ try Context 'Virtual disk does not exist and ensure set to present, so a new one should be created and mounted' { Mock ` - -CommandName Test-RunningAsAdministrator ` - -MockWith { $true } + -CommandName Assert-ElevatedUser Mock ` -CommandName Get-DiskImage ` @@ -567,8 +546,7 @@ try Context 'When folder does not exist in user provided path but an exception occurs after creating the virtual disk' { Mock ` - -CommandName Test-RunningAsAdministrator ` - -MockWith { $true } + -CommandName Assert-ElevatedUser Mock ` -CommandName Get-DiskImage `