Skip to content
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

VMHyperV: Added the ability to enable or disable the TPM on a VM #215

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
92 changes: 92 additions & 0 deletions source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -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 = @()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -206,6 +212,11 @@ function Set-TargetResource
[System.Boolean]
$SecureBoot = $true,

# Enable Trusted Platform Module for Generation 2 VMs
[Parameter()]
[System.Boolean]
$EnableTPM = $false,

# Enable Guest Services
[Parameter()]
[System.Boolean]
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
1 change: 1 addition & 0 deletions source/DSCResources/DSC_VMHyperV/DSC_VMHyperV.schema.mof
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
43 changes: 43 additions & 0 deletions source/Examples/Resources/VMHyperV/7-TPMEnabled.ps1
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'
}
}
}
17 changes: 17 additions & 0 deletions tests/Unit/DSC_VMHyperV.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
Expand Down
Loading