HP.Firmware.SureAdmin.psm1

#
# Copyright 2018-2022 HP Development Company, L.P.
# All Rights Reserved.
#
# NOTICE: All information contained herein is, and remains the property of HP Development Company, L.P.
#
# The intellectual and technical concepts contained herein are proprietary to HP Development Company, L.P
# and may be covered by U.S. and Foreign Patents, patents in process, and are protected by
# trade secret or copyright law. Dissemination of this information or reproduction of this material
# is strictly forbidden unless prior written permission is obtained from HP Development Company, L.P.

Set-StrictMode -Version 3.0
$ErrorActionPreference = 'Stop'
#requires -Modules "HP.Private"
#requires -Modules "HP.ClientManagement"

$MSALReferencedAssemblies = New-Object Collections.Generic.List[string]
$MSALReferencedAssemblies.Add('System.Runtime')
$MSALReferencedAssemblies.Add('System.IO.FileSystem')
$MSALReferencedAssemblies.Add('System.Security')
if ($PSEdition -eq "Core") {
  $MSALReferencedAssemblies.Add("$PSScriptRoot\MSAL_4.36.2\netcoreapp2.1\Microsoft.Identity.Client.dll")
  $MSALReferencedAssemblies.Add('netstandard')
  $MSALReferencedAssemblies.Add('System.Security.Cryptography.ProtectedData')
  $MSALReferencedAssemblies.Add('System.Threading')
}
else {
  $MSALReferencedAssemblies.Add("$PSScriptRoot\MSAL_4.36.2\net45\Microsoft.Identity.Client.dll")
}

Add-Type -TypeDefinition @'
using System;
using System.IO;
using System.Security.Cryptography;
using Microsoft.Identity.Client;
 
public static class TokenCacheHelper
{
  public static void EnableSerialization(ITokenCache tokenCache)
  {
    tokenCache.SetBeforeAccess(BeforeAccessNotification);
    tokenCache.SetAfterAccess(AfterAccessNotification);
  }
  public static readonly string CacheFilePath = Path.GetTempPath() + "hp/msalcache.dat";
  private static readonly object FileLock = new object();
  private static void BeforeAccessNotification(TokenCacheNotificationArgs args)
  {
    lock (FileLock)
    {
      args.TokenCache.DeserializeMsalV3(File.Exists(CacheFilePath) ? System.Security.Cryptography.ProtectedData.Unprotect(File.ReadAllBytes(CacheFilePath), null, DataProtectionScope.CurrentUser) : null);
    }
  }
 
  private static void AfterAccessNotification(TokenCacheNotificationArgs args)
  {
    if (args.HasStateChanged)
    {
      lock (FileLock)
      {
        Directory.CreateDirectory(Path.GetDirectoryName(CacheFilePath));
        File.WriteAllBytes(CacheFilePath, System.Security.Cryptography.ProtectedData.Protect(args.TokenCache.SerializeMsalV3(), null, DataProtectionScope.CurrentUser));
      }
    }
  }
}
'@
 -ReferencedAssemblies $MSALReferencedAssemblies -WarningAction Ignore -IgnoreWarnings

<#
.SYNOPSIS
  Get the current state of the HP Sure Admin feature
 
.DESCRIPTION
  This function returns the current state of the HP Sure Admin feature
 
.NOTES
  - Requires HP P21 enabled.
  - Requires HP BIOS with HP Sure Admin support.
  - This command requires elevated privileges.
 
.LINK
  [Blog post: HP Secure Platform Management with the HP Client Management Script Library](https://developers.hp.com/hp-client-management/blog/hp-secure-platform-management-hp-client-management-script-library)
 
.EXAMPLE
  Get-HPSureAdminState
#>

function Get-HPSureAdminState
{
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/get-hpsureadminstate")]
  param()

  $mode = "Disable"
  $sk = ""
  $signingKeyID = ""
  $ver = ""
  $lak1 = ""
  $sarc = 0
  $aarc = 0
  $local_access = ""
  $lak1_keyID = ""
  $lak1_key_enrollment_data = ""

  if ((Get-HPPrivateIsSureAdminSupported) -eq $true) {
    try { $mode = Get-HPBIOSSettingValue -Name "Enhanced BIOS Authentication Mode" } catch {}
    try { $sk = Get-HPBIOSSettingValue -Name "Secure Platform Management Signing Key" } catch {}
    try { $ver = Get-HPBIOSSettingValue -Name "Enhanced BIOS Authentication Mode Version" } catch {}
    try { $lak1 = Get-HPBIOSSettingValue -Name "Enhanced BIOS Authentication Mode Local Access Key 1" } catch {}
    try { $sarc = Get-HPBIOSSettingValue -Name "Enhanced BIOS Authentication Mode Settings Anti-Replay Counter" } catch {}
    try { $aarc = Get-HPBIOSSettingValue -Name "Enhanced BIOS Authentication Mode Actions Anti-Replay Counter" } catch {}

    #modify signingKeyID
    if ($sk) {
      #decode the base64 encoded string
      $sk_decoded = [Convert]::FromBase64String($sk)
      # hash the decoded string
      $sk_hash = Get-HPPrivateHash -Data $sk_decoded
      #encode the hashed value
      $signingKeyID = [System.Convert]::ToBase64String($sk_hash)
    }

    #calculate local access, lak1_keyID and lak1_key_enrollment_data values from lak1
    if ((-not $lak1) -and ((Get-HPBIOSSetupPasswordIsSet) -eq $true) -and ($mode -eq "Enable")) {
      $local_access = "BIOS Password Protection only"
      $lak1_keyID = "Not Configured"
    }
    elseif ((-not $lak1) -and ((Get-HPBIOSSetupPasswordIsSet) -eq $false) -and ($mode -eq "Enable")) {
      $local_access = "Not Protected"
      $lak1_keyID = "Not Configured"
    }
    elseif ($lak1 -and ($mode -eq "Enable")) {
      $local_access = "Configured"

      try {
        $lak1_length = $lak1.Length
        $lak1_substring = $lak1.substring(0,344)

        #decode the base64 encoded string
        $lak1_decoded = [Convert]::FromBase64String($lak1_substring)
        # hash the decoded string
        $lak1_hash = Get-HPPrivateHash -Data $lak1_decoded
        #encode the hashed value
        $lak1_keyID = [System.Convert]::ToBase64String($lak1_hash)

        if ($lak1_length -gt 344) {
          $pos = $lak1.IndexOf("==")
          $ked_substring = $lak1.substring($pos + 2)
          if ($ked_substring) {
            $lak1_key_enrollment_data = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($ked_substring))
          }
        }
      }
      catch {
        $lak1_keyID = ""
        $lak1_key_enrollment_data = ""
      }
    }
    else {
      $local_access = ""
      $lak1_keyID = ""
      $lak1_key_enrollment_data = ""
    }

    $result = [ordered]@{
      SureAdminMode = if ($mode -eq "Enable") { "On" } else { "Off" }
      SigningKeyID = $signingKeyID
      EnhancedAuthenticationVersion = $ver
      SettingsCounter = $sarc
      ActionsCounter = $aarc
      LocalAccess = $local_access
      LocalAccessKey1 = $lak1
      LAK1_KeyID = $lak1_keyID
      LAK1_KeyEnrollmentData = $lak1_key_enrollment_data
    }

    New-Object -TypeName PSObject -Property $result
  }
}

<#
.SYNOPSIS
  Generate a payload for authorizing a firmware update
 
.DESCRIPTION
  This function uses the provided key to sign and authorize a firmware update only to the specified file.
 
  On return, the function writes the created payload to the pipeline, or to the file specified in the OutputFile parameter.
  This payload can then be passed to the Update-HPFirmware function.
 
  Security note: Payloads should only be created on secure servers. Once created, the payload may be transferred to a client and applied via the Update-HPFirmware.
 
.PARAMETER File
  The firmware update binary (.BIN) file.
 
.PARAMETER SigningKeyFile
  The path to the Secure Platform Management signing key, as a PFX file. If the PFX file is protected by a password (recommended),
  the SigningKeyPassword parameter should also be provided.
 
.PARAMETER SigningKeyPassword
  The Secure Platform Management signing key file password, if required.
 
.PARAMETER SigningKeyCertificate
  The Secure Platform Management signing key certificate, as an X509Certificate object.
 
.PARAMETER Nonce
  The operation nonce. In order to prevent replay attacks, the Secure Platform Management subsystem will only accept commands with a nonce greater or equal to the last nonce sent.
  If not specified, the nonce is inferred from the current local time. This works okay in most cases, however this approach has a resolution of seconds, so when doing high volume or parallel operations, it is possible to infer the same counter for two or more commands. In those cases, the caller should use its own nonce derivation and provide it through this parameter.
 
.PARAMETER TargetUUID
  The computer UUID on which to perform this operation. If not specified the payload generated will work on any computer.
 
.PARAMETER SingleUse
  If specified, the payload cannot be replayed. This happens because the nonce must be higher than ActionsCounter and this counter is updated and incremented every time a command generated with SingleUse flag is accepted by the BIOS.
  If not specified, the payload can be replayed as many times as desired until a payload generated with a nonce higher than
  SettingsCounter is received. This happens because SettingsCounter is not incremented by the BIOS when accepting commands.
 
.PARAMETER OutputFile
  Write the resulting output to the specified file, instead of writing it to the pipeline.
 
.PARAMETER Quiet
  Suppress non-essential messages
 
.PARAMETER Bitlocker
  Provide an answer to the Bitlocker check prompt (if any). The value may be one of:
   stop - stop if Bitlocker is detected but not suspended, and prompt.
   stop is default when Bitlocker switch is provided.
   ignore - skip the Bitlocker check
   suspend - suspend Bitlocker if active, and continue
 
.PARAMETER Force
  Force the BIOS update, even if the target BIOS is already installed.
 
.PARAMETER RemoteSigningServiceKeyID
  The Signing Key ID to be used.
 
.PARAMETER RemoteSigningServiceURL
  The KMS server URL (I.e.: https://<KMSAppName>.azurewebsites.net/).
 
.PARAMETER CacheAccessToken
  This parameter should be specified for caching the access token when performing multiple operations on the KMS server, if not cached user have to re-enter credentials on each call of this function.
  If specified, the access token is cached in msalcache.dat file and user's credentials won't be asked again until it expires.
 
.NOTES
  - Supported on Windows Power Shell v5.
  - An HP BIOS with HP Sure Admin support is required for applying the payloads generated by this function.
 
.LINK
  [Blog post: HP Secure Platform Management with the HP Client Management Script Library](https://developers.hp.com/hp-client-management/blog/hp-secure-platform-management-hp-client-management-script-library)
 
.EXAMPLE
  New-HPSureAdminFirmwareUpdatePayload -File bios.bin -SigningKeyFile "$path\signing_key.pfx" -SigningKeyPassword "s3cr3t" -OutputFile PayloadFile.dat
  Update-HPFirmware -File bios.bin -PayloadFile PayloadFile.dat
#>

function New-HPSureAdminFirmwareUpdatePayload {
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/new-hpsureadminfirmwareupdatepayload")]
  param(
    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $true,Position = 1)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $true,Position = 1)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 0)]
    [System.IO.FileInfo]$File,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $true,Position = 2)]
    [System.IO.FileInfo]$SigningKeyFile,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 3)]
    [string]$SigningKeyPassword,

    [Parameter(ValueFromPipeline = $true,ParameterSetName = "SigningKeyCert",Mandatory = $true,Position = 2)]
    [System.Security.Cryptography.X509Certificates.X509Certificate2]$SigningKeyCertificate,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 4)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 3)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 1)]
    [uint32]$Nonce = [math]::Floor([decimal](Get-Date (Get-Date).ToUniversalTime() -UFormat "%s").Replace(',','.')),

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 5)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 4)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 2)]
    [guid]$TargetUUID = 'ffffffff-ffff-ffff-ffff-ffffffffffff',

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 6)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 5)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 3)]
    [switch]$SingleUse,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 7)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 6)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 4)]
    [System.IO.FileInfo]$OutputFile,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 8)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 7)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 5)]
    [switch]$Quiet,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 9)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 8)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 6)]
    [ValidateSet('stop','ignore','suspend')]
    [string]$Bitlocker = 'stop',

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 10)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 9)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 7)]
    [switch]$Force,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 8)]
    [string]$RemoteSigningServiceKeyID,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 9)]
    [string]$RemoteSigningServiceURL,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 10)]
    [switch]$CacheAccessToken
  )

  $params = @{
    file = $File
    SingleUse = $SingleUse
    Nonce = $Nonce
    TargetUUID = $TargetUUID
  }

  if ($PSCmdlet.ParameterSetName -eq "RemoteSigning") {
    $params.RemoteSigningServiceKeyID = $RemoteSigningServiceKeyID
    $params.RemoteSigningServiceURL = $RemoteSigningServiceURL
    $params.CacheAccessToken = $CacheAccessToken
  }
  else {
    $params.SigningKey = (Get-HPPrivateX509CertCoalesce -File $SigningKeyFile -password $SigningKeyPassword -cert $SigningKeycertificate -Verbose:$VerbosePreference).Full
  }

  [byte[]]$authorization = New-HPPrivateSureAdminFirmwareUpdateAuthorization @params
  $data = @{
    Authorization = $authorization
    FileName = $File.Name
    Quiet = $Quiet.IsPresent
    bitlocker = $Bitlocker
    Force = $Force.IsPresent
  } | ConvertTo-Json -Compress
  New-HPPrivatePortablePayload -Data $data -Purpose "hp:sureadmin:firmwareupdate" -OutputFile $OutputFile -Verbose:$VerbosePreference
}

function New-HPPrivateSureAdminFirmwareUpdateAuthorization
{
  [CmdletBinding()]
  param(
    [Parameter(ParameterSetName = "LocalSigning",Mandatory = $true,Position = 0)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 0)]
    [System.IO.FileInfo]$File,

    [Parameter(ParameterSetName = "LocalSigning",Mandatory = $true,Position = 1)]
    [System.Security.Cryptography.X509Certificates.X509Certificate2]$SigningKey,

    [Parameter(ParameterSetName = "LocalSigning",Mandatory = $false,Position = 2)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 1)]
    [uint32]$Nonce = [math]::Floor([decimal](Get-Date (Get-Date).ToUniversalTime() -UFormat "%s").Replace(',','.')),

    [Parameter(ParameterSetName = "LocalSigning",Mandatory = $false,Position = 3)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 2)]
    [guid]$TargetUUID = 'ffffffff-ffff-ffff-ffff-ffffffffffff',

    [Parameter(ParameterSetName = "LocalSigning",Mandatory = $false,Position = 4)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 3)]
    [switch]$SingleUse,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 4)]
    [string]$RemoteSigningServiceKeyID,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 5)]
    [string]$RemoteSigningServiceURL,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 6)]
    [switch]$CacheAccessToken,

    [Parameter(ParameterSetName = "LocalSigning",Mandatory = $false,Position = 5)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 7)]
    [switch]$SignatureInBase64
  )

  Write-Verbose "Creating authentication payload"

  $name = "Allowed BIOS Update Hash"
  $fileHash = (Get-FileHash -Path $File -Algorithm SHA256).Hash

  # set value using raw bytes
  [byte[]]$valuebytes = [byte[]] -split ($fileHash -replace '..','0x$& ')

  $setting = New-Object -TypeName SureAdminSetting
  $setting.Name = $Name
  $setting.Value = $fileHash

  $nameLen = [System.Text.Encoding]::Unicode.GetByteCount($Name)
  $valueLen = $valuebytes.Length

  $params = @{
    NameLen = $nameLen
    ValueLen = $valueLen
    SingleUse = $SingleUse
    Nonce = $Nonce
    TargetUUID = $TargetUUID
  }
  [byte[]]$header = Invoke-HPPrivateConstructHeader @params -Verbose:$VerbosePreference
  [byte[]]$payload = New-Object byte[] ($Header.Count + $nameLen + $valueLen)

  $namebytes = [System.Text.Encoding]::Unicode.GetBytes($Name)
  [System.Array]::Copy($Header,0,$payload,0,$Header.Length)
  [System.Array]::Copy($namebytes,0,$payload,$Header.Length,$namebytes.Length)
  [System.Array]::Copy($valuebytes,0,$payload,$Header.Length + $namebytes.Length,$valuebytes.Length)

  if ($PSCmdlet.ParameterSetName -eq "LocalSigning") {
    [byte[]]$signature = Invoke-HPPrivateSignData -Data $payload -Certificate $SigningKey -Verbose:$VerbosePreference
  }
  else {
    [byte[]]$signature = Invoke-HPPrivateRemoteSignData -Data $payload -CertificateId $RemoteSigningServiceKeyID -KMSUri $RemoteSigningServiceURL -CacheAccessToken:$CacheAccessToken -Verbose:$VerbosePreference
  }

  $tag = "<BEAM/>"
  if ($SignatureInBase64.IsPresent) {
    return $tag + [Convert]::ToBase64String($signature)
  }
  $tagBytes = [System.Text.Encoding]::Unicode.GetBytes($tag)
  [byte[]]$authorization = New-Object byte[] ($namebytes.Length + $valuebytes.Length + $tagBytes.Length + $Header.Length + $Signature.Length)
  $offset = 0
  [System.Array]::Copy($namebytes,0,$authorization,$offset,$namebytes.Length)
  $offset += $namebytes.Length
  [System.Array]::Copy($valuebytes,0,$authorization,$offset,$valuebytes.Length)
  $offset += $valuebytes.Length
  [System.Array]::Copy($tagBytes,0,$authorization,$offset,$tagBytes.Length)
  $offset += $tagBytes.Length
  [System.Array]::Copy($Header,0,$authorization,$offset,$Header.Length)
  $offset += $Header.Length
  [System.Array]::Copy($Signature,0,$authorization,$offset,$Signature.Length)

  #($authorization | Format-Hex)
  return $authorization
}

<#
.SYNOPSIS
  Generate a payload for authorizing multiple BIOS setting changes
 
.DESCRIPTION
  This function uses the provided key to sign and authorize multiple BIOS setting changes.
 
  On return, the function writes the created payload to the pipeline, or to the file specified in the OutputFile parameter.
  This payload can then be passed to the Set-HPSecurePlatformPayload function.
 
  Security note: Payloads should only be created on secure servers. Once created, the payload may be transferred to a client and applied via the Set-HPSecurePlatformPayload. Creating the payload and passing it to the Set-HPSecurePlatformPayload function via the pipeline is not a recommended production pattern.
 
.PARAMETER SigningKeyFile
  The path to the Secure Platform Management signing key, as a PFX file. If the PFX file is protected by a password (recommended),
  the SigningKeyPassword parameter should also be provided.
 
.PARAMETER SigningKeyPassword
  The Secure Platform Management signing key file password, if required.
 
.PARAMETER SigningKeyCertificate
  The Secure Platform Management signing key certificate, as an X509Certificate object.
 
.PARAMETER Nonce
  The operation nonce. In order to prevent replay attacks, the Secure Platform Management subsystem will only accept commands with a nonce greater or equal to the last nonce sent.
  If not specified, the nonce is inferred from the current local time. This works okay in most cases, however this approach has a resolution of seconds, so when doing high volume or parallel operations, it is possible to infer the same counter for two or more commands. In those cases, the caller should use its own nonce derivation and provide it through this parameter.
 
.PARAMETER InputFile
  The file (relative or absolute path) to the file to process containing one or more BIOS settings.
 
.PARAMETER InputFormat
  The input file format (XML, JSON, CSV, or BCU).
 
.PARAMETER TargetUUID
  The computer UUID on which to perform this operation. If not specified the payload generated will work on any computer.
 
.PARAMETER OutputFile
  Write the resulting output to the specified file, instead of writing it to the pipeline.
 
.PARAMETER OutputFormat
  The output file format (default or BCU).
 
.PARAMETER RemoteSigningServiceKeyID
  The Signing Key ID to be used.
 
.PARAMETER RemoteSigningServiceURL
  The KMS server URL (I.e.: https://<KMSAppName>.azurewebsites.net/).
 
.PARAMETER CacheAccessToken
  This parameter should be specified for caching the access token when performing multiple operations on the KMS server, if not cached user have to re-enter credentials on each call of this function.
  If specified, the access token is cached in msalcache.dat file and user's credentials won't be asked again until it expires.
 
.NOTES
  - Supported on Windows Power Shell v5.
  - An HP BIOS with HP Sure Admin support is required for applying the payloads generated by this function.
 
.LINK
  [Blog post: HP Secure Platform Management with the HP Client Management Script Library](https://developers.hp.com/hp-client-management/blog/hp-secure-platform-management-hp-client-management-script-library)
 
.EXAMPLE
  $payload = New-HPSureAdminBIOSSettingsListPayload -SigningKeyFile "$path\signing_key.pfx" -InputFile "settings.BCU" -Format BCU
  $payload | Set-HPSecurePlatformPayload
 
.EXAMPLE
  New-HPSureAdminBIOSSettingsListPayload -SigningKeyFile "$path\signing_key.pfx" -SigningKeyPassword "s3cr3t" -InputFile "settings.BCU" -Format BCU -OutputFile PayloadFile.dat
  Set-HPSecurePlatformPayload -PayloadFile PayloadFile.dat
#>

function New-HPSureAdminBIOSSettingsListPayload
{
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/new-hpsureadminbiossettingslistpayload")]
  param(
    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $true,Position = 0)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $true,Position = 0)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 0)]
    [System.IO.FileInfo]$InputFile,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $true,Position = 1)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $true,Position = 1)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 1)]
    [ValidateSet('XML','JSON','BCU','CSV')]
    [Alias('Format')]
    [string]$InputFormat = $null,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $true,Position = 2)]
    [System.IO.FileInfo]$SigningKeyFile,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 3)]
    [string]$SigningKeyPassword,

    [Parameter(ValueFromPipeline = $true,ParameterSetName = "SigningKeyCert",Mandatory = $true,Position = 3)]
    [System.Security.Cryptography.X509Certificates.X509Certificate2]$SigningKeyCertificate,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 5)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 5)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 2)]
    [uint32]$Nonce = [math]::Floor([decimal](Get-Date (Get-Date).ToUniversalTime() -UFormat "%s").Replace(',','.')),

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 6)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 6)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 3)]
    [guid]$TargetUUID = 'ffffffff-ffff-ffff-ffff-ffffffffffff',

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 7)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 7)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 4)]
    [System.IO.FileInfo]$OutputFile,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 5)]
    [string]$RemoteSigningServiceKeyID,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 6)]
    [string]$RemoteSigningServiceURL,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 7)]
    [switch]$CacheAccessToken,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 8)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 8)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 8)]
    [ValidateSet('default','BCU')]
    [string]$OutputFormat = 'default'
  )

  Write-Verbose "InputFormat specified: '$InputFormat'. Reading file..."
  [System.Collections.Generic.List[SureAdminSetting]]$settingsList = $null
  $settingsList = Get-HPPrivateSettingsFromFile -FileName $InputFile -Format $InputFormat

  $params = @{
    SettingsList = $settingsList
    Nonce = $Nonce
    TargetUUID = $TargetUUID
  }

  if ($PSCmdlet.ParameterSetName -eq "RemoteSigning") {
    $params.RemoteSigningServiceKeyID = $RemoteSigningServiceKeyID
    $params.RemoteSigningServiceURL = $RemoteSigningServiceURL
    $params.CacheAccessToken = $CacheAccessToken
  }
  else {
    $params.SigningKey = (Get-HPPrivateX509CertCoalesce -File $SigningKeyFile -password $SigningKeyPassword -cert $SigningKeycertificate -Verbose:$VerbosePreference).Full
  }

  $settingsList = New-HPPrivateSureAdminBIOSSettingsObject @params

  if ($OutputFormat -eq 'default') {
    $data = $settingsList | ConvertTo-Json
    New-HPPrivatePortablePayload -Data $data -Purpose "hp:sureadmin:biossettingslist" -OutputFile $OutputFile
  }
  elseif ($OutputFormat -eq 'BCU') {
    $dict = New-HPPrivateBIOSSettingsDefinition -SettingsDefinitionFile $InputFile -Format $InputFormat
    New-HPPrivateSureAdminSettingsBCU -settingsList $settingsList -SettingsDefinition $dict -OutputFile $OutputFile
  }
}

function New-HPPrivateBIOSSettingsDefinition
{
  [CmdletBinding()]
  param(
    $SettingsDefinitionFile,
    $Format
  )

  $dict = @{}
  switch ($format) {
    { $_ -eq 'XML' } {
      Write-Verbose "Reading XML settings definition $settingsDefinitionFile"
      [xml]$settingsDefinitionXml = Get-Content $SettingsDefinitionFile
      $entries = ([xml]$settingsDefinitionXml).ImagePal.BIOSSettings.BIOSSetting
      foreach ($entry in $entries) {
        [string[]]$valueList = @()
        foreach ($v in $entry.SelectNodes("ValueList/Value/text()"))
        {
          $valueList += $v.Value
        }
        if ($valueList -le 1) {
          [string[]]$valueList = @()
        }
        $dict[$entry.Name] = [pscustomobject]@{
          Name = $entry.Name
          Value = $entry.Value
          ValueList = $valueList
        }
      }
    }

    { $_ -eq 'BCU' } {
      Write-Verbose "Reading BCU settings definition $settingsDefinitionFile"

      $list = [ordered]@{}
      $currset = ""

      switch -regex -File $settingsDefinitionFile {
        '^\S.*$' {
          $currset = $matches[0].trim()
          if ($currset -ne "BIOSConfig 1.0" -and -not $currset.StartsWith(";")) {
            $list[$currset] = New-Object System.Collections.Generic.List[System.String]
          }
        }

        '^\s.*$' {
          # value (indented)
          $c = $matches[0].trim()
          $list[$currset].Add($c)
        }
      }

      foreach ($s in $list.keys) {
        [string[]]$valueList = @()
        if ($list[$s].Count -gt 1) {
          $valueList = $list[$s]
        }

        $dict[$s] = [pscustomobject]@{
          Name = $s
          Value = Get-HPPrivateDesiredValue -Value $list[$s]
          ValueList = $valueList
        }
      }
    }

    { $_ -eq 'CSV' } {
      Write-Verbose "Reading CSV settings definition $settingsDefinitionFile"
      $content = Get-HPPrivateFileContent $settingsDefinitionFile
      $items = $content | ConvertFrom-Csv

      foreach ($item in $items) {

        [string[]]$valueList = @()
        if ($item.CURRENT_VALUE.contains(',')) {
          foreach ($v in $item.CURRENT_VALUE -split ',')
          {
            if ($v.StartsWith("*")) {
              $valueList += $v.substring(1)
            }
            else {
              $valueList += $v
            }
          }
        }

        $dict[$item.Name] = [pscustomobject]@{
          Name = $item.Name
          Value = (Get-HPPrivateDesiredValue $item.CURRENT_VALUE)
          ValueList = $valueList
        }
      }
    }

    { $_ -eq 'JSON' } {
      Write-Verbose "Reading JSON settings definition $settingsDefinitionFile"
      [string]$content = Get-HPPrivateFileContent $settingsDefinitionFile
      $list = $Content | ConvertFrom-Json

      foreach ($item in $list) {

        [string[]]$valueList = @()
        if ($item.PSObject.Properties.Name -match 'Elements') {
          [string[]]$valueList = $item.Elements
        }
        elseif ($item.PSObject.Properties.Name -match 'PossibleValues') {
          [string[]]$valueList = $item.PossibleValues
        }

        $dict[$item.Name] = [pscustomobject]@{
          Name = $item.Name
          Value = $item.Value
          ValueList = $valueList
        }
      }
    }
  }

  $dict['SetSystemDefaults'] = [pscustomobject]@{
    Name = 'SetSystemDefaults'
    Value = ''
    ValueList = @()
  }

  $dict['Allowed BIOS Update Hash'] = [pscustomobject]@{
    Name = 'Allowed BIOS Update Hash'
    Value = ''
    ValueList = @()
  }

  return $dict
}

function New-HPPrivateSureAdminSettingsBCU
{
  [CmdletBinding()]
  param(
    $SettingsList,
    $SettingsDefinition,
    $Platform,
    [System.IO.FileInfo]$OutputFile,
    [switch]$SkipSettingDefinition
  )

  Write-Verbose "Found $($SettingsList.Count) settings"
  $now = Get-Date
  $output += "BIOSConfig 1.0`n"
  $output += ";`n"
  $output += "; Created by CMSL function $((Get-PSCallStack)[1].Command)`n"
  $output += "; Date=$now`n"
  $output += ";`n"
  $output += "; Found $($SettingsList.Count) settings`n"
  $output += ";`n"
  foreach ($entry in $SettingsList) {
    $output += "$($entry.Name)`n"
    if ($SkipSettingDefinition.IsPresent) {
      $output += "`t$($entry.Value)`n"
    }
    else {
      if (-not $SettingsDefinition -or -not $SettingsDefinition.ContainsKey($entry.Name)) {
        throw "Setting definition not found: $($entry.Name)"
      }
      $definition = $SettingsDefinition[$entry.Name]
      if ($entry.Value.contains(",") -and $definition.ValueList.Count -gt 0) {
        $entry.Value.Split(",") | ForEach-Object {
          $c = $_.trim()
          $output += "`t$c`n"
        }
      }
      elseif ($definition.ValueList.Count -gt 0) {
        foreach ($v in $definition.ValueList) {
          if ($v -eq $entry.Value) {
            $output += "`t*$($v)`n"
          }
          else {
            $output += "`t$($v)`n"
          }
        }
      }
      else {
        $output += "`t$($entry.Value)`n"
      }
    }
    $output += ";Signature=$($entry.AuthString)`n"
  }

  if ($OutputFile) {
    Write-Verbose "Will output to file $OutputFile"
    $f = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputFile)
    Out-File -FilePath $f -Encoding utf8 -InputObject $output
  }
  else {
    Write-Verbose 'Will output to console'
    $output
  }
}

function New-HPPrivatePortablePayload {
  param(
    [string]$Data,
    [string]$Purpose,
    [System.IO.FileInfo]$OutputFile
  )

  $output = New-Object -TypeName PortableFileFormat
  $output.timestamp = Get-Date
  $output.purpose = $Purpose
  $output.Data = [System.Text.Encoding]::UTF8.GetBytes($Data)

  if ($OutputFile) {
    Write-Verbose "Will output to file $OutputFile"
    $f = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputFile)
    $output | ConvertTo-Json -Compress | Out-File -FilePath $f -Encoding utf8
  }
  else {
    $output | ConvertTo-Json -Compress
  }
}

function New-HPPrivateSureAdminBIOSSettingsObject {
  [CmdletBinding()]
  param(
    [Parameter(ParameterSetName = "LocalSigning",Mandatory = $true,Position = 0)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 0)]
    [System.Collections.Generic.List[SureAdminSetting]]$SettingsList,

    [Parameter(ParameterSetName = "LocalSigning",Mandatory = $false,Position = 1)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 1)]
    [switch]$SingleUse,

    [Parameter(ParameterSetName = "LocalSigning",Mandatory = $false,Position = 2)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 2)]
    [uint32]$Nonce = [math]::Floor([decimal](Get-Date (Get-Date).ToUniversalTime() -UFormat "%s").Replace(',','.')),

    [Parameter(ParameterSetName = "LocalSigning",Mandatory = $false,Position = 3)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 3)]
    [guid]$TargetUUID = 'ffffffff-ffff-ffff-ffff-ffffffffffff',

    [Parameter(ParameterSetName = "LocalSigning",Mandatory = $true,Position = 4)]
    [System.Security.Cryptography.X509Certificates.X509Certificate2]$SigningKey,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 4)]
    [string]$RemoteSigningServiceKeyID,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 5)]
    [string]$RemoteSigningServiceURL,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 6)]
    [switch]$CacheAccessToken
  )

  Write-Verbose "Signing settings list"

  $params = @{
    Nonce = $Nonce
    TargetUUID = $TargetUUID
  }

  if ($PSCmdlet.ParameterSetName -eq "LocalSigning") {
    $params.SigningKey = $SigningKey
    for ($i = 0; $i -lt $SettingsList.Count; $i++) {
      $setting = $SettingsList[$i]
      $params.Name = $setting.Name
      $params.Value = $setting.Value

      if ($setting.AuthString -eq $null) {
        $SettingsList[$i] = New-HPPrivateSureAdminBIOSSettingObject @params -Verbose:$VerbosePreference
      }
    }
  }
  else {
    $params.CertificateId = $RemoteSigningServiceKeyID
    $params.KMSUri = $RemoteSigningServiceURL
    $params.CacheAccessToken = $CacheAccessToken
    $params.SettingsList = $SettingsList
    $SettingsList = Invoke-HPPrivateRemoteSignSureAdminSettings @params -Verbose:$VerbosePreference
  }

  return $SettingsList
}

function Invoke-HPPrivateRemoteSignSureAdminSettings {
  [CmdletBinding()]
  param(
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 0)]
    [guid]$TargetUUID = 'ffffffff-ffff-ffff-ffff-ffffffffffff',

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 1)]
    [uint32]$Nonce = [math]::Floor([decimal](Get-Date (Get-Date).ToUniversalTime() -UFormat "%s").Replace(',','.')),

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 2)]
    [System.Collections.Generic.List[SureAdminSetting]]$SettingsList,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 3)]
    [string]$CertificateId,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 4)]
    [string]$KMSUri,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 5)]
    [switch]$CacheAccessToken,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 6)]
    [switch]$SingleUse
  )

  if (-not $KMSUri.EndsWith('/')) {
    $KMSUri += '/'
  }
  $KMSUri += 'api/commands/sureadminauth'

  $jsonPayload = New-HPPrivateSureAdminRemoteSigningSettingsJson -settingsList $SettingsList -nonce $Nonce -TargetUUID $TargetUUID -CertificateId $CertificateId -SingleUse:$SingleUse
  $accessToken = Get-HPPrivateSureAdminKMSAccessToken -CacheAccessToken:$CacheAccessToken
  $response,$responseContent = Send-HPPrivateKMSRequest -KMSUri $KMSUri -JsonPayload $jsonPayload -AccessToken $accessToken

  if ($response -eq "OK") {
    $responseObject = $responseContent | ConvertFrom-Json

    $settings = New-Object System.Collections.Generic.List[SureAdminSetting]
    for ($i = 0; $i -lt $responseObject.settings.Count; $i++) {
      $settings.Add([SureAdminSetting]@{
          Name = $responseObject.settings[$i].Name
          Value = $responseObject.settings[$i].Value
          AuthString = $responseObject.settings[$i].AuthString
        }) | Out-Null
    }
    # Return a list of [SureAdminSetting]
    $settings
  }
  else {
    Invoke-HPPrivateKMSErrorHandle -ApiResponseContent $responseContent -Status $response
  }
}

function New-HPPrivateSureAdminBIOSSettingObject {
  [CmdletBinding()]
  param(
    [Parameter(ParameterSetName = "LocalSigning",Mandatory = $true,Position = 0)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 0)]
    [string]$Name,

    [Parameter(ParameterSetName = "LocalSigning",Mandatory = $true,Position = 1)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 1)]
    [AllowEmptyString()]
    [string]$Value,

    [Parameter(ParameterSetName = "LocalSigning",Mandatory = $false,Position = 2)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 2)]
    [switch]$SingleUse,

    [Parameter(ParameterSetName = "LocalSigning",Mandatory = $true,Position = 3)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 3)]
    [uint32]$Nonce,

    [Parameter(ParameterSetName = "LocalSigning",Mandatory = $true,Position = 4)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 4)]
    [guid]$TargetUUID,

    [Parameter(ParameterSetName = "LocalSigning",Mandatory = $true,Position = 5)]
    [System.Security.Cryptography.X509Certificates.X509Certificate2]$SigningKey,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 5)]
    [string]$RemoteSigningServiceKeyID,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 6)]
    [string]$RemoteSigningServiceURL,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 7)]
    [switch]$CacheAccessToken
  )

  [SureAdminSetting]$setting = New-Object -TypeName SureAdminSetting
  $setting.Name = $Name
  $setting.Value = $Value

  if ($PSCmdlet.ParameterSetName -eq "LocalSigning") {
    if ($Name -eq "Setup Password" -or $Name -eq "Power-On Password") {
      $SettingValueForSigning = "<utf-16/>" + $Value
    }
    else {
      $SettingValueForSigning = $Value
    }

    $nameLen = [System.Text.Encoding]::Unicode.GetByteCount($setting.Name)
    $valueLen = [System.Text.Encoding]::Unicode.GetByteCount($SettingValueForSigning)

    $params = @{
      NameLen = $nameLen
      ValueLen = $valueLen
      SingleUse = $SingleUse
      Nonce = $Nonce
      TargetUUID = $TargetUUID
    }
    [byte[]]$header = Invoke-HPPrivateConstructHeader @params -Verbose:$VerbosePreference
    [byte[]]$payload = Invoke-HPPrivateConstructPayload -Header $header -Name $setting.Name -Value $SettingValueForSigning -Verbose:$VerbosePreference
    [byte[]]$signature = Invoke-HPPrivateSignData -Data $payload -Certificate $SigningKey -Verbose:$VerbosePreference
    $setting.AuthString = Invoke-HPPrivateConstructAuthorization -Header $header -Signature $signature -Verbose:$VerbosePreference
  }
  else {
    $settings = New-Object System.Collections.Generic.List[SureAdminSetting]
    $settings.Add($setting)
    $setting = (Invoke-HPPrivateRemoteSignSureAdminSettings -TargetUUID $TargetUUID -Nonce $Nonce -CertificateId $RemoteSigningServiceKeyID -KMSUri $RemoteSigningServiceURL -CacheAccessToken:$CacheAccessToken -SingleUse:$SingleUse -SettingsList $settings)[0]
  }

  return $setting
}

function New-HPPrivateSureAdminRemoteSigningJson {
  [CmdletBinding()]
  param(
    [string]$CertificateId,
    [byte[]]$Data
  )

  $blob = [Convert]::ToBase64String($Data)
  $payload = [ordered]@{
    keyId = $CertificateId
    commandBlob = $blob
  }

  $payload | ConvertTo-Json -Compress
}

function New-HPPrivateSureAdminRemoteSigningSettingsJson {
  [CmdletBinding()]
  param(
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 0)]
    [guid]$TargetUUID = 'ffffffff-ffff-ffff-ffff-ffffffffffff',

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 1)]
    [uint32]$Nonce = [math]::Floor([decimal](Get-Date (Get-Date).ToUniversalTime() -UFormat "%s").Replace(',','.')),

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 2)]
    [System.Collections.Generic.List[SureAdminSetting]]$SettingsList,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 3)]
    [string]$CertificateId,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 4)]
    [switch]$SingleUse
  )

  $settings = New-Object System.Collections.ArrayList
  for ($i = 0; $i -lt $SettingsList.Count; $i++) {
    $settings.Add([pscustomobject]@{
        Name = $SettingsList[$i].Name
        Value = $SettingsList[$i].Value
        Nonce = $Nonce
        TargetUUID = $TargetUUID
        isSingleUse = $SingleUse.IsPresent
      }) | Out-Null
  }

  $payload = [ordered]@{
    keyId = $CertificateId
    settings = $settings
  }

  $payload | ConvertTo-Json -Compress
}


<#
.SYNOPSIS
  This is a private function for internal use only
 
.DESCRIPTION
  This is a private function for internal use only
 
.EXAMPLE
 
.NOTES
  - This is a private function for internal use only
#>

function Invoke-HPPrivateRemoteSignData {
  [CmdletBinding()]
  param(
    [byte[]]$Data,
    [string]$CertificateId,
    [string]$KMSUri,
    [switch]$CacheAccessToken
  )

  if (-not $KMSUri.EndsWith('/')) {
    $KMSUri += '/'
  }
  $KMSUri += 'api/commands/p21signature'

  $jsonPayload = New-HPPrivateSureAdminRemoteSigningJson -CertificateId $CertificateId -Data $Data
  $accessToken = Get-HPPrivateSureAdminKMSAccessToken -CacheAccessToken:$CacheAccessToken
  $response,$responseContent = Send-HPPrivateKMSRequest -KMSUri $KMSUri -JsonPayload $jsonPayload -AccessToken $accessToken

  if ($response -eq "OK") {
    $responseObject = $responseContent | ConvertFrom-Json
    [System.Convert]::FromBase64String($responseObject.signature)
  }
  else {
    Invoke-HPPrivateKMSErrorHandle -ApiResponseContent $responseContent -Status $response
  }
}

<#
.SYNOPSIS
  Generate a payload for resetting BIOS settings to default values
 
.DESCRIPTION
  This function uses the provided key to sign and authorize resetting BIOS settings to default values.
 
  On return, the function writes the created payload to the pipeline, or to the file specified in the OutputFile parameter.
  This payload can then be passed to the Set-HPSecurePlatformPayload function.
 
  Security note: Payloads should only be created on secure servers. Once created, the payload may be transferred to a client and applied via the Set-HPSecurePlatformPayload. Creating the payload and passing it to the Set-HPSecurePlatformPayload function via the pipeline is not a recommended production pattern.
 
.PARAMETER SigningKeyFile
  The path to the Secure Platform Management signing key, as a PFX file. If the PFX file is protected by a password (recommended),
  the SigningKeyPassword parameter should also be provided.
 
.PARAMETER SigningKeyPassword
  The Secure Platform Management signing key file password, if required.
 
.PARAMETER SigningKeyCertificate
  The Secure Platform Management signing key certificate, as an X509Certificate object.
 
.PARAMETER Nonce
  The operation nonce. In order to prevent replay attacks, the Secure Platform Management subsystem will only accept commands with a nonce greater or equal to the last nonce sent.
  If not specified, the nonce is inferred from the current local time. This works okay in most cases, however this approach has a resolution of seconds, so when doing high volume or parallel operations, it is possible to infer the same counter for two or more commands. In those cases, the caller should use its own nonce derivation and provide it through this parameter.
 
.PARAMETER TargetUUID
  The computer UUID on which to perform this operation. If not specified the payload generated will work on any computer.
 
.PARAMETER OutputFile
  Write the resulting output to the specified file, instead of writing it to the pipeline.
 
.PARAMETER RemoteSigningServiceKeyID
  The Signing Key ID to be used.
 
.PARAMETER RemoteSigningServiceURL
  The KMS server URL (I.e.: https://<KMSAppName>.azurewebsites.net/).
 
.PARAMETER CacheAccessToken
  This parameter should be specified for caching the access token when performing multiple operations on the KMS server, if not cached user have to re-enter credentials on each call of this function.
  If specified, the access token is cached in msalcache.dat file and user's credentials won't be asked again until it expires.
 
.NOTES
  - Supported on Windows Power Shell v5.
  - An HP BIOS with HP Sure Admin support is required for applying the payloads generated by this function.
 
.LINK
  [Blog post: HP Secure Platform Management with the HP Client Management Script Library](https://developers.hp.com/hp-client-management/blog/hp-secure-platform-management-hp-client-management-script-library)
 
.EXAMPLE
  $payload = New-HPSureAdminSettingDefaultsPayload -SigningKeyFile "$path\signing_key.pfx"
  $payload | Set-HPSecurePlatformPayload
 
.EXAMPLE
  New-HPSureAdminSettingDefaultsPayload -SigningKeyFile "$path\signing_key.pfx" -SigningKeyPassword "s3cr3t" -OutputFile PayloadFile.dat
  Set-HPSecurePlatformPayload -PayloadFile PayloadFile.dat
#>

function New-HPSureAdminSettingDefaultsPayload
{
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/new-hpsureadminsettingdefaultspayload")]
  param(
    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $true,Position = 0)]
    [System.IO.FileInfo]$SigningKeyFile,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 1)]
    [string]$SigningKeyPassword,

    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $true,Position = 0)]
    [System.Security.Cryptography.X509Certificates.X509Certificate2]$SigningKeyCertificate,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 2)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 1)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 0)]
    [uint32]$Nonce = [math]::Floor([decimal](Get-Date (Get-Date).ToUniversalTime() -UFormat "%s").Replace(',','.')),

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 3)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 2)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 1)]
    [guid]$TargetUUID = 'ffffffff-ffff-ffff-ffff-ffffffffffff',

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 4)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 3)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 2)]
    [System.IO.FileInfo]$OutputFile,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 3)]
    [string]$RemoteSigningServiceKeyID,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 4)]
    [string]$RemoteSigningServiceURL,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 5)]
    [switch]$CacheAccessToken
  )

  $params = @{
    Name = "SetSystemDefaults"
    Value = ""
    Nonce = $Nonce
    TargetUUID = $TargetUUID
  }

  if ($PSCmdlet.ParameterSetName -eq "RemoteSigning") {
    $params.RemoteSigningServiceKeyID = $RemoteSigningServiceKeyID
    $params.RemoteSigningServiceURL = $RemoteSigningServiceURL
    $params.CacheAccessToken = $CacheAccessToken
  }
  else {
    $params.SigningKey = (Get-HPPrivateX509CertCoalesce -File $SigningKeyFile -password $SigningKeyPassword -cert $SigningKeycertificate -Verbose:$VerbosePreference).Full
  }

  [SureAdminSetting]$setting = New-HPPrivateSureAdminBIOSSettingObject @params -Verbose:$VerbosePreference
  $data = $setting | ConvertTo-Json
  New-HPPrivatePortablePayload -Data $data -Purpose "hp:sureadmin:resetsettings" -OutputFile $OutputFile
}

<#
.SYNOPSIS
  Generate a payload for enabling the HP Sure Admin feature
 
.DESCRIPTION
  This function uses the provided key to sign and authorize the operation of enabling HP Sure Admin.
 
  On return, the function writes the created payload to the pipeline, or to the file specified in the OutputFile parameter.
  This payload can then be passed to the Set-HPSecurePlatformPayload function.
 
  Security note: Payloads should only be created on secure servers. Once created, the payload may be transferred to a client and applied via the Set-HPSecurePlatformPayload. Creating the payload and passing it to the Set-HPSecurePlatformPayload function via the pipeline is not a recommended production pattern.
 
.PARAMETER SigningKeyFile
  The path to the Secure Platform Management signing key, as a PFX file. If the PFX file is protected by a password (recommended),
  the SigningKeyPassword parameter should also be provided.
 
.PARAMETER SigningKeyPassword
  The Secure Platform Management signing key file password, if required.
 
.PARAMETER SigningKeyCertificate
  The Secure Platform Management signing key certificate, as an X509Certificate object.
 
.PARAMETER Nonce
  The operation nonce. In order to prevent replay attacks, the Secure Platform Management subsystem will only accept commands with a nonce greater or equal to the last nonce sent.
  If not specified, the nonce is inferred from the current local time. This works okay in most cases, however this approach has a resolution of seconds, so when doing high volume or parallel operations, it is possible to infer the same counter for two or more commands. In those cases, the caller should use its own nonce derivation and provide it through this parameter.
 
.PARAMETER TargetUUID
  The computer UUID on which to perform this operation. If not specified the payload generated will work on any computer.
 
.PARAMETER SingleUse
  If specified, the payload cannot be replayed. This happens because the nonce must be higher than ActionsCounter and this counter is updated and incremented every time a command generated with SingleUse flag is accepted by the BIOS.
  If not specified, the payload can be replayed as many times as desired until a payload generated with a nonce higher than
  SettingsCounter is received. This happens because SettingsCounter is not incremented by the BIOS when accepting commands.
 
.PARAMETER OutputFile
  Write the resulting output to the specified file, instead of writing it to the pipeline.
 
.PARAMETER RemoteSigningServiceKeyID
  The Signing Key ID to be used.
 
.PARAMETER RemoteSigningServiceURL
  The KMS server URL (I.e.: https://<KMSAppName>.azurewebsites.net/).
 
.PARAMETER CacheAccessToken
  This parameter should be specified for caching the access token when performing multiple operations on the KMS server, if not cached user have to re-enter credentials on each call of this function.
  If specified, the access token is cached in msalcache.dat file and user's credentials won't be asked again until it expires.
 
.NOTES
  - Supported on Windows Power Shell v5.
  - An HP BIOS with HP Sure Admin support is required for applying the payloads generated by this function.
 
.LINK
  [Blog post: HP Secure Platform Management with the HP Client Management Script Library](https://developers.hp.com/hp-client-management/blog/hp-secure-platform-management-hp-client-management-script-library)
 
.EXAMPLE
  $payload = New-HPSureAdminEnablePayload -SigningKeyFile "$path\signing_key.pfx"
  $payload | Set-HPSecurePlatformPayload
 
.EXAMPLE
  New-HPSureAdminEnablePayload -SigningKeyFile "$path\signing_key.pfx" -SigningKeyPassword "s3cr3t" -OutputFile PayloadFile.dat
  Set-HPSecurePlatformPayload -PayloadFile PayloadFile.dat
#>

function New-HPSureAdminEnablePayload
{
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/new-hpsureadminenablepayload")]
  param(
    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $true,Position = 1)]
    [System.IO.FileInfo]$SigningKeyFile,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 2)]
    [string]$SigningKeyPassword,

    [Parameter(ValueFromPipeline = $true,ParameterSetName = "SigningKeyCert",Mandatory = $true,Position = 1)]
    [System.Security.Cryptography.X509Certificates.X509Certificate2]$SigningKeyCertificate,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 3)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 2)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 0)]
    [uint32]$Nonce = [math]::Floor([decimal](Get-Date (Get-Date).ToUniversalTime() -UFormat "%s").Replace(',','.')),

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 4)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 3)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 1)]
    [guid]$TargetUUID = 'ffffffff-ffff-ffff-ffff-ffffffffffff',

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 5)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 4)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 2)]
    [switch]$SingleUse,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 6)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 5)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 3)]
    [System.IO.FileInfo]$OutputFile,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 4)]
    [string]$RemoteSigningServiceKeyID,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 5)]
    [string]$RemoteSigningServiceURL,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 6)]
    [switch]$CacheAccessToken
  )

  $params = @{
    Name = "Enhanced BIOS Authentication Mode"
    Value = "Enable"
    SingleUse = $SingleUse
    Nonce = $Nonce
    TargetUUID = $TargetUUID
    OutputFile = $OutputFile
  }

  if ($PSCmdlet.ParameterSetName -eq "SigningKeyFile") {
    $params.SigningKeyFile = $SigningKeyFile
    $params.SigningKeyPassword = $SigningKeyPassword
  }
  elseif ($PSCmdlet.ParameterSetName -eq "SigningKeyCert") {
    $params.SigningKeyCertificate = $SigningKeyCertificate
  }
  elseif ($PSCmdlet.ParameterSetName -eq "RemoteSigning") {
    $params.RemoteSigningServiceKeyID = $RemoteSigningServiceKeyID
    $params.RemoteSigningServiceURL = $RemoteSigningServiceURL
    $params.CacheAccessToken = $CacheAccessToken
  }

  New-HPSureAdminBIOSSettingValuePayload @params -Verbose:$VerbosePreference
}

<#
.SYNOPSIS
  Generate a payload for disabling the HP Sure Admin feature
 
.DESCRIPTION
  This function uses the provided key to sign and authorize the operation of disabling HP Sure Admin.
 
  On return, the function writes the created payload to the pipeline, or to the file specified in the OutputFile parameter.
  This payload can then be passed to the Set-HPSecurePlatformPayload function.
 
  Security note: Payloads should only be created on secure servers. Once created, the payload may be transferred to a client and applied via the Set-HPSecurePlatformPayload. Creating the payload and passing it to the Set-HPSecurePlatformPayload function via the pipeline is not a recommended production pattern.
 
.PARAMETER SigningKeyFile
  The path to the Secure Platform Management signing key, as a PFX file. If the PFX file is protected by a password (recommended),
  the SigningKeyPassword parameter should also be provided.
 
.PARAMETER SigningKeyPassword
  The Secure Platform Management signing key file password, if required.
 
.PARAMETER SigningKeyCertificate
  The Secure Platform Management signing key certificate, as an X509Certificate object.
 
.PARAMETER Nonce
  The operation nonce. In order to prevent replay attacks, the Secure Platform Management subsystem will only accept commands with a nonce greater or equal to the last nonce sent.
  If not specified, the nonce is inferred from the current local time. This works okay in most cases, however this approach has a resolution of seconds, so when doing high volume or parallel operations, it is possible to infer the same counter for two or more commands. In those cases, the caller should use its own nonce derivation and provide it through this parameter.
 
.PARAMETER TargetUUID
  The computer UUID on which to perform this operation. If not specified the payload generated will work on any computer.
 
.PARAMETER SingleUse
  If specified, the payload cannot be replayed. This happens because the nonce must be higher than ActionsCounter and this counter is updated and incremented every time a command generated with SingleUse flag is accepted by the BIOS.
  If not specified, the payload can be replayed as many times as desired until a payload generated with a nonce higher than
  SettingsCounter is received. This happens because SettingsCounter is not incremented by the BIOS when accepting commands.
 
.PARAMETER OutputFile
  Write the resulting output to the specified file, instead of writing it to the pipeline.
 
.PARAMETER RemoteSigningServiceKeyID
  The Signing Key ID to be used.
 
.PARAMETER RemoteSigningServiceURL
  The KMS server URL (I.e.: https://<KMSAppName>.azurewebsites.net/).
 
.PARAMETER CacheAccessToken
  This parameter should be specified for caching the access token when performing multiple operations on the KMS server, if not cached user have to re-enter credentials on each call of this function.
  If specified, the access token is cached in msalcache.dat file and user's credentials won't be asked again until it expires.
 
.NOTES
  - Supported on Windows Power Shell v5.
  - An HP BIOS with HP Sure Admin support is required for applying the payloads generated by this function.
 
.LINK
  [Blog post: HP Secure Platform Management with the HP Client Management Script Library](https://developers.hp.com/hp-client-management/blog/hp-secure-platform-management-hp-client-management-script-library)
 
.EXAMPLE
  $payload = New-HPSureAdminDisablePayload -SigningKeyFile "$path\signing_key.pfx"
  $payload | Set-HPSecurePlatformPayload
 
.EXAMPLE
  New-HPSureAdminDisablePayload -SigningKeyFile "$path\signing_key.pfx" -SigningKeyPassword "s3cr3t" -OutputFile PayloadFile.dat
  Set-HPSecurePlatformPayload -PayloadFile PayloadFile.dat
#>

function New-HPSureAdminDisablePayload
{
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/new-hpsureadmindisablepayload")]
  param(
    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $true,Position = 1)]
    [System.IO.FileInfo]$SigningKeyFile,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 2)]
    [string]$SigningKeyPassword,

    [Parameter(ValueFromPipeline = $true,ParameterSetName = "SigningKeyCert",Mandatory = $true,Position = 1)]
    [System.Security.Cryptography.X509Certificates.X509Certificate2]$SigningKeyCertificate,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 3)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 2)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 0)]
    [uint32]$Nonce = [math]::Floor([decimal](Get-Date (Get-Date).ToUniversalTime() -UFormat "%s").Replace(',','.')),

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 4)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 3)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 1)]
    [guid]$TargetUUID = 'ffffffff-ffff-ffff-ffff-ffffffffffff',

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 5)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 4)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 2)]
    [switch]$SingleUse,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 6)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 5)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 3)]
    [System.IO.FileInfo]$OutputFile,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 4)]
    [string]$RemoteSigningServiceKeyID,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 5)]
    [string]$RemoteSigningServiceURL,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 6)]
    [switch]$CacheAccessToken
  )

  $params = @{
    Name = "Enhanced BIOS Authentication Mode"
    Value = "Disable"
    SingleUse = $SingleUse
    Nonce = $Nonce
    TargetUUID = $TargetUUID
    OutputFile = $OutputFile
  }

  if ($PSCmdlet.ParameterSetName -eq "SigningKeyFile") {
    $params.SigningKeyFile = $SigningKeyFile
    $params.SigningKeyPassword = $SigningKeyPassword
  }
  elseif ($PSCmdlet.ParameterSetName -eq "SigningKeyCert") {
    $params.SigningKeyCertificate = $SigningKeyCertificate
  }
  elseif ($PSCmdlet.ParameterSetName -eq "RemoteSigning") {
    $params.RemoteSigningServiceKeyID = $RemoteSigningServiceKeyID
    $params.RemoteSigningServiceURL = $RemoteSigningServiceURL
    $params.CacheAccessToken = $CacheAccessToken
  }

  New-HPSureAdminBIOSSettingValuePayload @params -Verbose:$VerbosePreference
}

<#
.SYNOPSIS
  Generate a payload for provisioning a local access key
 
.DESCRIPTION
  This function uses the provided key to sign and authorize updating HP Sure Admin local access keys.
  Setting a local access key allows system administrators to authorize commands locally with the HP Sure Admin phone app.
 
  Check the function Convert-HPSureAdminCertToQRCode to know how to transferring a local access key to the HP Sure Admin phone app.
 
  On return, the function writes the created payload to the pipeline, or to the file specified in the OutputFile parameter.
  This payload can then be passed to the Set-HPSecurePlatformPayload function.
 
  Security note: Payloads should only be created on secure servers. Once created, the payload may be transferred to a client and applied via the Set-HPSecurePlatformPayload. Creating the payload and passing it to the Set-HPSecurePlatformPayload function via the pipeline is not a recommended production pattern.
 
.PARAMETER SigningKeyFile
  The path to the Secure Platform Management signing key, as a PFX file. If the PFX file is protected by a password (recommended),
  the SigningKeyPassword parameter should also be provided.
 
.PARAMETER SigningKeyPassword
  The Secure Platform Management signing key file password, if required.
 
.PARAMETER LocalAccessKeyFile
  The path to the local access key, as a PFX file. If the PFX file is protected by a password (recommended),
  the LocalAccessKeyPassword parameter should also be provided.
 
.PARAMETER LocalAccessKeyPassword
  The local access key file password, if required.
 
.PARAMETER SigningKeyCertificate
  The Secure Platform Management signing key certificate, as an X509Certificate object.
 
.PARAMETER Nonce
  The operation nonce. In order to prevent replay attacks, the Secure Platform Management subsystem will only accept commands with a nonce greater or equal to the last nonce sent.
  If not specified, the nonce is inferred from the current local time. This works okay in most cases, however this approach has a resolution of seconds, so when doing high volume or parallel operations, it is possible to infer the same counter for two or more commands. In those cases, the caller should use its own nonce derivation and provide it through this parameter.
 
.PARAMETER TargetUUID
  The computer UUID on which to perform this operation. If not specified the payload generated will work on any computer.
 
.PARAMETER SingleUse
  If specified, the payload cannot be replayed. This happens because the nonce must be higher than ActionsCounter and this counter is updated and incremented every time a command generated with SingleUse flag is accepted by the BIOS.
  If not specified, the payload can be replayed as many times as desired until a payload generated with a nonce higher than
  SettingsCounter is received. This happens because SettingsCounter is not incremented by the BIOS when accepting commands.
 
.PARAMETER OutputFile
  Write the resulting output to the specified file, instead of writing it to the pipeline.
 
.PARAMETER Id
  Int Id from 1,2 or 3 that gets appended to the setting name.
 
.PARAMETER KeyEnrollmentData
  KeyEnrollmentData to use to get Sure Admin Local Access key from certificate
 
.PARAMETER RemoteSigningServiceKeyID
  The Signing Key ID to be used.
 
.PARAMETER RemoteSigningServiceURL
  The KMS server URL (I.e.: https://<KMSAppName>.azurewebsites.net/).
 
.PARAMETER CacheAccessToken
  This parameter should be specified for caching the access token when performing multiple operations on the KMS server, if not cached user have to re-enter credentials on each call of this function.
  If specified, the access token is cached in msalcache.dat file and user's credentials won't be asked again until it expires.
 
.NOTES
  - Supported on Windows Power Shell v5.
  - An HP BIOS with HP Sure Admin support is required for applying the payloads generated by this function.
 
.LINK
  [Blog post: HP Secure Platform Management with the HP Client Management Script Library](https://developers.hp.com/hp-client-management/blog/hp-secure-platform-management-hp-client-management-script-library)
 
.EXAMPLE
  $payload = New-HPSureAdminLocalAccessKeyProvisioningPayload -SigningKeyFile "$path\signing_key.pfx" -LocalAccessKeyFile "$path\local_access_key.pfx"
  $payload | Set-HPSecurePlatformPayload
 
.EXAMPLE
  New-HPSureAdminLocalAccessKeyProvisioningPayload -SigningKeyFile "$path\signing_key.pfx" -SigningKeyPassword "s3cr3t" -LocalAccessKeyFile "$path\local_access_key.pfx" -LocalAccessKeyPassword "lak_s3cr3t" -OutputFile PayloadFile.dat
  Set-HPSecurePlatformPayload -PayloadFile PayloadFile.dat
#>

function New-HPSureAdminLocalAccessKeyProvisioningPayload
{
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/new-hpsureadminlocalaccesskeyprovisioningpayload")]
  param(
    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $true,Position = 1)]
    [System.IO.FileInfo]$SigningKeyFile,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 2)]
    [string]$SigningKeyPassword,

    [Parameter(ValueFromPipeline = $true,ParameterSetName = "SigningKeyCert",Mandatory = $true,Position = 1)]
    [System.Security.Cryptography.X509Certificates.X509Certificate2]$SigningKeyCertificate,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 3)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 2)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 0)]
    [uint32]$Nonce = [math]::Floor([decimal](Get-Date (Get-Date).ToUniversalTime() -UFormat "%s").Replace(',','.')),

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 4)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 3)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 1)]
    [guid]$TargetUUID = 'ffffffff-ffff-ffff-ffff-ffffffffffff',

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 5)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 4)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 2)]
    [switch]$SingleUse,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $true,Position = 6)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $true,Position = 6)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 3)]
    [System.IO.FileInfo]$LocalAccessKeyFile,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 7)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 7)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 4)]
    [string]$LocalAccessKeyPassword,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 8)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 8)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 5)]
    [ValidateSet(1,2,3)]
    [int]$Id = 1,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 9)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 9)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 6)]
    [string]$KeyEnrollmentData,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 6)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 5)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 7)]
    [System.IO.FileInfo]$OutputFile,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 8)]
    [string]$RemoteSigningServiceKeyID,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 9)]
    [string]$RemoteSigningServiceURL,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 10)]
    [switch]$CacheAccessToken
  )

  $localAccessKey = (Get-HPPrivateX509CertCoalesce -File $LocalAccessKeyFile -password $LocalAccessKeyPassword -cert $null -Verbose:$VerbosePreference).Full
  [string]$pubKeyBase64 = Get-HPPrivateSureAdminLocalAccessKeyFromCert -LocalAccessKey $localAccessKey -KeyEnrollmentData $KeyEnrollmentData

  $params = @{
    Name = "Enhanced BIOS Authentication Mode Local Access Key " + $Id
    Value = $pubKeyBase64
    SingleUse = $SingleUse
    Nonce = $Nonce
    TargetUUID = $TargetUUID
    OutputFile = $OutputFile
  }
  if ($PSCmdlet.ParameterSetName -eq "SigningKeyFile") {
    $params.SigningKeyFile = $SigningKeyFile
    $params.SigningKeyPassword = $SigningKeyPassword
  }
  elseif ($PSCmdlet.ParameterSetName -eq "SigningKeyCert") {
    $params.SigningKeyCertificate = $SigningKeyCertificate
  }
  elseif ($PSCmdlet.ParameterSetName -eq "RemoteSigning") {
    $params.RemoteSigningServiceKeyID = $RemoteSigningServiceKeyID
    $params.RemoteSigningServiceURL = $RemoteSigningServiceURL
    $params.CacheAccessToken = $CacheAccessToken
  }

  New-HPSureAdminBIOSSettingValuePayload @params -Verbose:$VerbosePreference
}

<#
.SYNOPSIS
  Generate a payload for authorizing a single BIOS setting change
 
.DESCRIPTION
  This function uses the provided key to sign and authorize a single BIOS setting change.
 
  On return, the function writes the created payload to the pipeline, or to the file specified in the OutputFile parameter.
  This payload can then be passed to the Set-HPSecurePlatformPayload function.
 
  Security note: Payloads should only be created on secure servers. Once created, the payload may be transferred to a client and applied via the Set-HPSecurePlatformPayload. Creating the payload and passing it to the Set-HPSecurePlatformPayload function via the pipeline is not a recommended production pattern.
 
.PARAMETER Name
  The name of a setting. Note that the setting name is usually case sensitive.
 
.PARAMETER Value
  The new value of a setting
 
.PARAMETER SigningKeyFile
  The path to the Secure Platform Management signing key, as a PFX file. If the PFX file is protected by a password (recommended),
  the SigningKeyPassword parameter should also be provided.
 
.PARAMETER SigningKeyPassword
  The Secure Platform Management signing key file password, if required.
 
.PARAMETER SigningKeyCertificate
  The Secure Platform Management signing key certificate, as an X509Certificate object.
 
.PARAMETER Nonce
  The operation nonce. In order to prevent replay attacks, the Secure Platform Management subsystem will only accept commands with a nonce greater or equal to the last nonce sent.
  If not specified, the nonce is inferred from the current local time. This works okay in most cases, however this approach has a resolution of seconds, so when doing high volume or parallel operations, it is possible to infer the same counter for two or more commands. In those cases, the caller should use its own nonce derivation and provide it through this parameter.
 
.PARAMETER TargetUUID
  The computer UUID on which to perform this operation. If not specified the payload generated will work on any computer.
 
.PARAMETER SingleUse
  If specified, the payload cannot be replayed. This happens because the nonce must be higher than ActionsCounter and this counter is updated and incremented every time a command generated with SingleUse flag is accepted by the BIOS.
  If not specified, the payload can be replayed as many times as desired until a payload generated with a nonce higher than
  SettingsCounter is received. This happens because SettingsCounter is not incremented by the BIOS when accepting commands.
 
.PARAMETER OutputFile
  Write the resulting output to the specified file, instead of writing it to the pipeline.
 
.PARAMETER RemoteSigningServiceKeyID
  The Signing Key ID to be used.
 
.PARAMETER RemoteSigningServiceURL
  The KMS server URL (I.e.: https://<KMSAppName>.azurewebsites.net/).
 
.PARAMETER CacheAccessToken
  This parameter should be specified for caching the access token when performing multiple operations on the KMS server, if not cached user have to re-enter credentials on each call of this function.
  If specified, the access token is cached in msalcache.dat file and user's credentials won't be asked again until it expires.
 
.NOTES
  - Supported on Windows Power Shell v5.
  - An HP BIOS with HP Sure Admin support is required for applying the payloads generated by this function.
 
.LINK
  [Blog post: HP Secure Platform Management with the HP Client Management Script Library](https://developers.hp.com/hp-client-management/blog/hp-secure-platform-management-hp-client-management-script-library)
 
.EXAMPLE
  $payload = New-HPSureAdminBIOSSettingValuePayload -Name "Setting Name" -Value "New Setting Value" -SigningKeyFile "$path\signing_key.pfx"
  $payload | Set-HPSecurePlatformPayload
 
.EXAMPLE
  New-HPSureAdminBIOSSettingValuePayload -Name "Setting Name" -Value "New Setting Value" -SigningKeyFile "$path\signing_key.pfx" -SigningKeyPassword "s3cr3t" -OutputFile PayloadFile.dat
  Set-HPSecurePlatformPayload -PayloadFile PayloadFile.dat
#>

function New-HPSureAdminBIOSSettingValuePayload
{
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/new-hpsureadminbiossettingvaluepayload")]
  param(
    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $true,Position = 0)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $true,Position = 0)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 0)]
    [string]$Name,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $true,Position = 1)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $true,Position = 1)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 1)]
    [AllowEmptyString()]
    [string]$Value,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $true,Position = 2)]
    [System.IO.FileInfo]$SigningKeyFile,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 3)]
    [string]$SigningKeyPassword,

    [Parameter(ValueFromPipeline = $true,ParameterSetName = "SigningKeyCert",Mandatory = $true,Position = 2)]
    [System.Security.Cryptography.X509Certificates.X509Certificate2]$SigningKeyCertificate,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 4)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 3)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 2)]
    [uint32]$Nonce = [math]::Floor([decimal](Get-Date (Get-Date).ToUniversalTime() -UFormat "%s").Replace(',','.')),

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 5)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 4)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 3)]
    [guid]$TargetUUID = 'ffffffff-ffff-ffff-ffff-ffffffffffff',

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 6)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 5)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 4)]
    [switch]$SingleUse,

    [Parameter(ParameterSetName = "SigningKeyFile",Mandatory = $false,Position = 7)]
    [Parameter(ParameterSetName = "SigningKeyCert",Mandatory = $false,Position = 6)]
    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 5)]
    [System.IO.FileInfo]$OutputFile,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 6)]
    [string]$RemoteSigningServiceKeyID,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $true,Position = 7)]
    [string]$RemoteSigningServiceURL,

    [Parameter(ParameterSetName = "RemoteSigning",Mandatory = $false,Position = 8)]
    [switch]$CacheAccessToken
  )

  if ($PSCmdlet.ParameterSetName -eq "RemoteSigning") {
    $params = @{
      Name = $Name
      Value = $Value
      SingleUse = $SingleUse
      Nonce = $Nonce
      TargetUUID = $TargetUUID
      RemoteSigningServiceKeyID = $RemoteSigningServiceKeyID
      RemoteSigningServiceURL = $RemoteSigningServiceURL
      CacheAccessToken = $CacheAccessToken
    }
  }
  else {
    $signingKey = (Get-HPPrivateX509CertCoalesce -File $SigningKeyFile -password $SigningKeyPassword -cert $SigningKeycertificate -Verbose:$VerbosePreference).Full
    $params = @{
      Name = $Name
      Value = $Value
      SingleUse = $SingleUse
      Nonce = $Nonce
      TargetUUID = $TargetUUID
      SigningKey = $signingKey
    }
  }

  [SureAdminSetting]$setting = New-HPPrivateSureAdminBIOSSettingObject @params -Verbose:$VerbosePreference
  $data = $setting | ConvertTo-Json
  New-HPPrivatePortablePayload -Data $data -Purpose "hp:sureadmin:biossetting" -OutputFile $OutputFile -Verbose:$VerbosePreference
}

function Invoke-HPPrivateConstructHeader {
  [CmdletBinding()]
  param(
    [uint32]$NameLen,
    [uint32]$ValueLen,
    [switch]$SingleUse,
    [uint32]$Nonce,
    [guid]$TargetUUID
  )

  $data = New-Object -TypeName SureAdminSignatureBlockHeader

  $data.Version = 1
  $data.NameLength = $NameLen
  $data.ValueLength = $ValueLen
  $data.OneTimeUse = [byte]($SingleUse.IsPresent)
  $data.Nonce = $Nonce
  $data.Reserved = 1
  $data.Target = $TargetUUID.ToByteArray()

  [byte[]]$header = (Convert-HPPrivateObjectToBytes -obj $data -Verbose:$VerbosePreference)[0]
  return $header
}

function Invoke-HPPrivateConstructPayload {
  [CmdletBinding()]
  param(
    [byte[]]$Header,
    [string]$Name,
    [string]$Value
  )

  $nameLen = [System.Text.Encoding]::Unicode.GetByteCount($Name)
  $valueLen = [System.Text.Encoding]::Unicode.GetByteCount($Value)
  [byte[]]$payload = New-Object byte[] ($Header.Count + $nameLen + $valueLen)

  $namebytes = [System.Text.Encoding]::Unicode.GetBytes($Name)
  [System.Array]::Copy($Header,0,$payload,0,$Header.Length)
  [System.Array]::Copy($namebytes,0,$payload,$Header.Length,$namebytes.Length)
  if ($valueLen -ne 0) {
    Write-Verbose "Copying value to payload"
    $valuebytes = [System.Text.Encoding]::Unicode.GetBytes($Value)
    [System.Array]::Copy($valuebytes,0,$payload,$Header.Length + $namebytes.Length,$valuebytes.Length)
  }
  else {
    Write-Verbose "No value was specified for this setting"
  }

  return $payload
}

function Invoke-HPPrivateConstructAuthorization {
  [CmdletBinding()]
  param(
    [byte[]]$Header,
    [byte[]]$Signature
  )

  [byte[]]$authorization = New-Object byte[] ($Header.Length + $Signature.Length)
  [System.Array]::Copy($Header,0,$authorization,0,$Header.Length)
  [System.Array]::Copy($Signature,0,$authorization,$Header.Length,$Signature.Length)

  [string]$encodedAuth = "<BEAM/>" + [Convert]::ToBase64String($authorization)
  return $encodedAuth
}

function Get-HPPrivatePublicKeyModulus ($cert)
{
  $key = $cert.PublicKey.key
  $parameters = $key.ExportParameters($false);
  return $parameters.Modulus
}

function Get-HPPrivateKeyNameFromCert ($cert)
{
  return $cert.Subject -replace "(CN=)(.*?),.*",'$2'
}

function Get-HPPrivatePrimesFromCert ($Certificate)
{
  $rsaPrivate = [xml]$Certificate.PrivateKey.ToXmlString($true)

  $p = [System.Convert]::FromBase64String($rsaPrivate.RSAKeyValue.P)
  $q = [System.Convert]::FromBase64String($rsaPrivate.RSAKeyValue.Q)

  $primes = [System.Byte[]]::new(256)

  for ($i = 0; $i -lt 128; $i++)
  {
    $primes[$i] = $p[$i]
  }

  for ($i = 0; $i -lt 128; $i++)
  {
    $primes[128 + $i] = $q[$i]
  }

  return $primes
}

function Get-HPPrivateRandomByteArray ($Length)
{
  $RandomBytes = New-Object Byte[] ($Length)

  $RNG = [Security.Cryptography.RNGCryptoServiceProvider]::Create()
  $RNG.GetBytes($RandomBytes)

  return $RandomBytes
}

function Get-HPPrivateRandomIV ()
{
  return Get-HPPrivateRandomByteArray 16
}

function Get-HPPrivateRandomSalt ()
{
  return Get-HPPrivateRandomByteArray 8
}

function Get-HPPrivatePbkdf2Bytes ($Passphrase,$Salt,$Iterations,$Length,$Metadata = $null)
{
  $Passphrase += $Metadata
  $PBKDF2 = New-Object System.Security.Cryptography.Rfc2898DeriveBytes ($Passphrase,$Salt,$Iterations)
  return $PBKDF2.GetBytes($Length)
}

function Get-HPPrivateDataEncryption ([byte[]]$AESKey,[byte[]]$Data,[byte[]]$IV)
{
  $aesManaged = New-Object System.Security.Cryptography.AesManaged
  $aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC
  $aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7
  $aesManaged.KeySize = 256
  $aesManaged.IV = $IV
  $aesManaged.key = $AESKey

  $encryptor = $aesManaged.CreateEncryptor()
  [byte[]]$encryptedData = $encryptor.TransformFinalBlock($Data,0,$Data.Length);
  $aesManaged.Dispose()

  return $encryptedData
}

function Get-HPPrivateKeyFromCert {
  [CmdletBinding()]
  param(
    [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate,
    [string]$Metadata,
    [string]$Passphrase
  )

  $iv = Get-HPPrivateRandomIV
  $salt = Get-HPPrivateRandomSalt
  $iterations = 100000
  $keysize = 32
  $aesKey = Get-HPPrivatePbkdf2Bytes $Passphrase $salt $iterations $keysize $Metadata

  $primes = Get-HPPrivatePrimesFromCert $Certificate
  $cipher = Get-HPPrivateDataEncryption $aesKey $primes $iv

  $encryptedPrimes = $salt + $iv + $cipher

  return [System.Convert]::ToBase64String($encryptedPrimes)
}

function Get-HPPrivateSureAdminLocalAccessKeyFromCert {
  [CmdletBinding()]
  param(
    [System.Security.Cryptography.X509Certificates.X509Certificate2]$LocalAccessKey,
    [string]$KeyEnrollmentData
  )
  $modulus = Get-HPPrivatePublicKeyModulus $LocalAccessKey
  $pubKeyBase64 = [System.Convert]::ToBase64String($modulus)

  if ($KeyEnrollmentData) {
    $KeyEnrollmentDataBytes = [System.Text.Encoding]::UTF8.GetBytes($KeyEnrollmentData)
    $pubKeyBase64 += [System.Convert]::ToBase64String($KeyEnrollmentDataBytes)
  }

  return $pubKeyBase64
}

<#
.SYNOPSIS
  Extract key id from a certificate
 
.DESCRIPTION
  The key id is used by HP Sure Admin Key Management Service (KMS) for remote signing
 
.PARAMETER Certificate
  The X509Certificate2 certificate
 
.PARAMETER CertificateFile
  The certificate in PFX file
 
.PARAMETER CertificateFilePassword
  The password for the PFX file
 
.EXAMPLE
  Get-HPSureAdminKeyId -Certificate X509Certificate2
 
.EXAMPLE
  Get-HPSureAdminKeyId -CertificateFile mypfxcert.pfx
#>

function Get-HPSureAdminKeyId {
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/get-hpsureadminkeyid")]
  param(
    [Parameter(ParameterSetName = "Cert",Mandatory = $true,Position = 0)]
    [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate,

    [Parameter(ParameterSetName = "File",Mandatory = $true,Position = 0)]
    [System.IO.FileInfo]$CertificateFile,

    [Parameter(ParameterSetName = "File",Mandatory = $false,Position = 1)]
    [string]$CertificateFilePassword
  )

  if ($PSCmdlet.ParameterSetName -eq "File") {
    if ($CertificateFilePassword) {
      [securestring]$CertificateFilePassword = ConvertTo-SecureString -AsPlainText -Force $CertificateFilePassword
      $Certificate = (Get-HPPrivatePublicKeyCertificateFromPFX -FileName $CertificateFile -password $CertificateFilePassword).Full
    }
    else {
      $Certificate = (Get-HPPrivatePublicKeyCertificateFromPFX -FileName $CertificateFile).Full
    }
  }

  $modulus = Get-HPPrivatePublicKeyModulus $Certificate
  $hashMod = Get-HPPrivateHash -Data $modulus
  return [System.Convert]::ToBase64String($hashMod)
}

function New-HPPrivateSureAdminEnrollmentJsonVer4 {
  [CmdletBinding()]
  param(
    [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate,
    [string]$AADRevocation,
    [string]$KeyName
  )

  # Get the pub key
  $hashModBase64 = Get-HPSureAdminKeyId -Certificate $Certificate

  # Get full cert
  $rawBytes = $Certificate.Export("Pfx","")
  $pvtKeyBase64 = [System.Convert]::ToBase64String($rawBytes)

  $data = [ordered]@{
    KeyEnrollmentData = $null
    Ver = "002"
    Type = "004"
    KeyId = $hashModBase64
    KeyAlgo = "06"
    PvtKey = $pvtKeyBase64
    KeyExp = "00000000"
    KeyName = $KeyName
    KeyBkupEn = "0"
    CanModKeyBkup = "0"
    CanExport = "0"
    AADRevocation = $AADRevocation
  }

  $json = $data | ConvertTo-Json -Compress
  return $json
}

function New-HPPrivateSureAdminEnrollmentJson {
  [CmdletBinding()]
  param(
    [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate,
    [string]$Model,
    [string]$SerialNumber,
    [string]$Passphrase,
    [string]$AADRevocation,
    [string]$KeyName
  )

  # Get the pub key
  $modulus = Get-HPPrivatePublicKeyModulus $Certificate
  $hashMod = Get-HPPrivateHash -Data $modulus
  $hashModBase64 = [System.Convert]::ToBase64String($hashMod)

  if (-not $KeyName) {
    # Get the private key
    $KeyName = Get-HPPrivateKeyNameFromCert $Certificate
  }
  if (-not $KeyName) {
    throw 'Certificate subject or parameter KeyName is required to identify the key in KMS server'
  }

  if ("" -eq $Passphrase) {
    $keyAlgo = "006"
    $ver = "002"
  }
  else {
    $keyAlgo = "007"
    $ver = "002"
  }

  $data = [ordered]@{
    Ver = $ver
    Type = "001"
    KeyId = $hashModBase64
    KeyAlgo = $keyAlgo
    PvtKey = $null
    KeyExp = "00000000"
    KeyName = $KeyName
    KeyBkupEn = "0"
    CanModKeyBkup = "0"
    CanExport = "0"
    AADRevocation = $AADRevocation
  }

  if ($Model) {
    $data.Model = $Model
  }
  if ($SerialNumber) {
    $data.SerNum = $SerialNumber
  }

  $json = $data | ConvertTo-Json -Compress
  $pvtKeyBase64 = Get-HPPrivateKeyFromCert -Certificate $Certificate -Metadata $json -Passphrase $Passphrase
  $data.PvtKey = $pvtKeyBase64

  $json = $data | ConvertTo-Json -Compress
  return $json
}

<#
.SYNOPSIS
  Generate a QR-Code for transferring the private key from a certificate file to the HP Sure Admin phone app
 
.DESCRIPTION
  This function extracts a private key from the provided certificate file and presents it in a form of QR-Code, which can be scanned with the HP Sure Admin phone app. Once scanned the app can be used for authorizing commands and BIOS setting changes.
 
  Security note: It is recommended to delete the QR-Code file once it is scanned with the app. Keeping the QR-Code stored locally in your computer is not a recommended production pattern since it contains sensitive information that can be used to authorize commands.
 
.PARAMETER LocalAccessKeyFile
  The path to the local access key, as a PFX file. If the PFX file is protected by a password (recommended), the LocalAccessKeyPassword parameter should also be provided.
 
.PARAMETER LocalAccessKeyPassword
  The local access key file password, if required.
 
.PARAMETER Model
  The computer model to be stored with the key in the phone app.
 
.PARAMETER SerialNumber
  The serial number to be stored with the key in the phone app.
 
.PARAMETER OutputFile
  Write the image to a specific file.
  If not specified a temporary file will be created.
 
.PARAMETER Format
  The format of your preference to save the QR-Code image file: Jpeg, Bmp, Png, Svg.
 
.PARAMETER ViewAs
  The 'Default' option creates a local file in your system and starts the default image viewer for presenting the QR-Code image.
  If 'Text' is provided, the QR-Code is displayed by using text characters in your console.
  If 'Image' is provided, the QR-Code image is displayed in the console temporary and once enter is pressed it disappears.
  If 'None' is provided, the QR-Code is not presented to the user. You may want to specify an OutputFile when using this option.
 
.PARAMETER Passphrase
  The password to protect QR-Code content.
 
.NOTES
  - Supported on Windows Power Shell v5.
  - An HP BIOS with HP Sure Admin support is required for applying the payloads generated by this function.
 
.LINK
  [Blog post: HP Secure Platform Management with the HP Client Management Script Library](https://developers.hp.com/hp-client-management/blog/hp-secure-platform-management-hp-client-management-script-library)
 
.EXAMPLE
  Convert-HPSureAdminCertToQRCode -LocalAccessKeyFile "$path\signing_key.pfx"
 
.EXAMPLE
  Convert-HPSureAdminCertToQRCode -Model "PC-Model" -SerialNumber "SN-1234" -LocalAccessKeyFile "$path\signing_key.pfx" -LocalAccessKeyPassword "s3cr3t"
 
.EXAMPLE
  Convert-HPSureAdminCertToQRCode -Model "PC-Model" -SerialNumber "SN-1234" -LocalAccessKeyFile "$path\signing_key.pfx" -Passphrase "s3cr3t" -ViewAs Image
 
.EXAMPLE
  Convert-HPSureAdminCertToQRCode -LocalAccessKeyFile "$path\signing_key.pfx" -Passphrase "s3cr3t" -Format "Svg"
#>

function Convert-HPSureAdminCertToQRCode {
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/convert-hpsureadmincerttoqrcode")]
  param(
    [Parameter(Mandatory = $true,Position = 0)]
    [System.IO.FileInfo]$LocalAccessKeyFile,

    [Parameter(Mandatory = $false,Position = 1)]
    [string]$LocalAccessKeyPassword,

    [Parameter(Mandatory = $false,Position = 2)]
    [string]$Model,

    [Parameter(Mandatory = $false,Position = 3)]
    [string]$SerialNumber,

    [Parameter(Mandatory = $false,Position = 4)]
    [System.IO.FileInfo]$OutputFile,

    [Parameter(Mandatory = $false,Position = 5)]
    [ValidateSet('Jpeg','Bmp','Png','Svg')]
    [string]$Format = "Jpeg",

    [Parameter(Mandatory = $false,Position = 6)]
    [ValidateSet('None','Text','Image','Default')]
    [string]$ViewAs = "Default",

    [Parameter(Mandatory = $false,Position = 7)]
    [string]$Passphrase
  )

  if (-not $Model)
  {
    $Model = Get-HPBIOSSettingValue -Name "Product Name"
  }

  if (-not $SerialNumber)
  {
    $SerialNumber = Get-HPBIOSSettingValue -Name "Serial Number"
  }

  if ($LocalAccessKeyPassword) {
    [securestring]$LocalAccessKeyPassword = ConvertTo-SecureString -AsPlainText -Force $LocalAccessKeyPassword
    $cert = (Get-HPPrivatePublicKeyCertificateFromPFX -FileName $LocalAccessKeyFile -password $LocalAccessKeyPassword).Full
  }
  else {
    $cert = (Get-HPPrivatePublicKeyCertificateFromPFX -FileName $LocalAccessKeyFile).Full
  }

  $data = New-HPPrivateSureAdminEnrollmentJson -Certificate $cert -Model $Model -SerialNumber $SerialNumber -Passphrase $Passphrase
  New-HPPrivateQRCode -Data $data -OutputFile $OutputFile -Format $Format -ViewAs $ViewAs
}

<#
.SYNOPSIS
  Send a local access key in PFX format to HP Sure Admin Key Management Service (KMS)
 
.DESCRIPTION
  This function extracts a private key from the provided certificate file, generates a JSON for the central-managed enrollment process and sends it to the HP Sure Admin Key Management Service (KMS).
  The connection with KMS server requires to the user to authenticate with a valid Microsoft account.
 
.PARAMETER LocalAccessKeyFile
  The path to the local access key, as a PFX file. If the PFX file is protected by a password (recommended),
  the LocalAccessKeyPassword parameter should also be provided.
 
.PARAMETER LocalAccessKeyPassword
  The local access key file password, if required.
 
.PARAMETER KMSAppName
  The application name on Azure KMS server that will be used to compose the URI for uploading the key
 
.PARAMETER KMSUri
  The complete URI for uploading the key (I.e.: https://<KMSAppName>.azurewebsites.net/)
 
.PARAMETER AADGroup
  The group name in Azure Active Directory that will have access to the key
 
.PARAMETER KeyName
  Key name to identify the certificate, if not specified it will use the certificate subject
 
.PARAMETER CacheAccessToken
  This parameter should be specified when uploading multiple keys if the user don't want to re-enter credentials on each call of this function.
  If specified, the access token is cached in msalcache.dat file and won't be asked until it expires.
 
.NOTES
  - Supported on Windows Power Shell v5.
  - An HP Sure Admin KMS server is required for using this feature.
 
.EXAMPLE
  Send-HPSureAdminLocalAccessKeyToKMS -LocalAccessKeyFile "$path\signing_key.pfx" -KMSUri "https://MyKMSURI.azurewebsites.net/" -AADGroup "MyAADGroupName"
 
.EXAMPLE
  Send-HPSureAdminLocalAccessKeyToKMS -LocalAccessKeyFile "$path\signing_key.pfx" -LocalAccessKeyPassword "pass" -KMSAppName "MyAppName" -AADGroup "MyAADGroupName"
#>

function Send-HPSureAdminLocalAccessKeyToKMS {
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/send-hpsureadminlocalaccesskeytokms")]
  param(
    [Parameter(ParameterSetName = "KMSUri",Mandatory = $true,Position = 0)]
    [Parameter(ParameterSetName = "KMSAppName",Mandatory = $true,Position = 0)]
    [System.IO.FileInfo]$LocalAccessKeyFile,

    [Parameter(ParameterSetName = "KMSUri",Mandatory = $false,Position = 1)]
    [Parameter(ParameterSetName = "KMSAppName",Mandatory = $false,Position = 1)]
    [string]$LocalAccessKeyPassword,

    [Parameter(ParameterSetName = "KMSUri",Mandatory = $true,Position = 2)]
    [string]$KMSUri,

    [Parameter(ParameterSetName = "KMSAppName",Mandatory = $true,Position = 2)]
    [string]$KMSAppName,

    [Parameter(ParameterSetName = "KMSUri",Mandatory = $true,Position = 3)]
    [Parameter(ParameterSetName = "KMSAppName",Mandatory = $true,Position = 3)]
    [string]$AADGroup,

    [Parameter(ParameterSetName = "KMSUri",Mandatory = $false,Position = 4)]
    [Parameter(ParameterSetName = "KMSAppName",Mandatory = $false,Position = 4)]
    [switch]$CacheAccessToken,

    [Parameter(ParameterSetName = "KMSUri",Mandatory = $false,Position = 5)]
    [Parameter(ParameterSetName = "KMSAppName",Mandatory = $false,Position = 5)]
    [string]$KeyName
  )

  if (-not $KMSUri) {
    $KMSUri = "https://$KMSAppName.azurewebsites.net/"
  }

  if (-not $KMSUri.contains('api/uploadkey')) {
    if (-not $KMSUri.EndsWith('/')) {
      $KMSUri += '/'
    }
    $KMSUri += 'api/uploadkey'
  }

  if ($LocalAccessKeyPassword) {
    [securestring]$LocalAccessKeyPassword = ConvertTo-SecureString -AsPlainText -Force $LocalAccessKeyPassword
    $cert = (Get-HPPrivatePublicKeyCertificateFromPFX -FileName $LocalAccessKeyFile -password $LocalAccessKeyPassword).Full
  }
  else {
    $cert = (Get-HPPrivatePublicKeyCertificateFromPFX -FileName $LocalAccessKeyFile).Full
  }

  $jsonPayload = New-HPPrivateSureAdminEnrollmentJson -Certificate $cert -AADRevocation $AADGroup -KeyName $KeyName
  $accessToken = Get-HPPrivateSureAdminKMSAccessToken -CacheAccessToken:$CacheAccessToken
  $response,$responseContent = Send-HPPrivateKMSRequest -KMSUri $KMSUri -JsonPayload $jsonPayload -AccessToken $accessToken

  if ($response -ne "OK") {
    Invoke-HPPrivateKMSErrorHandle -ApiResponseContent $responseContent -Status $response
  }
}

<#
.SYNOPSIS
  Add a signing key in PFX format to HP Sure Admin Key Management Service (KMS)
 
.DESCRIPTION
  This function extracts a private key from the provided certificate file, generates a JSON for the central-managed enrollment process and sends it to the HP Sure Admin Key Management Service (KMS).
  The connection with KMS server requires to the user to authenticate with a valid Microsoft account.
 
.PARAMETER SigningKeyFile
  The path to the signing key, as a PFX file. If the PFX file is protected by a password (recommended),
  the SigningKeyPassword parameter should also be provided.
 
.PARAMETER SigningKeyPassword
  The signing key file password, if required.
 
.PARAMETER Model
  The computer model to be stored with the key in the phone app.
 
.PARAMETER SerialNumber
  The serial number to be stored with the key in the phone app.
 
.PARAMETER KMSAppName
  The application name on Azure KMS server that will be used to compose the URI for uploading the key
 
.PARAMETER KMSUri
  The complete URI for uploading the key (I.e.: https://<KMSAppName>.azurewebsites.net/)
 
.PARAMETER AADGroup
  The group name in Azure Active Directory that will have access to the key
 
.PARAMETER CacheAccessToken
  This parameter should be specified when uploading multiple keys if the user don't want to re-enter credentials on each call of this function.
  If specified, the access token is cached in msalcache.dat file and won't be asked until it expires.
 
.NOTES
  - Supported on Windows Power Shell v5.
  - An HP Sure Admin KMS server is required for using this feature.
 
.EXAMPLE
  Add-HPSureAdminSigningKeyToKMS -SigningKeyFile "$path\signing_key.pfx" -KMSUri "https://MyKMSURI.azurewebsites.net/" -AADGroup "MyAADGroupName"
 
.EXAMPLE
  Add-HPSureAdminSigningKeyToKMS -SigningKeyFile "$path\signing_key.pfx" -SigningKeyPassword "pass" -KMSAppName "MyAppName" -AADGroup "MyAADGroupName"
#>

function Add-HPSureAdminSigningKeyToKMS {
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/add-hpsureadminsigningkeytokms")]
  param(
    [Parameter(ParameterSetName = "KMSUri",Mandatory = $true,Position = 0)]
    [Parameter(ParameterSetName = "KMSAppName",Mandatory = $true,Position = 0)]
    [System.IO.FileInfo]$SigningKeyFile,

    [Parameter(ParameterSetName = "KMSUri",Mandatory = $false,Position = 1)]
    [Parameter(ParameterSetName = "KMSAppName",Mandatory = $false,Position = 1)]
    [string]$SigningKeyPassword,

    [Parameter(ParameterSetName = "KMSUri",Mandatory = $true,Position = 3)]
    [string]$KMSUri,

    [Parameter(ParameterSetName = "KMSAppName",Mandatory = $true,Position = 3)]
    [string]$KMSAppName,

    [Parameter(ParameterSetName = "KMSUri",Mandatory = $true,Position = 4)]
    [Parameter(ParameterSetName = "KMSAppName",Mandatory = $true,Position = 4)]
    [string]$AADGroup,

    [Parameter(ParameterSetName = "KMSUri",Mandatory = $true,Position = 5)]
    [Parameter(ParameterSetName = "KMSAppName",Mandatory = $true,Position = 5)]
    [string]$KeyName,

    [Parameter(ParameterSetName = "KMSUri",Mandatory = $false,Position = 6)]
    [Parameter(ParameterSetName = "KMSAppName",Mandatory = $false,Position = 6)]
    [switch]$CacheAccessToken
  )

  if (-not $KMSUri) {
    $KMSUri = "https://$KMSAppName.azurewebsites.net/"
  }
  if (-not $KMSUri.EndsWith('/')) {
    $KMSUri += '/'
  }
  $KMSUri += 'api/signingkeys'

  if ($SigningKeyPassword) {
    [securestring]$SigningKeyPassword = ConvertTo-SecureString -AsPlainText -Force $SigningKeyPassword
    $cert = (Get-HPPrivatePublicKeyCertificateFromPFX -FileName $SigningKeyFile -password $SigningKeyPassword).Full
  }
  else {
    $cert = (Get-HPPrivatePublicKeyCertificateFromPFX -FileName $SigningKeyFile).Full
  }

  $jsonPayload = New-HPPrivateSureAdminEnrollmentJsonVer4 -Certificate $cert -AADRevocation $AADGroup -KeyName $KeyName
  $accessToken = Get-HPPrivateSureAdminKMSAccessToken -CacheAccessToken:$CacheAccessToken
  $response,$responseContent = Send-HPPrivateKMSRequest -KMSUri $KMSUri -JsonPayload $jsonPayload -AccessToken $accessToken

  if ($response -ne "OK") {
    Invoke-HPPrivateKMSErrorHandle -ApiResponseContent $responseContent -Status $response
  }
}


<#
.SYNOPSIS
  Remove a signing key from HP Sure Admin Key Management Service (KMS)
 
.DESCRIPTION
  This function sends a HTTP request to remove the signing key from the HP Sure Admin Key Management Service (KMS).
  The connection with KMS server requires to the user to authenticate with a valid Microsoft account.
 
.PARAMETER SigningKeyId
  The key id encoded in base64 that is used in the server to locate the key.
  Use Get-HPSureAdminKeyId to extract the key id from a pfx certificate.
 
.PARAMETER KMSUri
  The complete URI for uploading the key (I.e.: https://<KMSAppName>.azurewebsites.net/)
 
.PARAMETER CacheAccessToken
  This parameter should be specified when uploading multiple keys if the user don't want to re-enter credentials on each call of this function.
  If specified, the access token is cached in msalcache.dat file and won't be asked until it expires.
 
.NOTES
  - Supported on Windows Power Shell v5.
  - An HP Sure Admin KMS server is required for using this feature.
 
.EXAMPLE
  Remove-HPSureAdminSigningKeyFromKMS -SigningKeyId "<IdInBase64>" -KMSUri "https://MyKMSURI.azurewebsites.net/"
#>

function Remove-HPSureAdminSigningKeyFromKMS {
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/remove-hpsureadminsigningkeyfromkms")]
  param(
    [Parameter(ParameterSetName = "KMSUri",Mandatory = $true,Position = 0)]
    [Parameter(ParameterSetName = "KMSAppName",Mandatory = $true,Position = 0)]
    [string]$SigningKeyId,

    [Parameter(ParameterSetName = "KMSUri",Mandatory = $true,Position = 1)]
    [string]$KMSUri,

    [Parameter(ParameterSetName = "KMSAppName",Mandatory = $true,Position = 1)]
    [string]$KMSAppName,

    [Parameter(ParameterSetName = "KMSUri",Mandatory = $false,Position = 2)]
    [Parameter(ParameterSetName = "KMSAppName",Mandatory = $false,Position = 2)]
    [switch]$CacheAccessToken
  )

  if (-not $KMSUri) {
    $KMSUri = "https://$KMSAppName.azurewebsites.net/"
  }
  if (-not $KMSUri.EndsWith('/')) {
    $KMSUri += '/'
  }
  $KMSUri += 'api/signingkeys'
  $KMSUri = "$KMSUri/$SigningKeyId"

  $accessToken = Get-HPPrivateSureAdminKMSAccessToken -CacheAccessToken:$CacheAccessToken
  $response,$responseContent = Send-HPPrivateKMSRequest -Method 'DELETE' -KMSUri $KMSUri -AccessToken $accessToken

  if ($response -ne "OK") {
    Invoke-HPPrivateKMSErrorHandle -ApiResponseContent $responseContent -Status $response
  }
}

function Invoke-HPPrivateKMSErrorHandle {
  [CmdletBinding()]
  param(
    [string]$ApiResponseContent,
    [string]$Status
  )

  if ($Status -eq 'Not Found') {
    throw "URL not found"
  }

  try {
    $response = $ApiResponseContent | ConvertFrom-Json
  }
  catch {
    Write-Verbose $ApiResponseContent
    throw 'Error code malformed'
  }

  if ($response -and $response.PSObject.Properties.Name -contains 'errorCode') {
    switch ($response.errorCode) {
      # Internal errors codes are suppressed
      default { throw "Error code ($_)" }
    }
  }

  Write-Verbose $ApiResponseContent
  throw "Wrong URL or error code malformed"
}

function Set-HPPrivateSureAdminKMSAccessToken {
  param(
    [string]$AccessToken
  )

  $path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("msalcache.dat")
  $AccessToken | Out-File -FilePath $path -Encoding utf8
  Write-Verbose "Access token saved to cache"
}

<#
.SYNOPSIS
  Clear the KMS access token
 
.DESCRIPTION
  This function clears the access token that is used for sending keys to HP Sure Admin Key Management Service (KMS).
  The token is stored locally in msalcache.dat file when -CacheAccessToken parameter is specified in KMS functions such as Send-HPSureAdminLocalAccessKeyToKMS
 
.EXAMPLE
  Clear-HPSureAdminKMSAccessToken
#>

function Clear-HPSureAdminKMSAccessToken {
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/clear-hpsureadminkmsaccesstoken")]
  param(
  )

  $path = [System.IO.Path]::GetTempPath() + "hp/msalcache.dat"
  Remove-Item -Path $path -ErrorAction Ignore -Force
}

function Get-HPPrivateSureAdminKMSAccessToken {
  [CmdletBinding()]
  param(
    [switch]$CacheAccessToken
  )

  [string]$clientId = "40ef700f-b021-4fe4-81fe-b2536e9701c3"
  [string]$redirectUri = "http://localhost"
  [string[]]$scopes = ("https://graph.microsoft.com/User.Read", "https://graph.microsoft.com/GroupMember.Read.All")

  $clientApplicationBuilder = [Microsoft.Identity.Client.PublicClientApplicationBuilder]::Create($clientId)
  [void]$clientApplicationBuilder.WithRedirectUri($redirectUri)
  [void]$clientApplicationBuilder.WithClientId($clientId)
  $clientApplication = $clientApplicationBuilder.Build()

  if ($CacheAccessToken.IsPresent) {
    [TokenCacheHelper]::EnableSerialization($clientApplication.UserTokenCache)
    $authenticationResult = $null
    try {
      Write-Verbose "Trying to acquire token silently"
      [Microsoft.Identity.Client.IAccount[]]$accounts = $clientApplication.GetAccountsAsync().GetAwaiter().GetResult()
      if ($accounts -and $accounts.Count -gt 0) {
        $authenticationResult = $clientApplication.AcquireTokenSilent($scopes,$accounts[0]).ExecuteAsync().GetAwaiter().GetResult()
      }
    }
    catch {
      Write-Verbose "AcquireTokenSilent Exception: $($_.Exception)"
    }

    if ($authenticationResult) {
      return $authenticationResult.AccessToken
    }
  }
  else {
    Clear-HPSureAdminKMSAccessToken
  }

  # Aquire the access token using the interactive mode
  $aquireToken = $clientApplication.AcquireTokenInteractive($scopes)
  $parentWindow = [System.Diagnostics.Process]::GetCurrentProcess().MainWindowHandle
  [void]$aquireToken.WithParentActivityOrWindow($parentWindow)

  try {
    if ($PSEdition -eq 'Core') {
      # A timeout of two minutes is defined because netcore version of MSAL cannot detect if the user navigates away or simply closes the browser
      $timeout = New-TimeSpan -Minutes 2
      $tokenSource = New-Object System.Threading.CancellationTokenSource
      $taskAuthenticationResult = $aquireToken.ExecuteAsync($tokenSource.Token)
      $endTime = [datetime]::Now.Add($timeout)
      while (!$taskAuthenticationResult.IsCompleted) {
        if ([datetime]::Now -lt $endTime) {
          Start-Sleep -Seconds 1
        }
        else {
          $tokenSource.Cancel()
          throw [System.TimeoutException]"GetMsalTokenFailureOperationTimeout"
        }
      }
      $authenticationResult = $taskAuthenticationResult.Result
    }
    else {
      $authenticationResult = $aquireToken.ExecuteAsync().GetAwaiter().GetResult()
    }
  }
  catch {
    Write-Verbose $_.Exception
    if ($_.Exception.innerException -and $_.Exception.innerException.Message) {
      throw "Could not retrieve a valid access token: " + $_.Exception.innerException.Message
    }
    throw "Could not retrieve a valid access token: " + $_.Exception
  }

  if (-not $authenticationResult) {
    throw "Could not retrieve a valid access token"
  }

  return $authenticationResult.AccessToken
}

function Send-HPPrivateKMSRequest
{
  [CmdletBinding()]
  param(
    [string]$KMSUri,
    [string]$JsonPayload,
    [string]$AccessToken,
    [string]$Method = "POST"
  )

  Write-Verbose "HTTP Request $KMSUri : $Method => $jsonPayload"
  $userAgent = Get-HPPrivateUserAgent
  $request = [System.Net.HttpWebRequest]::Create($KMSUri)
  $request.set_UserAgent($userAgent)
  $request.Method = $Method
  $request.Timeout = -1
  $request.KeepAlive = $true
  $request.ReadWriteTimeout = -1
  $request.Headers.Add("Authorization","Bearer $AccessToken")
  if ($JsonPayload) {
    $content = [System.Text.Encoding]::UTF8.GetBytes($JsonPayload)
    $request.ContentType = "application/json"
    $request.ContentLength = $content.Length
    $stream = $request.GetRequestStream()
    $stream.Write($content,0,$content.Length)
    $stream.Flush()
    $stream.Close()
  }

  try {
    [System.Net.WebResponse]$response = $request.GetResponse()
  }
  catch [System.Net.WebException]{
    Write-Verbose $_.Exception.Message
    $response = $_.Exception.Response
  }

  if ($response.PSObject.Properties.Name -match 'StatusDescription') {
    $statusDescription = $response.StatusDescription
    $receiveStream = $response.GetResponseStream()
    $streamReader = New-Object System.IO.StreamReader $receiveStream
    $responseContent = $streamReader.ReadToEnd()
    $streamReader.Close()
    $streamReader.Dispose()
    Write-Verbose $responseContent
  }

  $response.Close()
  return $statusDescription,$responseContent
}

<#
.SYNOPSIS
  Set one or multiple device permissions on the HP Sure Admin Key Management Service (KMS)
 
.DESCRIPTION
  Device permissions allow IT administrators to manage local access of specific devices without having to provision a unique LAK key for each one.
  This function sends an HTTP request for mapping a device serial number to a user email, or to an AAD group.
  The connection with the KMS server requires the user to authenticate with a valid Microsoft account.
  Existing mappings are modified by the last configuration uploaded.
 
.PARAMETER JsonFile
  The path to the Json file containing multiple device permissions. JSON file must be structured as follows:
  [{"deviceId":"XYZ321","userEmailAddress":"user@kms.onmicrosoft.com","adGroupName":""},
  {"deviceId":"XYZ123","userEmailAddress":"user@kms.onmicrosoft.com"},
  {"deviceId":"ZYX321","adGroupName":"admins"},
  {"deviceId":"ABC000","userEmailAddress":"user@kms.onmicrosoft.com","adGroupName":"admins"}]
 
.PARAMETER KMSAppName
  The application name on Azure KMS server that will be used to compose the URI for uploading the key
 
.PARAMETER KMSUri
  The complete URI for uploading the permissions (I.e.: https://<KMSAppName>.azurewebsites.net/)
 
.PARAMETER CacheAccessToken
  This parameter should be specified when uploading multiple keys if the user don't want to re-enter credentials on each call of this function.
  If specified, the access token is cached in msalcache.dat file and won't be asked until it expires.
 
.NOTES
  - Supported on Windows Power Shell v5.
  - Supported on Windows Power Shell v7.
  - An HP Sure Admin KMS server is required for using this feature.
 
.EXAMPLE
  Set-HPSureAdminDevicePermissions -SerialNumber "XYZ123" -KMSAppName "MyAppName" -UserEmail "myuser@myappname.onmicrosoft.com"
 
.EXAMPLE
  Set-HPSureAdminDevicePermissions -SerialNumber "XYZ123" -KMSUri "https://MyKMSURI.azurewebsites.net/" -AADGroup "MyAADGroupName"
 
.EXAMPLE
  Set-HPSureAdminDevicePermissions -JsonFile MyJsonFile.json -KMSAppName "MyAppName" -CacheAccessToken
#>

function Set-HPSureAdminDevicePermissions {
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/set-hpsureadmindevicepermissions")]
  param(
    [Parameter(ParameterSetName = "KMSUriJsonFile",Mandatory = $true,Position = 1)]
    [string]$KMSUri,

    [Parameter(ParameterSetName = "KMSAppNameJsonFile",Mandatory = $true,Position = 1)]
    [string]$KMSAppName,

    [Parameter(ParameterSetName = "KMSAppNameJsonFile",Mandatory = $true,Position = 2)]
    [Parameter(ParameterSetName = "KMSUriJsonFile",Mandatory = $true,Position = 2)]
    [System.IO.FileInfo]$JsonFile,

    [Parameter(ParameterSetName = "KMSAppNameJsonFile",Mandatory = $false,Position = 3)]
    [Parameter(ParameterSetName = "KMSUriJsonFile",Mandatory = $false,Position = 3)]
    [switch]$CacheAccessToken
  )

  if (-not $KMSUri) {
    $KMSUri = "https://$KMSAppName.azurewebsites.net/"
  }

  if (-not $KMSUri.EndsWith('/')) {
    $KMSUri += '/'
  }
  $KMSUri += 'api/devicekeymappings/upload'

  if ($JsonFile) {
    $f = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($JsonFile)
    [string]$jsonPayload = Get-Content -Raw -Path $f -ErrorAction Stop
  }

  $entries = ($jsonPayload | ConvertFrom-Json)
  foreach ($entry in $entries) {
    if (($entry.PSObject.Properties.Name -match 'userEmailAddress')) {
      if ($entry.userEmailAddress -ne '') {
        Invoke-HPPrivateValidateEmail -EmailAddress $entry.userEmailAddress
      }
    }
  }

  $accessToken = Get-HPPrivateSureAdminKMSAccessToken -CacheAccessToken:$CacheAccessToken
  if ($entries.Count -gt 1) {
    $jsonPayload = $entries | ConvertTo-Json -Compress
  }
  $response,$responseContent = Send-HPPrivateKMSRequest -KMSUri $KMSUri -JsonPayload $jsonPayload -AccessToken $accessToken

  if ($response -ne "OK") {
    Invoke-HPPrivateKMSErrorHandle -ApiResponseContent $responseContent -Status $response
  }
}

<#
.SYNOPSIS
  Add one device permissions to HP Sure Admin Key Management Service (KMS)
 
.DESCRIPTION
  Device permissions allow IT administrators to manage local access of specific devices without having to provision a unique LAK key for each one.
  This function sends an HTTP request for mapping a device serial number to a user email, or to an AAD group.
  The connection with the KMS server requires the user to authenticate with a valid Microsoft account.
  Existing mappings are modified by the last configuration uploaded.
 
.PARAMETER SerialNumber
  The serial number that identifies the device.
 
.PARAMETER KMSAppName
  The application name on Azure KMS server that will be used to compose the URI for uploading the key
 
.PARAMETER KMSUri
  The complete URI for uploading the permissions (I.e.: https://<KMSAppName>.azurewebsites.net/)
 
.PARAMETER AADGroup
  The group name in Azure Active Directory that will have access to the key
 
.PARAMETER UserEmail
  The user email in Azure Active Directory that will have access to the key
 
.PARAMETER CacheAccessToken
  This parameter should be specified when uploading multiple keys if the user don't want to re-enter credentials on each call of this function.
  If specified, the access token is cached in msalcache.dat file and won't be asked until it expires.
 
.NOTES
  - Supported on Windows Power Shell v5.
  - Supported on Windows Power Shell v7.
  - An HP Sure Admin KMS server is required for using this feature.
 
.EXAMPLE
  Add-HPSureAdminDevicePermissions -SerialNumber "XYZ123" -KMSAppName "MyAppName" -UserEmail "myuser@myappname.onmicrosoft.com"
 
.EXAMPLE
  Add-HPSureAdminDevicePermissions -SerialNumber "XYZ123" -KMSUri "https://MyKMSURI.azurewebsites.net/" -AADGroup "MyAADGroupName"
#>

function Add-HPSureAdminDevicePermissions {
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/add-hpsureadmindevicepermissions")]
  param(
    [Parameter(ParameterSetName = "KMSUriBoth",Mandatory = $true,Position = 1)]
    [Parameter(ParameterSetName = "KMSUriAADGroup",Mandatory = $true,Position = 1)]
    [Parameter(ParameterSetName = "KMSUriUserEmail",Mandatory = $true,Position = 1)]
    [string]$KMSUri,

    [Parameter(ParameterSetName = "KMSAppNameBoth",Mandatory = $true,Position = 1)]
    [Parameter(ParameterSetName = "KMSAppNameAADGroup",Mandatory = $true,Position = 1)]
    [Parameter(ParameterSetName = "KMSAppNameUserEmail",Mandatory = $true,Position = 1)]
    [string]$KMSAppName,

    [Parameter(ParameterSetName = "KMSUriBoth",Mandatory = $true,Position = 2)]
    [Parameter(ParameterSetName = "KMSAppNameBoth",Mandatory = $true,Position = 2)]
    [Parameter(ParameterSetName = "KMSUriAADGroup",Mandatory = $true,Position = 2)]
    [Parameter(ParameterSetName = "KMSAppNameAADGroup",Mandatory = $true,Position = 2)]
    [Parameter(ParameterSetName = "KMSUriUserEmail",Mandatory = $true,Position = 2)]
    [Parameter(ParameterSetName = "KMSAppNameUserEmail",Mandatory = $true,Position = 2)]
    [string]$SerialNumber,

    [Parameter(ParameterSetName = "KMSUriBoth",Mandatory = $true,Position = 3)]
    [Parameter(ParameterSetName = "KMSAppNameBoth",Mandatory = $true,Position = 3)]
    [Parameter(ParameterSetName = "KMSUriAADGroup",Mandatory = $true,Position = 3)]
    [Parameter(ParameterSetName = "KMSAppNameAADGroup",Mandatory = $true,Position = 3)]
    [string]$AADGroup,

    [Parameter(ParameterSetName = "KMSUriBoth",Mandatory = $true,Position = 4)]
    [Parameter(ParameterSetName = "KMSAppNameBoth",Mandatory = $true,Position = 4)]
    [Parameter(ParameterSetName = "KMSUriUserEmail",Mandatory = $true,Position = 3)]
    [Parameter(ParameterSetName = "KMSAppNameUserEmail",Mandatory = $true,Position = 3)]
    [string]$UserEmail,

    [Parameter(ParameterSetName = "KMSUriBoth",Mandatory = $false,Position = 5)]
    [Parameter(ParameterSetName = "KMSAppNameBoth",Mandatory = $false,Position = 5)]
    [Parameter(ParameterSetName = "KMSUriAADGroup",Mandatory = $false,Position = 4)]
    [Parameter(ParameterSetName = "KMSAppNameAADGroup",Mandatory = $false,Position = 4)]
    [Parameter(ParameterSetName = "KMSUriUserEmail",Mandatory = $false,Position = 4)]
    [Parameter(ParameterSetName = "KMSAppNameUserEmail",Mandatory = $false,Position = 4)]
    [switch]$CacheAccessToken
  )

  if (-not $KMSUri) {
    $KMSUri = "https://$KMSAppName.azurewebsites.net/"
  }

  if (-not $KMSUri.EndsWith('/')) {
    $KMSUri += '/'
  }
  $KMSUri += 'api/devicekeymappings'

  $params = @{
    SerialNumber = $SerialNumber
  }
  if ($UserEmail) {
    Invoke-HPPrivateValidateEmail -EmailAddress $UserEmail
    $params.UserEmail = $UserEmail
  }
  if ($AADGroup) {
    $params.AADGroup = $AADGroup
  }
  [string]$jsonPayload = New-HPPrivateSureAdminDeviceKeyMappingJson @params

  $accessToken = Get-HPPrivateSureAdminKMSAccessToken -CacheAccessToken:$CacheAccessToken
  $response,$responseContent = Send-HPPrivateKMSRequest -Method 'POST' -KMSUri $KMSUri -JsonPayload $jsonPayload -AccessToken $accessToken

  if ($response -ne "OK") {
    Invoke-HPPrivateKMSErrorHandle -ApiResponseContent $responseContent -Status $response
  }
}

<#
.SYNOPSIS
  Edit one existing device permissions to HP Sure Admin Key Management Service (KMS)
 
.DESCRIPTION
  Device permissions allow IT administrators to manage local access of specific devices without having to provision a unique LAK key for each one.
  This function sends an HTTP request for mapping a device serial number to a user email, or to an AAD group.
  The connection with the KMS server requires the user to authenticate with a valid Microsoft account.
  Existing mappings are modified by the last configuration uploaded.
 
.PARAMETER SerialNumber
  The serial number that identifies the device.
 
.PARAMETER KMSAppName
  The application name on Azure KMS server that will be used to compose the URI for uploading the key
 
.PARAMETER KMSUri
  The complete URI for uploading the permissions (I.e.: https://<KMSAppName>.azurewebsites.net/)
 
.PARAMETER AADGroup
  The group name in Azure Active Directory that will have access to the key
 
.PARAMETER UserEmail
  The user email in Azure Active Directory that will have access to the key
 
.PARAMETER eTag
  The eTag informed by the function Get-HPSureAdminDevicePermissions (see examples)
 
.PARAMETER CacheAccessToken
  This parameter should be specified when uploading multiple keys if the user don't want to re-enter credentials on each call of this function.
  If specified, the access token is cached in msalcache.dat file and won't be asked until it expires.
 
.NOTES
  - Supported on Windows Power Shell v5.
  - Supported on Windows Power Shell v7.
  - An HP Sure Admin KMS server is required for using this feature.
 
.EXAMPLE
  Edit-HPSureAdminDevicePermissions -SerialNumber "XYZ123" -KMSAppName "MyAppName" -UserEmail "myuser@myappname.onmicrosoft.com" -eTag 'W/"datetime''2021-10-22T15%3A17%3A48.9645833Z''"'
 
.EXAMPLE
  $entry = Get-HPSureAdminDevicePermissions -KMSAppName 'MyAppName' -SerialNumber 'XYZ123'
  Edit-HPSureAdminDevicePermissions -SerialNumber "XYZ123" -KMSUri "https://MyKMSURI.azurewebsites.net/" -AADGroup "MyAADGroupName" -eTag $entry.eTag
#>

function Edit-HPSureAdminDevicePermissions {
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/edit-hpsureadmindevicepermissions")]
  param(
    [Parameter(ParameterSetName = "KMSUriBoth",Mandatory = $true,Position = 1)]
    [Parameter(ParameterSetName = "KMSUriAADGroup",Mandatory = $true,Position = 1)]
    [Parameter(ParameterSetName = "KMSUriUserEmail",Mandatory = $true,Position = 1)]
    [string]$KMSUri,

    [Parameter(ParameterSetName = "KMSAppNameBoth",Mandatory = $true,Position = 1)]
    [Parameter(ParameterSetName = "KMSAppNameAADGroup",Mandatory = $true,Position = 1)]
    [Parameter(ParameterSetName = "KMSAppNameUserEmail",Mandatory = $true,Position = 1)]
    [string]$KMSAppName,

    [Parameter(ParameterSetName = "KMSUriBoth",Mandatory = $true,Position = 2)]
    [Parameter(ParameterSetName = "KMSAppNameBoth",Mandatory = $true,Position = 2)]
    [Parameter(ParameterSetName = "KMSUriAADGroup",Mandatory = $true,Position = 2)]
    [Parameter(ParameterSetName = "KMSAppNameAADGroup",Mandatory = $true,Position = 2)]
    [Parameter(ParameterSetName = "KMSUriUserEmail",Mandatory = $true,Position = 2)]
    [Parameter(ParameterSetName = "KMSAppNameUserEmail",Mandatory = $true,Position = 2)]
    [string]$SerialNumber,

    [Parameter(ParameterSetName = "KMSUriBoth",Mandatory = $true,Position = 3)]
    [Parameter(ParameterSetName = "KMSAppNameBoth",Mandatory = $true,Position = 3)]
    [Parameter(ParameterSetName = "KMSUriAADGroup",Mandatory = $true,Position = 3)]
    [Parameter(ParameterSetName = "KMSAppNameAADGroup",Mandatory = $true,Position = 3)]
    [AllowEmptyString()]
    [string]$AADGroup,

    [Parameter(ParameterSetName = "KMSUriBoth",Mandatory = $true,Position = 4)]
    [Parameter(ParameterSetName = "KMSAppNameBoth",Mandatory = $true,Position = 4)]
    [Parameter(ParameterSetName = "KMSUriUserEmail",Mandatory = $true,Position = 3)]
    [Parameter(ParameterSetName = "KMSAppNameUserEmail",Mandatory = $true,Position = 3)]
    [AllowEmptyString()]
    [string]$UserEmail,

    [Parameter(ParameterSetName = "KMSUriBoth",Mandatory = $true,Position = 5)]
    [Parameter(ParameterSetName = "KMSAppNameBoth",Mandatory = $true,Position = 5)]
    [Parameter(ParameterSetName = "KMSUriAADGroup",Mandatory = $true,Position = 4)]
    [Parameter(ParameterSetName = "KMSAppNameAADGroup",Mandatory = $true,Position = 4)]
    [Parameter(ParameterSetName = "KMSUriUserEmail",Mandatory = $true,Position = 4)]
    [Parameter(ParameterSetName = "KMSAppNameUserEmail",Mandatory = $true,Position = 4)]
    [string]$eTag,

    [Parameter(ParameterSetName = "KMSUriBoth",Mandatory = $false,Position = 6)]
    [Parameter(ParameterSetName = "KMSAppNameBoth",Mandatory = $false,Position = 6)]
    [Parameter(ParameterSetName = "KMSUriAADGroup",Mandatory = $false,Position = 5)]
    [Parameter(ParameterSetName = "KMSAppNameAADGroup",Mandatory = $false,Position = 5)]
    [Parameter(ParameterSetName = "KMSUriUserEmail",Mandatory = $false,Position = 5)]
    [Parameter(ParameterSetName = "KMSAppNameUserEmail",Mandatory = $false,Position = 5)]
    [switch]$CacheAccessToken
  )

  if (-not $KMSUri) {
    $KMSUri = "https://$KMSAppName.azurewebsites.net/"
  }

  if (-not $KMSUri.EndsWith('/')) {
    $KMSUri += '/'
  }
  $KMSUri += 'api/devicekeymappings'
  $KMSUri = "$KMSUri/$SerialNumber"

  $params = @{
    eTag = $eTag
  }
  if ($PSBoundParameters.ContainsKey('UserEmail')) {
    if ($UserEmail -ne '') {
      Invoke-HPPrivateValidateEmail -EmailAddress $UserEmail
    }
    $params.UserEmail = $UserEmail
  }
  if ($PSBoundParameters.ContainsKey('AADGroup')) {
    $params.AADGroup = $AADGroup
  }
  [string]$jsonPayload = New-HPPrivateSureAdminDeviceKeyMappingJson @params

  $accessToken = Get-HPPrivateSureAdminKMSAccessToken -CacheAccessToken:$CacheAccessToken
  $response,$responseContent = Send-HPPrivateKMSRequest -Method 'PUT' -KMSUri $KMSUri -JsonPayload $jsonPayload -AccessToken $accessToken

  if ($response -ne "OK") {
    Invoke-HPPrivateKMSErrorHandle -ApiResponseContent $responseContent -Status $response
  }
}

function Invoke-HPPrivateValidateEmail {
  [CmdletBinding()]
  param(
    [string]$EmailAddress
  )

  try {
    New-Object System.Net.Mail.MailAddress ($EmailAddress) | Out-Null
  }
  catch {
    throw "Invalid user email address: $EmailAddress"
  }

  if (-not ($EmailAddress -match '^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$')) {
    throw "Invalid user email address: $EmailAddress"
  }

  return
}

function New-HPPrivateSureAdminDeviceKeyMappingJson {
  [CmdletBinding()]
  param(
    [string]$SerialNumber,
    [string]$UserEmail,
    [string]$AADGroup,
    $ContinuationToken,
    [string]$eTag
  )

  $data = [ordered]@{}

  if ($SerialNumber) {
    $data.deviceId = $SerialNumber
  }

  if ($PSBoundParameters.ContainsKey('UserEmail')) {
    $data.userEmailAddress = $UserEmail
  }

  if ($PSBoundParameters.ContainsKey('AADGroup')) {
    $data.adGroupName = $AADGroup
  }

  if ($eTag) {
    $data.eTag = $eTag
  }

  if ($ContinuationToken) {
    $data.continuationToken = $ContinuationToken
  }

  $json = $data | ConvertTo-Json -Compress
  return $json
}

<#
.SYNOPSIS
  Get device permissions from the HP Sure Admin Key Management Service (KMS)
 
.DESCRIPTION
  Device permissions allow IT administrators to manage local access of specific devices without having to provision a unique LAK key for each one.
  This function retrieves from KMS the permissions set for the specified device serial number.
  The connection with the KMS server requires the user to authenticate with a valid Microsoft account.
 
.PARAMETER SerialNumber
  The serial number that identifies the device.
 
.PARAMETER KMSAppName
  The application name on Azure KMS server that will be used to compose the URI for uploading the key
 
.PARAMETER KMSUri
  The complete URI for uploading the permissions (I.e.: https://<KMSAppName>.azurewebsites.net/)
 
.PARAMETER CacheAccessToken
  This parameter should be specified when uploading multiple keys if the user don't want to re-enter credentials on each call of this function.
  If specified, the access token is cached in msalcache.dat file and won't be asked until it expires.
 
.NOTES
  - Supported on Windows Power Shell v5.
  - Supported on Windows Power Shell v7.
  - An HP Sure Admin KMS server is required for using this feature.
 
.EXAMPLE
  Get-HPSureAdminDevicePermissions -SerialNumber "XYZ123" -KMSAppName "MyAppName"
 
.EXAMPLE
  Get-HPSureAdminDevicePermissions -SerialNumber "XYZ123" -KMSAppName "MyAppName" -CacheAccessToken
#>

function Get-HPSureAdminDevicePermissions {
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/get-hpsureadmindevicepermissions")]
  param(
    [Parameter(ParameterSetName = "KMSUri",Mandatory = $true,Position = 1)]
    [string]$KMSUri,

    [Parameter(ParameterSetName = "KMSAppName",Mandatory = $true,Position = 1)]
    [string]$KMSAppName,

    [Parameter(ParameterSetName = "KMSUri",Mandatory = $true,Position = 2)]
    [Parameter(ParameterSetName = "KMSAppName",Mandatory = $true,Position = 2)]
    [string]$SerialNumber,

    [Parameter(ParameterSetName = "KMSUri",Mandatory = $false,Position = 3)]
    [Parameter(ParameterSetName = "KMSAppName",Mandatory = $false,Position = 3)]
    [switch]$CacheAccessToken
  )

  if (-not $KMSUri) {
    $KMSUri = "https://$KMSAppName.azurewebsites.net/"
  }

  if (-not $KMSUri.EndsWith('/')) {
    $KMSUri += '/'
  }
  $KMSUri += 'api/devicekeymappings'
  $KMSUri = "$KMSUri/$SerialNumber"

  $accessToken = Get-HPPrivateSureAdminKMSAccessToken -CacheAccessToken:$CacheAccessToken
  $response,$responseContent = Send-HPPrivateKMSRequest -KMSUri $KMSUri -Method "GET" -AccessToken $accessToken

  if ($response -ne "OK") {
    Invoke-HPPrivateKMSErrorHandle -ApiResponseContent $responseContent -Status $response
  }

  $responseContent | ConvertFrom-Json
}

<#
.SYNOPSIS
  Search device permissions on HP Sure Admin Key Management Service (KMS)
 
.DESCRIPTION
  Device permissions allow IT administrators to manage local access of specific devices without having to provision a unique LAK key for each one.
  This function retrieves from KMS the permissions set for the specified device serial number.
  The connection with the KMS server requires the user to authenticate with a valid Microsoft account.
 
.PARAMETER SerialNumber
  The serial number that identifies the device.
 
.PARAMETER KMSAppName
  The application name on Azure KMS server that will be used to compose the URI for uploading the key
 
.PARAMETER KMSUri
  The complete URI for uploading the permissions (I.e.: https://<KMSAppName>.azurewebsites.net/)
 
.PARAMETER AADGroup
  The group name in Azure Active Directory that will have access to the key
 
.PARAMETER UserEmail
  The user email in Azure Active Directory that will have access to the key
 
.PARAMETER CacheAccessToken
  This parameter should be specified when uploading multiple keys if the user don't want to re-enter credentials on each call of this function.
  If specified, the access token is cached in msalcache.dat file and won't be asked until it expires.
 
.NOTES
  - Supported on Windows Power Shell v5.
  - Supported on Windows Power Shell v7.
  - An HP Sure Admin KMS server is required for using this feature.
 
.EXAMPLE
  Search-HPSureAdminDevicePermissions -SerialNumber "XYZ123" -KMSAppName "MyAppName"
 
.EXAMPLE
  Search-HPSureAdminDevicePermissions -SerialNumber "XYZ123" -KMSAppName "MyAppName" -CacheAccessToken
#>

function Search-HPSureAdminDevicePermissions {
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/search-hpsureadmindevicepermissions")]
  param(
    [Parameter(ParameterSetName = "KMSUriBoth",Mandatory = $true,Position = 1)]
    [Parameter(ParameterSetName = "KMSUriAADGroup",Mandatory = $true,Position = 1)]
    [Parameter(ParameterSetName = "KMSUriUserEmail",Mandatory = $true,Position = 1)]
    [Parameter(ParameterSetName = "KMSUriSerialNumber",Mandatory = $true,Position = 1)]
    [string]$KMSUri,

    [Parameter(ParameterSetName = "KMSAppNameBoth",Mandatory = $true,Position = 1)]
    [Parameter(ParameterSetName = "KMSAppNameAADGroup",Mandatory = $true,Position = 1)]
    [Parameter(ParameterSetName = "KMSAppNameUserEmail",Mandatory = $true,Position = 1)]
    [Parameter(ParameterSetName = "KMSAppNameSerialNumber",Mandatory = $true,Position = 1)]
    [string]$KMSAppName,

    [Parameter(ParameterSetName = "KMSUriBoth",Mandatory = $false,Position = 2)]
    [Parameter(ParameterSetName = "KMSAppNameBoth",Mandatory = $false,Position = 2)]
    [Parameter(ParameterSetName = "KMSUriAADGroup",Mandatory = $false,Position = 2)]
    [Parameter(ParameterSetName = "KMSAppNameAADGroup",Mandatory = $false,Position = 2)]
    [Parameter(ParameterSetName = "KMSUriUserEmail",Mandatory = $false,Position = 2)]
    [Parameter(ParameterSetName = "KMSAppNameUserEmail",Mandatory = $false,Position = 2)]
    [Parameter(ParameterSetName = "KMSUriSerialNumber",Mandatory = $true,Position = 2)]
    [Parameter(ParameterSetName = "KMSAppNameSerialNumber",Mandatory = $true,Position = 2)]
    [string]$SerialNumber,

    [Parameter(ParameterSetName = "KMSUriBoth",Mandatory = $true,Position = 3)]
    [Parameter(ParameterSetName = "KMSAppNameBoth",Mandatory = $true,Position = 3)]
    [Parameter(ParameterSetName = "KMSUriAADGroup",Mandatory = $true,Position = 3)]
    [Parameter(ParameterSetName = "KMSAppNameAADGroup",Mandatory = $true,Position = 3)]
    [string]$AADGroup,

    [Parameter(ParameterSetName = "KMSUriBoth",Mandatory = $true,Position = 4)]
    [Parameter(ParameterSetName = "KMSAppNameBoth",Mandatory = $true,Position = 4)]
    [Parameter(ParameterSetName = "KMSUriUserEmail",Mandatory = $true,Position = 3)]
    [Parameter(ParameterSetName = "KMSAppNameUserEmail",Mandatory = $true,Position = 3)]
    [string]$UserEmail,

    [Parameter(ParameterSetName = "KMSUriBoth",Mandatory = $false,Position = 5)]
    [Parameter(ParameterSetName = "KMSAppNameBoth",Mandatory = $false,Position = 5)]
    [Parameter(ParameterSetName = "KMSUriAADGroup",Mandatory = $false,Position = 4)]
    [Parameter(ParameterSetName = "KMSAppNameAADGroup",Mandatory = $false,Position = 4)]
    [Parameter(ParameterSetName = "KMSUriUserEmail",Mandatory = $false,Position = 4)]
    [Parameter(ParameterSetName = "KMSAppNameUserEmail",Mandatory = $false,Position = 4)]
    [Parameter(ParameterSetName = "KMSUriSerialNumber",Mandatory = $true,Position = 3)]
    [Parameter(ParameterSetName = "KMSAppNameSerialNumber",Mandatory = $true,Position = 3)]
    [switch]$CacheAccessToken
  )

  if (-not $KMSUri) {
    $KMSUri = "https://$KMSAppName.azurewebsites.net/"
  }

  if (-not $KMSUri.EndsWith('/')) {
    $KMSUri += '/'
  }
  $KMSUri += 'api/devicekeymappings/search'

  $params = @{}
  if ($SerialNumber) {
    $params.SerialNumber = $SerialNumber
  }
  if ($UserEmail) {
    $params.UserEmail = $UserEmail
  }
  if ($AADGroup) {
    $params.AADGroup = $AADGroup
  }

  do {
    [string]$jsonPayload = New-HPPrivateSureAdminDeviceKeyMappingJson @params
    $accessToken = Get-HPPrivateSureAdminKMSAccessToken -CacheAccessToken:$CacheAccessToken
    $response,$responseContent = Send-HPPrivateKMSRequest -Method 'POST' -KMSUri $KMSUri -JsonPayload $jsonPayload -AccessToken $accessToken

    if ($response -ne "OK") {
      Invoke-HPPrivateKMSErrorHandle -ApiResponseContent $responseContent -Status $response
    }

    $response = ($responseContent | ConvertFrom-Json)
    $response.deviceKeyMappings

    $params.continuationToken = $response.continuationToken
  } while ($response.continuationToken)
}

<#
.SYNOPSIS
  Remove a device permission from the HP Sure Admin Key Management Service (KMS)
 
.DESCRIPTION
  Device permissions allow IT administrators to manage local access of specific devices without having to provision a unique LAK key for each one.
  This function removes from KMS the permissions set for the specified device serial number.
  The connection with the KMS server requires the user to authenticate with a valid Microsoft account.
 
.PARAMETER SerialNumber
  The serial number that identifies the device.
 
.PARAMETER KMSAppName
  The application name on Azure KMS server that will be used to compose the URI for uploading the key
 
.PARAMETER KMSUri
  The complete URI for uploading the permissions (I.e.: https://<KMSAppName>.azurewebsites.net/)
 
.PARAMETER CacheAccessToken
  This parameter should be specified when uploading multiple keys if the user don't want to re-enter credentials on each call of this function.
  If specified, the access token is cached in msalcache.dat file and won't be asked until it expires.
 
.NOTES
  - Supported on Windows Power Shell v5.
  - Supported on Windows Power Shell v7.
  - An HP Sure Admin KMS server is required for using this feature.
 
.EXAMPLE
  Remove-HPSureAdminDevicePermissions -SerialNumber "XYZ123" -KMSAppName "MyAppName"
 
.EXAMPLE
  Remove-HPSureAdminDevicePermissions -SerialNumber "XYZ123" -KMSAppName "MyAppName" -CacheAccessToken
#>

function Remove-HPSureAdminDevicePermissions {
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/remove-hpsureadmindevicepermissions")]
  param(
    [Parameter(ParameterSetName = "KMSUri",Mandatory = $true,Position = 1)]
    [string]$KMSUri,

    [Parameter(ParameterSetName = "KMSAppName",Mandatory = $true,Position = 1)]
    [string]$KMSAppName,

    [Parameter(ParameterSetName = "KMSUri",Mandatory = $true,Position = 2)]
    [Parameter(ParameterSetName = "KMSAppName",Mandatory = $true,Position = 2)]
    [string]$SerialNumber,

    [Parameter(ParameterSetName = "KMSUri",Mandatory = $false,Position = 3)]
    [Parameter(ParameterSetName = "KMSAppName",Mandatory = $false,Position = 3)]
    [switch]$CacheAccessToken
  )

  if (-not $KMSUri) {
    $KMSUri = "https://$KMSAppName.azurewebsites.net/"
  }

  if (-not $KMSUri.EndsWith('/')) {
    $KMSUri += '/'
  }
  $KMSUri += 'api/devicekeymappings'
  $KMSUri = "$KMSUri/$SerialNumber"

  $accessToken = Get-HPPrivateSureAdminKMSAccessToken -CacheAccessToken:$CacheAccessToken
  $response,$responseContent = Send-HPPrivateKMSRequest -KMSUri $KMSUri -Method "DELETE" -AccessToken $accessToken

  if ($response -ne "OK") {
    Invoke-HPPrivateKMSErrorHandle -ApiResponseContent $responseContent -Status $response
  }
}

function New-HPPrivateQRCode {
  param(
    [string]$Data,
    [System.IO.FileInfo]$OutputFile,
    [ValidateSet('Jpeg','Bmp','Png','Svg')]
    [string]$Format = "Jpeg",
    [ValidateSet('None','Text','Image','Default')]
    [string]$ViewAs = "Default"
  )

  [void][System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

  $QRCode = [System.Byte[]]::CreateInstance([System.Byte],3918)
  switch (Test-OSBitness) {
    32 { $result = [DfmNativeQRCode]::create_qrcode32($data,$QRCode) }
    64 { $result = [DfmNativeQRCode]::create_qrcode64($data,$QRCode) }
  }

  $width = $height = $QRCode[0]
  $RGBBuffer = Convert-HPPrivateQRCodeToRGBBuffer -QRCode $QRCode

  [System.Drawing.Image]$img = New-HPPrivateImageFromRGBBuffer -RGBBuffer $RGBBuffer -Width $width -Height $height
  [System.Drawing.Image]$newImg = New-HPPrivateImageScale -Image $img -Width 250 -Height 250 -Border 10
  $img.Dispose()

  $path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputFile)
  if ($ViewAs -eq "Default" -and $OutputFile -eq $null)
  {
    $temp = [string][math]::Floor([decimal](Get-Date (Get-Date).ToUniversalTime() -UFormat "%s")) + "." + $Format
    $path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($temp)
  }

  if ($OutputFile -or $ViewAs -eq "Default") {
    if ($Format -eq "Svg") {
      Invoke-HPPrivateWriteQRCodeToSvgFile -QRCode $QRCode -Path $path
    }
    else {
      $newImg.Save($path,[System.Drawing.Imaging.ImageFormat]::$Format)
    }
  }

  if ($ViewAs) {
    if ($ViewAs -eq "Text") {
      Invoke-HPPrivateWriteSmallQRCodeToConsole -QRCode $QRCode
    }
    elseif ($ViewAs -eq "Image") {
      Invoke-HPPrivateDisplayQRCodeForm -Image $newImg -Width $newImg.Width -Height $newImg.Height
    }
    elseif ($ViewAs -eq "Default") {
      Start-Process $path
      Write-Host "The file $path contains sensitive information; please delete it once you have scanned with HP Sure Admin phone app"
      if ($OutputFile -eq $null) {
        Start-Sleep -Seconds 5
        Remove-Item -Path $path -ErrorAction Ignore -Force
        Write-Host "The file was deleted; please specify an -OutputFile to keep it"
      }
    }
  }

  $newImg.Dispose()
}

function Get-HPPrivateQRCodeModule {
  param(
    [byte[]]$QRCode,
    [int]$X,
    [int]$Y
  )

  $size = $QRCode[0]
  if (0 -le $X -and $X -lt $size -and 0 -le $Y -and $Y -lt $size) {
    $index = $Y * $size + $X;
    $k = $QRCode[(($index -shr 3) + 1)]
    $i = $index -band 7
    if ((($k -shr $i) -band 1) -ne 0) {
      return $true
    }
  }

  return $false
}

function Convert-HPPrivateQRCodeToRGBBuffer {
  param(
    [byte[]]$QRCode
  )

  $len = $QRCode[0]
  $channels = 3
  $size = $len * $len * $channels #RGB color channels
  [byte[]]$RGBBuffer = [byte[]]::CreateInstance([byte],$size)
  for ($y = 0; $y -lt $len; $y++) {
    for ($x = 0; $x -lt $len; $x++) {
      $index = (($x * $len) + $y) * $channels
      if ((Get-HPPrivateQRCodeModule -QRCode $QRCode -X $x -Y $y) -eq $false) {
        $RGBBuffer[$index + 0] = 0xFF
        $RGBBuffer[$index + 1] = 0xFF
        $RGBBuffer[$index + 2] = 0xFF
      }
    }
  }

  return $RGBBuffer
}

function Invoke-HPPrivateWriteSmallQRCodeToConsole {
  param(
    [byte[]]$QRCode
  )

  $white = ([char]0x2588)
  $black = ' '
  $whiteBlack = ([char]0x2580)
  $blackWhite = ([char]0x2584)

  $size = $QRCode[0]
  Write-Host "`n"

  Write-Host -NoNewline " "
  for ($x = 0; $x -lt $size + 2; $x++) {
    Write-Host -NoNewline $blackWhite
  }
  Write-Host ""

  for ($y = 0; $y -lt $size; $y += 2) {
    Write-Host -NoNewline " "
    for ($x = -1; $x -lt $size + 1; $x++) {
      if (-not (Get-HPPrivateQRCodeModule -QRCode $QRCode -X $x -Y $y) -and
        -not (Get-HPPrivateQRCodeModule -QRCode $QRCode -X $x -Y ($y + 1))) {
        Write-Host -NoNewline $white
      }
      elseif (-not (Get-HPPrivateQRCodeModule -QRCode $QRCode -X $x -Y $y) -and
        (Get-HPPrivateQRCodeModule -QRCode $QRCode -X $x -Y ($y + 1))) {
        Write-Host -NoNewline $whiteBlack
      }
      elseif ((Get-HPPrivateQRCodeModule -QRCode $QRCode -X $x -Y $y) -and
        -not (Get-HPPrivateQRCodeModule -QRCode $QRCode -X $x -Y ($y + 1))) {
        Write-Host -NoNewline $blackWhite
      }
      else {
        Write-Host -NoNewline $black
      }
    }
    Write-Host ""
  }
  Write-Host "`n"
}

function Invoke-HPPrivateWriteQRCodeToSvgFile {
  param(
    [byte[]]$QRCode,
    [string]$Path
  )

  $border = 2
  $size = $QRCode[0]
  $content = ('<?xml version="1.0" encoding="UTF-8"?>' +
    '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">' +
    '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="-' + $border + ' -' + $border + ' ' + ($size + $border * 2) + ' ' + ($size + $border * 2) + '" stroke="none">' +
    '<rect width="90%" height="90%" fill="#FFFFFF" dominant-baseline="central" />' +
    '<path d="')

  for ($y = 0; $y -lt $size; $y++) {
    for ($x = 0; $x -lt $size; $x++) {
      if ((Get-HPPrivateQRCodeModule -QRCode $QRCode -X $x -Y $y) -eq $true) {
        if ($x -ne 0 -or $y -ne 0) {
          $content += ' '
        }
        $content += 'M' + $x + ',' + $y + 'h1v1h-1z'
      }
    }
  }
  $content += ('" fill="#000000" />' +
    '</svg>')

  $content | Out-File -FilePath $Path -Encoding utf8
}

function New-HPPrivateImageScale {
  param(
    [System.Drawing.Image]$Image,
    [int]$Width,
    [int]$Height,
    [int]$Border = 10
  )

  $newImage = New-Object System.Drawing.Bitmap (($Width + $border * 2),($Height + $border * 2),[System.Drawing.Imaging.PixelFormat]::Format24bppRgb)
  $graphics = [System.Drawing.Graphics]::FromImage($newImage)
  $graphics.Clear([System.Drawing.Color]::White)
  $graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::NearestNeighbor
  $graphics.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::Half
  $graphics.DrawImage($Image,$border,$border,$Width,$Height)
  $graphics.Flush()
  $graphics.Dispose()

  return $newImage
}

function New-HPPrivateImageFromRGBBuffer {
  param(
    [byte[]]$RGBBuffer,
    [int]$Width,
    [int]$Height,
    [int]$Border = 10
  )

  $img = New-Object System.Drawing.Bitmap ($Width,$Height,[System.Drawing.Imaging.PixelFormat]::Format24bppRgb)
  $rect = New-Object System.Drawing.Rectangle (0,0,$img.Width,$img.Height)
  $bmpData = $img.LockBits($rect,[System.Drawing.Imaging.ImageLockMode]::ReadWrite,$img.PixelFormat)
  $bufferStride = $img.Width * 3
  $targetStride = $bmpData.Stride
  $imgPtr = $bmpData.Scan0.ToInt64()

  for ($y = 0; $y -lt $img.Height; $y++) {
    [System.Runtime.InteropServices.Marshal]::Copy($RGBBuffer,$y * $bufferStride,[IntPtr]($imgPtr + $y * $targetStride),$bufferStride)
  }
  $img.UnlockBits($bmpData)

  return $img
}

function Get-HPPrivateConsoleFontSize {
  param()

  $width = 0
  switch (Test-OSBitness) {
    32 { $width = [DfmNativeQRCode]::get_console_font_width32() }
    64 { $width = [DfmNativeQRCode]::get_console_font_width64() }
  }

  $height = 0
  switch (Test-OSBitness) {
    32 { $height = [DfmNativeQRCode]::get_console_font_height32() }
    64 { $height = [DfmNativeQRCode]::get_console_font_height64() }
  }

  return $width,$height
}

function Get-HPPrivateScreenScale {
  param()

  $result = 0
  switch (Test-OSBitness) {
    32 { $result = [DfmNativeQRCode]::get_screen_scale32() }
    64 { $result = [DfmNativeQRCode]::get_screen_scale64() }
  }

  return $result
}

function Invoke-HPPrivateDisplayQRCodeForm {
  param(
    [System.Drawing.Image]$Image,
    [int]$Width,
    [int]$Height
  )
  [void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

  $screenScale = Get-HPPrivateScreenScale
  $fontWidth,$fontHeight = (Get-HPPrivateConsoleFontSize)
  $cursorPosition = $host.UI.RawUI.CursorPosition.Y
  $windowHandle = [System.Diagnostics.Process]::GetCurrentProcess()[0].MainWindowHandle

  $imgSpace = $Height / $fontHeight
  for ($i = 0; $i -lt $imgSpace + 3; $i++) { Write-Host "" }
  Write-Host "Press Enter once you have scanned the QR-Code..."
  #Write-Host "Screen Scale:" $screenScale, "Width:" $Width, "Height:" $Height, "fontHeight:" $fontHeight
  $newWindowPosition = $host.UI.RawUI.WindowPosition.Y

  [System.Windows.Forms.Application]::EnableVisualStyles()
  $form = New-Object System.Windows.Forms.Form
  $form.ControlBox = $false
  $form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedToolWindow
  $form.ShowInTaskbar = $false
  $form.Width = $Width
  $form.Height = $Height
  $form.StartPosition = [System.Windows.Forms.FormStartPosition]::Manual
  $form.Add_KeyDown({
      if ($_.KeyCode -eq 'Escape' -or $_.KeyCode -eq 'Enter') {
        $form.Close()
      }
    })

  $pictureBox = New-Object System.Windows.Forms.PictureBox
  $pictureBox.Width = $Width
  $pictureBox.Height = $Height
  $pictureBox.Image = $Image
  $pictureBox.SizeMode = [System.Windows.Forms.PictureBoxSizeMode]::Zoom

  $form.controls.Add($pictureBox);

  $rect = New-Object RECT
  $status = [Win32Window]::GetWindowRect($windowHandle,[ref]$rect)
  $windowWidth = $host.UI.RawUI.WindowSize.Width * $fontWidth
  $windowHeight = $host.UI.RawUI.WindowSize.Height * $fontHeight

  $topMargin = $fontHeight * (4 + ($cursorPosition - $newWindowPosition))
  $leftMargin = $fontWidth * $screenScale
  if (($Width + $leftMargin) -gt $windowWidth)
  {
    $form.Width = $windowWidth
  }

  $top = [int]($rect.Top + $topMargin)
  $left = [int]($rect.Left + $leftMargin)
  $form.Location = New-Object System.Drawing.Point ($left,$top)

  $caller = New-Object Win32Window -ArgumentList $windowHandle
  [void]$form.ShowDialog($caller)
}

# SIG # Begin signature block
# MIIuAwYJKoZIhvcNAQcCoIIt9DCCLfACAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAQZ1k2NCv2J79l
# HiysRTIomp1GMUs0XXmHAAEyijGxHqCCE2wwggXAMIIEqKADAgECAhAP0bvKeWvX
# +N1MguEKmpYxMA0GCSqGSIb3DQEBCwUAMGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKzApBgNV
# BAMTIkRpZ2lDZXJ0IEhpZ2ggQXNzdXJhbmNlIEVWIFJvb3QgQ0EwHhcNMjIwMTEz
# MDAwMDAwWhcNMzExMTA5MjM1OTU5WjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMM
# RGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQD
# ExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqGSIb3DQEBAQUAA4IC
# DwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEppz1Yq3aa
# za57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllV
# cq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT
# +CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd
# 463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+
# EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/CNdaSaTC5qmgZ92k
# J7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtmmnTK3kse5w5j
# rubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7
# f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJU
# KSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+wh
# X8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQAB
# o4IBZjCCAWIwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5n
# P+e6mK4cD08wHwYDVR0jBBgwFoAUsT7DaQP4v0cB1JgmGggC72NkK8MwDgYDVR0P
# AQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMH8GCCsGAQUFBwEBBHMwcTAk
# BggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEkGCCsGAQUFBzAC
# hj1odHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRIaWdoQXNzdXJh
# bmNlRVZSb290Q0EuY3J0MEsGA1UdHwREMEIwQKA+oDyGOmh0dHA6Ly9jcmwzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydEhpZ2hBc3N1cmFuY2VFVlJvb3RDQS5jcmwwHAYD
# VR0gBBUwEzAHBgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcNAQELBQADggEBAEHx
# qRH0DxNHecllao3A7pgEpMbjDPKisedfYk/ak1k2zfIe4R7sD+EbP5HU5A/C5pg0
# /xkPZigfT2IxpCrhKhO61z7H0ZL+q93fqpgzRh9Onr3g7QdG64AupP2uU7SkwaT1
# IY1rzAGt9Rnu15ClMlIr28xzDxj4+87eg3Gn77tRWwR2L62t0+od/P1Tk+WMieNg
# GbngLyOOLFxJy34riDkruQZhiPOuAnZ2dMFkkbiJUZflhX0901emWG4f7vtpYeJa
# 3Cgh6GO6Ps9W7Zrk9wXqyvPsEt84zdp7PiuTUy9cUQBY3pBIowrHC/Q7bVUx8ALM
# R3eWUaNetbxcyEMRoacwggawMIIEmKADAgECAhAIrUCyYNKcTJ9ezam9k67ZMA0G
# CSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0
# IFRydXN0ZWQgUm9vdCBHNDAeFw0yMTA0MjkwMDAwMDBaFw0zNjA0MjgyMzU5NTla
# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE
# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz
# ODQgMjAyMSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDVtC9C
# 0CiteLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYwn6SOaNhc9es0JAfhS0/TeEP0F9ce
# 2vnS1WcaUk8OoVf8iJnBkcyBAz5NcCRks43iCH00fUyAVxJrQ5qZ8sU7H/Lvy0da
# E6ZMswEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1hz1RGeiQIXhFLqGfLOEYwhrMxe6T
# SXBCMo/7xuoc82VokaJNTIIRSFJo3hC9FFdd6BgTZcV/sk+FLEikVoQ11vkunKoA
# FdE3/hoGlMJ8yOobMubKwvSnowMOdKWvObarYBLj6Na59zHh3K3kGKDYwSNHR7Oh
# D26jq22YBoMbt2pnLdK9RBqSEIGPsDsJ18ebMlrC/2pgVItJwZPt4bRc4G/rJvmM
# 1bL5OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYoX7BzzosmJQayg9Rc9hUZTO1i4F4z
# 8ujo7AqnsAMrkbI2eb73rQgedaZlzLvjSFDzd5Ea/ttQokbIYViY9XwCFjyDKK05
# huzUtw1T0PhH5nUwjewwk3YUpltLXXRhTT8SkXbev1jLchApQfDVxW0mdmgRQRNY
# mtwmKwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZYIpkVMHMIRroOBl8ZhzNeDhFMJlP
# /2NPTLuqDQhTQXxYPUez+rbsjDIJAsxsPAxWEQIDAQABo4IBWTCCAVUwEgYDVR0T
# AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHwYD
# VR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMG
# A1UdJQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYY
# aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2Fj
# ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNV
# HR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRU
# cnVzdGVkUm9vdEc0LmNybDAcBgNVHSAEFTATMAcGBWeBDAEDMAgGBmeBDAEEATAN
# BgkqhkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6PvDqZ01bgAhql+Eg08yy25nRm95Ry
# sQDKr2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V1T9J9Ce7FoFFUP2cvbaF4HZ+N3HL
# IvdaqpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+3NiAGhEZGM1hmYFW9snjdufE5Btf
# Q/g+lP92OT2e1JnPSt0o618moZVYSNUa/tcnP/2Q0XaG3RywYFzzDaju4ImhvTnh
# OE7abrs2nfvlIVNaw8rpavGiPttDuDPITzgUkpn13c5UbdldAhQfQDN8A+KVssIh
# dXNSy0bYxDQcoqVLjc1vdjcshT8azibpGL6QB7BDf5WIIIJw8MzK7/0pNVwfiThV
# 9zeKiwmhywvpMRr/LhlcOXHhvpynCgbWJme3kuZOX956rEnPLqR0kq3bPKSchh/j
# wVYbKyP/j7XqiHtwa+aguv06P0WmxOgWkVKLQcBIhEuWTatEQOON8BUozu3xGFYH
# Ki8QxAwIZDwzj64ojDzLj4gLDb879M4ee47vtevLt/B3E+bnKD+sEq6lLyJsQfmC
# XBVmzGwOysWGw/YmMwwHS6DTBwJqakAwSEs0qFEgu60bhQjiWQ1tygVQK+pKHJ6l
# /aCnHwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0YW6/aOImYIbqyK+p/pQd52MbOoZW
# eE4wggbwMIIE2KADAgECAhAI+qTPsJ3byDJ7SsgX0LBUMA0GCSqGSIb3DQEBCwUA
# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE
# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz
# ODQgMjAyMSBDQTEwHhcNMjIwMzA5MDAwMDAwWhcNMjMwMzA5MjM1OTU5WjB1MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTESMBAGA1UEBxMJUGFsbyBB
# bHRvMRAwDgYDVQQKEwdIUCBJbmMuMRkwFwYDVQQLExBIUCBDeWJlcnNlY3VyaXR5
# MRAwDgYDVQQDEwdIUCBJbmMuMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKC
# AYEA2KwFARbsSL8FnMdZ++xo7iVdqg+ZOY0S2KkvYQdNNcvrcfHTdNpNgf65RuIt
# VQxdJXzmZcAOXJUPjRQRduvFf/I8jqR4UwBLsNoy/sEuQIDCfezNSQz8TPredjUG
# Lr6Y9ie1vYryqJ110Mj6NtXZQidlytEneq3z73Ec7TRFKp8iiiwNpTcbhAq93pq6
# bjnc98ajFUBHJu9Gfk1Or3haR6m7YH0LRLVWm18I2OKrcPLk67hWRj6Aa7/heBkk
# F8TfGCUwGBHhblrprBVECR3M4zTnMygBfxVEzYsdyAytPy0DgqzZ7+rHY0yvgDUx
# Fi/d1SyqNDCf6FBBudNjzw7TULEBHlJjk96xhd1z4X5ctL1kW4duC7Mba6H8A1lI
# qM5qa+8Fr88IJhnl21PlkBp+XAk3lBaeJ/DVpORIv3bhUV8OLae6ElQBGvqQoEY/
# AaNerghhFjiqAhaUG3z3Y7ruhVaCmuw/SMVS79dxESj/J1qHWVnF1tn2a4liq/RY
# VeFTAgMBAAGjggIGMIICAjAfBgNVHSMEGDAWgBRoN+Drtjv4XxGG+/5hewiIZfRO
# QjAdBgNVHQ4EFgQUAjIiVx974XGZre7F5HqNCJiWZbowDgYDVR0PAQH/BAQDAgeA
# MBMGA1UdJQQMMAoGCCsGAQUFBwMDMIG1BgNVHR8Ega0wgaowU6BRoE+GTWh0dHA6
# Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5n
# UlNBNDA5NlNIQTM4NDIwMjFDQTEuY3JsMFOgUaBPhk1odHRwOi8vY3JsNC5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEz
# ODQyMDIxQ0ExLmNybDA+BgNVHSAENzA1MDMGBmeBDAEEATApMCcGCCsGAQUFBwIB
# FhtodHRwOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwgZQGCCsGAQUFBwEBBIGHMIGE
# MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wXAYIKwYBBQUH
# MAKGUGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRH
# NENvZGVTaWduaW5nUlNBNDA5NlNIQTM4NDIwMjFDQTEuY3J0MAwGA1UdEwEB/wQC
# MAAwDQYJKoZIhvcNAQELBQADggIBAFrOPeL4ph8SmHwwcUQO7nPnapyOS0I50w70
# nVZ9CtrgyA7hiZmVm/CsC1JU8zg1dNyfH7wCDaoMAnqtybcdmhIXc4STwfcpiKOH
# nL3fRQcZ2zCCXmX5lkWYWni9Nqx603JQ8yiUSl1sMyv0Cd4RasOBHnjQuekDDKNT
# QvOiEA3NCZDGEjtIjE+TGqLW2kUEtjxzyr0mnhmidRaHry5C1GKu0mlKExwabOLW
# xGrXj4FPtKmWXZh00lMbbdeHm1Zqn9CTsO6xt8CQXSemcpb7lXY80um71wQO23ub
# tQGDe4QpShomqPmEIVxM5/B6Yih/0Lb8mt60SLfT5EOVS/Dhd86lSHcncL9JLxaq
# WwbQhIwpEa4b3MiZqyemqb0+YIBn5yG43M4oLzRPTo2mPwG19OtnMVZsrcjGEzLz
# EiBb9/YXsf8G5LAh86x2kRKDad35NNNojUJYVBtD7MGEsL37XF+6kWXsp92on2b2
# QLEL/5ZzJHmfrJ8m0TXMb4sMSI2KnHtCvEjG2MIAnjFEvNZ1ZFsKS78mwylDyHL0
# yTuv08JqDuommKgjmyvtLEeb6OYsOnSVQIcyV4XCY1kFA8mDuIsIlbWE3Nyv94Of
# N+4jNKcDzniYb5LmKlXraIM8PjPpYb34DlNpzCDN7/tJuMFsy/NwArj1SiL630mg
# Dm0fS5OgMYIZ7TCCGekCAQEwfTBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT
# aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExAhAI+qTPsJ3byDJ7SsgX0LBU
# MA0GCWCGSAFlAwQCAQUAoHwwEAYKKwYBBAGCNwIBDDECMAAwGQYJKoZIhvcNAQkD
# MQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJ
# KoZIhvcNAQkEMSIEIBWAzJcbs1a19y3tlzmDgYfmTu4YyPdT2VMK1A8V0Yh1MA0G
# CSqGSIb3DQEBAQUABIIBgIB0iAOL6cGPhMw6Mvx2o/kClQyS/JXevIoPF/lKQ08u
# KewG7bWu92GbeUx8o52Lob/NW/SRtTns2E044YxV5hWXCknrV7zEHocuoNxprahC
# MA6PWLE8PPEoiPzMStFdCc1zdf2R8N8ybvFjKVs3opAvYwpZ5MYrUAKOyZU/YRMH
# 7VsFl+TtiOTuNJEmtabLsgW76v/iBPczuupqCJWm4Fr0R04sMcuXoGk+E6G9mv3+
# RZ8ik89Cp19LXDRXurM8kCG97hO4npDvg5laJv5xWcsqrgsCK+z1hcZB5lheAqsu
# r91oX98UUH3b72VI3wbC72mq4P4dzZZFiAmRanzaXtzmZgA33H255SOa/DL1u3JG
# S8457N2alIiFdUvk2LTpBdVl8ZFebj4t34SZ5eyJK7pX61/aXt3eHrLkzxzSJ1Ee
# 5dkcPnJo2uSXgwBo8jiQdrySWcAUw/1Hih3d3ISgaumn9VHT5lltu1SJrNZ3HSom
# tqo/eyyNUlaJj5vvE002PaGCF0Mwghc/BgorBgEEAYI3AwMBMYIXLzCCFysGCSqG
# SIb3DQEHAqCCFxwwghcYAgEDMQ8wDQYJYIZIAWUDBAIBBQAwdwYLKoZIhvcNAQkQ
# AQSgaARmMGQCAQEGCWCGSAGG/WwHATAxMA0GCWCGSAFlAwQCAQUABCDnIgp7mUqP
# UbC/pu22dIlyA3FTBb78uYgZ3ebx68KHXgIQer2eXjhG7SB/cnRt80f5pxgPMjAy
# MjA5MDcxNzM1MTZaoIITDTCCBsYwggSuoAMCAQICEAp6SoieyZlCkAZjOE2Gl50w
# DQYJKoZIhvcNAQELBQAwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0
# LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hB
# MjU2IFRpbWVTdGFtcGluZyBDQTAeFw0yMjAzMjkwMDAwMDBaFw0zMzAzMTQyMzU5
# NTlaMEwxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEkMCIG
# A1UEAxMbRGlnaUNlcnQgVGltZXN0YW1wIDIwMjIgLSAyMIICIjANBgkqhkiG9w0B
# AQEFAAOCAg8AMIICCgKCAgEAuSqWI6ZcvF/WSfAVghj0M+7MXGzj4CUu0jHkPECu
# +6vE43hdflw26vUljUOjges4Y/k8iGnePNIwUQ0xB7pGbumjS0joiUF/DbLW+YTx
# mD4LvwqEEnFsoWImAdPOw2z9rDt+3Cocqb0wxhbY2rzrsvGD0Z/NCcW5QWpFQiNB
# Wvhg02UsPn5evZan8Pyx9PQoz0J5HzvHkwdoaOVENFJfD1De1FksRHTAMkcZW+KY
# Lo/Qyj//xmfPPJOVToTpdhiYmREUxSsMoDPbTSSF6IKU4S8D7n+FAsmG4dUYFLcE
# RfPgOL2ivXpxmOwV5/0u7NKbAIqsHY07gGj+0FmYJs7g7a5/KC7CnuALS8gI0TK7
# g/ojPNn/0oy790Mj3+fDWgVifnAs5SuyPWPqyK6BIGtDich+X7Aa3Rm9n3RBCq+5
# jgnTdKEvsFR2wZBPlOyGYf/bES+SAzDOMLeLD11Es0MdI1DNkdcvnfv8zbHBp8QO
# xO9APhk6AtQxqWmgSfl14ZvoaORqDI/r5LEhe4ZnWH5/H+gr5BSyFtaBocraMJBr
# 7m91wLA2JrIIO/+9vn9sExjfxm2keUmti39hhwVo99Rw40KV6J67m0uy4rZBPeev
# pxooya1hsKBBGBlO7UebYZXtPgthWuo+epiSUc0/yUTngIspQnL3ebLdhOon7v59
# emsCAwEAAaOCAYswggGHMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYG
# A1UdJQEB/wQMMAoGCCsGAQUFBwMIMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCG
# SAGG/WwHATAfBgNVHSMEGDAWgBS6FtltTYUvcyl2mi91jGogj57IbzAdBgNVHQ4E
# FgQUjWS3iSH+VlhEhGGn6m8cNo/drw0wWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDov
# L2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0UlNBNDA5NlNIQTI1
# NlRpbWVTdGFtcGluZ0NBLmNybDCBkAYIKwYBBQUHAQEEgYMwgYAwJAYIKwYBBQUH
# MAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBYBggrBgEFBQcwAoZMaHR0cDov
# L2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0UlNBNDA5NlNI
# QTI1NlRpbWVTdGFtcGluZ0NBLmNydDANBgkqhkiG9w0BAQsFAAOCAgEADS0jdKbR
# 9fjqS5k/AeT2DOSvFp3Zs4yXgimcQ28BLas4tXARv4QZiz9d5YZPvpM63io5WjlO
# 2IRZpbwbmKrobO/RSGkZOFvPiTkdcHDZTt8jImzV3/ZZy6HC6kx2yqHcoSuWuJtV
# qRprfdH1AglPgtalc4jEmIDf7kmVt7PMxafuDuHvHjiKn+8RyTFKWLbfOHzL+lz3
# 5FO/bgp8ftfemNUpZYkPopzAZfQBImXH6l50pls1klB89Bemh2RPPkaJFmMga8vy
# e9A140pwSKm25x1gvQQiFSVwBnKpRDtpRxHT7unHoD5PELkwNuTzqmkJqIt+ZKJl
# lBH7bjLx9bs4rc3AkxHVMnhKSzcqTPNc3LaFwLtwMFV41pj+VG1/calIGnjdRncu
# G3rAM4r4SiiMEqhzzy350yPynhngDZQooOvbGlGglYKOKGukzp123qlzqkhqWUOu
# X+r4DwZCnd8GaJb+KqB0W2Nm3mssuHiqTXBt8CzxBxV+NbTmtQyimaXXFWs1DoXW
# 4CzM4AwkuHxSCx6ZfO/IyMWMWGmvqz3hz8x9Fa4Uv4px38qXsdhH6hyF4EVOEhwU
# KVjMb9N/y77BDkpvIJyu2XMyWQjnLZKhGhH+MpimXSuX4IvTnMxttQ2uR2M4Rxdb
# bxPaahBuH0m3RFu0CAqHWlkEdhGhp3cCExwwggauMIIElqADAgECAhAHNje3JFR8
# 2Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNV
# BAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0yMjAzMjMwMDAwMDBaFw0z
# NzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwg
# SW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1
# NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
# AQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbSg9GeTKJtoLDMg/la9hGhRBVCX6SI
# 82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9/UO0hNoR8XOxs+4rgISKIhjf69o9
# xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXnHwZljZQp09nsad/ZkIdGAHvbREGJ
# 3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0VAshaG43IbtArF+y3kp9zvU5Emfv
# DqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4fsbVYTXn+149zk6wsOeKlSNbwsDET
# qVcplicu9Yemj052FVUmcJgmf6AaRyBD40NjgHt1biclkJg6OBGz9vae5jtb7IHe
# IhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0QCirc0PO30qhHGs4xSnzyqqWc0Jo
# n7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvvmz3+DrhkKvp1KCRB7UK/BZxmSVJQ
# 9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T/jnA+bIwpUzX6ZhKWD7TA4j+s4/T
# Xkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk42PgpuE+9sJ0sj8eCXbsq11GdeJg
# o1gJASgADoRU7s7pXcheMBK9Rp6103a50g5rmQzSM7TNsQIDAQABo4IBXTCCAVkw
# EgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUuhbZbU2FL3MpdpovdYxqII+e
# yG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQD
# AgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEF
# BQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRw
# Oi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNy
# dDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGln
# aUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglg
# hkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIBAH1ZjsCTtm+YqUQiAX5m1tghQuGw
# GC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxpwc8dB+k+YMjYC+VcW9dth/qEICU0
# MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIlzpVpP0d3+3J0FNf/q0+KLHqrhc1D
# X+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQcAp876i8dU+6WvepELJd6f8oVInw
# 1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfeKuv2nrF5mYGjVoarCkXJ38SNoOeY
# +/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+jSbl3ZpHxcpzpSwJSpzd+k1OsOx0I
# SQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJshIUDQtxMkzdwdeDrknq3lNHGS1yZr
# 5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6OOmc4d0j/R0o08f56PGYX/sr2H7y
# Rp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDwN7+YAN8gFk8n+2BnFqFmut1VwDop
# hrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR81fZvAT6gt4y3wSJ8ADNXcL50CN/
# AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2VVQrH4D6wPIOK+XW+6kvRBVK5xMO
# Hds3OBqhK/bt1nz8MIIFjTCCBHWgAwIBAgIQDpsYjvnQLefv21DiCEAYWjANBgkq
# hkiG9w0BAQwFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j
# MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBB
# c3N1cmVkIElEIFJvb3QgQ0EwHhcNMjIwODAxMDAwMDAwWhcNMzExMTA5MjM1OTU5
# WjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL
# ExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJv
# b3QgRzQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1K
# PDAiMGkz7MKnJS7JIT3yithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2r
# snnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C
# 8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBf
# sXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY
# QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8
# rhsDdV14Ztk6MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaY
# dj1ZXUJ2h4mXaXpI8OCiEhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+
# wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw
# ++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+N
# P8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7F
# wI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo4IBOjCCATYwDwYDVR0TAQH/BAUw
# AwEB/zAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wHwYDVR0jBBgwFoAU
# Reuir/SSy4IxLVGLp6chnfNtyA8wDgYDVR0PAQH/BAQDAgGGMHkGCCsGAQUFBwEB
# BG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsG
# AQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1
# cmVkSURSb290Q0EuY3J0MEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwEQYDVR0gBAow
# CDAGBgRVHSAAMA0GCSqGSIb3DQEBDAUAA4IBAQBwoL9DXFXnOF+go3QbPbYW1/e/
# Vwe9mqyhhyzshV6pGrsi+IcaaVQi7aSId229GhT0E0p6Ly23OO/0/4C5+KH38nLe
# JLxSA8hO0Cre+i1Wz/n096wwepqLsl7Uz9FDRJtDIeuWcqFItJnLnU+nBgMTdydE
# 1Od/6Fmo8L8vC6bp8jQ87PcDx4eo0kxAGTVGamlUsLihVo7spNU96LHc/RzY9Hda
# XFSMb++hUD38dglohJ9vytsgjTVgHAIDyyCwrFigDkBjxZgiwbJZ9VVrzyerbHbO
# byMt9H5xaiNrIv8SuFQtJ37YOtnwtoeW/VvRXKwYw02fc7cBqZ9Xql4o4rmUMYID
# djCCA3ICAQEwdzBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIElu
# Yy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEyNTYg
# VGltZVN0YW1waW5nIENBAhAKekqInsmZQpAGYzhNhpedMA0GCWCGSAFlAwQCAQUA
# oIHRMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcN
# MjIwOTA3MTczNTE2WjArBgsqhkiG9w0BCRACDDEcMBowGDAWBBSFCPOGUVyz0wd9
# trS3wH8bSl5B3jAvBgkqhkiG9w0BCQQxIgQgFk94GbeH2GmIbPNR6iN05dT16qII
# T6sbyPotOXEhEjIwNwYLKoZIhvcNAQkQAi8xKDAmMCQwIgQgnaaQFcNJxsGJeEW6
# NYKtcMiPpCk722q+nCvSU5J55jswDQYJKoZIhvcNAQEBBQAEggIAfUth08Lv1U1w
# xbsnr95WHr9jiZGriFX8EyBP0R5nbtoEh3K8mPmU0MsKwcQFwVLIqTKd0uMypR5J
# rmi3fuxCV/1dB7ol/5dhVQyGmiZEbrzzIGp9HkIQhyELLmTO0oXoEsk/KWKi+/x1
# F4jspncUQ8Wmpf00M6qPtV6gUMONRcwBikn60t5ChFnzNszsjLLq8TgJaM6Fpj01
# oQu2Zwget/599Nr2RI40M1zUwTO57UAhGSCAQ33BOLPS0M+UehHWumXVtnt0vHyS
# Y9Cgc6BOdN38RO8eHeNrJ2auoLttK06HsP93kD3fnmd66fXfTlQte7GIREgIDt8m
# DkKu9Z95o9A7pDRVSIUficslLgVGY5I1L0lVwm69sF/2qSFZMeCD2Wa6oKmCoJdP
# i8V5XH0re5ke1TUVxMvQidYXtlzDbMPBXUJU0qOe+U282zAYXhej2YsVV5J2I3fu
# H90BDgf3pEvt2DbPzIP4vNmyV/RQMQU5icy6KPFeZlPqz6SkDVCSta+wS4opShpR
# qyQBJS7ET14j2w3+7PARwFFHbaGr64DLCmvCpv7HPMMBQPOg/f4iLnwtXID9huGm
# m9soH7pHO1DMlNppUMBV7EeouPzbTGw8DK8xEWaoslytSM11icPlyir1naijl580
# 3UTOoGOBUXwMS9dEAsJ4YthBBWNScwQ=
# SIG # End signature block