ozo-ad-windows-enable-bitlocker.ps1

#Requires -Version 5.1 -RunAsAdministrator

<#PSScriptInfo
    .VERSION 1.1.0
    .GUID c253d6ab-fb10-4d07-b03c-f2b630a39fa1
    .AUTHOR Andy Lievertz <alievertz@onezeroone.dev>
    .COMPANYNAME One Zero One
    .COPYRIGHT This script is released under the terms of the GNU General Public License ("GPL") version 2.0.
    .TAGS
    .LICENSEURI https://github.com/onezeroone-dev/OZO-AD-Windows-Enable-BitLocker/blob/main/LICENSE
    .PROJECTURI https://github.com/onezeroone-dev/OZO-AD-Windows-Enable-BitLocker
    .ICONURI
    .EXTERNALMODULEDEPENDENCIES
    .REQUIREDSCRIPTS
    .EXTERNALSCRIPTDEPENDENCIES
    .RELEASENOTES https://github.com/onezeroone-dev/OZO-AD-Windows-Enable-BitLocker/blob/main/CHANGELOG.md
#>


<#
    .DESCRIPTION
    Enables BitLocker on all local fixed volumes after verifying that the endpoint meets the BitLocker prerequisites and has successfully recorded a recovery password in Active Directory.
    .PARAMETER GPOName
    The name of the group policy containing the BitLocker settings. The script will proceed only if this GPO is applied.
    .PARAMETER Restart
    Restarts the computer after enabling BitLocker to perform the hardware test and begin encrypting.
    .PARAMETER SkipSecureBootCheck
    By default, the script will proceed only if Secure Boot is enabled, however, Secure Boot is not a strict requirement for enabling BitLocker. Specifying this parameter will allow the script to continue even if Secure Boot if off.
    .EXAMPLE
    powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -NonInteractive -NoProfile -File ozo-ad-windows-enable-bitlocker.ps1 -GPOName "OZO BitLocker Settings Policy" -SkipSecureBootCheck
    .EXAMPLE
    ozo-ad-windows-enable-bitlocker -GPOName "All Workstations Settings" -Restart
    .LINK
    https://github.com/onezeroone-dev/OZO-AD-Windows-Enable-BitLocker/blob/main/README.md
    .NOTES
    Messages are written to the Windows Event Viewer "One Zero One" provider. When this provider is not available, messages are written to the Microsoft-Windows-PowerShell provider with Event ID 4100.
#>


# PARAMETERS
[CmdletBinding(SupportsShouldProcess = $true)] Param (
    [Parameter(Mandatory=$true,HelpMessage="BitLocker settings GPO name")][String]$GPOName,
    [Parameter(Mandatory=$false,HelpMessage="Restarts the computer after enabling BitLocker")][Switch]$Restart,
    [Parameter(Mandatory=$false,HelpMessage="Disables checking if Secure Boot is on")][Switch]$SkipSecureBootCheck
)

# CLASSES
Class OZOMain {
    # PROPERTIES: Booleans, Strings
    [Boolean] $Restart     = $false
    [XML]     $rsopXml     = $null
    # PROPERTIES: Lists
    [System.Collections.Generic.List[PSCustomObject]] $blOSVolumes = @()
    [System.Collections.Generic.List[PSCustomObject]] $blDataVolumes = @()

    # METHODS: Constructor method
    OZOMain($GPOName,$Restart,$SkipSecureBootCheck) {
        # Set properties
        $this.Restart = $Restart
        # Declare ourselves to the world
        Write-OZOProvider -Message "Starting process." -Level "Information"
        # Determine if the environment validates, the GPO is refreshed, the required GPO is adopted, [Secure Boot is enabled], and the TPM is good
        If (($this.ValidateEnvironment() -And $this.GenerateRSoP() -And $this.EvaluateGPOSettings($GPOName) -And $this.EvaluateSecureBoot($SkipSecureBootCheck) -And $this.EvaluateTPM()) -eq $true) {
            # The environment validates, the GPO is refreshed, the required GPO is adopted, [Secure Boot is enabled], and the TPM is good; iterate on the BitLocker volume mount points
            ForEach ($blVolume in (Get-BitLockerVolume)) {
                # Determine if this volume has a letter
                If (($blVolume.MountPoint -Split(":")[0]).ToUpper() -CMatch "^[A-Z]") {
                    # Volume has a letter; determine if volume is an operating system volume
                    If ($blVolume.VolumeType -eq "OperatingSystem") {
                        # Volume is operating system; create blvolume object and add to blOSVolumes
                        $this.blOSVolumes.Add(([OZOBLVolume]::new($blVolume.MountPoint)))
                    }
                    # Determine if volume is a data volume
                    If ($blVolume.VolumeType -eq "Data") {
                        # Volume is a data; create a blVolume object and add to blDataVolumes
                        $this.blDataVolumes.Add(([OZOBLVolume]::new($blVolume.MountPoint)))
                    }
                }
            }
            # Log findings
            Write-OZOProvider -Message ("Found " + $this.blOSVolumes.Count.ToString() + " OperatingSystem volumes and " + $this.blDataVolumes.Count.ToString() + " Data volumes.") -Level "Information"
            # Process the OS volume
            ForEach ($blOSVolume in $this.blOSVolumes) {
                # Switch on physical volume type + BitLocker volume type + BitLocker volume status + BitLocker protection status
                Switch -Wildcard ($blOSVolume.phyVolume.DriveType + " " + $blOSVolume.blVolume.VolumeType + " " + $blOSVolume.blVolume.VolumeStatus + " " + $blOSVolume.blVolume.ProtectionStatus) {
                    ("Removable*") {
                        # Removable volume; do nothing
                        Write-OZOProvider -Message ($blOSVolume.blVolume.MountPoint + " is a removable volume; skipping") -Level "Warning"
                    }
                    ("Fixed OperatingSystem FullyEncrypted On") {
                        # Fixed operating system volume with BitLocker enabled and protection status On; determine if protection is not TPM + RecoveryPassword
                        If ($blOSVolume.blVolume.KeyProtector.KeyProtectorType -Contains "Tpm" -And $blOSVolume.blVolume.KeyProtector.KeyProtectorType -Contains "RecoveryPassword") {
                            # Volume has correct protectors
                            Write-OZOProvider -Message ($blOSVolume.blVolume.MountPoint + " is encrypted and already has the correct protectors; skipping") -Level "Information"
                        } Else {
                            # Volume does not have the correct protectors; decrypt then encrypt with proper protectors
                            Write-OZOProvider -Message ($blOSVolume.blVolume.MountPoint + " is encrypted but does not have the correct protectors; decrypting then re-encrypting. Any encrypted fixed volumes will also be decrypted but will not be re-encrypted--they will be encrypted on the next run of this script after the OS volume has finished encrypting.") -Level "Warning"
                            # First decrypt any fixed volumes
                            ForEach ($blDataVolume in $this.blDataVolumes) { $blDataVolume.DisableBitLocker() }
                            # Then decrypt the OS volume
                            $blOSVolume.DisableBitLocker()
                            # Then encrypt the OS volume - the Data volumes will be re-encrypted on the next run of this script after the OS volume has finished encrypting
                            $blOSVolume.EnableBitLocker()
                        }
                    }
                    ("Fixed OperatingSystem FullyEncrypted Off") {
                        # Fixed operating system volume with BitLocker enabled but no key protectors; decrypt and encrypt
                        Write-OZOProvider -Message ($blOSVolume.blVolume.MountPoint + " is encrypted but has no protectors; decrypting then re-encrypting. Any encrypted fixed volumes will also be decrypted but will not be re-encrypted--they will be encrypted on the next run of this script after the OS volume has finished encrypting.") -Level "Warning"
                        # First decrypt any fixed volumes
                        ForEach ($blDataVolume in $this.blDataVolumes) { $blDataVolume.DisableBitLocker() }
                        # Then decrypt the OS volume
                        $blOSVolume.DisableBitLocker()
                        # Then encrypt the OS volume - the Data volumes will be re-encrypted on the next run of this script after the OS volume has finished encrypting
                        $blOSVolume.EnableBitLocker()
                    }
                    ("Fixed OperatingSystem FullyDecrypted Off") {
                        # Fixed operating system volume with BitLocker disabled; determine if there is a Tpm AND RecoveryPassword protectors
                        If ($blOSVolume.blVolume.KeyProtector.KeyProtectorType -Contains "Tpm" -And $blOSVolume.blVolume.KeyProtector.KeyProtectorType -Contains "RecoveryPassword") {
                            # Fixed volume is not encrypted but has Tpm AND RecoveryPassword protectors = pending reboot; skip
                            Write-OZOProvider -Message ($blOSVolume.blVolume.MountPoint + " is pending reboot; skipping.") -Level "Warning"
                        } Else {
                            # Fixed volume with no encryption and does not have a TPM protector AND a RecoveryPassword protector; encrypt
                            Write-OZOProvider -Message ($blOSVolume.blVolume.MountPoint + " is not encrypted; encrypting.") -Level "Warning"
                            $blOSVolume.EnableBitLocker()
                        }
                    }
                    default {
                        # Nothing to do
                        Write-OZOProvider -Message ($blOSVolume.blVolume.MountPoint + " meets no criteria (likely already encrypted with correct protectors)") -Level "Information"
                    }
                }
            }
            # Determine if the OS volume is already encrypted
            If ((Get-BitLockerVolume | Where-Object {$_.VolumeType -eq "OperatingSystem"}).VolumeStatus -eq "FullyEncrypted") {
                # OS volume is already encrypted; process the Data volume(s)
                ForEach ($blDataVolume in $this.blDataVolumes) {
                    # Switch on physical volume type + BitLocker volume type + BitLocker volume status + BitLocker protection status
                    Switch -Wildcard ($blDataVolume.phyVolume.DriveType + " " + $blDataVolume.blVolume.VolumeType + " " + $blDataVolume.blVolume.VolumeStatus + " " + $blDataVolume.blVolume.ProtectionStatus) {
                        ("Removable*") {
                            # Removable volume; do nothing
                            Write-OZOProvider -Message ($blDataVolume.blVolume.MountPoint + " is a removable volume; skipping") -Level "Warning"
                        }
                        ("Fixed Data FullyEncrypted On") {
                            # Fixed data volume with BitLocker enabled and protection status On; determine if protection is not RecoveryPassword
                            If ($blDataVolume.blVolume.KeyProtector.KeyProtectorType -Contains "RecoveryPassword") {
                                # Volume has correct protectors
                                Write-OZOProvider -Message ($blDataVolume.blVolume.MountPoint + " is encrypted and already has the correct protectors; skipping") -Level "Information"
                            } Else {
                                # Volume does not have the correct protectors; decrypt then encrypt with proper protectors
                                Write-OZOProvider -Message ($blDataVolume.blVolume.MountPoint + " is encrypted but does not have the correct protectors; decrypting then re-encrypting.") -Level "Warning"
                                $blDataVolume.DisableBitLocker()
                                $blDataVolume.EnableBitLocker()
                            }
                        }
                        ("Fixed Data FullyEncrypted Off") {
                            # Fixed data volume with BitLocker enabled but no key protectors; decrypt and encrypt
                            Write-OZOProvider -Message ($blDataVolume.blVolume.MountPoint + " is encrypted but has no protectors; decrypting then re-encrypting.") -Level "Warning"
                            $blDataVolume.DisableBitLocker()
                            $blDataVolume.EnableBitLocker()
                        }
                        ("Fixed Data FullyDecrypted Off") {
                            # Fixed volume with BitLocker disabled; enable BitLocker regardless of protector status
                            Write-OZOProvider -Message ($blDataVolume.blVolume.MountPoint + " is not encrypted; encrypting.") -Level "Warning"
                            $blDataVolume.EnableBitLocker()
                        }
                        default {
                            # Nothing to do
                            Write-OZOProvider -Message ($blDataVolume.blVolume.MountPoint + " meets no criteria (likely already encrypted with correct protectors)") -Level "Information"
                        }
                    }
                }
            } Else {
                # OS volume is not already encrypted; skip the fixed volumes
                Write-OZOProvider -Message ("OS volume is not encrypted; skipping encrypting fixed volumes.") -Level "Warning"
            }
            # Call RestartComputer to determine if a restart is requested and (if yes) execute it
            $this.RestartComputer()
        }
        # Declare ourselves to the world
        Write-OZOProvider -Message "Process complete." -Level "Information"
    }
    # METHODS: Environment validation method
    Hidden [Boolean] ValidateEnvironment() {
        # Control variable
        [Boolean] $Return = $true
        # Try to quietly import the BitLocker module
        Try {
            Import-Module -Name "BitLocker" -ErrorAction Stop *> $null
            # Success
        } Catch {
            # Failure
            Write-OZOProvider -Message "Error importing the BitLocker module." -Level "Error"
            $Return = $false
        }
        # Try to quietly import the TrustedPlatformModule module
        Try {
            Import-Module -Name "TrustedPlatformModule" -ErrorAction Stop *> $null
            # Success
        } Catch {
            # Failure
            Write-OZOProvider -Message "Error importing the TrustedPlatformModule module." -Level "Error"
            $Return = $false
        }
        # Return
        return $Return
    }
    # METHODS: Generate RSoP method
    Hidden [Boolean] GenerateRSoP() {
        # Control variable
        [Boolean] $Return = $true
        # Local variables
        [String]$rsopXmlPath = (Join-Path -Path $Env:windir -ChildPath ("Temp\" + (New-Guid).Guid + "-RSoP.xml"))
        # Try to generate an RSoP
        Try {
            Start-Process -FilePath (Join-Path -Path $Env:windir -ChildPath "System32\gpresult.exe") -ArgumentList "/scope","computer","/X",$rsopXmlPath -NoNewWindow -Wait -ErrorAction Stop
            # Success; try to read in XML
            Try {
                $this.rsopXml = [Xml](Get-Content -Path $rsopXmlPath -ErrorAction Stop)
                # Success; try to remove the file
                Try {
                    Remove-Item -Force -Path $rsopXmlPath -ErrorAction Stop
                    # Success
                } Catch {
                    Write-OZOProvider -Message ("Failed to remove temporary RSoP XML results file, " + $rsopXmlPath + ".") -Level "Warning"
                }
            } Catch {
                # Failure
                Write-OZOProvider -Message ("Failed to read RSoP XML results file, " + $rsopXmlPath + ".") -Level "Warning"
            }
        } Catch {
            # Failure
            Write-OZOProvider -Message ("Failed to generate RSoP XML results file. Error message is: " + $_) -Level "Warning"
            $Return = $false
        }
        # Return
        return $Return
    }
    # METHODS: Evaluate GPO settings method
    Hidden [Boolean] EvaluateGPOSettings($GPOName) {
        # Control variable
        [Boolean] $Return = $true
        # Determine if the GPO name appears in the RSoP XML
        If ($this.rsopXml.Rsop.ComputerResults.GPO.Name -Contains $GPOName) {
            # GPO Name appears in the XML; determine if it is access denied
            If (($this.rsopXml.Rsop.ComputerResults.GPO | Where-Object {$_.Name -eq $GPOName}).AccessDenied -eq $true) {
                # Access is denied
                Write-OZOProvider -Message ("Access is denied to " + $GPOName + ".") -Level "Error"
                $Return = $false
            }
        } Else {
            # GPO Name does not appear in the XML
            Write-OZOProvider -Message ($GPOName + " not found.") -Level "Error"
            $Return = $false
        }
        # Return
        return $Return
    }
    # METHODS: Evaluate SecureBoot method
    Hidden [Boolean] EvaluateSecureBoot($SkipSecureBootCheck) {
        # Control variable
        [Boolean] $Return = $true
        # Determine if we are not skipping Secure Boot check
        If ($SkipSecureBootCheck -eq $false) {
            # We are not skipping Secure Boot check; determine if SecureBoot is present and enabled
            Try {
                Confirm-SecureBootUEFI -ErrorAction Stop
                # Success
            } Catch {
                # Failure
                Write-OZOProvider -Message "UEFI Secure Boot is present and disabled or not present." -Level "Error"
                $Return = $false
            }
        }
        # Return
        return $return
    }
    # METHODS: Evaluate TPM method
    Hidden [Boolean] EvaluateTPM() {
        # Control variable
        [Boolean] $Return = $true
        # Local variable
        [PSCustomObject] $Tpm = $null
        # Try to get the TPM
        Try {
            $Tpm = (Get-Tpm -ErrorAction Stop)
            # Success; determine if TPM
            If (((Get-WmiObject -class Win32_Tpm -namespace root\CIMV2\Security\MicrosoftTpm).SpecVersion -Split(",")) -Contains "2.0") {
                # TPM 2.0; determine if it does not meet requirements
                If (($Tpm.TpmPresent -And $Tpm.TpmReady -And $Tpm.TpmEnabled -And $Tpm.TpmActivated) -eq $false) {
                    # One or more requirements not met
                    Write-OZOProvider -Message "TPM 2.0 is not present and ready and enabled and activated." -Level "Error"
                    $Return = $false
                }
            } Else {
                # TPM 1.2; determine if it does not meet requirements
                If (($Tpm.TpmPresent -And $Tpm.TpmReady) -eq $false) {
                    # One or more requirements not met
                    Write-OZOProvider -Message "TPM 1.2 is not present and ready." -Level "Error"
                    $Return = $false
                }
            }
        } Catch {
            # Failure
            Write-OZOProvider -Message "TPM not found." -Level "Error"
            $Return = $false
        }
        # Return
        return $Return
    }
    # METHODS: RestartComputer method
    Hidden [Void] RestartComputer() {
        # Determine if the operator requested restart
        If ($this.Restart -eq $true) {
            # Operator requested restart
            Write-OZOProvider -Message "Computer will restart in 30 seconds." -Level "Warning"
            # Sleep for 30 seconds
            Start-Sleep -Seconds 30
            # Force restart
            Restart-Computer -Force
        }
    }
}

Class OZOBLVolume {
    # PROPERTIES: Booleans and Strings
    [Boolean] $Validates  = $false
    [String]  $mountPoint = $null
    # PROPERTIES: System Objects
    [System.Object] $blVolume  = $null
    [System.Object] $phyVolume = $null
    # METHODS: Constructor Method
    OZOBLVolume([String]$MountPoint) {
        # Set properties
        $this.mountPoint = $MountPoint
        # Read the volume
        $this.ReadVolume()
    }
    # METHODS: Read volume method
    Hidden [Void] ReadVolume() {
        # Store the BitLocker volume object
        $this.blVolume = (Get-BitLockerVolume -MountPoint $this.mountPoint)
        # Store the physical volume object
        $this.phyVolume = (Get-Volume -DriveLetter ($this.mountPoint -Split(":"))[0])
    }
    # METHODS:
    [Void] EnableBitLocker() {
        # Determine if volume is operating system
        If ($this.blVolume.VolumeType -eq "OperatingSystem") {
            # Volume is operating system; call RemoveTpmProtector, RemoveRecoveryPasswordProtector, AddRecoveryPasswordProtctor, and BackupKeyProtector to set conditions for enabling BitLocker
            If (($this.RemoveTpmProtector() -And $this.RemoveRecoveryPasswordProtector() -And $this.AddRecoveryPasswordProtector() -And $this.BackupKeyProtector()) -eq $true) {
                # TPM Protector is removed, RecoveryPassword protector is removed and added, and keys are backed up to AD; try to enable BitLocker with TPM protector
                Try {
                    Enable-BitLocker -MountPoint $this.blVolume.MountPoint -TpmProtector -ErrorAction Stop
                    # Success
                    Write-OZOProvider -Message ("Enabled Bitlocker on " + $this.blVolume.MountPoint + ".") -Level "Information"
                } Catch {
                    # Failure
                    Write-OZOProvider -Message ("Error enabling BitLocker on " + $this.blVolume.MountPoint + ". Error message is: " + $_) -Level "Error"
                }
            }
        } Else {
            # Volume is data; determine if OS volume is already encrypted
            If ((Get-BitLockerVolume | Where-Object {$_.VolumeType -eq "OperatingSystem"}).VolumeStatus -eq "FullyEncrypted") {
                # OS volume is already encrypted; call RemoveRecoveryPasswordProtector to set conditions for enabling BitLocker
                If ($this.RemoveRecoveryPasswordProtector() -eq $true) {
                    # RecoveryPassword protector is removed; enable BitLocker with RecoveryPasswordProtector
                    Try {
                        Enable-BitLocker -MountPoint $this.blVolume.MountPoint -RecoveryPasswordProtector -ErrorAction Stop
                        # Success
                        Write-OZOProvider -Message ("Enabled Bitlocker on " + $this.blVolume.MountPoint + ".") -Level "Information"
                        # Try to enable automagic unlock
                        Try {
                            Enable-BitLockerAutoUnlock -MountPoint $this.blVolume.MountPoint -ErrorAction Stop
                            # Success
                            Write-OZOProvider -Message ("Set auto-unlock for " + $this.blVolume.MountPoint + ".") -Level "Information"
                        } Catch {
                            # Failure
                            Write-OZOProvider -Message ("Error setting auto-unlock on " + $this.blVolume.MountPoint + ". Error message is: " + $_) -Level "Error"
                        }
                        # Reread the volume
                        $this.ReadVolume()
                        # Determine if key is not backed up to AD
                        If ($this.BackupKeyProtector() -eq $false) {
                            # Key was not backed up to AD; call DisableBitLocker and determine if it has not been disabled
                            If ($this.DisableBitLocker() -eq $false) {
                                # BitLocker has not been disabled
                                Write-OZOProvider -Message ("Error disabling BitLocker on " + $this.blVolume.MountPoint + ". Error message is: " + $_) -Level "Error"
                            }
                        }
                    } Catch {
                        # Failure
                        Write-OZOProvider -Message ("Error enabling BitLocker on " + $this.blVolume.MountPoint + ". Error message is: " + $_) -Level "Error"
                    }
                }
            } Else {
                # OS volume is not yet encrypted
                Write-OZOProvider -Message ("Can not enable BitLocker on " + $this.blVolume.MountPoint + " because the OS volume is not BitLocker encrypted.") -Level "Warning"
            }
        }
        # Re-read the volume
        $this.ReadVolume()
    }
    # METHODS: Disable BitLocker method
    Hidden [Boolean] DisableBitLocker() {
        # Control variable
        [Boolean] $Return = $true
        # Try to decrypt
        Try {
            Disable-BitLocker -MountPoint $this.blVolume.MountPoint -ErrorAction Stop
            # Success; wait until decryption is complete
            Do {
                Start-Sleep -Seconds 60
                Write-OZOProvider -Message ("Waiting for decryption to complete on " + $this.blVolume.MountPoint + ".") -Level "Warning"
            } Until ((Get-BitLockerVolume -MountPoint $this.blVolume.MountPoint).VolumeStatus -eq "FullyDecrypted")
            # Decryption complete; log
            Write-OZOProvider -Message ("Decryption of " + $this.blVolume.MountPoint + " is complete.") -Level "Information"
        } Catch {
            # Failure
            Write-OZOProvider -Message ("Decryption of " + $this.blVolume.MountPoint + " failed.") -Level "Error"
            $Return = $false
        }
        # Re-read the volume
        $this.ReadVolume()
        # Return
        return $Return
    }
    # METHODS: Remove TPM Protector method
    Hidden [Boolean] RemoveTpmProtector() {
        # Control variable
        $Return = $true
        # Determine if a TPM Protector is defined
        If (($this.blVolume.KeyProtector | Where-Object {$_.KeyprotectorType -eq "Tpm"}).Count -gt 0) {
            # Found existing TPM protector (dealbreaker)
            Write-OZOProvider -Message ("TPM protector for " + $this.blVolume.MountPoint + " already exists; attempting to remove.") -Level "Warning"
            # Try to remove
            ForEach ($keyProtectorID in ($this.blVolume.KeyProtector | Where-Object {$_.KeyProtectorType -eq "Tpm"}).KeyProtectorID) {
                Try {
                    Remove-BitLockerKeyProtector -MountPoint $this.blVolume.MountPoint -KeyProtectorId $keyProtectorID -ErrorAction Stop
                    # Success
                    Write-OZOProvider -Message ("Removed Key Protector ID " + $keyProtectorID + " from " + $this.blVolume.MountPoint + ".") -Level "Information"
                } Catch {
                    # Failure
                    Write-OZOProvider -Message ("Unable to remove Key Protector ID " + $keyProtectorID + " from " + $this.blVolume.MountPoint + "; aborting.") -Level "Error"
                    $Return = $false
                }
            }
        }
        # Re-read the volume
        $this.ReadVolume()
        # Return
        return $Return
    }
    # METHODS: Remove RecoveryPassword Protector method
    Hidden [Boolean] RemoveRecoveryPasswordProtector() {
        # Control variable
        $Return = $true
        # Iterate through any recovery password protectors
        ForEach ($keyProtectorID in ($this.blVolume.KeyProtector | Where-Object {$_.KeyProtectorType -eq "RecoveryPassword"}).KeyProtectorID) {
            # Remove the protector
            Remove-BitLockerKeyProtector -MountPoint $this.mountPoint -KeyProtectorId $keyProtectorID
            Write-OZOProvider -Message ("Removed recovery password protector " + $keyProtectorID + " for " + $this.blVolume.MountPoint + ".") -Level "Information"
        }
        # Re-read volume
        $this.ReadVolume()
        # Return
        return $Return
    }
    # METHODS: Add RecoveryPassword Protector method
    Hidden [Boolean] AddRecoveryPasswordProtector() {
        # Control variable
        $Return = $true
        # Try to add a recovery password protector
        Try {
            Add-BitLockerKeyProtector -MountPoint $this.blVolume.MountPoint -RecoveryPasswordProtector -ErrorAction Stop
            # Success
            Write-OZOProvider -Message ("Added Recovery Password protector for " + $this.blVolume.MountPoint + ".") -Level "Information"
        } Catch {
            # Failure
            Write-OZOProvider -Message ("Error adding Recovery Password protector for " + $this.blVolume.MountPoint + ". Error message is: " + $_) -Level "Error"
            $Return = $false
        }
        # Re-read volume
        $this.ReadVolume()
        # Return
        return $Return
    }
    # METHODS: Backup Key Protector method
    Hidden [Boolean] BackupKeyProtector() {
        # Control variable
        [Boolean] $Return = $false
        # Ensure there is a recovery password protector
        If (($this.blVolume.KeyProtector.KeyProtectorType) -NotContains "RecoveryPassword") { $this.AddRecoveryPasswordProtector() }
        # Iterate through all RecoveryPassword protectors and back them up to AD
        ForEach ($keyProtector in ($this.blVolume.KeyProtector | Where-Object {$_.KeyProtectorType -eq "RecoveryPassword"})) {
            # Try to back up the key protector
            Try {
                Backup-BitLockerKeyProtector -MountPoint $this.blVolume.MountPoint -KeyProtectorId $keyProtector.KeyProtectorID -ErrorAction Stop
                # Success
                $Return = $true
                Write-OZOProvider -Message ("Recovery Password protector for " + $this.blVolume.MountPoint + " backed up to AD.") -Level "Information"
            } Catch {
                # Failure
                Write-OZOProvider -Message ("Error backing up Recovery Password protector for " + $this.blVolume.MountPoint + " to AD. Error message is: " + $_) -Level "Error"
                $Return = $false
            }
        }
        # Re-read volume
        $this.ReadVolume()
        # Return
        return $Return
    }
}

# FUNCTIONS
Function Get-OZOPSArgumentList {
    # Parameters
    Param (
        [Parameter(Mandatory=$true)][System.Object]$InvocationInfo
    )
    # Local variables
    [System.Collections.Generic.List[String]]$ArgumentList = @()
    # Add arguments to list
    $ArgumentList.Add("-ExecutionPolicy")
    $ArgumentList.Add("ByPass")
    $ArgumentList.Add("-WindowStyle")
    $ArgumentList.Add("Hidden")
    $ArgumentList.Add("-NonInteractive")
    $ArgumentList.Add("-File")
    $ArgumentList.Add(('"' + $InvocationInfo.MyCommand.Definition + '"'))
    # Iterate through the bound parameters and add to list
    ForEach ($BoundParameter in $InvocationInfo.BoundParameters.GetEnumerator()) {
        Switch ($BoundParameter.Value.GetType().Name) {
            "String" {
                $ArgumentList.Add("-" + $BoundParameter.Key)
                $ArgumentList.Add('"' + $BoundParameter.Value + '"')
            }
            "SwitchParameter" {
                If ($BoundParameter.Value -eq $true) { $ArgumentList.Add("-" + $BoundParameter.Key) }
            }
            default {
                $ArgumentList.Add("-" + $BoundParameter.Key)
                $ArgumentList.Add($BoundParameter.Value)
            }
        }
    }
    # Return
    return $ArgumentList
}

Function Write-OZOProvider {
    [CmdLetBinding()] Param(
        [Parameter(Mandatory=$true)] [String] $Message,
        [Parameter(Mandatory=$false)][String] $Level = "Information"
    )
    # Initialize variables
    [String] $Source = $null
    [Int32]  $Id     = 1000
    # Switch on level to set Id
    Switch($Level.ToLower()) {
        "information" { $Id = 1000 }
        "warning"     { $Id = 1001 }
        "error"       { $Id = 1002 }
        default       { $Id = 1000 }
    }
    # Set the script name
    If ([String]::IsNullOrEmpty($MyInvocation.ScriptName)) {
        $Source = "Command-line input:"
    } Else {
        $Source = ((Split-Path -Path $MyInvocation.ScriptName -Leaf) + ":")
    }
    # Try to write to the One Zero One log
    Try {
        # Write to One Zero One and suppress output
        New-WinEvent -ProviderName "One Zero One" -Id $Id -Payload $Source,$Message -ErrorAction Stop
        # Success
    } Catch {
        # Failure; write to Microsoft-Windows-PowerShell and suppress output
        New-WinEvent -ProviderName "Microsoft-Windows-PowerShell" -Id 4100 -Payload $Source,"Script output.",$Message
    }
}

# MAIN
# Determine if this is a 64 bit process
If ([Environment]::Is64BitOperatingSystem -eq $true -And [Environment]::Is64BitProcess -eq $false) {
    # Process is not 64-bit; log
    Write-OZOProvider -Message ("Running in 32-bit PowerShell; attempting to re-launch " + $MyInvocation.InvocationName + " in 64-bit PowerShell") -Level "Warning"
    # Re-launch using 64-bit PowerShell
    Start-Process -FilePath (Join-Path -Path $Env:Windir -ChildPath "SysNative\WindowsPowerShell\v1.0\powershell.exe") -ArgumentList (Get-OZOPSArgumentList -InvocationInfo $MyInvocation) -Wait -NoNewWindow -ErrorAction Stop
} Else {
    # OS is 64-bit and PowerShell is 64-bit; log
    Write-OZOProvider -Message "Running in 64-bit PowerShell." -Level "Information"
    # Create a OZOMain object
    [OZOMain]::new($GPOName,$Restart,$SkipSecureBootCheck) | Out-Null
}

# SIG # Begin signature block
# MIIfcwYJKoZIhvcNAQcCoIIfZDCCH2ACAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCA7Fh6LLPaYVKTO
# yNE7a89g8bigrDi7wIFbfKQUGO1OVKCCDPgwggZyMIIEWqADAgECAghkM1HTxzif
# CDANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx
# EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8G
# A1UEAwwoU1NMLmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTAe
# Fw0xNjA2MjQyMDQ0MzBaFw0zMTA2MjQyMDQ0MzBaMHgxCzAJBgNVBAYTAlVTMQ4w
# DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENv
# cnAxNDAyBgNVBAMMK1NTTC5jb20gQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBD
# QSBSU0EgUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCfgxNzqrDG
# bSHL24t6h3TQcdyOl3Ka5LuINLTdgAPGL0WkdJq/Hg9Q6p5tePOf+lEmqT2d0bKU
# Vz77OYkbkStW72fL5gvjDjmMxjX0jD3dJekBrBdCfVgWQNz51ShEHZVkMGE6ZPKX
# 13NMfXsjAm3zdetVPW+qLcSvvnSsXf5qtvzqXHnpD0OctVIFD+8+sbGP0EmtpuNC
# GVQ/8y8Ooct8/hP5IznaJRy4PgBKOm8yMDdkHseudQfYVdIYyQ6KvKNc8HwKp4WB
# wg6vj5lc02AlvINaaRwlE81y9eucgJvcLGfE3ckJmNVz68Qho+Uyjj4vUpjGYDdk
# jLJvSlRyGMwnh/rNdaJjIUy1PWT9K6abVa8mTGC0uVz+q0O9rdATZlAfC9KJpv/X
# gAbxwxECMzNhF/dWH44vO2jnFfF3VkopngPawismYTJboFblSSmNNqf1x1KiVgMg
# Lzh4gL32Bq5BNMuURb2bx4kYHwu6/6muakCZE93vUN8BuvIE1tAx3zQ4XldbyDge
# VtSsSKbt//m4wTvtwiS+RGCnd83VPZhZtEPqqmB9zcLlL/Hr9dQg1Zc0bl0EawUR
# 0tOSjAknRO1PNTFGfnQZBWLsiePqI3CY5NEv1IoTGEaTZeVYc9NMPSd6Ij/D+KNV
# t/nmh4LsRR7Fbjp8sU65q2j3m2PVkUG8qQIDAQABo4H7MIH4MA8GA1UdEwEB/wQF
# MAMBAf8wHwYDVR0jBBgwFoAU3QQJB6L1en1SUxKSle44gCUNplkwMAYIKwYBBQUH
# AQEEJDAiMCAGCCsGAQUFBzABhhRodHRwOi8vb2NzcHMuc3NsLmNvbTARBgNVHSAE
# CjAIMAYGBFUdIAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwOwYDVR0fBDQwMjAwoC6g
# LIYqaHR0cDovL2NybHMuc3NsLmNvbS9zc2wuY29tLXJzYS1Sb290Q0EuY3JsMB0G
# A1UdDgQWBBRUwv4QlQCTzWr158DX2bJLuI8M4zAOBgNVHQ8BAf8EBAMCAYYwDQYJ
# KoZIhvcNAQELBQADggIBAPUPJodwr5miyvXWyfCNZj05gtOII9iCv49UhCe204MH
# 154niU2EjlTRIO5gQ9tXQjzHsJX2vszqoz2OTwbGK1mGf+tzG8rlQCbgPW/M9r1x
# xs19DiBAOdYF0q+UCL9/wlG3K7V7gyHwY9rlnOFpLnUdTsthHvWlM98CnRXZ7WmT
# V7pGRS6AvGW+5xI+3kf/kJwQrfZWsqTU+tb8LryXIbN2g9KR+gZQ0bGAKID+260P
# Z+34fdzZcFt6umi1s0pmF4/n8OdX3Wn+vF7h1YyfE7uVmhX7eSuF1W0+Z0duGwdc
# +1RFDxYRLhHDsLy1bhwzV5Qe/kI0Ro4xUE7bM1eV+jjk5hLbq1guRbfZIsr0WkdJ
# LCjoT4xCPGRo6eZDrBmRqccTgl/8cQo3t51Qezxd96JSgjXktefTCm9r/o35pNfV
# HUvnfWII+NnXrJlJ27WEQRQu9i5gl1NLmv7xiHp0up516eDap8nMLDt7TAp4z5T3
# NmC2gzyKVMtODWgqlBF1JhTqIDfM63kXdlV4cW3iSTgzN9vkbFnHI2LmvM4uVEv9
# XgMqyN0eS3FE0HU+MWJliymm7STheh2ENH+kF3y0rH0/NVjLw78a3Z9UVm1F5VPz
# iIorMaPKPlDRADTsJwjDZ8Zc6Gi/zy4WZbg8Zv87spWrmo2dzJTw7XhQf+xkR6Od
# MIIGfjCCBGagAwIBAgIQZ2iSsNbwOsjnLExSAX6F6DANBgkqhkiG9w0BAQsFADB4
# MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24x
# ETAPBgNVBAoMCFNTTCBDb3JwMTQwMgYDVQQDDCtTU0wuY29tIENvZGUgU2lnbmlu
# ZyBJbnRlcm1lZGlhdGUgQ0EgUlNBIFIxMB4XDTI0MTExNjEwMzUyOFoXDTI1MTEx
# NjEwMzUyOFowZTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCENvbG9yYWRvMQ8wDQYD
# VQQHDAZEZW52ZXIxGDAWBgNVBAoMD0FuZHJldyBMaWV2ZXJ0ejEYMBYGA1UEAwwP
# QW5kcmV3IExpZXZlcnR6MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA
# vIBAQzK0aahepOrPmvCEqfd6dMZC4GvV7kflKwrn4QPJGfqhFmUtadP1e3ange8O
# QZ3/w7UjOTAUNUHfhjbSgUBlKjbS6EWQKZuRFzI3SNkMJkcjTX4uS2P4QsnwM+SW
# IE5me3CTssdjtgue+Iiy53TMgW8JpoxiULVxmm3bhCRUAgxWeT6tzjytR1UyGcMc
# cm/YE6TOgsCHiZoo4X4HJD9iHDrNldArq04Jl6FsADxEswttKyfqpIRJLoAysVl1
# f8CEDBwhszJrEXBnAlWViJFfNY+dKP4jhf7lLqSvPCuADqP2jvM0Ym5I8qDGMz9j
# XPSMLF58MFB4vM4viS7nLRFJ8S1Q98vQvB8W4kk0WPuiZbZTHsROzohE1VSbLnIY
# ag5dDOWI8L6yutAsfdZFYFmSTKcMSiOj5VbK4LhAJUL2G8vPwpTGFgr+cEp0p62F
# P0WXK+/cRfGqodI5S+bg+9rQTD9zf829DwraSRAt5P5zrQk4WPst3JW/vIKNx7cV
# AgMBAAGjggGVMIIBkTAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFFTC/hCVAJPN
# avXnwNfZsku4jwzjMHoGCCsGAQUFBwEBBG4wbDBIBggrBgEFBQcwAoY8aHR0cDov
# L2NlcnQuc3NsLmNvbS9TU0xjb20tU3ViQ0EtQ29kZVNpZ25pbmctUlNBLTQwOTYt
# UjEuY2VyMCAGCCsGAQUFBzABhhRodHRwOi8vb2NzcHMuc3NsLmNvbTBRBgNVHSAE
# SjBIMAgGBmeBDAEEATA8BgwrBgEEAYKpMAEDAwEwLDAqBggrBgEFBQcCARYeaHR0
# cHM6Ly93d3cuc3NsLmNvbS9yZXBvc2l0b3J5MBMGA1UdJQQMMAoGCCsGAQUFBwMD
# ME0GA1UdHwRGMEQwQqBAoD6GPGh0dHA6Ly9jcmxzLnNzbC5jb20vU1NMY29tLVN1
# YkNBLUNvZGVTaWduaW5nLVJTQS00MDk2LVIxLmNybDAdBgNVHQ4EFgQUSj8HrSK7
# f/j+Dz31jJFhOF7rJUMwDgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4IC
# AQBf4lcc6FUJ1W/opNz8yjS9qLUy9cQt0s35BhasB5QoTbDaW4jv9xnFGhQVg6n+
# jhL0i94Vsywd/MRBb8lYGpuBZnS/7LHuRZu7qUuud+IMDyRHIyBK6koN5bfyA5VY
# c7bFbNpbe1s1hMWke8di4qgMLZKDfyG/RtA0swf5t4UgQLPP0h+koZ8X8V5+P0V0
# 1HsdXyXd+ojo38EoZyCKfQL2aAwMPwzZfCbmI5SRXNOc6K8oqXzQcendhlKSfVBo
# Zgpi+1updqbD4jmJfYdK5AYPxJ3YH6td6ETtr8owL+bmX8lQjlXPOwVnC11rVlNB
# VjqtaJRUClLtiNiYSTKVfjdmGVJ4+sNov0dWhHc0A9o5NX/05VVYTlImuJpnG5Og
# o7w6kWRdsgE8gM58jWf7XfI6aQS0Np/z2B+ZBj0K93khEHBX7cvvORa92LCHiVeP
# km+zEAMXgxIPs/e8cmcc/o3CORgzEwxlH9Z3UOWCuXSHD3P2RPNDAY+WPdjSHm9f
# JFlGq+f9iKyedxYa/NNjNag/5EbZ+Z2NldtSMNeFdsejGJ/TJHF1PyJd4aXx9J1i
# B/IZBOoJYyh9xpQ3ljZUKE/4otPi7INpuDFwgWiUHZZJVvrGTWwxH1Yhf8P+VpFf
# aNqsBuvklUcUDs3RNE0f1qlgFfcnAepFF+RiBRqmsj29fjGCEdEwghHNAgEBMIGM
# MHgxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3Rv
# bjERMA8GA1UECgwIU1NMIENvcnAxNDAyBgNVBAMMK1NTTC5jb20gQ29kZSBTaWdu
# aW5nIEludGVybWVkaWF0ZSBDQSBSU0EgUjECEGdokrDW8DrI5yxMUgF+hegwDQYJ
# YIZIAWUDBAIBBQCgfDAQBgorBgEEAYI3AgEMMQIwADAZBgkqhkiG9w0BCQMxDAYK
# KwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG
# 9w0BCQQxIgQgEfM+SpoiLm8nDRgj7VEeaknMatvzh5pcTz5chPXlLIUwDQYJKoZI
# hvcNAQEBBQAEggGAmo27o6OPl71oWgdk5VURAVTLW1TxUJrKDAl0Jtl53zsvtvpQ
# M9JAl8eqjYynohl0k2m8OlBKwQLQ2jbHExMp/JHsJkrGSXNBObXispmCEI8JX2jI
# MOL2TkZYM6ZVI7OiYip51A6LrOih2fjM6Qe2cjxwzTdOUX2MUnmwJiB645SQrbRM
# qXYx/24lP9tvRbFePWB/lTODqq6VY9uySswbklMVEADpdQbf/nsSS+TYlnb2w+fO
# H2DvqW9iL06c7L7vhbNGKYTMg36iOC86xb7FoB8JGC1n+aNOwi5da40FRuxHZpTt
# U80hArZWiRfyt3FaOuxGNKp9TjgJnB6tiQabumdF7yUiafWdtIaBsxPgbsdDQmK4
# yFJFv99U/o0BVepIGRi0QZSDXe7aKJn9iEMPx4Uff6hvDQfnVMkJvV5uV47hHbR3
# eH8FuzTcsdySfAQDsE2+zVq/WQaw2Ifb8pzgtVOylZQAEuZSgUUDgCFzGBKf6IeR
# 3QqKdoYfBttbeDNMoYIPFzCCDxMGCisGAQQBgjcDAwExgg8DMIIO/wYJKoZIhvcN
# AQcCoIIO8DCCDuwCAQMxDTALBglghkgBZQMEAgEwdwYLKoZIhvcNAQkQAQSgaARm
# MGQCAQEGDCsGAQQBgqkwAQMGATAxMA0GCWCGSAFlAwQCAQUABCBfcVzWNsokObL4
# tDmd0XRXm2w/53mWwClJQ6m50QnB+wIIQeFD9XxsuC8YDzIwMjUwNzI4MDIwMzE2
# WjADAgEBoIIMADCCBPwwggLkoAMCAQICEFparOgaNW60YoaNV33gPccwDQYJKoZI
# hvcNAQELBQAwczELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQH
# DAdIb3VzdG9uMREwDwYDVQQKDAhTU0wgQ29ycDEvMC0GA1UEAwwmU1NMLmNvbSBU
# aW1lc3RhbXBpbmcgSXNzdWluZyBSU0EgQ0EgUjEwHhcNMjQwMjE5MTYxODE5WhcN
# MzQwMjE2MTYxODE4WjBuMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO
# BgNVBAcMB0hvdXN0b24xETAPBgNVBAoMCFNTTCBDb3JwMSowKAYDVQQDDCFTU0wu
# Y29tIFRpbWVzdGFtcGluZyBVbml0IDIwMjQgRTEwWTATBgcqhkjOPQIBBggqhkjO
# PQMBBwNCAASnYXL1MOl6xIMUlgVC49zonduUbdkyb0piy2i8t3JlQEwA74cjK8g9
# mRC8GH1cAAVMIr8M2HdZpVgkV1LXBLB8o4IBWjCCAVYwHwYDVR0jBBgwFoAUDJ0Q
# JY6apxuZh0PPCH7hvYGQ9M8wUQYIKwYBBQUHAQEERTBDMEEGCCsGAQUFBzAChjVo
# dHRwOi8vY2VydC5zc2wuY29tL1NTTC5jb20tdGltZVN0YW1waW5nLUktUlNBLVIx
# LmNlcjBRBgNVHSAESjBIMDwGDCsGAQQBgqkwAQMGATAsMCoGCCsGAQUFBwIBFh5o
# dHRwczovL3d3dy5zc2wuY29tL3JlcG9zaXRvcnkwCAYGZ4EMAQQCMBYGA1UdJQEB
# /wQMMAoGCCsGAQUFBwMIMEYGA1UdHwQ/MD0wO6A5oDeGNWh0dHA6Ly9jcmxzLnNz
# bC5jb20vU1NMLmNvbS10aW1lU3RhbXBpbmctSS1SU0EtUjEuY3JsMB0GA1UdDgQW
# BBRQTySs77U+YxMjCZIm7Lo6luRdIjAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcN
# AQELBQADggIBAJigjwMAkbyrxGRBf0Ih4r+rbCB57lTuwViC6nH2fZSciMogpqSz
# rSeVZ2eIb5vhj9rT7jqWXZn02Fncs4YTrA1QyxJW36yjC4jl5/bsFCaWuXzGXt2Y
# 6Ifp//A3Z0sNTMWTTBobmceM3sqnovdX9ToRFP+29r5yQnPcgRTI2PvrVSqLxY9E
# yk9/0cviM3W29YBl080ENblRcu3Y8RsfzRtVT/2snuDocRxvRYmd0TPaMgIj2xII
# 651QnPp1hiq9xU0AyovLzbsi5wlR5Ip4i/i8+x+HwYJNety5cYtdWJ7uQP6YaZtW
# /jNoHp76qNftq/IlSx6xEYBRjFBxHSq2fzhUQ5oBawk2OsZ2j0wOf7q7AqjCt6t/
# +fbmWjrAWYWZGj/RLjltqdFPBpIKqdhjVIxaGgzVhaE/xHKBg4k4DfFZkBYJ9BWu
# P93Tm+paWBDwXI7Fg3alGsboErWPWlvwMAmpeJUjeKLZY26JPLt9ZWceTVWuIyuj
# erqb5IMmeqLJm5iFq/Qy4YPGyPiolw5w1k9OeO4ErmS2FKvk1ejvw4SWR+S1VyWn
# ktY442WaoStxBCCVWZdMWFeB+EpL8uoQNq1MhSt/sIUjUudkyZLIbMVQjj7b6gPX
# nD6mS8FgWiCAhuM1a/hgA+6o1sJWizHdmcpYDhyNzorf9KVRE6iR7rcmMIIG/DCC
# BOSgAwIBAgIQbVIYcIfoI02FYADQgI+TVjANBgkqhkiG9w0BAQsFADB8MQswCQYD
# VQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xGDAWBgNV
# BAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBSb290IENlcnRp
# ZmljYXRpb24gQXV0aG9yaXR5IFJTQTAeFw0xOTExMTMxODUwMDVaFw0zNDExMTIx
# ODUwMDVaMHMxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwH
# SG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxLzAtBgNVBAMMJlNTTC5jb20gVGlt
# ZXN0YW1waW5nIElzc3VpbmcgUlNBIENBIFIxMIICIjANBgkqhkiG9w0BAQEFAAOC
# Ag8AMIICCgKCAgEArlEQE9L5PCCgIIXeyVAcZMnh/cXpNP8KfzFI6HJaxV6oYf3x
# h/dRXPu35tDBwhOwPsJjoqgY/Tg6yQGBqt65t94wpx0rAgTVgEGMqGri6vCI6rEt
# SZVy9vagzTDHcGfFDc0Eu71mTAyeNCUhjaYTBkyANqp9m6IRrYEXOKdd/eREsqVD
# mhryd7dBTS9wbipm+mHLTHEFBdrKqKDM3fPYdBOro3bwQ6OmcDZ1qMY+2Jn1o0l4
# N9wORrmPcpuEGTOThFYKPHm8/wfoMocgizTYYeDG/+MbwkwjFZjWKwb4hoHT2WK8
# pvGW/OE0Apkrl9CZSy2ulitWjuqpcCEm2/W1RofOunpCm5Qv10T9tIALtQo73GHI
# lIDU6xhYPH/ACYEDzgnNfwgnWiUmMISaUnYXijp0IBEoDZmGT4RTguiCmjAFF5OV
# NbY03BQoBb7wK17SuGswFlDjtWN33ZXSAS+i45My1AmCTZBV6obAVXDzLgdJ1A1r
# yyXz4prLYyfJReEuhAsVp5VouzhJVcE57dRrUanmPcnb7xi57VPhXnCuw26hw1Hd
# +ulK3jJEgbc3rwHPWqqGT541TI7xaldaWDo85k4lR2bQHPNGwHxXuSy3yczyOg57
# TcqqG6cE3r0KR6jwzfaqjTvN695GsPAPY/h2YksNgF+XBnUD9JBtL4c34AcCAwEA
# AaOCAYEwggF9MBIGA1UdEwEB/wQIMAYBAf8CAQAwHwYDVR0jBBgwFoAU3QQJB6L1
# en1SUxKSle44gCUNplkwgYMGCCsGAQUFBwEBBHcwdTBRBggrBgEFBQcwAoZFaHR0
# cDovL3d3dy5zc2wuY29tL3JlcG9zaXRvcnkvU1NMY29tUm9vdENlcnRpZmljYXRp
# b25BdXRob3JpdHlSU0EuY3J0MCAGCCsGAQUFBzABhhRodHRwOi8vb2NzcHMuc3Ns
# LmNvbTA/BgNVHSAEODA2MDQGBFUdIAAwLDAqBggrBgEFBQcCARYeaHR0cHM6Ly93
# d3cuc3NsLmNvbS9yZXBvc2l0b3J5MBMGA1UdJQQMMAoGCCsGAQUFBwMIMDsGA1Ud
# HwQ0MDIwMKAuoCyGKmh0dHA6Ly9jcmxzLnNzbC5jb20vc3NsLmNvbS1yc2EtUm9v
# dENBLmNybDAdBgNVHQ4EFgQUDJ0QJY6apxuZh0PPCH7hvYGQ9M8wDgYDVR0PAQH/
# BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQCSGXUNplpCzxkH2fL8lPrAm/AV6USW
# Wi9xM91Q5RN7mZN3D8T7cm1Xy7qmnItFukgdtiUzLbQokDJyFTrF1pyLgGw/2hU3
# FJEywSN8crPsBGo812lyWFgAg0uOwUYw7WJQ1teICycX/Fug0KB94xwxhsvJBiRT
# pQyhu/2Kyu1Bnx7QQBA1XupcmfhbQrK5O3Q/yIi//kN0OkhQEiS0NlyPPYoRboHW
# C++wogzV6yNjBbKUBrMFxABqR7mkA0x1Kfy3Ud08qyLC5Z86C7JFBrMBfyhfPpKV
# lIiiTQuKz1rTa8ZW12ERoHRHcfEjI1EwwpZXXK5J5RcW6h7FZq/cZE9kLRZhvnRK
# tb+X7CCtLx2h61ozDJmifYvuKhiUg9LLWH0Or9D3XU+xKRsRnfOuwHWuhWch8G7k
# EmnTG9CtD9Dgtq+68KgVHtAWjKk2ui1s1iLYAYxnDm13jMZm0KpRM9mLQHBK5Gb4
# dFgAQwxOFPBslf99hXWgLyYE33vTIi9p0gYqGHv4OZh1ElgGsvyKdUUJkAr5hfbD
# X6pYScJI8v9VNYm1JEyFAV9x4MpskL6kE2Sy8rOqS9rQnVnIyPWLi8N9K4GZvPit
# /Oy+8nFL6q5kN2SZbox5d69YYFe+rN1sDD4CpNWwBBTI/q0V4pkgvhL99IV2Xasj
# HZf4peSrHdL4RjGCAlkwggJVAgEBMIGHMHMxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI
# DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxLzAt
# BgNVBAMMJlNTTC5jb20gVGltZXN0YW1waW5nIElzc3VpbmcgUlNBIENBIFIxAhBa
# WqzoGjVutGKGjVd94D3HMAsGCWCGSAFlAwQCAaCCAWEwGgYJKoZIhvcNAQkDMQ0G
# CyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNTA3MjgwMjAzMTZaMCgGCSqG
# SIb3DQEJNDEbMBkwCwYJYIZIAWUDBAIBoQoGCCqGSM49BAMCMC8GCSqGSIb3DQEJ
# BDEiBCCZspbWlMo/bC1T/O+yE4lasFgWDMSWER6GVKThXu3sNDCByQYLKoZIhvcN
# AQkQAi8xgbkwgbYwgbMwgbAEIJ1xf43CN2Wqzl5KsOH1ddeaF9Qc7tj9r+8D/T29
# iUfnMIGLMHekdTBzMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNV
# BAcMB0hvdXN0b24xETAPBgNVBAoMCFNTTCBDb3JwMS8wLQYDVQQDDCZTU0wuY29t
# IFRpbWVzdGFtcGluZyBJc3N1aW5nIFJTQSBDQSBSMQIQWlqs6Bo1brRiho1XfeA9
# xzAKBggqhkjOPQQDAgRIMEYCIQDzyboXrNXXbAe7pfYzRf31HLIm61P5aHoDCcby
# LdR97gIhALkaBln90lf+WD1Rrg8skEQkrYEoA1lV9ogL5hfHxEYt
# SIG # End signature block