From 27ada6b4e82cdd0180056d91b865edcbbe4c35bd Mon Sep 17 00:00:00 2001 From: Jonathan Merriweather Date: Thu, 14 Dec 2023 23:26:14 +1100 Subject: [PATCH 1/3] 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 7184a62..b6c836a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ For older change log history see the [historic changelog](HISTORIC_CHANGELOG.md) - Fix multiple DNS IP adresses does not work #190 - NetworkSetting parameter is now optional and no default actions are taken if not specified - Switch to use VM image `windows-latest` to build phase. + - 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 69012559a64440188c1411f1afe0c66a4230d440 Mon Sep 17 00:00:00 2001 From: Jonathan Merriweather Date: Thu, 14 Dec 2023 23:36:52 +1100 Subject: [PATCH 2/3] 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 8a2f282b4e23c2e2fa5f4df9bccce644b87048cf Mon Sep 17 00:00:00 2001 From: Jonathan Merriweather Date: Fri, 15 Dec 2023 00:01:18 +1100 Subject: [PATCH 3/3] 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 } }