-
Notifications
You must be signed in to change notification settings - Fork 64
VMHyperV: Added the ability to enable or disable the TPM on a VM #215
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
27ada6b
6901255
8a2f282
e877c92
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 = $false, | ||
|
Comment on lines
+215
to
+218
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix inverted TPM enable/disable logic in The new $vmTPMEnabled = Test-VMTpmEnabled -Name $Name
if ($EnableTPM -ne $vmTPMEnabled)
{
# ...
if (-not $EnableTPM)
{
# prepare key protector
$setVMPropertyParams = @{
VMCommand = 'Enable-VMTPM'
}
}
else
{
$setVMPropertyParams = @{
VMCommand = 'Disable-VMTPM'
}
}
Set-VMProperty @setVMPropertyParams
}If You can correct this by making the - # 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)
- {
- # 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'
- RestartIfNeeded = $RestartIfNeeded
- }
- }
- else
- {
- $setVMPropertyParams = @{
- VMName = $Name
- VMCommand = 'Disable-VMTPM'
- RestartIfNeeded = $RestartIfNeeded
- }
- }
+ # 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 ($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, create a new one before enabling TPM.
+ if ($isVMKeyProtectorDefault)
+ {
+ Set-VMKeyProtector -VMName $Name -NewLocalKeyProtector
+ }
+
+ $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)
+ Set-VMProperty @setVMPropertyParams
+ Write-Verbose -Message ($script:localizedData.VMPropertySet -f 'TPMEnabled', $EnableTPM)Without this change, any attempt to enable TPM on an existing VM will actually disable it, and vice versa. Also applies to: 440-476 🤖 Prompt for AI Agents |
||
|
|
||
| # Enable Guest Services | ||
| [Parameter()] | ||
| [System.Boolean] | ||
|
|
@@ -425,6 +436,44 @@ 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) | ||
| { | ||
| # 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' | ||
| 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 +625,25 @@ 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) | ||
| { | ||
| # 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 | ||
| } | ||
| } | ||
|
|
||
| if ($EnableGuestService) | ||
|
|
@@ -687,6 +755,11 @@ function Test-TargetResource | |
| [System.Boolean] | ||
| $SecureBoot = $true, | ||
|
|
||
| # Enable Trusted Platform Module for Generation 2 VMs | ||
| [Parameter()] | ||
| [System.Boolean] | ||
| $EnableTPM = $false, | ||
|
|
||
| [Parameter()] | ||
| [System.Boolean] | ||
| $EnableGuestService = $false, | ||
|
|
@@ -864,6 +937,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 +1068,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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Align You’ve added a writable Consider either:
Based on learnings, … 🤖 Prompt for AI Agents |
||
| [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; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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' | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
| } | ||
|
Comment on lines
481
to
485
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix TPM-related tests: wrong parameter name, wrong helper, and inverted expectations The TPM tests in this context have several concrete issues:
A minimal fix that aligns the tests with the implementation and semantics would be: 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 }
+ Mock -CommandName Test-VMTpmEnabled -MockWith { return $false }
Test-TargetResource -Name 'Generation2VM' -Generation 2 @testParams | Should -Be $true
}
@@
- 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 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 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
+ }This ensures:
As per coding guidelines, … Also applies to: 522-530 🤖 Prompt for AI Agents |
||
|
|
||
|
|
@@ -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) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Return
EnableTPMfromGet-TargetResourceinstead of (or in addition to)TpmEnabledThis block correctly queries TPM state via
Get-VMSecurity, but the hashtable below exposes it asTpmEnabled:The public schema and Set/Test functions use
EnableTPMas the configurable property name. To keep the resource contract consistent:EnableTPM = $vmTPMStatesoGet-TargetResourcereturns the configured/writable property; orTpmEnabledvalue, add it as a[Read]property in the schema and also returnEnableTPMhere.Right now, the configured
EnableTPMvalue will not appear inGet-DscConfiguration, and consumers will instead see an undocumentedTpmEnabledproperty.Based on learnings, …
Also applies to: 56-57, 82-99
🤖 Prompt for AI Agents