Tests/Unit/xBitlockerCommon.tests.ps1

$script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'Misc' -ChildPath 'xBitlockerCommon.psm1')) -Force

# Begin Testing
try
{
    InModuleScope 'xBitlockerCommon' {

        function Get-BitlockerVolume
        {
            param
            (
                [Parameter()]
                [System.String]
                $MountPoint
            )
        }

        Describe 'xBitlockerCommon\Test-BitlockerEnabled' {

            Context 'When OS Volume is not Encrypted and No Key Protectors Assigned' {
                Mock `
                    -CommandName Get-BitlockerVolume `
                    -ModuleName 'xBitlockerCommon' `
                    -MockWith {
                    # Decrypted with no Key Protectors
                    return @{
                        VolumeType           = 'OperatingSystem'
                        MountPoint           = $MountPoint
                        CapacityGB           = 500
                        VolumeStatus         = 'FullyDecrypted'
                        EncryptionPercentage = 0
                        KeyProtector         = @()
                        AutoUnlockEnabled    = $null
                        ProtectionStatus     = 'Off'
                    }
                }

                It 'Should Fail The Test (TPM and RecoveryPassword Protectors)' {
                    Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TPMProtector' -RecoveryPasswordProtector $true | Should -Be $false
                }
            }

            Context 'When OS Volume is Encrypted using TPM and Recovery Password Protectors' {
                Mock `
                    -CommandName Get-BitlockerVolume `
                    -ModuleName 'xBitlockerCommon' `
                    -MockWith {
                    # Encrypted with TPM and Recovery Password Key Protectors
                    return @{
                        VolumeType           = 'OperatingSystem'
                        MountPoint           = $MountPoint
                        CapacityGB           = 500
                        VolumeStatus         = 'FullyEncrypted'
                        EncryptionPercentage = 100
                        KeyProtector         = @(
                            @{
                                KeyProtectorType = 'Tpm'
                            },
                            @{
                                KeyProtectorType = 'RecoveryPassword'
                            }
                        )
                        AutoUnlockEnabled    = $null
                        ProtectionStatus     = 'On'
                    }
                }

                It 'Should Pass The Test (TPM and RecoveryPassword Protectors)' {
                    Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TPMProtector' -RecoveryPasswordProtector $true -verbose | Should -Be $true
                }
            }

            Context 'When OS Volume is Decrypted, but has TPM and Recovery Password Protectors assigned' {
                Mock `
                    -CommandName Get-BitlockerVolume `
                    -ModuleName 'xBitlockerCommon' `
                    -MockWith {
                    # Encrypted with TPM and Recovery Password Key Protectors
                    return @{
                        VolumeType           = 'OperatingSystem'
                        MountPoint           = $MountPoint
                        CapacityGB           = 500
                        VolumeStatus         = 'FullyDecrypted'
                        EncryptionPercentage = 0
                        KeyProtector         = @(
                            @{
                                KeyProtectorType = 'Tpm'
                            },
                            @{
                                KeyProtectorType = 'RecoveryPassword'
                            }
                        )
                        AutoUnlockEnabled    = $null
                        ProtectionStatus     = 'Off'
                    }
                }

                It 'Should Fail The Test (TPM and RecoveryPassword Protectors)' {
                    Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TPMProtector' -RecoveryPasswordProtector $true | Should -Be $false
                }
            }

            Context 'When Test-BitlockerEnabled is called and Get-BitlockerVolume returns null' {
                It 'Should return False' {
                    Mock -CommandName Get-BitLockerVolume -Verifiable

                    Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' | Should -Be $false
                }
            }

            Context 'When Test-BitlockerEnabled 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
                        }
                    }

                    Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' | Should -Be $false
                }
            }

            Context 'When Test-BitlockerEnabled 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')
                        }
                    }

                    Test-BitlockerEnabled -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 Test-BitlockerEnabled 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 Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false }

                    Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -AdAccountOrGroupProtector $true | Should -Be $false
                }
            }

            Context 'When Test-BitlockerEnabled 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 Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false }

                    Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -PasswordProtector $true | Should -Be $false
                }
            }

            Context 'When Test-BitlockerEnabled 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 Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false }

                    Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -Pin $fakePin | Should -Be $false
                }
            }

            Context 'When Test-BitlockerEnabled 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 Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false }

                    Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -RecoveryKeyProtector $true | Should -Be $false
                }
            }

            Context 'When Test-BitlockerEnabled 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 Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false }

                    Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -RecoveryPasswordProtector $true | Should -Be $false
                }
            }

            Context 'When Test-BitlockerEnabled 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 Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false }

                    Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'StartupKeyProtector' -StartupKeyProtector $true | Should -Be $false
                }
            }

            Context 'When Test-BitlockerEnabled 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 Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false }

                    Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -StartupKeyProtector $true | Should -Be $false
                }
            }

            Context 'When Test-BitlockerEnabled 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 Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false }

                    Test-BitlockerEnabled -MountPoint 'C:' -PrimaryProtector 'TpmProtector' -TpmProtector $true | Should -Be $false
                }
            }
        }

        Describe 'xBitlockerCommon\Assert-HasPrereqsForBitlocker' {
            function Get-WindowsFeature
            {
                param
                (
                    [string]
                    $FeatureName
                )
            }

            function Get-OSEdition
            {

            }

            Context 'When OS is Server Core and all required features are installed' {
                Mock -CommandName Get-OSEdition -MockWith {
                    'Server Core'
                }

                Mock -CommandName Get-WindowsFeature -MockWith {
                    if ($FeatureName -eq 'RSAT-Feature-Tools-BitLocker-RemoteAdminTool')
                    {
                        return $null
                    }
                    else
                    {
                        return @{
                            DisplayName  = $FeatureName
                            Name         = $FeatureName
                            InstallState = 'Installed'
                        }
                    }
                }

                It 'Should not generate any error messages' {
                    Mock -CommandName Write-Error
                    Assert-HasPrereqsForBitlocker
                    Assert-MockCalled -Command Write-Error -Exactly -Times 0 -Scope It
                }

                It 'Should run the Assert-HasPrereqsForBitlocker function without exceptions' {
                    {Assert-HasPrereqsForBitlocker} | Should -Not -Throw
                }
            }

            Context 'When OS is Full Server and all required features are installed' {
                Mock -CommandName Get-OSEdition -MockWith {
                    return 'Server'
                }

                Mock -CommandName Get-WindowsFeature -MockWith {
                    param
                    (
                        [string]
                        $FeatureName
                    )

                    return @{
                        DisplayName  = $FeatureName
                        Name         = $FeatureName
                        InstallState = 'Installed'
                    }
                }

                It 'Should not generate any error messages' {
                    Mock -CommandName Write-Error
                    Assert-HasPrereqsForBitlocker
                    Assert-MockCalled -Command Write-Error -Exactly -Times 0 -Scope It
                }

                It 'Should run the Assert-HasPrereqsForBitlocker function without exceptions' {
                    {Assert-HasPrereqsForBitlocker} | Should -Not -Throw
                }
            }

            Context 'When OS is Full Server without the required features installed' {
                Mock -CommandName Get-OSEdition -MockWith {
                    return 'Server'
                }

                Mock -CommandName Get-WindowsFeature -MockWith {
                    return @{
                        DisplayName  = $FeatureName
                        Name         = $FeatureName
                        InstallState = 'Available'
                    }
                }

                Mock -CommandName Write-Error

                It 'Should give an error that Bitlocker Windows Feature needs to be installed' {
                    {Assert-HasPrereqsForBitlocker} | Should -Throw
                    Assert-MockCalled -Command Write-Error -Exactly -Times 1 -Scope It -ParameterFilter {
                        $Message -eq 'The Bitlocker feature needs to be installed before the xBitlocker module can be used'
                    }
                }

                It 'Should give an error that RSAT-Feature-Tools-BitLocker Windows Feature needs to be installed' {
                    {Assert-HasPrereqsForBitlocker} | Should -Throw
                    Assert-MockCalled -Command Write-Error -Exactly -Times 1 -Scope It -ParameterFilter {
                        $Message -eq 'The RSAT-Feature-Tools-BitLocker feature needs to be installed before the xBitlocker module can be used'
                    }
                }

                It 'Should give an error that RSAT-Feature-Tools-BitLocker-RemoteAdminTool Windows Feature needs to be installed' {
                    {Assert-HasPrereqsForBitlocker} | Should -Throw
                    Assert-MockCalled -Command Write-Error -Exactly -Times 1 -Scope It -ParameterFilter {
                        $Message -eq 'The RSAT-Feature-Tools-BitLocker-RemoteAdminTool feature needs to be installed before the xBitlocker module can be used'
                    }
                }

                It 'The Assert-HasPrereqsForBitlocker function should throw an exceptions about missing required Windows Features' {
                    {Assert-HasPrereqsForBitlocker} | Should -Throw 'Required Bitlocker features need to be installed before xBitlocker can be used'
                }
            }

            Context 'When OS is Server Core without the required features installed' {
                Mock -CommandName Get-OSEdition -MockWith {
                    return 'Server Core'
                }

                Mock -CommandName Get-WindowsFeature -MockWith {
                    param
                    (
                        [string]
                        $FeatureName
                    )

                    if ($FeatureName -eq 'RSAT-Feature-Tools-BitLocker-RemoteAdminTool')
                    {
                        return $null
                    }
                    else
                    {

                        return @{
                            DisplayName  = $FeatureName
                            Name         = $FeatureName
                            InstallState = 'Available'
                        }
                    }
                }

                Mock -CommandName Write-Error

                It 'Should give an error that Bitlocker Windows Feature needs to be installed' {
                    {Assert-HasPrereqsForBitlocker} | Should -Throw
                    Assert-MockCalled -Command Write-Error -Exactly -Times 1 -Scope It -ParameterFilter {
                        $Message -eq 'The Bitlocker feature needs to be installed before the xBitlocker module can be used'
                    }
                }

                It 'Should give an error that RSAT-Feature-Tools-BitLocker Windows Feature needs to be installed' {
                    {Assert-HasPrereqsForBitlocker} | Should -Throw
                    Assert-MockCalled -Command Write-Error -Exactly -Times 1 -Scope It -ParameterFilter {
                        $Message -eq 'The RSAT-Feature-Tools-BitLocker feature needs to be installed before the xBitlocker module can be used'
                    }
                }

                It 'Should not give an error that RSAT-Feature-Tools-BitLocker-RemoteAdminTool Windows Feature needs to be installed as this Windows Features is not available on Server Core.' {
                    {Assert-HasPrereqsForBitlocker} | Should -Throw
                    Assert-MockCalled -Command Write-Error -Exactly -Times 0 -Scope It -ParameterFilter {
                        $Message -eq 'The RSAT-Feature-Tools-BitLocker-RemoteAdminTool feature needs to be installed before the xBitlocker module can be used'
                    }
                }

                It 'The Assert-HasPrereqsForBitlocker function should throw an exceptions about missing required Windows Features' {
                    {Assert-HasPrereqsForBitlocker} | Should -Throw 'Required Bitlocker features need to be installed before xBitlocker can be used'
                }
            }
        }

        Describe 'xBitLockerCommon\Get-OSEdition' {
            It 'Should return "Server Core" if the OS is Windows Server Core' {
                Mock -CommandName Get-ItemProperty -MockWith {
                    [PSCustomObject] @{
                        InstallationType = 'Server Core'
                        PSPath           = 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\software\microsoft\windows nt\currentversion'
                        PSParentPath     = 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\software\microsoft\windows nt'
                        PSChildName      = 'currentversion'
                        PSDrive          = 'HKLM'
                        PSProvider       = 'Microsoft.PowerShell.Core\Registry'
                    }
                }

                $OSVersion = Get-OSEdition
                $OSVersion | Should -Be 'Server Core'
            }

            It 'Should return "Server" if the OS is Full Windows Server' {
                Mock -CommandName Get-ItemProperty -MockWith {
                    [PSCustomObject] @{
                        InstallationType = 'Server'
                        PSPath           = 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\software\microsoft\windows nt\currentversion'
                        PSParentPath     = 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\software\microsoft\windows nt'
                        PSChildName      = 'currentversion'
                        PSDrive          = 'HKLM'
                        PSProvider       = 'Microsoft.PowerShell.Core\Registry'
                    }
                }

                $OSVersion = Get-OSEdition
                $OSVersion | Should -Be 'Server'
            }

            It 'Should run without exceptions' {
                Mock -CommandName Get-ItemProperty -MockWith {
                    [PSCustomObject] @{
                        InstallationType = 'Some other os'
                        PSPath           = 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\software\microsoft\windows nt\currentversion'
                        PSParentPath     = 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\software\microsoft\windows nt'
                        PSChildName      = 'currentversion'
                        PSDrive          = 'HKLM'
                        PSProvider       = 'Microsoft.PowerShell.Core\Registry'
                    }
                }
                {Get-OSEdition} | Should -Not -Throw
            }
        }

        Describe 'xBitLockerCommon\Enable-BitlockerInternal' -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 Enable-BitlockerInternal is called Get-BitlockerVolume returns null' {
                It 'Should throw an exception' {
                    Mock -CommandName Get-BitLockerVolume -Verifiable

                    { Enable-BitlockerInternal -MountPoint 'C:' -Pin $fakePin -PrimaryProtector 'PasswordProtector' } | Should -Throw -ExpectedMessage 'Unable to find Bitlocker Volume associated with Mount Point'
                }
            }

            Context 'When Enable-BitlockerInternal 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 }

                    { Enable-BitlockerInternal -MountPoint $mountPoint -TpmProtector $true -PrimaryProtector $PrimaryProtector } | Should -Throw -ExpectedMessage 'If TpmProtector is used, it must be the PrimaryProtector.'
                }
            }

            Context 'When Enable-BitlockerInternal is called with Pin specified and TpmProtector not specified' {
                It 'Should throw an exception' {
                    Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV }

                    { Enable-BitlockerInternal -MountPoint $mountPoint -Pin $fakePin -PrimaryProtector 'PasswordProtector' } | Should -Throw -ExpectedMessage 'A TpmProtector must be used if Pin is used.'
                }
            }

            Context 'When Enable-BitlockerInternal is called with Pin specified and TpmProtector not specified' {
                It 'Should throw an exception' {
                    Mock -CommandName Get-BitLockerVolume -Verifiable -MockWith { return $encryptedBLV }

                    { Enable-BitlockerInternal -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 Enable-BitlockerInternal 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

                    Enable-BitlockerInternal @defaultEnableParams
                }
            }

            Context 'When Enable-BitlockerInternal 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

                    { Enable-BitlockerInternal @defaultEnableParams } | Should -Throw -ExpectedMessage 'Failed to successfully enable Bitlocker on MountPoint'
                }
            }

            Context 'When Enable-BitlockerInternal 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)

                    Enable-BitlockerInternal @defaultEnableParams

                    $defaultEnableParams.Remove('AutoUnlock')
                }
            }

            Context 'When Enable-BitlockerInternal 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
                    }

                    Enable-BitlockerInternal @tpmOnlyEnableParams
                }
            }

            Context 'When Enable-BitlockerInternal 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
                    }

                    Enable-BitlockerInternal @tpmAndPinOnlyEnableParams
                }
            }

            Context 'When Enable-BitlockerInternal 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:\'
                    }

                    Enable-BitlockerInternal @tpmAndStartupOnlyEnableParams
                }
            }

            Context 'When Enable-BitlockerInternal 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
                    }

                    Enable-BitlockerInternal @passwordEnableParams
                }
            }

            Context 'When Enable-BitlockerInternal 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
                    }

                    Enable-BitlockerInternal @recoveryPasswordEnableParams
                }
            }

            Context 'When Enable-BitlockerInternal 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'
                    }

                    Enable-BitlockerInternal @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 Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false}
                    Mock -CommandName Add-BitLockerKeyProtector -Verifiable -ParameterFilter {$MountPoint -eq 'AdAccountOrGroupProtector'}

                    Enable-BitlockerInternal -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 Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false}
                    Mock -CommandName Add-BitLockerKeyProtector -Verifiable -ParameterFilter {$MountPoint -eq 'PasswordProtector'}

                    Enable-BitlockerInternal -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 Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false}
                    Mock -CommandName Add-BitLockerKeyProtector -Verifiable -ParameterFilter {$MountPoint -eq 'RecoveryKeyProtector'}

                    Enable-BitlockerInternal -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 Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false}
                    Mock -CommandName Add-BitLockerKeyProtector -Verifiable -ParameterFilter {$MountPoint -eq 'RecoveryPasswordProtector'}

                    Enable-BitlockerInternal -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 Test-CollectionContainsKeyProtector -Verifiable -MockWith { return $false}
                    Mock -CommandName Add-BitLockerKeyProtector -Verifiable -ParameterFilter {$MountPoint -eq 'StartupKeyProtector'}

                    Enable-BitlockerInternal -MountPoint 'StartupKeyProtector' -PrimaryProtector 'RecoveryPasswordProtector' -RecoveryPasswordProtector $true -StartupKeyProtector $true
                }
            }
        }

        Describe 'xBitLockerCommon\Test-CollectionContainsKeyProtector' -Tag 'Helper' {
            $testKeyProtectorCollection = @(
                @{
                    KeyProtectorType = 'RecoveryPassword'
                }

                @{
                    KeyProtectorType = 'AdAccountOrGroup'
                }

                @{
                    KeyProtectorType = 'StartupKeyProtector'
                }
            )

            Context 'When Test-CollectionContainsKeyProtector is called and the target KeyProtector exists in the collection' {
                It 'Should return True' {
                    Test-CollectionContainsKeyProtector -Type 'AdAccountOrGroup' -KeyProtectorCollection $testKeyProtectorCollection | Should -Be $true
                }
            }

            Context 'When Test-CollectionContainsKeyProtector is called and the target KeyProtector does not exist in the collection' {
                It 'Should return False' {
                    Test-CollectionContainsKeyProtector -Type 'AdAccountOrGroup2' -KeyProtectorCollection $testKeyProtectorCollection | Should -Be $false
                }
            }

            Context 'When Test-CollectionContainsKeyProtector is called with the StartsWith switch and the target KeyProtector exists in the collection' {
                It 'Should return True' {
                    Test-CollectionContainsKeyProtector -Type 'AdAccount' -KeyProtectorCollection $testKeyProtectorCollection -StartsWith $true | Should -Be $true
                }
            }

            Context 'When Test-CollectionContainsKeyProtector is called with the StartsWith switch and the target KeyProtector does not exist in the collection' {
                It 'Should return False' {
                    Test-CollectionContainsKeyProtector -Type 'Account' -KeyProtectorCollection $testKeyProtectorCollection -StartsWith $true | Should -Be $false
                }
            }

            Context 'When Test-CollectionContainsKeyProtector is called with the Contains switch and the target KeyProtector exists in the collection' {
                It 'Should return True' {
                    Test-CollectionContainsKeyProtector -Type 'Account' -KeyProtectorCollection $testKeyProtectorCollection -Contains $true | Should -Be $true
                }
            }

            Context 'When Test-CollectionContainsKeyProtector is called with the Contains switch and the target KeyProtector does not exist in the collection' {
                It 'Should return False' {
                    Test-CollectionContainsKeyProtector -Type 'NotInCollection' -KeyProtectorCollection $testKeyProtectorCollection -Contains $true | Should -Be $false
                }
            }
        }

        Describe 'xBitLockerCommon\Add-ToPSBoundParametersFromHashtable' -Tag 'Helper' {
            AfterEach {
                Assert-VerifiableMock
            }

            Context 'When Add-ToPSBoundParametersFromHashtable 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
                    }

                    Add-ToPSBoundParametersFromHashtable -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\Remove-FromPSBoundParametersUsingHashtable' -Tag 'Helper' {
            AfterEach {
                Assert-VerifiableMock
            }

            Context 'When Remove-FromPSBoundParametersUsingHashtable is called and both ParamsToKeep and ParamsToRemove are specified' {
                It 'Should throw an exception' {
                    { Remove-FromPSBoundParametersUsingHashtable -PSBoundParametersIn @{} -ParamsToKeep @('Param1') -ParamsToRemove @('Param2') } | `
                        Should -Throw -ExpectedMessage 'Parameter set cannot be resolved using the specified named parameters.'
                }
            }

            Context 'When Remove-FromPSBoundParametersUsingHashtable is called with ParamsToKeep' {
                It 'Should remove any parameter not specified in ParamsToKeep' {
                    $psBoundParametersIn = @{
                        Param1 = 1
                        Param2 = 2
                        Param3 = 3
                    }

                    $paramsToKeep = @('Param1', 'Param2')

                    Remove-FromPSBoundParametersUsingHashtable -PSBoundParametersIn $psBoundParametersIn -ParamsToKeep $paramsToKeep

                    $psBoundParametersIn.ContainsKey('Param1') | Should -Be $true
                    $psBoundParametersIn.ContainsKey('Param2') | Should -Be $true
                    $psBoundParametersIn.ContainsKey('Param3') | Should -Be $false
                }
            }

            Context 'When Remove-FromPSBoundParametersUsingHashtable is called with ParamsToRemove' {
                It 'Should remove any parameter specified in ParamsToRemove' {
                    $psBoundParametersIn = @{
                        Param1 = 1
                        Param2 = 2
                        Param3 = 3
                    }

                    $paramsToRemove = @(
                        'Param1',
                        'param2'
                    )

                    Remove-FromPSBoundParametersUsingHashtable -PSBoundParametersIn $psBoundParametersIn -ParamsToRemove $paramsToRemove

                    $psBoundParametersIn.ContainsKey('Param1') | Should -Be $false
                    $psBoundParametersIn.ContainsKey('Param2') | Should -Be $false
                    $psBoundParametersIn.ContainsKey('Param3') | Should -Be $true
                }
            }
        }
    }
}
finally
{
}