diff --git a/DSCResources/MSFT_xBLAutoBitlocker/MSFT_xBLAutoBitlocker.psm1 b/DSCResources/MSFT_xBLAutoBitlocker/MSFT_xBLAutoBitlocker.psm1 index 373968a..7eeb2a4 100644 --- a/DSCResources/MSFT_xBLAutoBitlocker/MSFT_xBLAutoBitlocker.psm1 +++ b/DSCResources/MSFT_xBLAutoBitlocker/MSFT_xBLAutoBitlocker.psm1 @@ -497,5 +497,3 @@ function GetAutoBitlockerStatus } Export-ModuleMember -Function *-TargetResource - - diff --git a/DSCResources/MSFT_xBLBitlocker/MSFT_xBLBitlocker.psm1 b/DSCResources/MSFT_xBLBitlocker/MSFT_xBLBitlocker.psm1 index 36a86cf..1cc4fe5 100644 --- a/DSCResources/MSFT_xBLBitlocker/MSFT_xBLBitlocker.psm1 +++ b/DSCResources/MSFT_xBLBitlocker/MSFT_xBLBitlocker.psm1 @@ -296,5 +296,3 @@ function Test-TargetResource } Export-ModuleMember -Function *-TargetResource - - diff --git a/DSCResources/MSFT_xBLTpm/MSFT_xBLTpm.psm1 b/DSCResources/MSFT_xBLTpm/MSFT_xBLTpm.psm1 index 02007e2..ba203e3 100644 --- a/DSCResources/MSFT_xBLTpm/MSFT_xBLTpm.psm1 +++ b/DSCResources/MSFT_xBLTpm/MSFT_xBLTpm.psm1 @@ -130,5 +130,3 @@ function Test-TargetResource Export-ModuleMember -Function *-TargetResource - - diff --git a/Misc/xBitlockerCommon.psm1 b/Misc/xBitlockerCommon.psm1 index f1d8d9d..53d7843 100644 --- a/Misc/xBitlockerCommon.psm1 +++ b/Misc/xBitlockerCommon.psm1 @@ -1,4 +1,83 @@ -#A common function used to enable Bitlocker on a disk. +<# + .SYNOPSIS + Enables Bitlocker and Bitlocker features on the requested disk. + + .PARAMETER MountPoint + The MountPoint name as reported in Get-BitLockerVolume. + + .PARAMETER PrimaryProtector + The type of key protector that will be used as the primary key + protector. + + .PARAMETER AdAccountOrGroup + Specifies an account using the format Domain\User. + + .PARAMETER AdAccountOrGroupProtector + Indicates that BitLocker uses an AD DS account as a protector for the + volume encryption key. + + .PARAMETER AllowImmediateReboot + Whether the computer can be immediately rebooted after enabling + Bitlocker on an OS drive. Defaults to false. + + .PARAMETER AutoUnlock + Whether volumes should be enabled for auto unlock using + Enable-BitlockerAutoUnlock. + + .PARAMETER EncryptionMethod + Indicates that BitLocker uses the TPM as a protector for the volume + encryption key. + + .PARAMETER HardwareEncryption + Indicates that the volume uses hardware encryption. + + .PARAMETER Password + Specifies a secure string object that contains a password. + + .PARAMETER PasswordProtector + Indicates that BitLocker uses a password as a protector for the volume + encryption key. + + .PARAMETER Pin + Specifies a secure string object that contains a PIN. + + .PARAMETER RecoveryKeyPath + Specifies a path to a recovery key. + + .PARAMETER RecoveryKeyProtector + Indicates that BitLocker uses a recovery key as a protector for the + volume encryption key. + + .PARAMETER RecoveryPasswordProtector + Indicates that BitLocker uses a recovery password as a protector for + the volume encryption key. + + .PARAMETER Service + Indicates that the system account for this computer unlocks the + encrypted volume. + + .PARAMETER SkipHardwareTest + Indicates that BitLocker does not perform a hardware test before it + begins encryption. + + .PARAMETER StartupKeyPath + Specifies a path to a startup key. + + .PARAMETER StartupKeyProtector + Indicates that BitLocker uses a startup key as a protector for the + volume encryption key. + + .PARAMETER TpmProtector + Indicates that BitLocker uses the TPM as a protector for the volume + encryption key. + + .PARAMETER UsedSpaceOnly + Indicates that BitLocker does not encrypt disk space which contains + unused data. + + .PARAMETER VerbosePreference + Used to modify the default VerbosePreference for the function. +#> function EnableBitlocker { # Suppressing this rule because $global:DSCMachineStatus is used to trigger a reboot. @@ -113,41 +192,7 @@ function EnableBitlocker throw "A TpmProtector must be used if Pin is used." } - if ($PSBoundParameters.ContainsKey("AdAccountOrGroupProtector") -and $PrimaryProtector -notlike "AdAccountOrGroupProtector" -and !(ContainsKeyProtector -Type "AdAccountOrGroup" -KeyProtectorCollection $blv.KeyProtector)) - { - Write-Verbose "Adding AdAccountOrGroupProtector" - Add-BitLockerKeyProtector -MountPoint $MountPoint -AdAccountOrGroupProtector -AdAccountOrGroup $AdAccountOrGroup - } - - if ($PSBoundParameters.ContainsKey("PasswordProtector") -and $PrimaryProtector -notlike "PasswordProtector" -and !(ContainsKeyProtector -Type "Password" -KeyProtectorCollection $blv.KeyProtector)) - { - Write-Verbose "Adding PasswordProtector" - Add-BitLockerKeyProtector -MountPoint $MountPoint -PasswordProtector -Password $Password.Password - } - - if ($PSBoundParameters.ContainsKey("RecoveryKeyProtector") -and $PrimaryProtector -notlike "RecoveryKeyProtector" -and !(ContainsKeyProtector -Type "ExternalKey" -KeyProtectorCollection $blv.KeyProtector)) - { - Write-Verbose "Adding RecoveryKeyProtector" - Add-BitLockerKeyProtector -MountPoint $MountPoint -RecoveryKeyProtector -RecoveryKeyPath $RecoveryKeyPath - } - - if ($PSBoundParameters.ContainsKey("RecoveryPasswordProtector") -and $PrimaryProtector -notlike "RecoveryPasswordProtector" -and !(ContainsKeyProtector -Type "RecoveryPassword" -KeyProtectorCollection $blv.KeyProtector)) - { - Write-Verbose "Adding RecoveryPasswordProtector" - Add-BitLockerKeyProtector -MountPoint $MountPoint -RecoveryPasswordProtector - } - - if ($PSBoundParameters.ContainsKey("StartupKeyProtector") -and $PrimaryProtector -notlike "TpmProtector" -and $PrimaryProtector -notlike "StartupKeyProtector" -and !(ContainsKeyProtector -Type "ExternalKey" -KeyProtectorCollection $blv.KeyProtector)) - { - Write-Verbose "Adding StartupKeyProtector" - Add-BitLockerKeyProtector -MountPoint $MountPoint -StartupKeyProtector -StartupKeyPath $StartupKeyPath - } - - if ($PSBoundParameters.ContainsKey("TpmProtector") -and $PrimaryProtector -notlike "TpmProtector" -and !(ContainsKeyProtector -Type "Tpm" -KeyProtectorCollection $blv.KeyProtector -StartsWith $true)) - { - Write-Verbose "Adding TpmProtector" - Add-BitLockerKeyProtector -MountPoint $MountPoint -TpmProtector $TpmProtector - } + Add-MissingBitLockerKeyProtector @PSBoundParameters -Verbose:$VerbosePreference #Now enable Bitlocker with the primary key protector if ($blv.VolumeStatus -eq "FullyDecrypted") @@ -234,10 +279,10 @@ function EnableBitlocker #Run Enable-Bitlocker Write-Verbose "Running Enable-Bitlocker" - $newBlv = Enable-Bitlocker @params + $blv = Enable-Bitlocker @params #Check if the Enable succeeded - if ($null -ne $newBlv) + if ($null -ne $blv) { if ($blv.VolumeType -eq "OperatingSystem") #Only initiate reboot if this is an OS drive { @@ -256,12 +301,12 @@ function EnableBitlocker { throw "Failed to successfully enable Bitlocker on MountPoint $($MountPoint)" } + } - #Finally, enable AutoUnlock if requested - if ($AutoUnlock -eq $true -and $blv.VolumeType -ne "OperatingSystem") - { - Enable-BitlockerAutoUnlock -MountPoint $MountPoint - } + # Finally, enable AutoUnlock if requested + if ($AutoUnlock -eq $true -and $blv.VolumeType -ne 'OperatingSystem' -and !$blv.AutoUnlockEnabled) + { + Enable-BitlockerAutoUnlock -MountPoint $MountPoint } } else @@ -270,7 +315,290 @@ function EnableBitlocker } } -#A common function used to test if Bitlocker is enabled on a disk with the appropriate settings +<# + .SYNOPSIS + Checks if any required secondary Key Protectors are missing, and adds + them to the requested volume. + + .PARAMETER MountPoint + The MountPoint name as reported in Get-BitLockerVolume. + + .PARAMETER PrimaryProtector + The type of key protector that will be used as the primary key + protector. + + .PARAMETER AdAccountOrGroup + Specifies an account using the format Domain\User. + + .PARAMETER AdAccountOrGroupProtector + Indicates that BitLocker uses an AD DS account as a protector for the + volume encryption key. + + .PARAMETER AllowImmediateReboot + Whether the computer can be immediately rebooted after enabling + Bitlocker on an OS drive. Defaults to false. + + .PARAMETER AutoUnlock + Whether volumes should be enabled for auto unlock using + Enable-BitlockerAutoUnlock. + + .PARAMETER EncryptionMethod + Indicates that BitLocker uses the TPM as a protector for the volume + encryption key. + + .PARAMETER HardwareEncryption + Indicates that the volume uses hardware encryption. + + .PARAMETER Password + Specifies a secure string object that contains a password. + + .PARAMETER PasswordProtector + Indicates that BitLocker uses a password as a protector for the volume + encryption key. + + .PARAMETER Pin + Specifies a secure string object that contains a PIN. + + .PARAMETER RecoveryKeyPath + Specifies a path to a recovery key. + + .PARAMETER RecoveryKeyProtector + Indicates that BitLocker uses a recovery key as a protector for the + volume encryption key. + + .PARAMETER RecoveryPasswordProtector + Indicates that BitLocker uses a recovery password as a protector for + the volume encryption key. + + .PARAMETER Service + Indicates that the system account for this computer unlocks the + encrypted volume. + + .PARAMETER SkipHardwareTest + Indicates that BitLocker does not perform a hardware test before it + begins encryption. + + .PARAMETER StartupKeyPath + Specifies a path to a startup key. + + .PARAMETER StartupKeyProtector + Indicates that BitLocker uses a startup key as a protector for the + volume encryption key. + + .PARAMETER TpmProtector + Indicates that BitLocker uses the TPM as a protector for the volume + encryption key. + + .PARAMETER UsedSpaceOnly + Indicates that BitLocker does not encrypt disk space which contains + unused data. + + .PARAMETER VerbosePreference + Used to modify the default VerbosePreference for the function. +#> +function Add-MissingBitLockerKeyProtector +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $MountPoint, + + [Parameter(Mandatory = $true)] + [ValidateSet("PasswordProtector","RecoveryPasswordProtector","StartupKeyProtector","TpmProtector")] + [System.String] + $PrimaryProtector, + + [Parameter()] + [System.String] + $AdAccountOrGroup, + + [Parameter()] + [System.Boolean] + $AdAccountOrGroupProtector, + + [Parameter()] + [System.Boolean] + $AllowImmediateReboot = $false, + + [Parameter()] + [System.Boolean] + $AutoUnlock = $false, + + [Parameter()] + [ValidateSet("Aes128","Aes256")] + [System.String] + $EncryptionMethod, + + [Parameter()] + [System.Boolean] + $HardwareEncryption, + + [Parameter()] + [System.Management.Automation.PSCredential] + $Password, + + [Parameter()] + [System.Boolean] + $PasswordProtector, + + [Parameter()] + [System.Management.Automation.PSCredential] + $Pin, + + [Parameter()] + [System.String] + $RecoveryKeyPath, + + [Parameter()] + [System.Boolean] + $RecoveryKeyProtector, + + [Parameter()] + [System.Boolean] + $RecoveryPasswordProtector, + + [Parameter()] + [System.Boolean] + $Service, + + [Parameter()] + [System.Boolean] + $SkipHardwareTest, + + [Parameter()] + [System.String] + $StartupKeyPath, + + [Parameter()] + [System.Boolean] + $StartupKeyProtector, + + [Parameter()] + [System.Boolean] + $TpmProtector, + + [Parameter()] + [System.Boolean] + $UsedSpaceOnly, + + [Parameter()] + $VerbosePreference + ) + + if ($PSBoundParameters.ContainsKey("AdAccountOrGroupProtector") -and $PrimaryProtector -notlike "AdAccountOrGroupProtector" -and !(ContainsKeyProtector -Type "AdAccountOrGroup" -KeyProtectorCollection $blv.KeyProtector)) + { + Write-Verbose "Adding AdAccountOrGroupProtector" + Add-BitLockerKeyProtector -MountPoint $MountPoint -AdAccountOrGroupProtector -AdAccountOrGroup $AdAccountOrGroup + } + + if ($PSBoundParameters.ContainsKey("PasswordProtector") -and $PrimaryProtector -notlike "PasswordProtector" -and !(ContainsKeyProtector -Type "Password" -KeyProtectorCollection $blv.KeyProtector)) + { + Write-Verbose "Adding PasswordProtector" + Add-BitLockerKeyProtector -MountPoint $MountPoint -PasswordProtector -Password $Password.Password + } + + if ($PSBoundParameters.ContainsKey("RecoveryKeyProtector") -and $PrimaryProtector -notlike "RecoveryKeyProtector" -and !(ContainsKeyProtector -Type "ExternalKey" -KeyProtectorCollection $blv.KeyProtector)) + { + Write-Verbose "Adding RecoveryKeyProtector" + Add-BitLockerKeyProtector -MountPoint $MountPoint -RecoveryKeyProtector -RecoveryKeyPath $RecoveryKeyPath + } + + if ($PSBoundParameters.ContainsKey("RecoveryPasswordProtector") -and $PrimaryProtector -notlike "RecoveryPasswordProtector" -and !(ContainsKeyProtector -Type "RecoveryPassword" -KeyProtectorCollection $blv.KeyProtector)) + { + Write-Verbose "Adding RecoveryPasswordProtector" + Add-BitLockerKeyProtector -MountPoint $MountPoint -RecoveryPasswordProtector + } + + if ($PSBoundParameters.ContainsKey("StartupKeyProtector") -and $PrimaryProtector -notlike "TpmProtector" -and $PrimaryProtector -notlike "StartupKeyProtector" -and !(ContainsKeyProtector -Type "ExternalKey" -KeyProtectorCollection $blv.KeyProtector)) + { + Write-Verbose "Adding StartupKeyProtector" + Add-BitLockerKeyProtector -MountPoint $MountPoint -StartupKeyProtector -StartupKeyPath $StartupKeyPath + } +} + +<# + .SYNOPSIS + Tests whether Bitlocker and the requested features have been enabled + on the target disk. + + .PARAMETER MountPoint + The MountPoint name as reported in Get-BitLockerVolume. + + .PARAMETER PrimaryProtector + The type of key protector that will be used as the primary key + protector. + + .PARAMETER AdAccountOrGroup + Specifies an account using the format Domain\User. + + .PARAMETER AdAccountOrGroupProtector + Indicates that BitLocker uses an AD DS account as a protector for the + volume encryption key. + + .PARAMETER AllowImmediateReboot + Whether the computer can be immediately rebooted after enabling + Bitlocker on an OS drive. Defaults to false. + + .PARAMETER AutoUnlock + Whether volumes should be enabled for auto unlock using + Enable-BitlockerAutoUnlock. + + .PARAMETER EncryptionMethod + Indicates that BitLocker uses the TPM as a protector for the volume + encryption key. + + .PARAMETER HardwareEncryption + Indicates that the volume uses hardware encryption. + + .PARAMETER Password + Specifies a secure string object that contains a password. + + .PARAMETER PasswordProtector + Indicates that BitLocker uses a password as a protector for the volume + encryption key. + + .PARAMETER Pin + Specifies a secure string object that contains a PIN. + + .PARAMETER RecoveryKeyPath + Specifies a path to a recovery key. + + .PARAMETER RecoveryKeyProtector + Indicates that BitLocker uses a recovery key as a protector for the + volume encryption key. + + .PARAMETER RecoveryPasswordProtector + Indicates that BitLocker uses a recovery password as a protector for + the volume encryption key. + + .PARAMETER Service + Indicates that the system account for this computer unlocks the + encrypted volume. + + .PARAMETER SkipHardwareTest + Indicates that BitLocker does not perform a hardware test before it + begins encryption. + + .PARAMETER StartupKeyPath + Specifies a path to a startup key. + + .PARAMETER StartupKeyProtector + Indicates that BitLocker uses a startup key as a protector for the + volume encryption key. + + .PARAMETER TpmProtector + Indicates that BitLocker uses the TPM as a protector for the volume + encryption key. + + .PARAMETER UsedSpaceOnly + Indicates that BitLocker does not encrypt disk space which contains + unused data. + + .PARAMETER VerbosePreference + Used to modify the default VerbosePreference for the function. +#> function TestBitlocker { [CmdletBinding()] @@ -380,7 +708,7 @@ function TestBitlocker Write-Verbose "No key protectors on MountPoint: $($MountPoint)" return $false } - elseif ($AutoUnlock -eq $true -and $blv.AutoUnlockEnabled -ne $true) + elseif ($AutoUnlock -eq $true -and $blv.AutoUnlockEnabled -ne $true -and $blv.VolumeType -ne 'OperatingSystem') { Write-Verbose "AutoUnlock is not enabled for MountPoint: $($MountPoint)" return $false @@ -491,18 +819,14 @@ function ContainsKeyProtector [Parameter()] [string] $Type, - + [Parameter()] $KeyProtectorCollection, - + [Parameter()] [bool] $StartsWith = $false, - - [Parameter()] - [bool] - $EndsWith = $false, - + [Parameter()] [bool] $Contains = $false @@ -520,10 +844,6 @@ function ContainsKeyProtector { return $true } - elseif ($EndsWith -eq $true -and $keyProtector.KeyProtectorType.ToString().EndsWith($Type)) - { - return $true - } elseif ($Contains -eq $true -and $keyProtector.KeyProtectorType.ToString().Contains($Type)) { return $true @@ -534,16 +854,29 @@ function ContainsKeyProtector return $false } -#Takes $PSBoundParameters from another function and adds in the keys and values from the given Hashtable +<# + .SYNOPSIS + Takes $PSBoundParameters from another function and adds in the keys and + values from the given Hashtable. + + .PARAMETER PSBoundParametersIn + The $PSBoundParameters Hashtable from the calling function. + + .PARAMETER ParamsToAdd + A Hashtable containing new Key/Value pairs to add to the given + PSBoundParametersIn Hashtable. +#> function AddParameters { + [CmdletBinding()] param ( - [Parameter()] + [Parameter(Mandatory = $true)] + [System.Object] $PSBoundParametersIn, - + [Parameter()] - [Hashtable] + [System.Collections.Hashtable] $ParamsToAdd ) @@ -560,45 +893,60 @@ function AddParameters } } -#Takes $PSBoundParameters from another function. If ParamsToRemove is specified, it will remove each param. -#If ParamsToKeep is specified, everything but those params will be removed. If both ParamsToRemove and ParamsToKeep -#are specified, only ParamsToKeep will be used. +<# + .SYNOPSIS + Takes $PSBoundParameters from another function, and modifies it based + on the contents of the ParamsToRemove or ParamsToKeep parameters. If + ParamsToRemove is specified, it will remove each param. If ParamsToKeep + is specified, everything but those params will be removed. + + .PARAMETER PSBoundParametersIn + The $PSBoundParameters Hashtable from the calling function. + + .PARAMETER ParamsToKeep + A String array containing the list of parameter names to keep in the + given PSBoundParametersIn HashTable. + + .PARAMETER ParamsToRemove + A String array containing the list of parameter names to remove in the + given PSBoundParametersIn HashTable. +#> function RemoveParameters { + [CmdletBinding()] param ( - [Parameter()] + [Parameter(Mandatory = $true)] + [System.Object] $PSBoundParametersIn, - - [Parameter()] - [string[]] + + [Parameter(Mandatory = $true, ParameterSetName = 'KeepParameters')] + [System.String[]] $ParamsToKeep, - - [Parameter()] - [string[]] + + [Parameter(Mandatory = $true, ParameterSetName = 'RemoveParameters')] + [System.String[]] $ParamsToRemove ) - if ($null -ne $ParamsToKeep -and $ParamsToKeep.Count -gt 0) + if ($ParamsToKeep.Count -gt 0) { - [string[]]$ParamsToRemove = @() - - $lowerParamsToKeep = StringArrayToLower -Array $ParamsToKeep + $ParamsToKeep = $ParamsToKeep.ToLower() foreach ($key in $PSBoundParametersIn.Keys) { - if (!($lowerParamsToKeep.Contains($key.ToLower()))) + if (!($ParamsToKeep.Contains($key.ToLower()))) { $ParamsToRemove += $key } } } - if ($null -ne $ParamsToRemove -and $ParamsToRemove.Count -gt 0) + if ($ParamsToRemove.Count -gt 0) { foreach ($param in $ParamsToRemove) { - $PSBoundParametersIn.Remove($param) | Out-Null + $null = $PSBoundParametersIn.Remove($param) } } } @@ -612,5 +960,4 @@ function Get-OSEdition (Get-ItemProperty -Path 'HKLM:/software/microsoft/windows nt/currentversion' -Name InstallationType).InstallationType } - Export-ModuleMember -Function * diff --git a/README.md b/README.md index 290c9e7..04d5941 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,6 @@ Defaults to false. * *Identity:Not actually used, so could be anything * AllowClear:Indicates that the provisioning process clears the TPM, if necessary, to move the TPM closer to complying with Windows Server 2012 standards * AllowPhysicalPresence:Indicates that the provisioning process may send physical presence commands that require a user to be present in order to continue. - * AllowImmediateReboot:Whether the computer can rebooted immediately after initializing the TPM ## Versions @@ -124,6 +123,9 @@ Defaults to false. * Update appveyor.yml to use the default template. * Added default template files .gitattributes, and .vscode settings. * Fixes most PSScriptAnalyzer issues. +* Fix issue where AutoUnlock is not set if requested, if the disk was + originally encrypted and AutoUnlock was not used. +* Add remaining Unit Tests for xBitlockerCommon. ### 1.2.0.0 diff --git a/Tests/Unit/xBitlockerCommon.tests.ps1 b/Tests/Unit/xBitlockerCommon.tests.ps1 index 608e1e9..0d5845c 100644 --- a/Tests/Unit/xBitlockerCommon.tests.ps1 +++ b/Tests/Unit/xBitlockerCommon.tests.ps1 @@ -100,6 +100,120 @@ try TestBitlocker -MountPoint 'C:' -PrimaryProtector 'TPMProtector' -RecoveryPasswordProtector $true | Should -Be $false } } + + Context 'When TestBitlocker is called and Get-BitlockerVolume returns null' { + It 'Should return False' { + Mock -CommandName Get-BitLockerVolume -Verifiable + + TestBitlocker -MountPoint 'C:' -PrimaryProtector 'TpmProtector' | Should -Be $false + } + } + + Context 'When TestBitlocker is called and Get-BitlockerVolume returns a volume with no key protectors' { + It 'Should return False' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { + return @{ + KeyProtector = $null + } + } + + TestBitlocker -MountPoint 'C:' -PrimaryProtector 'TpmProtector' | Should -Be $false + } + } + + Context 'When TestBitlocker is called, AutoUnlock is requested on a non-OS disk, and AutoUnlock is not enabled' { + It 'Should return False' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { + return @{ + AutoUnlockEnabled = $false + VolumeType = 'Data' + KeyProtector = @('Protector1') + } + } + + TestBitlocker -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -AutoUnlock $true | Should -Be $false + } + } + + $defaultBLV = @( + @{ + KeyProtector = @('Protector1') + } + ) + + $fakePin = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'fakepin', (New-Object -TypeName System.Security.SecureString) + + Context 'When TestBitlocker is called, a AdAccountOrGroupProtector protector is requested, and does not exist on the disk' { + It 'Should return False' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } + Mock -CommandName ContainsKeyProtector -Verifiable -MockWith { return $false } + + TestBitlocker -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -AdAccountOrGroupProtector $true | Should -Be $false + } + } + + Context 'When TestBitlocker is called, a PasswordProtector protector is requested, and does not exist on the disk' { + It 'Should return False' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } + Mock -CommandName ContainsKeyProtector -Verifiable -MockWith { return $false } + + TestBitlocker -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -PasswordProtector $true | Should -Be $false + } + } + + Context 'When TestBitlocker is called, a Pin protector is requested, and does not exist on the disk' { + It 'Should return False' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } + Mock -CommandName ContainsKeyProtector -Verifiable -MockWith { return $false } + + TestBitlocker -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -Pin $fakePin | Should -Be $false + } + } + + Context 'When TestBitlocker is called, a RecoveryKeyProtector protector is requested, and does not exist on the disk' { + It 'Should return False' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } + Mock -CommandName ContainsKeyProtector -Verifiable -MockWith { return $false } + + TestBitlocker -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -RecoveryKeyProtector $true | Should -Be $false + } + } + + Context 'When TestBitlocker is called, a RecoveryPasswordProtector protector is requested, and does not exist on the disk' { + It 'Should return False' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } + Mock -CommandName ContainsKeyProtector -Verifiable -MockWith { return $false } + + TestBitlocker -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -RecoveryPasswordProtector $true | Should -Be $false + } + } + + Context 'When TestBitlocker is called, a StartupKeyProtector protector is requested without a primary TPM protector, and does not exist on the disk' { + It 'Should return False' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } + Mock -CommandName ContainsKeyProtector -Verifiable -MockWith { return $false } + + TestBitlocker -MountPoint 'C:' -PrimaryProtector 'StartupKeyProtector' -StartupKeyProtector $true | Should -Be $false + } + } + + Context 'When TestBitlocker is called, a StartupKeyProtector protector is requested with a primary TPM protector, and does not exist on the disk' { + It 'Should return False' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } + Mock -CommandName ContainsKeyProtector -Verifiable -MockWith { return $false } + + TestBitlocker -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -StartupKeyProtector $true | Should -Be $false + } + } + + Context 'When TestBitlocker is called, a TpmProtector protector is requested, and does not exist on the disk' { + It 'Should return False' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $defaultBLV } + Mock -CommandName ContainsKeyProtector -Verifiable -MockWith { return $false } + + TestBitlocker -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -TpmProtector $true | Should -Be $false + } + } } Describe 'xBitlockerCommon\CheckForPreReqs' { @@ -322,6 +436,437 @@ try {Get-OSEdition} | Should -Not -Throw } } + + Describe 'xBitLockerCommon\EnableBitlocker' -Tag 'Helper' { + # Override Bitlocker cmdlets + function Enable-Bitlocker {} + function Enable-BitlockerAutoUnlock {} + + AfterEach { + Assert-VerifiableMock + } + + $fakePin = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'fakepin', (New-Object -TypeName System.Security.SecureString) + $mountPoint = 'C:' + $encryptedBLV = @{ + VolumeStatus = 'FullyEncrypted' + } + $encryptedOSBLV = @{ + VolumeStatus = 'FullyEncrypted' + VolumeType = 'OperatingSystem' + } + $decryptedOSBLV = @{ + VolumeStatus = 'FullyDecrypted' + VolumeType = 'OperatingSystem' + } + + Context 'When EnableBitlocker is called Get-BitlockerVolume returns null' { + It 'Should throw an exception' { + Mock -CommandName Get-BitLockerVolume -Verifiable + + { EnableBitlocker -MountPoint 'C:' -Pin $fakePin -PrimaryProtector 'PasswordProtector' } | Should -Throw -ExpectedMessage 'Unable to find Bitlocker Volume associated with Mount Point' + } + } + + Context 'When EnableBitlocker is called with TpmProtector set to True and PrimaryProtector not set to TpmProtector' { + $badPrimaryProtectorCases = @( + @{ + PrimaryProtector = 'PasswordProtector' + } + + @{ + PrimaryProtector = 'RecoveryPasswordProtector' + } + + @{ + PrimaryProtector = 'StartupKeyProtector' + } + ) + + It 'Should throw an exception' -TestCases $badPrimaryProtectorCases { + param + ( + [System.String] + $PrimaryProtector + ) + + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } + + { EnableBitlocker -MountPoint $mountPoint -TpmProtector $true -PrimaryProtector $PrimaryProtector } | Should -Throw -ExpectedMessage 'If TpmProtector is used, it must be the PrimaryProtector.' + } + } + + Context 'When EnableBitlocker is called with Pin specified and TpmProtector not specified' { + It 'Should throw an exception' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } + + { EnableBitlocker -MountPoint $mountPoint -Pin $fakePin -PrimaryProtector 'PasswordProtector' } | Should -Throw -ExpectedMessage 'A TpmProtector must be used if Pin is used.' + } + } + + Context 'When EnableBitlocker is called with Pin specified and TpmProtector not specified' { + It 'Should throw an exception' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } + + { EnableBitlocker -MountPoint $mountPoint -Pin $fakePin -PrimaryProtector 'PasswordProtector' } | Should -Throw -ExpectedMessage 'A TpmProtector must be used if Pin is used.' + } + } + + $defaultEnableParams = @{ + MountPoint = $mountPoint + Pin = $fakePin + PrimaryProtector = 'TpmProtector' + TpmProtector = $true + EncryptionMethod = 'Aes256' + HardwareEncryption = $true + Service = $true + SkipHardwareTest = $true + UsedSpaceOnly = $true + AllowImmediateReboot = $true + StartupKeyProtector = $true + } + + Context 'When EnableBitlocker is called and the volume is not yet encrypted' { + It 'Should enable Bitlocker with the correct key protectors and parameters' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $decryptedOSBLV } + Mock -CommandName Enable-Bitlocker -Verifiable -MockWith { return $encryptedOSBLV } + Mock -CommandName Start-Sleep -Verifiable + Mock -CommandName Restart-Computer -Verifiable + + EnableBitlocker @defaultEnableParams + } + } + + Context 'When EnableBitlocker is called, the volume is not yet encrypted, and Enable-Bitlocker does not return a result' { + It 'Should throw an exception' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $decryptedOSBLV } + Mock -CommandName Enable-Bitlocker -Verifiable + + { EnableBitlocker @defaultEnableParams } | Should -Throw -ExpectedMessage 'Failed to successfully enable Bitlocker on MountPoint' + } + } + + Context 'When EnableBitlocker is called, the volume is not yet encrypted and is not an OS drive, and AutoUnlock is specified' { + It 'Should enable Bitlocker with the correct key protectors and parameters and enable AutoUnlock' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { + return @{ + VolumeStatus = 'FullyDecrypted' + VolumeType = 'Data' + } + } + Mock -CommandName Enable-Bitlocker -Verifiable -MockWith { return $encryptedBLV } + Mock -CommandName Enable-BitlockerAutoUnlock -Verifiable + + $defaultEnableParams.Add('AutoUnlock', $true) + + EnableBitlocker @defaultEnableParams + + $defaultEnableParams.Remove('AutoUnlock') + } + } + + Context 'When EnableBitlocker is called with TPM only and the volume is not yet encrypted' { + It 'Should enable Bitlocker with the correct key protectors and parameters' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $decryptedOSBLV } + Mock -CommandName Enable-Bitlocker -Verifiable -MockWith { return $encryptedBLV } + + $tpmOnlyEnableParams = @{ + MountPoint = $mountPoint + PrimaryProtector = 'TpmProtector' + TpmProtector = $true + } + + EnableBitlocker @tpmOnlyEnableParams + } + } + + Context 'When EnableBitlocker is called with TPM and pin only and the volume is not yet encrypted' { + It 'Should enable Bitlocker with the correct key protectors and parameters' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $decryptedOSBLV } + Mock -CommandName Enable-Bitlocker -Verifiable -MockWith { return $encryptedBLV } + + $tpmAndPinOnlyEnableParams = @{ + MountPoint = $mountPoint + PrimaryProtector = 'TpmProtector' + TpmProtector = $true + Pin = $fakePin + } + + EnableBitlocker @tpmAndPinOnlyEnableParams + } + } + + Context 'When EnableBitlocker is called with TPM and pin only and the volume is not yet encrypted' { + It 'Should enable Bitlocker with the correct key protectors and parameters' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $decryptedOSBLV } + Mock -CommandName Enable-Bitlocker -Verifiable -MockWith { return $encryptedBLV } + + $tpmAndStartupOnlyEnableParams = @{ + MountPoint = $mountPoint + PrimaryProtector = 'TpmProtector' + TpmProtector = $true + StartupKeyProtector = $true + StartupKeyPath = 'C:\' + } + + EnableBitlocker @tpmAndStartupOnlyEnableParams + } + } + + Context 'When EnableBitlocker is called with a Password Protector and the volume is not yet encrypted' { + It 'Should enable Bitlocker with the correct key protectors and parameters' { + Mock -CommandName Get-BitLockerVolume -MockWith { return $decryptedOSBLV } + Mock -CommandName Enable-Bitlocker -MockWith { return $encryptedBLV } + + $passwordEnableParams = @{ + MountPoint = $mountPoint + PrimaryProtector = 'PasswordProtector' + PasswordProtector = $true + Password = $fakePin + } + + EnableBitlocker @passwordEnableParams + } + } + + Context 'When EnableBitlocker is called with a Recovery Password Protector and the volume is not yet encrypted' { + It 'Should enable Bitlocker with the correct key protectors and parameters' { + Mock -CommandName Get-BitLockerVolume -MockWith { return $decryptedOSBLV } + Mock -CommandName Enable-Bitlocker -MockWith { return $encryptedBLV } + + $recoveryPasswordEnableParams = @{ + MountPoint = $mountPoint + PrimaryProtector = 'RecoveryPasswordProtector' + RecoveryPasswordProtector = $true + Password = $fakePin + } + + EnableBitlocker @recoveryPasswordEnableParams + } + } + + Context 'When EnableBitlocker is called with a StartupKey Protector and the volume is not yet encrypted' { + It 'Should enable Bitlocker with the correct key protectors and parameters' { + Mock -CommandName Get-BitLockerVolume -MockWith { return $decryptedOSBLV } + Mock -CommandName Enable-Bitlocker -MockWith { return $encryptedBLV } + + $startupKeyEnableParams = @{ + MountPoint = $mountPoint + PrimaryProtector = 'StartupKeyProtector' + StartupKeyProtector = $true + StartupKeyPath = 'C:\Path' + } + + EnableBitlocker @startupKeyEnableParams + } + } + } + + Describe 'xBitLockerCommon\Add-MissingBitLockerKeyProtector' -Tag 'Helper' { + # Override Bitlocker cmdlets + function Add-BitLockerKeyProtector {} + + # Suppress Write-Verbose output + Mock -CommandName Write-Verbose + + AfterEach { + Assert-VerifiableMock + } + + $fakePin = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'fakepin', (New-Object -TypeName System.Security.SecureString) + $mountPoint = 'C:' + $encryptedBLV = @{ + VolumeStatus = 'FullyEncrypted' + } + + Context 'When Add-MissingBitLockerKeyProtector is called, the AdAccountOrGroupProtector protector is requested but not yet present on the volume, and is not the PrimaryKeyProtector' { + It 'Should add the AdAccountOrGroupProtector protector' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } + Mock -CommandName ContainsKeyProtector -Verifiable -MockWith { return $false} + Mock -CommandName Add-BitLockerKeyProtector -Verifiable -ParameterFilter {$MountPoint -eq 'AdAccountOrGroupProtector'} + + EnableBitlocker -MountPoint 'AdAccountOrGroupProtector' -Pin $fakePin -PrimaryProtector 'TpmProtector' -TpmProtector $true -AdAccountOrGroupProtector $true + } + } + + Context 'When Add-MissingBitLockerKeyProtector is called, the PasswordProtector protector is requested but not yet present on the volume, and is not the PrimaryKeyProtector' { + It 'Should add the PasswordProtector protector' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } + Mock -CommandName ContainsKeyProtector -Verifiable -MockWith { return $false} + Mock -CommandName Add-BitLockerKeyProtector -Verifiable -ParameterFilter {$MountPoint -eq 'PasswordProtector'} + + EnableBitlocker -MountPoint 'PasswordProtector' -Pin $fakePin -PrimaryProtector 'TpmProtector' -TpmProtector $true -PasswordProtector $true + } + } + + Context 'When Add-MissingBitLockerKeyProtector is called, the RecoveryKeyProtector protector is requested but not yet present on the volume, and is not the PrimaryKeyProtector' { + It 'Should add the RecoveryKeyProtector protector' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } + Mock -CommandName ContainsKeyProtector -Verifiable -MockWith { return $false} + Mock -CommandName Add-BitLockerKeyProtector -Verifiable -ParameterFilter {$MountPoint -eq 'RecoveryKeyProtector'} + + EnableBitlocker -MountPoint 'RecoveryKeyProtector' -Pin $fakePin -PrimaryProtector 'TpmProtector' -TpmProtector $true -RecoveryKeyProtector $true + } + } + + Context 'When Add-MissingBitLockerKeyProtector is called, the RecoveryPasswordProtector protector is requested but not yet present on the volume, and is not the PrimaryKeyProtector' { + It 'Should add the RecoveryPasswordProtector protector' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } + Mock -CommandName ContainsKeyProtector -Verifiable -MockWith { return $false} + Mock -CommandName Add-BitLockerKeyProtector -Verifiable -ParameterFilter {$MountPoint -eq 'RecoveryPasswordProtector'} + + EnableBitlocker -MountPoint 'RecoveryPasswordProtector' -Pin $fakePin -PrimaryProtector 'TpmProtector' -TpmProtector $true -RecoveryPasswordProtector $true + } + } + + Context 'When Add-MissingBitLockerKeyProtector is called, the StartupKeyProtector protector is requested but not yet present on the volume, and is not the PrimaryKeyProtector' { + It 'Should add the StartupKeyProtector protector' { + Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV } + Mock -CommandName ContainsKeyProtector -Verifiable -MockWith { return $false} + Mock -CommandName Add-BitLockerKeyProtector -Verifiable -ParameterFilter {$MountPoint -eq 'StartupKeyProtector'} + + EnableBitlocker -MountPoint 'StartupKeyProtector' -PrimaryProtector 'RecoveryPasswordProtector' -RecoveryPasswordProtector $true -StartupKeyProtector $true + } + } + } + + Describe 'xBitLockerCommon\ContainsKeyProtector' -Tag 'Helper' { + $testKeyProtectorCollection = @( + @{ + KeyProtectorType = 'RecoveryPassword' + } + + @{ + KeyProtectorType = 'AdAccountOrGroup' + } + + @{ + KeyProtectorType = 'StartupKeyProtector' + } + ) + + Context 'When ContainsKeyProtector is called and the target KeyProtector exists in the collection' { + It 'Should return True' { + ContainsKeyProtector -Type 'AdAccountOrGroup' -KeyProtectorCollection $testKeyProtectorCollection | Should -Be $true + } + } + + Context 'When ContainsKeyProtector is called and the target KeyProtector does not exist in the collection' { + It 'Should return False' { + ContainsKeyProtector -Type 'AdAccountOrGroup2' -KeyProtectorCollection $testKeyProtectorCollection | Should -Be $false + } + } + + Context 'When ContainsKeyProtector is called with the StartsWith switch and the target KeyProtector exists in the collection' { + It 'Should return True' { + ContainsKeyProtector -Type 'AdAccount' -KeyProtectorCollection $testKeyProtectorCollection -StartsWith $true | Should -Be $true + } + } + + Context 'When ContainsKeyProtector is called with the StartsWith switch and the target KeyProtector does not exist in the collection' { + It 'Should return False' { + ContainsKeyProtector -Type 'Account' -KeyProtectorCollection $testKeyProtectorCollection -StartsWith $true | Should -Be $false + } + } + + Context 'When ContainsKeyProtector is called with the Contains switch and the target KeyProtector exists in the collection' { + It 'Should return True' { + ContainsKeyProtector -Type 'Account' -KeyProtectorCollection $testKeyProtectorCollection -Contains $true | Should -Be $true + } + } + + Context 'When ContainsKeyProtector is called with the Contains switch and the target KeyProtector does not exist in the collection' { + It 'Should return False' { + ContainsKeyProtector -Type 'NotInCollection' -KeyProtectorCollection $testKeyProtectorCollection -Contains $true | Should -Be $false + } + } + } + + Describe 'xBitLockerCommon\AddParameters' -Tag 'Helper' { + AfterEach { + Assert-VerifiableMock + } + + Context 'When AddParameters is called, a parameter is added, and a parameter is changed' { + It 'Should add a new parameter and change the existing parameter' { + $param1 = 'abc' + $param2 = $null + $param2new = 'notnull' + $param3 = 'def' + $param4 = 'ghi' + + $psBoundParametersIn = @{ + Param1 = $param1 + Param2 = $param2 + Param3 = $param3 + } + + $paramsToAdd = @{ + Param2 = $param2new + Param4 = $param4 + } + + AddParameters -PSBoundParametersIn $psBoundParametersIn -ParamsToAdd $paramsToAdd + + $psBoundParametersIn.ContainsKey('Param1') -and $psBoundParametersIn['Param1'] -eq $param1 | Should -Be $true + $psBoundParametersIn.ContainsKey('Param2') -and $psBoundParametersIn['Param2'] -eq $param2new | Should -Be $true + $psBoundParametersIn.ContainsKey('Param3') -and $psBoundParametersIn['Param3'] -eq $param3 | Should -Be $true + $psBoundParametersIn.ContainsKey('Param4') -and $psBoundParametersIn['Param4'] -eq $param4 | Should -Be $true + } + } + } + + Describe 'xBitLockerCommon\RemoveParameters' -Tag 'Helper' { + AfterEach { + Assert-VerifiableMock + } + + Context 'When RemoveParameters is called and both ParamsToKeep and ParamsToRemove are specified' { + It 'Should throw an exception' { + { RemoveParameters -PSBoundParametersIn @{} -ParamsToKeep @('Param1') -ParamsToRemove @('Param2') } | ` + Should -Throw -ExpectedMessage 'Parameter set cannot be resolved using the specified named parameters.' + } + } + + Context 'When RemoveParameters is called with ParamsToKeep' { + It 'Should remove any parameter not specified in ParamsToKeep' { + $psBoundParametersIn = @{ + Param1 = 1 + Param2 = 2 + Param3 = 3 + } + + $paramsToKeep = @('Param1', 'Param2') + + RemoveParameters -PSBoundParametersIn $psBoundParametersIn -ParamsToKeep $paramsToKeep + + $psBoundParametersIn.ContainsKey('Param1') | Should -Be $true + $psBoundParametersIn.ContainsKey('Param2') | Should -Be $true + $psBoundParametersIn.ContainsKey('Param3') | Should -Be $false + } + } + + Context 'When RemoveParameters is called with ParamsToRemove' { + It 'Should remove any parameter specified in ParamsToRemove' { + $psBoundParametersIn = @{ + Param1 = 1 + Param2 = 2 + Param3 = 3 + } + + $paramsToRemove = @( + 'Param1', + 'param2' + ) + + RemoveParameters -PSBoundParametersIn $psBoundParametersIn -ParamsToRemove $paramsToRemove + + $psBoundParametersIn.ContainsKey('Param1') | Should -Be $false + $psBoundParametersIn.ContainsKey('Param2') | Should -Be $false + $psBoundParametersIn.ContainsKey('Param3') | Should -Be $true + } + } + } } } finally