diff --git a/CHANGELOG.md b/CHANGELOG.md index 3285d6a..47dd122 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ For older change log history see the [historic changelog](HISTORIC_CHANGELOG.md) - BREAKING CHANGE: Rename embedded instance class #203 - Fix multiple DNS IP adresses does not work #190 - NetworkSetting parameter is now optional and no default actions are taken if not specified + - VhdFile + - Fix issue with Get- and Test-DscConfiguration if disk is mounted #207. ## [3.18.0] - 2022-06-04 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7571850..48dd895 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -25,7 +25,7 @@ stages: - job: Package_Module displayName: 'Package Module' pool: - vmImage: 'ubuntu-latest' + vmImage: 'windows-latest' steps: - pwsh: | dotnet tool install --global GitVersion.Tool @@ -154,7 +154,7 @@ stages: displayName: 'Publish Code Coverage' dependsOn: Test_Unit pool: - vmImage: 'ubuntu-latest' + vmImage: 'windows-latest' timeoutInMinutes: 0 steps: - task: DownloadPipelineArtifact@2 @@ -177,7 +177,7 @@ stages: summaryFileLocation: '$(Build.SourcesDirectory)/$(buildFolderName)/$(testResultFolderName)/JaCoCo_coverage.xml' pathToSources: '$(Build.SourcesDirectory)/$(sourceFolderName)/' - script: | - bash <(curl -s https://codecov.io/bash) -f "./$(buildFolderName)/$(testResultFolderName)/JaCoCo_coverage.xml" + bash <(curl -s https://codecov.io/bash) -f "$(Build.SourcesDirectory)/$(buildFolderName)/$(testResultFolderName)/JaCoCo_coverage.xml" displayName: 'Publish Code Coverage to Codecov.io' condition: succeededOrFailed() @@ -196,7 +196,7 @@ stages: - job: Deploy_Module displayName: 'Deploy Module' pool: - vmImage: 'ubuntu-latest' + vmImage: 'windows-latest' steps: - task: DownloadPipelineArtifact@2 displayName: 'Download Pipeline Artifact' diff --git a/source/DSCResources/DSC_VhdFileDirectory/DSC_VhdFileDirectory.psm1 b/source/DSCResources/DSC_VhdFileDirectory/DSC_VhdFileDirectory.psm1 index e05f600..056fb20 100644 --- a/source/DSCResources/DSC_VhdFileDirectory/DSC_VhdFileDirectory.psm1 +++ b/source/DSCResources/DSC_VhdFileDirectory/DSC_VhdFileDirectory.psm1 @@ -45,6 +45,18 @@ function Get-TargetResource # Mount VHD. $mountVHD = EnsureVHDState -Mounted -vhdPath $vhdPath + if ($null -eq $mountVHD) + { + Write-Verbose -Message ( -join @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.NoDiskMounted -f $vhdPath) + )) + return @{ + VhdPath = $VhdPath + FileDirectory = @() + } + } + $itemsFound = foreach ($Item in $FileDirectory) { $item = GetItemToCopy -item $item @@ -64,7 +76,10 @@ function Get-TargetResource $finalPath = Join-Path $letterDrive $item.DestinationPath - Write-Verbose "Getting the current value at $finalPath ..." + Write-Verbose -Message ( -join @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.GetCurrentValue -f $finalPath) + )) if (Test-Path $finalPath) { @@ -207,6 +222,15 @@ function Test-TargetResource # mount the vhd. $mountedVHD = EnsureVHDState -Mounted -vhdPath $VhdPath + if ($mountedVHD.Attached -and $null -eq $mountedVHD.DiskNumber) + { + Write-Verbose -Message ( -join @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.SkippingAttachedDisk -f $VhdPath) + )) + return $true + } + try { # Show the drive letters after mount @@ -214,7 +238,10 @@ function Test-TargetResource $mountedDrive = $mountedVHD | Get-Disk | Get-Partition | Where-Object -FilterScript { $_.Type -ne 'Recovery' } | Get-Volume $letterDrive = (-join $mountedDrive.DriveLetter) + ':\' - Write-Verbose $letterDrive + Write-Verbose -Message ( -join @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.GetDriveLetter -f $letterDrive) + )) # return test result equal to true unless one of the tests in the loop below fails. $result = $true @@ -223,7 +250,10 @@ function Test-TargetResource { $itemToCopy = GetItemToCopy -item $item $destination = $itemToCopy.DestinationPath - Write-Verbose ("Testing the file with relative VHD destination $destination") + Write-Verbose -Message ( -join @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.TestRelativePath -f $letterDrive) + )) $destination = $itemToCopy.DestinationPath $finalDestinationPath = $letterDrive $finalDestinationPath = Join-Path $letterDrive $destination @@ -244,11 +274,17 @@ function Test-TargetResource { # Verify if the file exist inside the folder $fileName = Split-Path $itemToCopy.SourcePath -Leaf - Write-Verbose "Checking if $fileName exist under $finalDestinationPath" + Write-Verbose -Message ( -join @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.TestFileExists -f $fileName,$finalDestinationPath) + )) $fileExistInDestination = Test-Path (Join-Path $finalDestinationPath $fileName) # Report if the file exist on the destination folder. - Write-Verbose "File exist on the destination under $finalDestinationPath :- $fileExistInDestination" + Write-Verbose -Message ( -join @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.TestFileExistsInDestination -f $finalDestinationPath,$fileExistInDestination) + )) $result = $fileExistInDestination $result = $result -and -not(ItemHasChanged -sourcePath $itemToCopy.SourcePath -destinationPath (Join-Path $finalDestinationPath $fileName) -CheckSum $CheckSum) } @@ -287,8 +323,10 @@ function Test-TargetResource EnsureVHDState -Dismounted -vhdPath $VhdPath } - - Write-Verbose "Test returned $result" + Write-Verbose -Message ( -join @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.TestResult -f $result) + )) return $result } @@ -308,20 +346,64 @@ function EnsureVHDState $vhdPath ) + Write-Verbose -Message ( -join @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.EnsureEntry -f $vhdPath, $PSCmdlet.ParameterSetName) + )) + if (-not (Get-Module -ListAvailable 'Hyper-V')) { throw 'Hyper-v-Powershell Windows Feature is required to run this resource. Please install Hyper-v feature and try again' } + $vhd = Get-VHD -Path $vhdPath + Write-Verbose -Message ( -join @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.DiskDetails -f $vhdPath, $vhd.Attached, $vhd.DiskNumber) + )) + if ($PSCmdlet.ParameterSetName -eq 'Mounted' -and $vhd.Attached -and $null -eq $vhd.DiskNumber) + { + Write-Verbose -Message ( -join @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.UnableToMount -f $vhdPath) + )) + return $vhd + } + + if ($PSCmdlet.ParameterSetName -eq 'Mounted' -and $vhd.DiskNumber -ge 0) + { + Write-Verbose -Message ( -join @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.AlreadyMounted -f $vhdPath) + )) + return $vhd + } + + if ($PSCmdlet.ParameterSetName -eq 'Dismounted' -and $null -eq $vhd.DiskNumber) + { + Write-Verbose -Message ( -join @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.AlreadyDismounted -f $vhdPath) + )) + return + } + if ($PSCmdlet.ParameterSetName -eq 'Mounted') { + Write-Verbose -Message ( -join @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.Mounting -f $vhdPath) + )) # Try mounting the VHD. $mountedVHD = Mount-VHD -Path $vhdPath -Passthru -ErrorAction SilentlyContinue -ErrorVariable var # If mounting the VHD failed. Dismount the VHD and mount it again. if ($var) { - Write-Verbose 'Mounting Failed. Attempting to dismount and mount it back' + Write-Verbose -Message ( -join @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.FailedToMount -f $vhdPath) + )) Dismount-VHD $vhdPath $mountedVHD = Mount-VHD -Path $vhdPath -Passthru -ErrorAction SilentlyContinue @@ -334,8 +416,11 @@ function EnsureVHDState } else { - Dismount-VHD $vhdPath -ea SilentlyContinue - + Write-Verbose -Message ( -join @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.Dismounting -f $vhdPath) + )) + Dismount-VHD -Path $vhdPath -ErrorAction SilentlyContinue } } @@ -447,7 +532,10 @@ function SetVHDFile $ensure ) - Write-Verbose "Setting the VHD file $($PSCmdlet.ParameterSetName)" + Write-Verbose -Message ( -join @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.SetVhdFileContent -f $PSCmdlet.ParameterSetName)) + ) if ($PSCmdlet.ParameterSetName -eq 'Copy') { New-Item -Path (Split-Path $destinationPath) -ItemType Directory -ErrorAction SilentlyContinue @@ -468,7 +556,10 @@ function SetVHDFile } elseif ($PSCmdlet.ParameterSetName -eq 'Set') { - Write-Verbose "Attempting to change the attribute of the file $destinationPath to value $attribute" + Write-Verbose -Message ( -join @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.SetFileAttributes -f $destinationPath, $attribute)) + ) Set-ItemProperty -Path $destinationPath -Name Attributes -Value $attribute } elseif (!($ensure)) diff --git a/source/DSCResources/DSC_VhdFileDirectory/README.md b/source/DSCResources/DSC_VhdFileDirectory/README.md index 47ebf7e..8c54a36 100644 --- a/source/DSCResources/DSC_VhdFileDirectory/README.md +++ b/source/DSCResources/DSC_VhdFileDirectory/README.md @@ -9,6 +9,9 @@ file attribute to 'ReadOnly' or 'Hidden'). This resource is particularly useful when bootstrapping DSC Configurations into a VM. +If the VHD is going to be attached to a VM, the resource will silently +skip mounting and dismounting the VHD and thus not copy any content. + ## Requirements * The Hyper-V Role has to be installed on the machine. diff --git a/source/DSCResources/DSC_VhdFileDirectory/en-US/DSC_VhdFileDirectory.strings.psd1 b/source/DSCResources/DSC_VhdFileDirectory/en-US/DSC_VhdFileDirectory.strings.psd1 index 3b35d66..e8ac9b8 100644 --- a/source/DSCResources/DSC_VhdFileDirectory/en-US/DSC_VhdFileDirectory.strings.psd1 +++ b/source/DSCResources/DSC_VhdFileDirectory/en-US/DSC_VhdFileDirectory.strings.psd1 @@ -1,2 +1,20 @@ ConvertFrom-StringData @' +GetCurrentValue = Getting the current value at {0}... +GetDriveLetter = Drive letter of mounted drive is {0}. +NoDiskMounted = {0} was not mounted. Ensure it is not attached to a running VM. +TestRelativePath = Testing the file with relative VHD destination {0}. +TestFileExists = Checking if {0} exist under {1}. +TestFileExistsInDestination = File exist on the destination under {0} :- {1}. +TestResult = Test returned {0}. +EnsureEntry = Ensuring that {0} is {1}. +DiskDetails = Disk {0}, attached: {1}, disk number {2}. +SkippingAttachedDisk = Disk {0} is attached to a running VM. Skipping file operations as they would fail. +UnableToMount = Unable to mount {0} when it is attached to a running VM. +Mounting = Attempting to mount {0}. +Dismounting = Attempting to dismount {0}. +AlreadyMounted = {0} is already mounted. +AlreadyDismounted = {0} is already dismounted. +FailedToMount = Mounting {0} failed. Attempting to dismount and mount it back. +SetVhdFileContent = Setting the VHD file {0}. +SetFileAttributes = Attempting to change the attribute of the file {0} to value {1}. '@ diff --git a/tests/Unit/DSC_VhdFileDirectory.Tests.ps1 b/tests/Unit/DSC_VhdFileDirectory.Tests.ps1 index 41a9a66..64d5137 100644 --- a/tests/Unit/DSC_VhdFileDirectory.Tests.ps1 +++ b/tests/Unit/DSC_VhdFileDirectory.Tests.ps1 @@ -102,6 +102,11 @@ Mock -CommandName Mount-Vhd { [PSCustomObject] @{ Path = 'TestDrive:\VhdExists.v Mock -CommandName Dismount-Vhd { } +Mock -CommandName Get-Vhd { [PSCustomObject] @{ + Path = 'TestDrive:\VhdExists.vhdx' + DiskNumber = 1 + }} + Mock -CommandName Get-Disk { New-CimInstance -ClassName MSFT_Disk -Namespace root/Microsoft/Windows/Storage -ClientOnly } @@ -253,6 +258,40 @@ Describe 'DSC_VhdFileDirectory\Get-TargetResource' -Tag 'Get' { } } ) + + # Mount: Get-Vhd -> Skip mount and Get-Vhd -> Mount + $testCases_MountDismount = @( + @{ + TestName = 'Disk is attached to running VM' + VhdPath = 'TestDrive:\VhdExists.vhdx' + Type = 'Mount' + DiskNumber = $null + Attached = $true + GetVhdCount = 1 + MountVhdCount = 0 + DismountVhdCount = 0 + } + @{ + TestName = 'Disk is mounted' + VhdPath = 'TestDrive:\VhdExists.vhdx' + Type = 'Mount' + DiskNumber = 1 + Attached = $false + GetVhdCount = 1 + MountVhdCount = 0 + DismountVhdCount = 0 + } + @{ + TestName = 'Disk is already dismounted' + VhdPath = 'TestDrive:\VhdExists.vhdx' + Type = 'Dismount' + DiskNumber = $null + Attached = $false + GetVhdCount = 1 + MountVhdCount = 0 + DismountVhdCount = 0 + } + ) #endRegion It 'Should correctly return state when: ' -TestCases $testCases_Get { @@ -270,6 +309,47 @@ Describe 'DSC_VhdFileDirectory\Get-TargetResource' -Tag 'Get' { $result['VhdPath'] | Should -Be $ExpectedResult['VhdPath'] } + + It 'Should mount and/or dismount disk: ' -TestCases $testCases_MountDismount { + param( + $VhdPath, + [nullable[int]]$DiskNumber, + $Attached, + $Type, + $GetVhdCount, + $MountVhdCount, + $DismountVhdCount + ) + + [pscustomobject] $mockedobject = if ($null -eq $DiskNumber) + { + @{ + Path = $VhdPath + Attached = $Attached + } + } + else + { + @{ + Path = $VhdPath + DiskNumber = $DiskNumber + Attached = $Attached + } + } + Mock -CommandName Get-Vhd { $mockedobject } + + $null = if ($Type -eq 'Mount') + { + EnsureVHDState -VhdPath $VhdPath -Mounted + } + else + { + EnsureVHDState -VhdPath $VhdPath -Dismounted + } + Assert-MockCalled -CommandName Get-VHD -Exactly -Times $GetVhdCount -Scope It + Assert-MockCalled -CommandName Mount-VHD -Exactly -Times $MountVhdCount -Scope It + Assert-MockCalled -CommandName Dismount-VHD -Exactly -Times $DismountVhdCount -Scope It + } } Describe "DSC_VhdFileDirectory\Test-TargetResource" -Tag 'Test' {