diff --git a/CHANGELOG.md b/CHANGELOG.md index 51d1f67..339de78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,11 @@ * Updated appveyor.yml to include codecov. * Added .codecov.yml. * Added codecov badges to Readme. +* MSFT_xVHD: + * Support setting the disk type. + * Added unit tests. + * Added example Sample\_xVHD\_FixedVHD.ps1 + * Style fixes ## 3.8.0.0 diff --git a/DSCResources/MSFT_xVHD/MSFT_xVHD.psm1 b/DSCResources/MSFT_xVHD/MSFT_xVHD.psm1 index d2fb91b..f1e769c 100644 --- a/DSCResources/MSFT_xVHD/MSFT_xVHD.psm1 +++ b/DSCResources/MSFT_xVHD/MSFT_xVHD.psm1 @@ -1,38 +1,61 @@ +<# +.SYNOPSIS + Gets MSFT_xVHD resource current state. + +.PARAMETER Name + The desired VHD file name. + +.PARAMETER Path + The desired Path where the VHD will be created. + +.PARAMETER Generation + Virtual disk format. +#> function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( - [parameter(Mandatory)] - [String]$Name, + [Parameter(Mandatory = $true)] + [String] + $Name, - [parameter(Mandatory)] - [String]$Path, + [Parameter(Mandatory = $true)] + [String] + $Path, - # Virtual disk format - Vhd or Vhdx + [Parameter()] [ValidateSet("Vhd","Vhdx")] - [String]$Generation = "Vhd" + [String] + $Generation = "Vhd" ) - + # Check if Hyper-V module is present for Hyper-V cmdlets - if(!(Get-Module -ListAvailable -Name Hyper-V)) + if (!(Get-Module -ListAvailable -Name Hyper-V)) { - Throw "Please ensure that Hyper-V role is installed with its PowerShell module" + Throw 'Please ensure that Hyper-V role is installed with its PowerShell module' } - + # Construct the full path for the vhdFile $vhdName = GetNameWithExtension -Name $Name -Generation $Generation $vhdFilePath = Join-Path -Path $Path -ChildPath $vhdName - Write-Debug -Message "Vhd full path is $vhdFilePath" + Write-Verbose -Message "Vhd full path is $vhdFilePath" $vhd = Get-VHD -Path $vhdFilePath -ErrorAction SilentlyContinue + + $ensure = 'Absent' + if ($vhd) + { + $ensure = 'Present' + } + @{ Name = $Name Path = $Path ParentPath = $vhd.ParentPath Generation = $vhd.VhdFormat - Ensure = if($vhd){"Present"}else{"Absent"} + Ensure = $ensure ID = $vhd.DiskIdentifier Type = $vhd.VhdType FileSizeBytes = $vhd.FileSize @@ -41,184 +64,258 @@ function Get-TargetResource } } -function Set-TargetResource -{ - [CmdletBinding()] - param - ( - # Name of the VHD File - [parameter(Mandatory)] - [String]$Name, +<# +.SYNOPSIS + Configures MSFT_xVHD resource state. - # Folder where the VHD will be created - [parameter(Mandatory)] - [String]$Path, +.PARAMETER Name + The desired VHD file name. - # Parent VHD file path, for differencing disk - [String]$ParentPath, +.PARAMETER Path + The desired Path where the VHD will be created. - # Size of Vhd to be created - [Uint64]$MaximumSizeBytes, +.PARAMETER ParentPath + Parent VHD file path, for differencing disk. - # Virtual disk format - Vhd or Vhdx - [ValidateSet("Vhd","Vhdx")] - [String]$Generation = "Vhd", +.PARAMETER MaximumSizeBytes + Maximum size of VHD to be created. - # Should the VHD be created or deleted - [ValidateSet("Present","Absent")] - [String]$Ensure = "Present" +.PARAMETER Type + Virtual disk type. + +.PARAMETER Generation + Virtual disk format. + +.PARAMETER Ensure + Ensures that the VHD is Present or Absent. +#> +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [String] + $Name, + + [Parameter(Mandatory = $true)] + [String] + $Path, + + [Parameter()] + [String] + $ParentPath, + + [Parameter()] + [Uint64] + $MaximumSizeBytes, + + [Parameter()] + [ValidateSet('Dynamic', 'Fixed', 'Differencing')] + [String] + $Type = 'Dynamic', + + [Parameter()] + [ValidateSet('Vhd', 'Vhdx')] + [String] + $Generation = 'Vhd', + + [Parameter()] + [ValidateSet('Present', 'Absent')] + [String] + $Ensure = 'Present' ) - - # Check if Hyper-V module is present for Hyper-V cmdlets - if(!(Get-Module -ListAvailable -Name Hyper-V)) - { - Throw "Please ensure that Hyper-V role is installed with its PowerShell module" - } # Construct the full path for the vhdFile $vhdName = GetNameWithExtension -Name $Name -Generation $Generation $vhdFilePath = Join-Path -Path $Path -ChildPath $vhdName - Write-Debug -Message "Vhd full path is $vhdFilePath" + Write-Verbose -Message "Vhd full path is $vhdFilePath" Write-Verbose -Message "Checking if $vhdFilePath is $Ensure ..." # If vhd should be absent, delete it - if($Ensure -eq "Absent") + if ($Ensure -eq 'Absent') { - if (Test-Path $vhdFilePath) + if (Test-Path -Path $vhdFilePath) { Write-Verbose -Message "$vhdFilePath is not $Ensure" - Remove-Item -Path $vhdFilePath -Force -ErrorAction Stop + Remove-Item -Path $vhdFilePath -Force -ErrorAction Stop } Write-Verbose -Message "$vhdFilePath is $Ensure" - } + } else { # Check if the Vhd is present try { - $vhd = Get-VHD -Path $vhdFilePath -ErrorAction Stop + $vhd = Get-VHD -Path $vhdFilePath -ErrorAction Stop - # If this is a differencing disk, check the parent path - if($ParentPath) + # If this is a differencing disk, check the parent path + if ($ParentPath) + { + Write-Verbose -Message "Checking if $vhdFilePath parent path is $ParentPath ..." + + # If the parent path is not set correct, fix it + if ($vhd.ParentPath -ne $ParentPath) + { + Write-Verbose -Message "$vhdFilePath parent path is not $ParentPath." + Set-VHD -Path $vhdFilePath -ParentPath $ParentPath + Write-Verbose -Message "$vhdFilePath parent path is now $ParentPath." + } + else { - Write-Verbose -Message "Checking if $vhdFilePath parent path is $ParentPath ..." - - # If the parent path is not set correct, fix it - if($vhd.ParentPath -ne $ParentPath) - { - Write-Verbose -Message "$vhdFilePath parent path is not $ParentPath." - Set-VHD -Path $vhdFilePath -ParentPath $ParentPath - Write-Verbose -Message "$vhdFilePath parent path is now $ParentPath." - } - else - { - Write-Verbose -Message "$vhdFilePath is $Ensure and parent path is set to $ParentPath." - } + Write-Verbose -Message "$vhdFilePath is $Ensure and parent path is set to $ParentPath." } + } + + # This is a fixed disk, check the size + elseif ($PSBoundParameters.ContainsKey('MaximumSizeBytes')) + { + Write-Verbose -Message "Checking if $vhdFilePath size is $MaximumSizeBytes ..." - # This is a fixed disk, check the size + # If the size is not correct, fix it + if ($vhd.Size -ne $MaximumSizeBytes) + { + Write-Verbose -Message "$vhdFilePath size is not $MaximumSizeBytes." + Resize-VHD -Path $vhdFilePath -SizeBytes $MaximumSizeBytes + Write-Verbose -Message "$vhdFilePath size is now $MaximumSizeBytes." + } else { - Write-Verbose -Message "Checking if $vhdFilePath size is $MaximumSizeBytes ..." - - # If the size is not correct, fix it - if($vhd.Size -ne $MaximumSizeBytes) - { - Write-Verbose -Message "$vhdFilePath size is not $MaximumSizeBytes." - Resize-VHD -Path $vhdFilePath -SizeBytes $MaximumSizeBytes - Write-Verbose -Message "$vhdFilePath size is now $MaximumSizeBytes." - } - else - { - Write-Verbose -Message "$vhdFilePath is $Ensure and size is $MaximumSizeBytes." - } + Write-Verbose -Message "$vhdFilePath is $Ensure and size is $MaximumSizeBytes." } - } + } - # Vhd file is not present - catch [System.Management.Automation.ActionPreferenceStopException] - { - + if ($vhd.Type -ne $Type) + { + Write-Verbose -Message 'This module can''t convert disk types' + } + } + + # Vhd file is not present + catch + { Write-Verbose -Message "$vhdFilePath is not $Ensure" - if($ParentPath) + if ($ParentPath) { $null = New-VHD -Path $vhdFilePath -ParentPath $ParentPath } else { - $null = New-VHD -Path $vhdFilePath -SizeBytes $MaximumSizeBytes + $params = @{ + Path = $vhdFilePath + SizeBytes = $MaximumSizeBytes + $Type = $True + } + $null = New-VHD @params } + Write-Verbose -Message "$vhdFilePath is now $Ensure" + } } - - } - } +<# +.SYNOPSIS + Tests if MSFT_xVHD resource state is in the desired state or not. + +.PARAMETER Name + The desired VHD file name. + +.PARAMETER Path + The desired Path where the VHD will be created. + +.PARAMETER ParentPath + Parent VHD file path, for differencing disk. + +.PARAMETER MaximumSizeBytes + Maximum size of VHD to be created. + +.PARAMETER Type + Virtual disk type. + +.PARAMETER Generation + Virtual disk format. + +.PARAMETER Ensure + Ensures that the VHD is Present or Absent. +#> function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( - # Name of the VHD File - [parameter(Mandatory)] - [String]$Name, - - # Folder where the VHD will be created - [parameter(Mandatory)] - [String]$Path, - - # Parent VHD file path, for differencing disk - [String]$ParentPath, - - # Size of Vhd to be created - [Uint64]$MaximumSizeBytes, - - # Virtual disk format - Vhd or Vhdx - [ValidateSet("Vhd","Vhdx")] - [String]$Generation = "Vhd", - - # Should the VHD be created or deleted - [ValidateSet("Present","Absent")] - [String]$Ensure = "Present" + [Parameter(Mandatory = $true)] + [String] + $Name, + + [Parameter(Mandatory = $true)] + [String] + $Path, + + [Parameter()] + [String] + $ParentPath, + + [Parameter()] + [Uint64] + $MaximumSizeBytes, + + [Parameter()] + [ValidateSet('Vhd', 'Vhdx')] + [String] + $Generation = 'Vhd', + + [Parameter()] + [ValidateSet('Dynamic', 'Fixed', 'Differencing')] + [String] + $Type = 'Dynamic', + + [Parameter()] + [ValidateSet('Present', 'Absent')] + [String] + $Ensure = 'Present' ) - #region input validation - # Check if Hyper-V module is present for Hyper-V cmdlets - if(!(Get-Module -ListAvailable -Name Hyper-V)) + if (!(Get-Module -ListAvailable -Name Hyper-V)) { Throw "Please ensure that Hyper-V role is installed with its PowerShell module" } - if(! ($ParentPath -or $MaximumSizeBytes)) + # input validation + if ($Type -ne 'Differencing' -and -not $MaximumSizeBytes) { - Throw "Either specify ParentPath or MaximumSizeBytes property." + Throw 'Specify MaximumSizeBytes property for Fixed and Dynamic VHDs.' } - - if($ParentPath) + + if ($ParentPath -and $Type -ne 'Differencing') { - # Ensure only one value is specified - differencing disk or new disk - if($MaximumSizeBytes) - { - Throw "Cannot specify both ParentPath and MaximumSizeBytes. Specify only one and try again." - } - - if(! (Test-Path -Path $ParentPath)) + Throw 'Parent path is only supported for Differencing disks' + } + + if (-not $ParentPath -and $Type -eq 'Differencing') + { + Throw 'Differencing requires a parent path' + } + + if ($ParentPath) + { + if (!(Test-Path -Path $ParentPath)) { Throw "$ParentPath does not exists" } - + # Check if the generation matches parenting disk - if($Generation -and ($ParentPath.Split('.')[-1] -ne $Generation)) + if ($Generation -and ($ParentPath.Split('.')[-1] -ne $Generation)) { - Throw "Generation $geneartion should match ParentPath extension $($ParentPath.Split('.')[-1])" + Throw "Generation $Generation should match ParentPath extension $($ParentPath.Split('.')[-1])" } } - if(!(Test-Path -Path $Path)) + + if (!(Test-Path -Path $Path)) { Throw "$Path does not exists" } @@ -226,7 +323,7 @@ function Test-TargetResource # Construct the full path for the vhdFile $vhdName = GetNameWithExtension -Name $Name -Generation $Generation $vhdFilePath = Join-Path -Path $Path -ChildPath $vhdName - Write-Debug -Message "Vhd full path is $vhdFilePath" + Write-Verbose -Message "Vhd full path is $vhdFilePath" # Add the logic here and at the end return either $true or $false. $result = Test-VHD -Path $vhdFilePath -ErrorAction SilentlyContinue @@ -234,16 +331,28 @@ function Test-TargetResource return ($result -and ($Ensure -eq "Present")) } -# Appends the generation to the name provided if it is not part of the name already. +<# +.SYNOPSIS + Appends generation appropriate file extension if not already specified. + +.PARAMETER Name + The desired VHD file name. + +.PARAMETER Generation + Virtual disk format. +#> function GetNameWithExtension { - param( - # Name of the VHD File - [parameter(Mandatory)] - [String]$Name, - [parameter(Mandatory)] - [String]$Generation ='Vhd' - ) + param + ( + [Parameter(Mandatory = $true)] + [String] + $Name, + + [Parameter(Mandatory = $true)] + [String] + $Generation = 'Vhd' + ) # If the name ends with vhd or vhdx don't append the generation to the vhdname. if ($Name -like '*.vhd' -or $Name -like '*.vhdx') @@ -251,11 +360,11 @@ function GetNameWithExtension $extension = $Name.Split('.')[-1] if ($Generation -ne $extension) { - throw "the extension $extension on the name does match the generation $Generation" + throw "the extension $extension on the name does not match the generation $Generation" } else - { - Write-Debug -Message "Vhd full name is $vhdName" + { + Write-Verbose -Message "Vhd full name is $vhdName" $vhdName = $Name } } @@ -263,11 +372,10 @@ function GetNameWithExtension { # Append generation to the name $vhdName = "$Name.$Generation" - Write-Debug -Message "Vhd full name is $vhdName" + Write-Verbose -Message "Vhd full name is $vhdName" } - return $vhdName + $vhdName } Export-ModuleMember -Function *-TargetResource - diff --git a/DSCResources/MSFT_xVHD/MSFT_xVHD.schema.mof b/DSCResources/MSFT_xVHD/MSFT_xVHD.schema.mof index ede60f5..f41e9d2 100644 --- a/DSCResources/MSFT_xVHD/MSFT_xVHD.schema.mof +++ b/DSCResources/MSFT_xVHD/MSFT_xVHD.schema.mof @@ -8,7 +8,7 @@ class MSFT_xVHD : OMI_BaseResource [Write, Description("Virtual disk format - Vhd or Vhdx"), ValueMap{"Vhd","Vhdx"}, Values{"Vhd","Vhdx"}] String Generation; [Write, Description("Should the VHD be created or deleted"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; [Read, Description("Virtual Disk Identifier")] String ID; - [Read, Description("Type of Vhd - Dynamic, Fixed, Differencing")] String Type; + [Write, Description("Type of Vhd - Dynamic, Fixed, Differencing"), ValueMap{"Dynamic","Fixed","Differencing"}, Values{"Dynamic","Fixed","Differencing"}] String Type; [Read, Description("Current size of the VHD")] Uint64 FileSizeBytes; [Read, Description("Is the VHD attached to a VM or not")] Boolean IsAttached; }; diff --git a/Examples/Sample_xVHD_AdditionalPropertyVHD.ps1 b/Examples/Sample_xVHD_AdditionalPropertyVHD.ps1 index 9138d0d..c552cbe 100644 --- a/Examples/Sample_xVHD_AdditionalPropertyVHD.ps1 +++ b/Examples/Sample_xVHD_AdditionalPropertyVHD.ps1 @@ -2,26 +2,34 @@ configuration Sample_xVHD_AdditionalPropertyVHD { param ( - [Parameter(Mandatory)] - [string]$Name, - - [Parameter(Mandatory)] - [string]$Path, - - [Parameter(Mandatory)] - [string]$ParentPath, + [Parameter(Mandatory = $true)] + [string] + $Name, - [Parameter(Mandatory)] - [string]$MaximumSizeBytes, - - [ValidateSet("Vhd","Vhdx")] - [string]$Generation = "Vhd", + [Parameter(Mandatory = $true)] + [string] + $Path, - [ValidateSet("Present","Absent")] - [string]$Ensure = "Present" + [Parameter(Mandatory = $true)] + [string] + $ParentPath, + + [Parameter(Mandatory = $true)] + [string] + $MaximumSizeBytes, + + [Parameter()] + [ValidateSet('Vhd', 'Vhdx')] + [string] + $Generation = 'Vhd', + + [Parameter()] + [ValidateSet('Present', 'Absent')] + [string] + $Ensure = 'Present' ) - Import-DscResource -Module xHyper-V + Import-DscResource -ModuleName xHyper-V Node localhost { diff --git a/Examples/Sample_xVHD_DiffVHD.ps1 b/Examples/Sample_xVHD_DiffVHD.ps1 index 012c738..303544a 100644 --- a/Examples/Sample_xVHD_DiffVHD.ps1 +++ b/Examples/Sample_xVHD_DiffVHD.ps1 @@ -2,25 +2,38 @@ configuration Sample_xVhd_DiffVhd { param ( - [string[]]$NodeName = 'localhost', - - [Parameter(Mandatory)] - [string]$Name, - - [Parameter(Mandatory)] - [string]$Path, - - [Parameter(Mandatory)] - [string]$ParentPath, - - [ValidateSet("Vhd","Vhdx")] - [string]$Generation = "Vhd", - - [ValidateSet("Present","Absent")] - [string]$Ensure = "Present" + [Parameter()] + [string[]] + $NodeName = 'localhost', + + [Parameter(Mandatory = $true)] + [string] + $Name, + + [Parameter(Mandatory = $true)] + [string] + $Path, + + [Parameter(Mandatory = $true)] + [string] + $ParentPath, + + [Parameter()] + [ValidateSet('Vhd', 'Vhdx')] + [string] + $Generation = 'Vhd', + + [Parameter()] + [ValidateSet('Dynamic', 'Fixed', 'Differencing')] + [string]$Type = 'Differencing', + + [Parameter()] + [ValidateSet('Present', 'Absent')] + [string] + $Ensure = 'Present' ) - Import-DscResource -module xHyper-V + Import-DscResource -ModuleName xHyper-V Node $NodeName { @@ -30,7 +43,13 @@ configuration Sample_xVhd_DiffVhd Ensure = 'Present' Name = 'Hyper-V' } - + + WindowsFeature HyperVPowerShell + { + Ensure = 'Present' + Name = 'Hyper-V-PowerShell' + } + xVhd DiffVhd { Ensure = $Ensure @@ -38,7 +57,8 @@ configuration Sample_xVhd_DiffVhd Path = $Path ParentPath = $ParentPath Generation = $Generation - DependsOn = '[WindowsFeature]HyperV' + Type = $Type + DependsOn = '[WindowsFeature]HyperV', '[WindowsFeature]HyperVPowerShell' } } } diff --git a/Examples/Sample_xVHD_FixedVHD.ps1 b/Examples/Sample_xVHD_FixedVHD.ps1 new file mode 100644 index 0000000..94777a1 --- /dev/null +++ b/Examples/Sample_xVHD_FixedVHD.ps1 @@ -0,0 +1,60 @@ +configuration Sample_xVhd_FixedVhd +{ + param + ( + [Parameter()] + [string[]] + $NodeName = 'localhost', + + [Parameter(Mandatory = $true)] + [string] + $Name, + + [Parameter(Mandatory = $true)] + [string] + $Path, + + [Parameter()] + [ValidateSet('Vhd', 'Vhdx')] + [string] + $Generation = 'Vhd', + + [Parameter()] + [ValidateSet('Dynamic', 'Fixed', 'Differencing')] + [string] + $Type = 'Fixed', + + [Parameter()] + [ValidateSet('Present', 'Absent')] + [string] + $Ensure = 'Present' + ) + + Import-DscResource -ModuleName xHyper-V + + Node $NodeName + { + # Install HyperV feature, if not installed - Server SKU only + WindowsFeature HyperV + { + Ensure = 'Present' + Name = 'Hyper-V' + } + + WindowsFeature HyperVPowerShell + { + Ensure = 'Present' + Name = 'Hyper-V-PowerShell' + } + + xVhd DiffVhd + { + Ensure = $Ensure + Name = $Name + Path = $Path + Generation = $Generation + Type = $Type + DependsOn = '[WindowsFeature]HyperV', '[WindowsFeature]HyperVPowerShell' + } + } +} diff --git a/Examples/Sample_xVHD_MissingPropertyVHD.ps1 b/Examples/Sample_xVHD_MissingPropertyVHD.ps1 index f623ab7..baa7037 100644 --- a/Examples/Sample_xVHD_MissingPropertyVHD.ps1 +++ b/Examples/Sample_xVHD_MissingPropertyVHD.ps1 @@ -2,17 +2,23 @@ configuration Sample_xVHD_MissingPropertyVHD { param ( - [Parameter(Mandatory)] - [string]$Name, - - [Parameter(Mandatory)] - [string]$Path, - - [ValidateSet("Vhd","Vhdx")] - [string]$Generation = "Vhd", + [Parameter(Mandatory = $true)] + [string] + $Name, - [ValidateSet("Present","Absent")] - [string]$Ensure = "Present" + [Parameter(Mandatory = $true)] + [string] + $Path, + + [Parameter()] + [ValidateSet('Vhd', 'Vhdx')] + [string] + $Generation = 'Vhd', + + [Parameter()] + [ValidateSet('Present', 'Absent')] + [string] + $Ensure = 'Present' ) Import-DscResource -module xHyper-V diff --git a/Examples/Sample_xVHD_NewVHD.ps1 b/Examples/Sample_xVHD_NewVHD.ps1 index 935d023..f607afb 100644 --- a/Examples/Sample_xVHD_NewVHD.ps1 +++ b/Examples/Sample_xVHD_NewVHD.ps1 @@ -2,25 +2,33 @@ configuration Sample_xVHD_NewVhd { param ( - [string[]]$NodeName = 'localhost', - - [Parameter(Mandatory)] - [string]$Name, - - [Parameter(Mandatory)] - [string]$Path, - - [Parameter(Mandatory)] - [Uint64]$MaximumSizeBytes, - - [ValidateSet("Vhd","Vhdx")] - [string]$Generation = "Vhd", - - [ValidateSet("Present","Absent")] - [string]$Ensure = "Present" + [Parameter()] + [string[]] + $NodeName = 'localhost', + + [Parameter(Mandatory = $true)] + [string] + $Name, + + [Parameter(Mandatory = $true)] + [string] + $Path, + + [Parameter(Mandatory = $true)] + [Uint64] + $MaximumSizeBytes, + + [Parameter()] + [ValidateSet('Vhd', 'Vhdx')] + [string]$Generation = 'Vhd', + + [Parameter()] + [ValidateSet('Present', 'Absent')] + [string] + $Ensure = 'Present' ) - Import-DscResource -module xHyper-V + Import-DscResource -ModuleName xHyper-V Node $NodeName { @@ -30,7 +38,13 @@ configuration Sample_xVHD_NewVhd Ensure = 'Present' Name = 'Hyper-V' } - + + WindowsFeature HyperVPowerShell + { + Ensure = 'Present' + Name = 'Hyper-V-PowerShell' + } + xVhd NewVhd { Ensure = $Ensure @@ -38,7 +52,7 @@ configuration Sample_xVHD_NewVhd Path = $Path Generation = $Generation MaximumSizeBytes = $MaximumSizeBytes - DependsOn = '[WindowsFeature]HyperV' + DependsOn = '[WindowsFeature]HyperV', '[WindowsFeature]HyperVPowerShell' } } } diff --git a/README.md b/README.md index 94e8a11..9698ad6 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,8 @@ Manages VHDs in a Hyper-V host. * **`[Uint64]` MaximumSizeBytes** _(Write)_: Maximum size of VHD to be created. * **`[String]` Generation** _(Write)_: Virtual disk format. The default value is Vhd. { *Vhd* | Vhdx }. +* **`[String]` Type** _(Write)_: Virtual disk type. + The default value is Dynamic. { *Dynamic* | Fixed | Differencing }. * **`[String]` Ensure** _(Write)_: Ensures that the VHD is Present or Absent. The default value is Present. { *Present* | Absent }. @@ -83,6 +85,7 @@ Manages VHDs in a Hyper-V host. #### Examples xVHD * [Create a new VHD](/Examples/Sample_xVHD_NewVHD.ps1) +* [Create a new Fixed VHD](/Examples/Sample_xVHD_FixedVHD.ps1) * [Create a differencing VHD](/Examples/Sample_xVHD_DiffVHD.ps1) ### xVhdFile diff --git a/Tests/Unit/MSFT_xVHD.tests.ps1 b/Tests/Unit/MSFT_xVHD.tests.ps1 new file mode 100644 index 0000000..83d779c --- /dev/null +++ b/Tests/Unit/MSFT_xVHD.tests.ps1 @@ -0,0 +1,302 @@ +#region HEADER + +# Unit Test Template Version: 1.2.0 +$script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) +} + +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force + +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName 'xHyper-V' ` + -DSCResourceName 'MSFT_xVHD' ` + -TestType Unit + +#endregion HEADER + +function Invoke-TestSetup +{ + +} + +function Invoke-TestCleanup +{ + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} + +# Begin Testing +try +{ + Invoke-TestSetup + + InModuleScope 'MSFT_xVHD' { + Describe 'MSFT_xVHD\Get-TargetResource' { + # Create an empty function to be able to mock the missing Hyper-V cmdlet + function Get-VHD + { + + } + + Context 'Should stop when Hyper-V module is missing' { + Mock -CommandName Get-Module -ParameterFilter { ($Name -eq 'Hyper-V') -and ($ListAvailable -eq $true) } -MockWith { + return $false + } + + It 'Should throw when the module is missing' { + { Test-TargetResource -Name 'server.vhdx' -Path 'C:\VMs' -Type 'Fixed' -MaximumSizeBytes 1GB } | + Should Throw 'Please ensure that Hyper-V role is installed with its PowerShell module' + } + } + + # Mocks "Get-Module -Name Hyper-V" so that the DSC resource thinks the Hyper-V module is on the test system + Mock -CommandName Get-Module -ParameterFilter { ($Name -eq 'Hyper-V') -and ($ListAvailable -eq $true) } -MockWith { + return $true + } + + Mock -CommandName GetNameWithExtension -MockWith { 'server.vhdx' } + + Context 'VHD Present' { + It 'Should return a hashtable with Ensure being Present' { + Mock -CommandName Get-VHD -MockWith { + [pscustomobject]@{ + Path = 'server.vhdx' + } + } + + $getTargetResult = Get-TargetResource -Name 'server' -Path 'c:\boguspath' -Generation 'vhdx' + $getTargetResult.Ensure | Should Be 'Present' + $getTargetResult | Should BeOfType hashtable + } + } + + Context 'VHD Not Present' { + It 'Should return a hashtable with Ensure being Absent' { + Mock -CommandName Get-VHD + + $getTargetResult = Get-TargetResource -Name 'server' -Path 'c:\boguspath' -Generation 'vhdx' + $getTargetResult.Ensure | Should Be 'Absent' + $getTargetResult | Should BeOfType hashtable + } + } + } + + Describe 'MSFT_xVHD\GetNameWithExtension' { + Context 'Name does not have extension' { + It 'Should return server.vhdx with generation vhdx' { + GetNameWithExtension -Name 'server' -Generation 'vhdx' | + Should Be 'server.vhdx' + } + + It 'Should return server.vhd with generation vhd' { + GetNameWithExtension -Name 'server' -Generation 'vhd' | + Should Be 'server.vhd' + } + + It 'Should not throw' { + { GetNameWithExtension -Name 'server' -Generation 'vhd' } | + Should Not Throw + } + } + + Context 'Name has extension' { + It 'Should return server.vhdx with Name server.vhdx and generation vhdx' { + GetNameWithExtension -Name 'server.vhd' -Generation 'vhd' | + Should Be 'server.vhd' + } + + It 'Should throw with mismatch with extension from name and generation' { + { GetNameWithExtension -Name 'server.vhdx' -Generation 'vhd' } | + Should Throw 'the extension vhdx on the name does not match the generation vhd' + } + } + } + + Describe 'MSFT_xVHD\Test-TargetResource' { + # Create an empty function to be able to mock the missing Hyper-V cmdlet + function Test-VHD + { + + } + + Context 'Should stop when Hyper-V module is missing' { + Mock -CommandName Get-Module -ParameterFilter { ($Name -eq 'Hyper-V') -and ($ListAvailable -eq $true) } -MockWith { + return $false + } + + It 'Should throw when the module is missing' { + { Test-TargetResource -Name 'server.vhdx' -Path 'C:\VMs' -Type 'Fixed' -MaximumSizeBytes 1GB } | + Should Throw 'Please ensure that Hyper-V role is installed with its PowerShell module' + } + } + + # Mocks "Get-Module -Name Hyper-V" so that the DSC resource thinks the Hyper-V module is on the test system + Mock -CommandName Get-Module -ParameterFilter { ($Name -eq 'Hyper-V') -and ($ListAvailable -eq $true) } -MockWith { + return $true + } + + Context 'Parameter validation' { + It 'Fixed and Dynamic VHDs need MaximumSizeBytes specified' { + { Test-TargetResource -Name 'server' -Path 'C:\VMs' -Type 'Dynamic' } | + Should Throw 'Specify MaximumSizeBytes property for Fixed and Dynamic VHDs.' + } + + It 'Parent Path is passed for a non Differencing disk' { + { Test-TargetResource -Name 'server' -Path 'C:\VMs' -ParentPath 'C:\VMs\Parent' -Type 'Fixed' -MaximumSizeBytes 1GB } | + Should Throw 'Parent path is only supported for Differencing disks' + } + + It 'Differencing disk needs a Parent Path' { + { Test-TargetResource -Name 'server' -Path 'C:\VMs' -Type 'Differencing' } | + Should Throw 'Differencing requires a parent path' + } + } + + Context 'ParentPath specified' { + It 'Should throw when ParentPath does not exist' { + Mock -CommandName Test-Path -MockWith { $false } + + { Test-TargetResource -Name 'server' -Path 'C:\VMs' -Type 'Differencing' -ParentPath 'c:\boguspath' } | + Should Throw 'c:\boguspath does not exists' + } + + # "Generation $Generation should match ParentPath extension $($ParentPath.Split('.')[-1])" + It 'Should throw when file extension and generation have a mismatch' { + Mock -CommandName Test-Path -MockWith { $true } + + { Test-TargetResource -Name 'server' -Path 'C:\VMs' -Type 'Differencing' -ParentPath 'c:\boguspath.vhd' -Generation 'Vhdx' } | + Should Throw 'Generation Vhdx should match ParentPath extension vhd' + } + } + + Context 'Path does not exist' { + It 'Should throw when the path does not exist' { + Mock -CommandName Test-Path -MockWith { $false } + + { Test-TargetResource -Name 'server.vhdx' -Path 'C:\VMs' -Type 'Fixed' -MaximumSizeBytes 1GB } | + Should Throw 'C:\VMs does not exists' + } + } + + Context 'Vhd exists' { + BeforeEach { + Mock -CommandName Test-Path -MockWith { $true } + Mock -CommandName GetNameWithExtension -MockWith { 'server.vhdx' } + Mock -CommandName Test-VHD -MockWith { $true } + } + + It 'Should not throw' { + { Test-TargetResource -Name 'server.vhdx' -Path 'C:\VMs' -Type 'Fixed' -MaximumSizeBytes 1GB } | + Should not Throw + } + + It 'Should return a boolean and it should be true' { + $testResult = Test-TargetResource -Name 'server.vhdx' -Path 'C:\VMs' -Type 'Fixed' -MaximumSizeBytes 1GB + $testResult | Should BeOfType bool + $testResult -eq $true | Should Be $true + } + } + + Context 'Vhd does not exist' { + BeforeEach { + Mock -CommandName Test-Path -MockWith { $true } + Mock -CommandName GetNameWithExtension -MockWith { 'server.vhdx' } + Mock -CommandName Test-VHD -MockWith { $false } + } + + It 'Should not throw' { + { Test-TargetResource -Name 'server.vhdx' -Path 'C:\VMs' -Type 'Fixed' -MaximumSizeBytes 1GB } | + Should not Throw + } + + It 'Should return a boolean and it should be false' { + $testResult = Test-TargetResource -Name 'server.vhdx' -Path 'C:\VMs' -Type 'Fixed' -MaximumSizeBytes 1GB + $testResult | Should BeOfType bool + $testResult -eq $true | Should Be $false + } + } + } + + Describe 'MSFT_xVHD\Set-TargetResource' { + # Create an empty function to be able to mock the missing Hyper-V cmdlet + function Get-VHD + { + + } + + function Set-VHD + { + + } + + function Resize-VHD + { + + } + + function New-VHD + { + + } + + Context 'Ensure is Absent' { + Mock -CommandName Test-Path -MockWith { $true } + Mock -CommandName Remove-Item + Mock -CommandName GetNameWithExtension -MockWith { 'server.vhdx' } + + It 'Should remove when Ensure is Absent and vhdx exists' { + $null = Set-TargetResource -Name 'server.vhdx' -Path 'TestDrive:\' -Ensure 'Absent' + Assert-MockCalled -CommandName Remove-Item -Times 1 -Exactly + } + } + + Context 'Ensure is Present' { + BeforeEach { + Mock -CommandName Get-VHD -MockWith { + [pscustomobject]@{ + Path = 'server.vhdx' + ParentPath = 'c:\boguspath\server.vhdx' + Size = 1073741824 + Type = 'Differencing' + } + } + + Mock -CommandName Set-VHD + Mock -CommandName Resize-VHD + Mock -CommandName GetNameWithExtension -MockWith { 'server.vhdx' } + Mock -CommandName New-VHD -MockWith { } + } + + It 'Should Create a VHD when Ensure is present and no VHD exists yet for non Differencing disk' { + Mock -CommandName Get-VHD -MockWith { throw } + + $null = Set-TargetResource -Name 'server.vhdx' -Path 'TestDrive:\' -Ensure 'Present' + Assert-MockCalled -CommandName New-VHD -Exactly -Times 1 -Scope It + } + + It 'Should Create a VHD when Ensure is present and no VHD exists yet for Differencing disk' { + Mock -CommandName Get-VHD -MockWith { throw } + + $null = Set-TargetResource -Name 'server.vhdx' -Path 'TestDrive:\' -Ensure 'Present' -ParentPath 'c:\boguspath\server.vhdx' -Type 'Differencing' + Assert-MockCalled -CommandName New-VHD -Exactly -Times 1 -Scope It + } + + It 'Should resize a VHD which has a different size as intended' { + $null = Set-TargetResource -Name 'server.vhdx' -Path 'TestDrive:\' -MaximumSizeBytes 2GB -Ensure 'Present' + Assert-MockCalled -CommandName Resize-VHD -Exactly -Times 1 -Scope It + } + + It 'Should update the parentpath if it is different from intent' { + $null = Set-TargetResource -Name 'server.vhdx' -Path 'TestDrive:\' -ParentPath 'c:\boguspath2\server.vhdx' -Ensure 'Present' + Assert-MockCalled -CommandName Set-VHD -Exactly -Times 1 -Scope It + } + } + } + } +} +finally +{ + Invoke-TestCleanup +}