From 05797fc808fba615260545a675f3676c8d5c6c77 Mon Sep 17 00:00:00 2001 From: Jonathan Merriweather Date: Thu, 14 Dec 2023 23:26:14 +1100 Subject: [PATCH 1/8] Added the ability to enable or disable the TPM on a VM --- CHANGELOG.md | 1 + .../DSC_VMHyperV/DSC_VMHyperV.psm1 | 72 +++++++++++++++++++ tests/Unit/DSC_VMHyperV.Tests.ps1 | 17 +++++ 3 files changed, 90 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3477643..c5c475d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ For older change log history see the [historic changelog](HISTORIC_CHANGELOG.md) - Switch to use VM image `windows-latest` to build phase. - Use latest DscCommunity scripts and files - Changed `HyperVDsc.Common` to a buildable module. + - Added support to enable or disable the TPM on a virtual machine ## [3.18.0] - 2022-06-04 diff --git a/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 b/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 index 112942f..32422c4 100644 --- a/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 +++ b/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 @@ -47,12 +47,17 @@ function Get-TargetResource } $vmSecureBootState = $false + $vmTPMState = $false if ($vmobj.Generation -eq 2) { # Retrieve secure boot status (can only be enabled on Generation 2 VMs) and convert to a boolean. $vmSecureBootState = ($vmobj | Get-VMFirmware).SecureBoot -eq 'On' + + # Retrieve TPM status (can only be enabled on Generation 2 VMs) and return boolean. + $vmTPMState = ($vmobj | Get-VMSecurity).TpmEnabled } + $guestServiceId = 'Microsoft:{0}\6C09BB55-D683-4DA0-8931-C9BF705F6480' -f $vmObj.Id $macAddress = @() @@ -90,6 +95,7 @@ function Get-TargetResource Path = $vmobj.Path Generation = $vmobj.Generation SecureBoot = $vmSecureBootState + TpmEnabled = $vmTPMState StartupMemory = $vmobj.MemoryStartup MinimumMemory = $vmobj.MemoryMinimum MaximumMemory = $vmobj.MemoryMaximum @@ -206,6 +212,11 @@ function Set-TargetResource [System.Boolean] $SecureBoot = $true, + # Enable Trusted Platform Module for Generation 2 VMs + [Parameter()] + [System.Boolean] + $EnableTPM = $true, + # Enable Guest Services [Parameter()] [System.Boolean] @@ -425,6 +436,34 @@ function Set-TargetResource Set-VMProperty @setVMPropertyParams Write-Verbose -Message ($script:localizedData.VMPropertySet -f 'SecureBoot', $SecureBoot) } + + # Retrive the current TPM state + $vmTPMEnabled = Test-VMTpmEnabled -Name $Name + if ($EnableTPM -ne $vmTPMEnabled) + { + Write-Verbose -Message ($script:localizedData.VMPropertyShouldBe -f 'TPMEnabled', $EnableTPM, $vmTPMEnabled) + + # Cannot change the TPM state whilst the VM is powered on. + if (-not $EnableTPM) + { + $setVMPropertyParams = @{ + VMName = $Name + VMCommand = 'Enable-VMTPM' + RestartIfNeeded = $RestartIfNeeded + } + } + else + { + $setVMPropertyParams = @{ + VMName = $Name + VMCommand = 'Disable-VMTPM' + RestartIfNeeded = $RestartIfNeeded + } + } + + Set-VMProperty @setVMPropertyParams + Write-Verbose -Message ($script:localizedData.VMPropertySet -f 'TPMEnabled', $EnableTPM) + } } if ($Notes -ne $null) @@ -576,6 +615,15 @@ function Set-TargetResource { Set-VMFirmware -VMName $Name -EnableSecureBoot Off } + + <# + TPM is only applicable to Generation 2 VMs and it defaults to disabled. + Therefore, we only need to explicitly set it to enabled if specified. + #> + if ($EnableTPM -eq $true) + { + Enable-VMTPM -VMName $Name + } } if ($EnableGuestService) @@ -687,6 +735,11 @@ function Test-TargetResource [System.Boolean] $SecureBoot = $true, + # Enable Trusted Platform Module for Generation 2 VMs + [Parameter()] + [System.Boolean] + $EnableTPM = $true, + [Parameter()] [System.Boolean] $EnableGuestService = $false, @@ -864,6 +917,13 @@ function Test-TargetResource Write-Verbose -Message ($script:localizedData.VMPropertyShouldBe -f 'SecureBoot', $SecureBoot, $vmSecureBoot) return $false } + + $vmTPMEnabled = Test-VMTpmEnabled -Name $Name + if ($EnableTPM -ne $vmTPMEnabled) + { + Write-Verbose -Message ($script:localizedData.VMPropertyShouldBe -f 'TPMEnabled', $EnableTPM, $vmTPMEnabled) + return $false + } } $guestServiceId = 'Microsoft:{0}\6C09BB55-D683-4DA0-8931-C9BF705F6480' -f $vmObj.Id @@ -988,6 +1048,18 @@ function Test-VMSecureBoot return (Get-VMFirmware -VM $vm).SecureBoot -eq 'On' } +function Test-VMTpmEnabled +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name + ) + $vm = Get-VM -Name $Name + return (Get-VMSecurity -VM $vm).TpmEnabled +} + #endregion Export-ModuleMember -Function *-TargetResource diff --git a/tests/Unit/DSC_VMHyperV.Tests.ps1 b/tests/Unit/DSC_VMHyperV.Tests.ps1 index 13097b0..a0629da 100644 --- a/tests/Unit/DSC_VMHyperV.Tests.ps1 +++ b/tests/Unit/DSC_VMHyperV.Tests.ps1 @@ -391,6 +391,12 @@ try Assert-MockCalled -CommandName Get-VMFirmware -Scope It -Exactly 1 } + It 'Calls Get-VMSecurity if a generation 2 VM' { + Mock -CommandName Get-VMSecurity -MockWith { return $true } + $null = Get-TargetResource -Name 'Generation2VM' -VhdPath $stubVhdxDisk.Path + Assert-MockCalled -CommandName Get-VMSecurity -Scope It -Exactly 1 + } + It 'Hash table contains key EnableGuestService' { $targetResource = Get-TargetResource -Name 'RunningVM' -VhdPath $stubVhdxDisk.Path $targetResource.ContainsKey('EnableGuestService') | Should -Be $true @@ -474,6 +480,7 @@ try It 'Returns $true when VM .vhdx file is specified with a generation 2 VM' { Mock -CommandName Test-VMSecureBoot -MockWith { return $true } + Mock -CommandName Test-VMTpmEnabled -MockWith { return $false } Test-TargetResource -Name 'Generation2VM' -Generation 2 @testParams | Should -Be $true } @@ -512,6 +519,16 @@ try Test-TargetResource -Name 'Generation2VM' -SecureBoot $false -Generation 2 @testParams | Should -Be $false } + It 'Returns $true when TpmEnabled is disabled and requested "TpmEnabled" = "$true"' { + Mock -CommandName Test-VMSecurity -MockWith { return $false } + Test-TargetResource -Name 'Generation2VM' -TpmEnabled $true -Generation 2 @testParams | Should -Be $true + } + + It 'Returns $false when TpmEnabled is disabled and requested "TpmEnabled" = "$false"' { + Mock -CommandName Test-VMSecurity -MockWith { return $false } + Test-TargetResource -Name 'Generation2VM' TpmEnabled $false -Generation 2 @testParams | Should -Be $false + } + It 'Returns $true when VM has snapshot chain' { Mock -CommandName Get-VhdHierarchy -MockWith { return @($studVhdxDiskSnapshot, $stubVhdxDisk) From 54fb417181a687562244327c48d09fb257a350b1 Mon Sep 17 00:00:00 2001 From: Jonathan Merriweather Date: Thu, 14 Dec 2023 23:36:52 +1100 Subject: [PATCH 2/8] Fixed module defaults, updated module schema.mof, added example --- .../DSC_VMHyperV/DSC_VMHyperV.psm1 | 4 +- .../DSC_VMHyperV/DSC_VMHyperV.schema.mof | 1 + .../Resources/VMHyperV/7-TPMEnabled.ps1 | 43 +++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 source/Examples/Resources/VMHyperV/7-TPMEnabled.ps1 diff --git a/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 b/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 index 32422c4..468b6ba 100644 --- a/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 +++ b/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 @@ -215,7 +215,7 @@ function Set-TargetResource # Enable Trusted Platform Module for Generation 2 VMs [Parameter()] [System.Boolean] - $EnableTPM = $true, + $EnableTPM = $false, # Enable Guest Services [Parameter()] @@ -738,7 +738,7 @@ function Test-TargetResource # Enable Trusted Platform Module for Generation 2 VMs [Parameter()] [System.Boolean] - $EnableTPM = $true, + $EnableTPM = $false, [Parameter()] [System.Boolean] diff --git a/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.schema.mof b/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.schema.mof index 8416488..491d835 100644 --- a/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.schema.mof +++ b/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.schema.mof @@ -17,6 +17,7 @@ class DSC_VMHyperV : OMI_BaseResource [Write, Description("Specifies if the VM should be Present (created) or Absent (removed). The default value is `Present`."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; [Write, Description("Notes about the VM.")] String Notes; [Write, Description("Specifies if Secure Boot should be enabled for Generation 2 virtual machines. **Only supports generation 2 virtual machines**. Default value is `$true`.")] Boolean SecureBoot; + [Write, Description("Specifies if Trusted Platform Module (TPM) should be enabled for Generation 2 virtual machines. **Only supports generation 2 virtual machines**. Default value is `$false`.")] Boolean EnableTPM; [Write, Description("Enable Guest Service Interface for the VM. The default value is `$false`.")] Boolean EnableGuestService; [Write, Description("Enable AutomaticCheckpoints for the VM.")] Boolean AutomaticCheckpointsEnabled; [Read, Description("Returns the unique ID for the VM.")] String ID; diff --git a/source/Examples/Resources/VMHyperV/7-TPMEnabled.ps1 b/source/Examples/Resources/VMHyperV/7-TPMEnabled.ps1 new file mode 100644 index 0000000..d5163bb --- /dev/null +++ b/source/Examples/Resources/VMHyperV/7-TPMEnabled.ps1 @@ -0,0 +1,43 @@ +<# + .DESCRIPTION + Create a new VM. +#> +configuration Example +{ + param + ( + [System.String[]] + $NodeName = 'localhost', + + [Parameter(Mandatory = $true)] + [System.String] + $VMName, + + [Parameter(Mandatory = $true)] + [System.String] + $VhdPath + ) + + Import-DscResource -ModuleName 'HyperVDsc' + + Node $NodeName + { + # Install HyperV feature, if not installed - Server SKU only + WindowsFeature HyperV + { + Ensure = 'Present' + Name = 'Hyper-V' + } + + # Ensures a VM with default settings + VMHyperV NewVM + { + Ensure = 'Present' + Name = $VMName + VhdPath = $VhdPath + Generation = 2 + EnableTPM = $true + DependsOn = '[WindowsFeature]HyperV' + } + } +} From 5867ff0b18fcca0e5844fefac28701baeba89992 Mon Sep 17 00:00:00 2001 From: Jonathan Merriweather Date: Fri, 15 Dec 2023 00:01:18 +1100 Subject: [PATCH 3/8] Initialise the VMKeyProtector if it is not initialised --- .../DSC_VMHyperV/DSC_VMHyperV.psm1 | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 b/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 index 468b6ba..b1dcbfb 100644 --- a/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 +++ b/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 @@ -446,6 +446,16 @@ function Set-TargetResource # Cannot change the TPM state whilst the VM is powered on. if (-not $EnableTPM) { + # The default value for the key protector is 0,0,0,4 + $keyProtectorDefaultValue = @(0,0,0,4) + # compare the default key protector value and the VM's key protector value + $isVMKeyProtectorDefault = -not(Compare-Object -ReferenceObject (Get-VMKeyProtector -VMName $Name) -DifferenceObject $keyProtectorDefaultValue) + + # If the VM has a default key protector, we need to create a new one before enabling the TPM + if ($isVMKeyProtectorDefault) { + Set-VMKeyProtector -VMName $Name -NewLocalKeyProtector + } + $setVMPropertyParams = @{ VMName = $Name VMCommand = 'Enable-VMTPM' @@ -622,6 +632,16 @@ function Set-TargetResource #> if ($EnableTPM -eq $true) { + # The default value for the key protector is 0,0,0,4 + $keyProtectorDefaultValue = @(0,0,0,4) + # compare the default key protector value and the VM's key protector value + $isVMKeyProtectorDefault = -not(Compare-Object -ReferenceObject (Get-VMKeyProtector -VMName $Name) -DifferenceObject $keyProtectorDefaultValue) + + # If the VM has a default key protector, we need to create a new one before enabling the TPM + if ($isVMKeyProtectorDefault) { + Set-VMKeyProtector -VMName $Name -NewLocalKeyProtector + } + Enable-VMTPM -VMName $Name } } From ed85809fbf3e6e9d50fc4b0a7ab37babd7f9d10f Mon Sep 17 00:00:00 2001 From: Jonathan Merriweather Date: Sat, 27 Jun 2026 08:35:34 +1000 Subject: [PATCH 4/8] Resolve PR #215 review feedback for TPM support - Fix inverted Enable/Disable-VMTPM logic in Set-TargetResource existing-VM path - Return EnableTPM (was TpmEnabled) from Get-TargetResource to match schema - Fix broken TPM unit tests (correct -EnableTPM param, mock Test-VMTpmEnabled, correct expectations) and add missing Get-VMSecurity/Test-VMTpmEnabled mocks so existing Gen2 tests no longer hit the unimplemented stub - Link CHANGELOG entry to issue #214 and expand example comment-based help --- CHANGELOG.md | 2 +- .../DSC_VMHyperV/DSC_VMHyperV.psm1 | 6 ++--- .../Resources/VMHyperV/7-TPMEnabled.ps1 | 22 ++++++++++++++++++- tests/Unit/DSC_VMHyperV.Tests.ps1 | 21 ++++++++++++------ 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5c475d..5beecaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,7 @@ For older change log history see the [historic changelog](HISTORIC_CHANGELOG.md) - Switch to use VM image `windows-latest` to build phase. - Use latest DscCommunity scripts and files - Changed `HyperVDsc.Common` to a buildable module. - - Added support to enable or disable the TPM on a virtual machine + - Added support to enable or disable the TPM on a virtual machine - Fixes [Issue #214](https://github.com/dsccommunity/HyperVDsc/issues/214). ## [3.18.0] - 2022-06-04 diff --git a/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 b/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 index b1dcbfb..d309884 100644 --- a/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 +++ b/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 @@ -95,7 +95,7 @@ function Get-TargetResource Path = $vmobj.Path Generation = $vmobj.Generation SecureBoot = $vmSecureBootState - TpmEnabled = $vmTPMState + EnableTPM = $vmTPMState StartupMemory = $vmobj.MemoryStartup MinimumMemory = $vmobj.MemoryMinimum MaximumMemory = $vmobj.MemoryMaximum @@ -443,8 +443,8 @@ function Set-TargetResource { Write-Verbose -Message ($script:localizedData.VMPropertyShouldBe -f 'TPMEnabled', $EnableTPM, $vmTPMEnabled) - # Cannot change the TPM state whilst the VM is powered on. - if (-not $EnableTPM) + # Bring the TPM state in line with the desired state (restarts the VM if needed). + if ($EnableTPM) { # The default value for the key protector is 0,0,0,4 $keyProtectorDefaultValue = @(0,0,0,4) diff --git a/source/Examples/Resources/VMHyperV/7-TPMEnabled.ps1 b/source/Examples/Resources/VMHyperV/7-TPMEnabled.ps1 index d5163bb..7ef02ce 100644 --- a/source/Examples/Resources/VMHyperV/7-TPMEnabled.ps1 +++ b/source/Examples/Resources/VMHyperV/7-TPMEnabled.ps1 @@ -1,6 +1,26 @@ <# + .SYNOPSIS + Creates a Generation 2 VM with the Trusted Platform Module (TPM) enabled. + .DESCRIPTION - Create a new VM. + Creates a new Generation 2 virtual machine and enables the Trusted + Platform Module (TPM) on it. + + .PARAMETER NodeName + The names of one or more nodes to compile a configuration for. + Defaults to 'localhost'. + + .PARAMETER VMName + The name of the virtual machine to create. + + .PARAMETER VhdPath + The path to the VHDX file to associate with the virtual machine. + + .EXAMPLE + Example -VMName 'TPMVM' -VhdPath 'C:\VMs\TPMVM.vhdx' + + Compiles a configuration that creates a Generation 2 VM named 'TPMVM' + with TPM enabled. #> configuration Example { diff --git a/tests/Unit/DSC_VMHyperV.Tests.ps1 b/tests/Unit/DSC_VMHyperV.Tests.ps1 index a0629da..2aa7e8d 100644 --- a/tests/Unit/DSC_VMHyperV.Tests.ps1 +++ b/tests/Unit/DSC_VMHyperV.Tests.ps1 @@ -387,12 +387,13 @@ try It 'Calls Get-VMFirmware if a generation 2 VM' { Mock -CommandName Get-VMFirmware -MockWith { return $true } + Mock -CommandName Get-VMSecurity -MockWith { return @{ TpmEnabled = $false } } $null = Get-TargetResource -Name 'Generation2VM' -VhdPath $stubVhdxDisk.Path Assert-MockCalled -CommandName Get-VMFirmware -Scope It -Exactly 1 } It 'Calls Get-VMSecurity if a generation 2 VM' { - Mock -CommandName Get-VMSecurity -MockWith { return $true } + Mock -CommandName Get-VMSecurity -MockWith { return @{ TpmEnabled = $false } } $null = Get-TargetResource -Name 'Generation2VM' -VhdPath $stubVhdxDisk.Path Assert-MockCalled -CommandName Get-VMSecurity -Scope It -Exactly 1 } @@ -519,14 +520,18 @@ try Test-TargetResource -Name 'Generation2VM' -SecureBoot $false -Generation 2 @testParams | Should -Be $false } - It 'Returns $true when TpmEnabled is disabled and requested "TpmEnabled" = "$true"' { - Mock -CommandName Test-VMSecurity -MockWith { return $false } - Test-TargetResource -Name 'Generation2VM' -TpmEnabled $true -Generation 2 @testParams | Should -Be $true + It 'Returns $true when TPM is enabled and requested "EnableTPM" = "$true"' { + Mock -CommandName Test-VMTpmEnabled -MockWith { return $true } + + Test-TargetResource -Name 'Generation2VM' -EnableTPM $true -Generation 2 @testParams | + Should -BeTrue } - It 'Returns $false when TpmEnabled is disabled and requested "TpmEnabled" = "$false"' { - Mock -CommandName Test-VMSecurity -MockWith { return $false } - Test-TargetResource -Name 'Generation2VM' TpmEnabled $false -Generation 2 @testParams | Should -Be $false + It 'Returns $false when TPM is disabled and requested "EnableTPM" = "$true"' { + Mock -CommandName Test-VMTpmEnabled -MockWith { return $false } + + Test-TargetResource -Name 'Generation2VM' -EnableTPM $true -Generation 2 @testParams | + Should -BeFalse } It 'Returns $true when VM has snapshot chain' { @@ -622,6 +627,8 @@ try Mock -CommandName Get-VMNetworkAdapter -MockWith { return $stubVM.NetworkAdapters.IpAddresses } Mock -CommandName Set-VMState -MockWith { return $true } Mock -CommandName Set-VMMemory + # Default TPM state for generation 2 VMs so the new TPM code path does not hit the Hyper-V stub. + Mock -CommandName Test-VMTpmEnabled -MockWith { return $false } It 'Removes an existing VM when "Ensure" = "Absent"' { Set-TargetResource -Name 'RunningVM' -Ensure Absent @testParams From 5187f7e10774e2fd00bd8db4f42d2ebcd8e7affb Mon Sep 17 00:00:00 2001 From: Jonathan Merriweather Date: Sat, 27 Jun 2026 09:03:47 +1000 Subject: [PATCH 5/8] Fix TPM key protector brace style --- source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 b/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 index d309884..b7a1d57 100644 --- a/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 +++ b/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 @@ -452,7 +452,8 @@ function Set-TargetResource $isVMKeyProtectorDefault = -not(Compare-Object -ReferenceObject (Get-VMKeyProtector -VMName $Name) -DifferenceObject $keyProtectorDefaultValue) # If the VM has a default key protector, we need to create a new one before enabling the TPM - if ($isVMKeyProtectorDefault) { + if ($isVMKeyProtectorDefault) + { Set-VMKeyProtector -VMName $Name -NewLocalKeyProtector } @@ -638,7 +639,8 @@ function Set-TargetResource $isVMKeyProtectorDefault = -not(Compare-Object -ReferenceObject (Get-VMKeyProtector -VMName $Name) -DifferenceObject $keyProtectorDefaultValue) # If the VM has a default key protector, we need to create a new one before enabling the TPM - if ($isVMKeyProtectorDefault) { + if ($isVMKeyProtectorDefault) + { Set-VMKeyProtector -VMName $Name -NewLocalKeyProtector } From 5c8dd62c15bdef8b9681375c89aa553edca0fa33 Mon Sep 17 00:00:00 2001 From: Jonathan Merriweather Date: Sat, 27 Jun 2026 09:21:47 +1000 Subject: [PATCH 6/8] Add inputs and outputs to TPM example help --- source/Examples/Resources/VMHyperV/7-TPMEnabled.ps1 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/Examples/Resources/VMHyperV/7-TPMEnabled.ps1 b/source/Examples/Resources/VMHyperV/7-TPMEnabled.ps1 index 7ef02ce..56f3ad6 100644 --- a/source/Examples/Resources/VMHyperV/7-TPMEnabled.ps1 +++ b/source/Examples/Resources/VMHyperV/7-TPMEnabled.ps1 @@ -16,6 +16,12 @@ .PARAMETER VhdPath The path to the VHDX file to associate with the virtual machine. + .INPUTS + None. + + .OUTPUTS + None. + .EXAMPLE Example -VMName 'TPMVM' -VhdPath 'C:\VMs\TPMVM.vhdx' From 9edcb0b972f0a72c041ce7c868ece3a9c3253902 Mon Sep 17 00:00:00 2001 From: Jonathan Merriweather Date: Sat, 27 Jun 2026 11:46:09 +1000 Subject: [PATCH 7/8] Enable TPM without prompting for existing generation 2 VMs in Set-TargetResource --- .../DSC_VMHyperV/DSC_VMHyperV.psm1 | 6 ++++ tests/Unit/DSC_VMHyperV.Tests.ps1 | 29 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 b/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 index b7a1d57..b03b18b 100644 --- a/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 +++ b/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 @@ -460,6 +460,9 @@ function Set-TargetResource $setVMPropertyParams = @{ VMName = $Name VMCommand = 'Enable-VMTPM' + ChangeProperty = @{ + Confirm = $false + } RestartIfNeeded = $RestartIfNeeded } } @@ -468,6 +471,9 @@ function Set-TargetResource $setVMPropertyParams = @{ VMName = $Name VMCommand = 'Disable-VMTPM' + ChangeProperty = @{ + Confirm = $false + } RestartIfNeeded = $RestartIfNeeded } } diff --git a/tests/Unit/DSC_VMHyperV.Tests.ps1 b/tests/Unit/DSC_VMHyperV.Tests.ps1 index 2aa7e8d..83a86ca 100644 --- a/tests/Unit/DSC_VMHyperV.Tests.ps1 +++ b/tests/Unit/DSC_VMHyperV.Tests.ps1 @@ -802,6 +802,35 @@ try Assert-MockCalled -CommandName Set-VMProperty -ParameterFilter { $VMCommand -eq 'Set-VMFirmware' } -Exactly 1 -Scope It } + It 'Enables TPM without prompting when an existing generation 2 VM has TPM disabled' { + Mock -CommandName Test-VMSecureBoot -MockWith { return $true } + Mock -CommandName Test-VMTpmEnabled -MockWith { return $false } + Mock -CommandName Get-VMKeyProtector -MockWith { return @(0, 0, 0, 4) } + Mock -CommandName Set-VMKeyProtector + Mock -CommandName Set-VMProperty + + Set-TargetResource -Name 'StoppedVM' -Generation 2 -EnableTPM $true @testParams + + Assert-MockCalled -CommandName Set-VMKeyProtector -Exactly 1 -Scope It + Assert-MockCalled -CommandName Set-VMProperty -ParameterFilter { + $VMCommand -eq 'Enable-VMTPM' -and + $ChangeProperty.Confirm -eq $false + } -Exactly 1 -Scope It + } + + It 'Disables TPM without prompting when an existing generation 2 VM has TPM enabled' { + Mock -CommandName Test-VMSecureBoot -MockWith { return $true } + Mock -CommandName Test-VMTpmEnabled -MockWith { return $true } + Mock -CommandName Set-VMProperty + + Set-TargetResource -Name 'StoppedVM' -Generation 2 -EnableTPM $false @testParams + + Assert-MockCalled -CommandName Set-VMProperty -ParameterFilter { + $VMCommand -eq 'Disable-VMTPM' -and + $ChangeProperty.Confirm -eq $false + } -Exactly 1 -Scope It + } + It 'Does call "Enable-VMIntegrationService" when "EnableGuestService" = "$true"' { Mock -CommandName Enable-VMIntegrationService Set-TargetResource -Name 'RunningVM' -EnableGuestService $true @testParams From 51d847d507a1d9b65900f3ce70521eb9c57253c4 Mon Sep 17 00:00:00 2001 From: Jonathan Merriweather Date: Sat, 27 Jun 2026 13:10:08 +1000 Subject: [PATCH 8/8] Enhance Set-TargetResource to handle TPM configuration for Generation 2 VMs --- .../DSC_VMHyperV/DSC_VMHyperV.psm1 | 3 ++ tests/Unit/DSC_VMHyperV.Tests.ps1 | 45 ++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 b/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 index b03b18b..1c672e5 100644 --- a/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 +++ b/source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1 @@ -436,7 +436,10 @@ function Set-TargetResource Set-VMProperty @setVMPropertyParams Write-Verbose -Message ($script:localizedData.VMPropertySet -f 'SecureBoot', $SecureBoot) } + } + if ($vmObj.Generation -eq 2) + { # Retrive the current TPM state $vmTPMEnabled = Test-VMTpmEnabled -Name $Name if ($EnableTPM -ne $vmTPMEnabled) diff --git a/tests/Unit/DSC_VMHyperV.Tests.ps1 b/tests/Unit/DSC_VMHyperV.Tests.ps1 index 83a86ca..68ea4da 100644 --- a/tests/Unit/DSC_VMHyperV.Tests.ps1 +++ b/tests/Unit/DSC_VMHyperV.Tests.ps1 @@ -117,6 +117,35 @@ try return $stubVM } + Mock -CommandName Get-VM -ParameterFilter { $Name -eq 'StoppedGeneration2VM' } -MockWith { + $stubVM = [Microsoft.HyperV.PowerShell.VirtualMachine]::CreateTypeInstance() + $stubVM.Name = 'StoppedGeneration2VM' + $stubVM.HardDrives = @( + $stubVhdxDisk, + $stubVhdDisk + ) + $stubVM.Path = $StubVMConfig.FullPath + $stubVM.Generation = 2 + $stubVM.MemoryStartup = 512MB + $stubVM.MemoryMinimum = 128MB + $stubVM.MemoryMaximum = 4096MB + $stubVM.ProcessorCount = 1 + $stubVM.ID = $mockVmGuid + $stubVM.CPUUsage = 10 + $stubVM.MemoryAssigned = 512MB + $stubVM.Uptime = New-TimeSpan -Hours 12 + $stubVM.CreationTime = (Get-Date).AddHours(-12) + $stubVM.DynamicMemoryEnabled = $true + $stubVM.Notes = '' + $stubVM.State = 'Off' + $stubVM.NetworkAdapters = @( + $stubNIC1, + $stubNIC2 + ) + + return $stubVM + } + Mock -CommandName Get-VM -ParameterFilter { $Name -eq 'PausedVM' } -MockWith { $stubVM = [Microsoft.HyperV.PowerShell.VirtualMachine]::CreateTypeInstance() $stubVM.Name = 'PausedVM' @@ -809,7 +838,7 @@ try Mock -CommandName Set-VMKeyProtector Mock -CommandName Set-VMProperty - Set-TargetResource -Name 'StoppedVM' -Generation 2 -EnableTPM $true @testParams + Set-TargetResource -Name 'StoppedGeneration2VM' -Generation 2 -EnableTPM $true @testParams Assert-MockCalled -CommandName Set-VMKeyProtector -Exactly 1 -Scope It Assert-MockCalled -CommandName Set-VMProperty -ParameterFilter { @@ -823,7 +852,7 @@ try Mock -CommandName Test-VMTpmEnabled -MockWith { return $true } Mock -CommandName Set-VMProperty - Set-TargetResource -Name 'StoppedVM' -Generation 2 -EnableTPM $false @testParams + Set-TargetResource -Name 'StoppedGeneration2VM' -Generation 2 -EnableTPM $false @testParams Assert-MockCalled -CommandName Set-VMProperty -ParameterFilter { $VMCommand -eq 'Disable-VMTPM' -and @@ -831,6 +860,18 @@ try } -Exactly 1 -Scope It } + It 'Does not configure TPM on an existing generation 1 VM when generation 2 is requested' { + Mock -CommandName Test-VMSecureBoot -MockWith { return $true } + Mock -CommandName Test-VMTpmEnabled -MockWith { return $false } + Mock -CommandName Set-VMProperty + + Set-TargetResource -Name 'StoppedVM' -Generation 2 -EnableTPM $true @testParams + + Assert-MockCalled -CommandName Set-VMProperty -ParameterFilter { + $VMCommand -in @('Enable-VMTPM', 'Disable-VMTPM') + } -Exactly 0 -Scope It + } + It 'Does call "Enable-VMIntegrationService" when "EnableGuestService" = "$true"' { Mock -CommandName Enable-VMIntegrationService Set-TargetResource -Name 'RunningVM' -EnableGuestService $true @testParams