HP.Private.psm1

# HP CONFIDENTIAL
# __________________
#
# Copyright (C)2018 HP Inc
# All Rights Reserved.
#
# NOTICE: All information contained herein is, and remains the property of HP Inc.
#
# The intellectual and technical concepts contained herein are proprietary to HP Inc
# 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 Inc.


$ErrorActionPreference = 'Stop'


Add-Type -TypeDefinition @'
   using System;
   using System.Runtime.InteropServices;
 
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct arr4k_t
    {
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = 4096)] public byte[] raw;
    };
 
    public static class X509Utilities
    {
        [DllImport("dfmbios32.dll", EntryPoint = "get_public_key_from_pem", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
        public static extern int get_public_key_from_pem32([In] string file, [In,Out] ref arr4k_t modulus, [In,Out] ref UInt32 modulus_size, [In,Out] ref UInt32 exponent);
        [DllImport("dfmbios64.dll", EntryPoint = "get_public_key_from_pem", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
        public static extern int get_public_key_from_pem64([In] string file, [In,Out] ref arr4k_t modulus, [In,Out] ref UInt32 modulus_size, [In,Out] ref UInt32 exponent);
     };
'@



function Get-HPPrivateReadINI {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $True)] $file,
    [Parameter(Mandatory = $False)] [int]$maxRetries = 0
  )

  Write-Verbose "Reading INI style file '$file'"
  [System.IO.StreamReader]$streamReader = $null
  $CommentCount = 0
  $name = $null

  try {
    if ($file.StartsWith("http://") -or
      $file.StartsWith("https://") -or
      $file.StartsWith("ftp://")) {
      Write-Verbose ("Reading network file: $file")
      $webClient = New-Object System.Net.WebClient

      if ($file.StartsWith("ftp://"))
      {
        $webClient.Credentials = new-object -type System.Net.NetworkCredential -ArgumentList "anonymous","anonymous"
      }
      else {
        $webClient.UseDefaultCredentials = $true;  
      }
      
      $webClient.Proxy = [System.Net.WebRequest]::GetSystemWebProxy()


      [int]$retries = $maxRetries
      do {
        try {
          Write-Verbose "Downloading CVA file $file, try $($maxRetries-$retries) / $maxRetries"
          $data = $webClient.DownloadData($file)
          $retries = 0
        }
        catch {
          $retries = $retries - 1
          Write-Verbose ("Download failed: $($_.Exception)")
          if ($retries -le 0) { throw $_ }
          Start-Sleep 5

        }
      } while ($retries -gt 0)


      $memoryStream = New-Object System.IO.MemoryStream ($data, [System.Text.Encoding]::Default)
      $streamReader = New-Object System.IO.StreamReader ($memoryStream)
    }
    else {
      Write-Verbose ("Reading filesystem file: $file")
      $streamReader = New-Object -TypeName System.IO.StreamReader -ArgumentList $file
    }

    $ini = @{ }
    while (($line = $streamReader.ReadLine()) -ne $null) {
      switch -regex ($line) {
        "^\[(.+)\]$" { # Section
          $section = $matches[1]
          $ini[$section] = @{ }
          $CommentCount = 0
        }
        "^(;.*)$" { # Comment
          if (!(Test-Path variable:\section)) {
            $section = "No-Section"
            $ini[$section] = @{ }
          }
          $value = $matches[1]
          $CommentCount = $CommentCount + 1
          $name = "Comment" + $CommentCount
          $ini[$section][$name] = $value
        }
        "(.+?)\s*=\s*(.*)" { # Key
          if (!($section)) {
            $section = "No-Section"
            $ini[$section] = @{ }
          }
          $name, $value = $matches[1..2]
          $ini[$section][$name] = $value
          continue
        }
        "^(?!(.*[=])).*" { # section text block
          if (!($section)) {
            $section = "No-Section"
            $ini[$section] = @{ }
          }

          if ($ini[$section]["_body"] -eq $null) {
            $ini[$section]["_body"] = @()
          }

          $ini[$section]["_body"] += ($matches.Values | Where-Object { $_.StartsWith("[") -eq $false })
        }
      }
    }
  }
  finally {
    if ($streamReader) {
      $streamReader.Close()
      $streamReader.Dispose()
      $streamReader = $null
    }

    if ($memoryStream) {
      $memoryStream.Close()
      $memoryStream.Dispose()
      $memoryStream = $null
    }

    if ($webClient) {
      $webClient.Dispose()
      $webClient = $null
    }

  }
  return $ini
}




# this is what the downloaded filename will be
function Get-HPPrivateTemporaryFileName ($filename, [System.IO.DirectoryInfo]$cacheDir = [System.IO.Path]::GetTempPath() + "hp") {
  $cacheDir = Join-Path -Path $cacheDir -ChildPath $filename
  $cacheDir.FullName
}




function getLockedStreamForWrite {
  [CmdletBinding()]
  param([string]$target, [int]$maxRetries = 10)

  Write-Verbose "Opening exclusive access to file $target with maximum retries of $maxRetries"
  $lock_wait = $false

  do {
    try {
      $lock_wait = $false
      $result = New-Object -TypeName System.IO.FileStream -ArgumentList $target, Create, Write, None
    }
    catch {
      Write-Verbose ("******* $($_ | fl)")
      $lock_wait = $true
      if ($maxRetries -gt 0) {
        Start-Sleep -Seconds 30
        $maxRetries = $maxRetries - 1
      }
      else {
        throw "Could not obtain exclusive access to file '$target' and all retries were exhausted."
      }

    }
  }
  while ($lock_wait -eq $true)
  $result
}

# check for collision with other processes
function getSharedFileInformation {
  [CmdletBinding()]
  param($file, [string]$mode, [switch]$wait, [int]$maxRetries, [switch]$progress, [switch]$skipSignatureCheck)

  $return = $true
  $length = 0
  $sig = $false

  Write-Verbose ("Getting file information for file $file with access rights=$mode, wait = $wait, maxRetries=$maxRetries, skipAuthenticode=$($skipSignatureCheck.IsPresent)")
  if (-not $wait.IsPresent) {
    Write-Verbose ("This operation will not be retried.")
    $maxRetries = 0
  }

  do {
    # file length
    try {
      $length = (Get-ChildItem -File $file -ErrorAction Stop).length
    }
    catch {
      return (-1, $true, $skipSignatureCheck.IsPresent)
    }

    Write-Verbose ("Target file length on disk is $length bytes")
    try {
      $fs = [System.IO.File]::Open($file, "Open", $mode)
      $return = $true
      $fs.Close()
      $fs.Dispose()
      Write-Verbose "Able to read from file '$file', it doesn't seem locked."


      if ($skipSignatureCheck.IsPresent) {
        Write-Verbose "Not checking Authenticode signature for file $file"
        $sig = $true
      }
      else {
        $sig = Get-HPPrivateCheckSignature -File $file
      }
      break
    }
    catch [System.IO.FileNotFoundException] {
      return (-1, $true, $skipSignatureCheck.IsPresent)
    }
    catch {
      Write-Verbose "Internal error: $_.Message"
      $return = $false
      if ($maxRetries -gt 0) {
        if ($progress) {
          Write-Progress -Activity "Blocked by another process, will retry for ($maxRetries) tries"
        }

        Write-Verbose ("Sleeping for 30 seconds since someone else has '$file' locked")
        Start-Sleep -Seconds 30
        Write-Verbose ("Woke up")
      }
      $maxRetries = $maxRetries - 1
    }
  } while ($maxRetries -gt 0)
  ($length, $return, $sig)
}


# download a file
function Invoke-HPPrivateDownloadFile {
  [CmdletBinding()]
  param
  (
    [string]$url,
    [string]$target,
    [bool]$progress,
    [string]$noclobber,
    [switch]$panic,
    [int]$maxRetries = 0,
    [switch]$skipSignatureCheck
  )
  Write-Verbose ("Requesting to download $url to $target with progress: $progress and signatureCheckSkip: $skipSignatureCheck")
  [System.Net.ServicePointManager]::SecurityProtocol = Get-HPPrivateAllowedHttpsProtocols

  try {
    if (Test-Path $target -PathType Leaf) {
      # target file exists
      switch ($noclobber) {
        "no" {
          if ($panic.IsPresent) { throw "File $target already exists, will not overwrite." }
          Write-Host -ForegroundColor Magenta "File $target already exists, will not overwrite."
          return
        }
        "yes" {
          if ($progress -eq $true) { Write-Verbose "Overwriting existing file $target" }
        }
        "skip" { }
      }

    }
    else {
      #create lead directory if needed
      $lead = Split-Path $target
      if (!(Test-Path $lead)) {
        Write-Verbose "Creating directory '$lead'"
        $tmp = New-Item -ItemType Directory -Force -Path $lead 
      }
    }

    $uri = New-Object "System.Uri" "$url"

    $retries = $maxRetries
    $request = [System.Net.HttpWebRequest]::Create($uri)
    $request.set_Timeout(30000)

    if (-not $request -is [System.Net.FtpWebRequest]) {
      $request.set_UserAgent($LIBRARY_UA);
    }

    do {

      try {
        Write-Verbose "Executing query on $uri, try $($maxRetries-$retries) / $maxRetries"
        $response = $request.GetResponse()
        $retries = 0
      }
      catch {
        $retries = $retries - 1
        Write-Verbose ("Query failed $($_.Exception)")
        if ($retries -le 0) { throw $_ }
        Start-Sleep 5
      }

    } while ($retries -gt 0)

    $totalLength = [System.Math]::Floor($response.get_ContentLength() / 1024)

    # Someone else may be downloading this file at this time, so we'll wait until they release the
    # lock and then we check the size
    Write-Verbose ("Target file is $target")

    $r = getSharedFileInformation -File $target -Mode "Read" -Wait -maxRetries $maxRetries -progress:$progress -skipSignatureCheck:$skipSignatureCheck
    if ($noclobber -eq "skip") {
      if (($r[0] -eq $response.get_ContentLength()) -and ($r[2] -eq $true)) {
        Write-Verbose "File already exists or another process has finished downloading this file for us."
        return
      }
      else {
        Write-Verbose ("Existing file $target doesn't seem correct (size=$($r[0]) vs expected $($response.get_ContentLength()), signature_check=$($r[2]), deleting it.")
      }
    }

    $responseStream = $response.GetResponseStream()
    $targetStream = getLockedStreamForWrite -maxRetries $maxRetries -target $target
    #try {
    $buffer = New-Object byte[] 10KB
    $count = $responseStream.Read($buffer, 0, $buffer.length)
    $downloadedBytes = $count

    while ($count -gt 0) {
      $targetStream.Write($buffer, 0, $count)
      $count = $responseStream.Read($buffer, 0, $buffer.length)
      $downloadedBytes = $downloadedBytes + $count
      if ($progress -eq $true) {
        Write-Progress -Activity "Downloading file '$($url.split('/') | Select -Last 1)'" -Status "Downloaded ($([System.Math]::Floor($downloadedBytes/1024))K of $($totalLength)K): " -PercentComplete ((([System.Math]::Floor($downloadedBytes / 1024)) / $totalLength) * 100)
      }
    }

    if ($progress -eq $true) {
      Write-Verbose ("Finished downloading '$($url.split('/') | Select -Last 1)'")
      Write-Progress -Activity "Finished downloading file '$($url.split('/') | Select -Last 1)'"
    }

  }
  finally {
    if ($targetStream) {
      $targetStream.Flush()
      $targetStream.Close()
      $targetStream.Dispose()
    }

    if ($responseStream) {
      $responseStream.Close()
      $responseStream.Dispose()
    }

    if ($response) {
      $response.Close()
    }
  }

}

function Get-HPPrivateAllowedHttpsProtocols {
  $c = [System.Net.SecurityProtocolType]([System.Net.SecurityProtocolType].GetEnumNames() | Where-Object { $_ -ne "Ssl3" -and $_ -ne "Tls" })
  Write-Verbose "Removing obsolete protocols SSL3 and TLS, now supporting: $c"
  $c
}


function Get-HPPrivateCacheDirPath {

  [CmdletBinding()]
  param([System.IO.DirectoryInfo]$seed)

  if (-not $seed) {
    $seed = [System.IO.Path]::GetTempPath() + "hp"
  }
  Join-Path -Path $seed -ChildPath "cache"
  Write-Verbose "Local caching path is: $seed"
}

# check authenticode signature
# check CVA and Softpaq hash to determine download of the Softpaq
#
# tests (remove these comments once we are happy with the function)
#
# PASS: Get-HPPrivateCheckSignature -file C:\windows\System32\notepad.exe -signedBy "Microsoft Windows" -Verbose
# PASS: Get-HPPrivateCheckSignature -file C:\windows\System32\notepad.exe -Verbose
# PASS: Get-HPPrivateCheckSignature -file .\sp99062.exe -CVAfile .\sp99062.cva -Verbose
# PASS: Get-HPPrivateCheckSignature -file .\sp99062.exe -CVAfile .\sp99062.cva -Verbose -signedBy "HP Inc."
function Get-HPPrivateCheckSignature {
  param(
    [Parameter(Position = 0, Mandatory = $true)]
    [string]$file,
    
    [Parameter(Mandatory = $false, Position = 1)]
    [string]$CVAfile = $null,

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

  try {

    $c = Get-AuthenticodeSignature -FilePath $file

    if ($c.Status -ne "Valid") {
      Write-Verbose ("$file is not signed or certificate is invalid.")
      return $false
    }
    if ($signedBy) {
      $signer = $c.SignerCertificate.Subject.split(",")[0].trim().split("=")[1]

      if ($signer -ne $signedBy) {
        Write-Verbose ("$file is not signed by $signedBy, it is signed by $signer, failing.")
        return $false
      }
      else {
        Write-Verbose ("$file is signed by $signedBy.")
        # return $true
      }
    }

    if ($CVAfile) {
      Write-Verbose "Verifying '$file' using '$CVAFile'"

      $targetFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($file)
      $targetCVA = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($CVAFile)

      # Getting hash value of CVA
      $read_file = Get-HPPrivateReadINI -file $targetCVA  
      $CVA_SHA256 = $read_file | Out-SoftpaqField -field SoftPaqSHA256
      $CVA_MD5 = $read_file | Out-SoftpaqField -field SoftPaqMD5

      Write-Verbose "CVA has MD5 hash '$CVA_MD5' and sha256 hash '$CVA_SHA256'"
      if ($CVA_SHA256 -ne $null) {
        return $CVA_SHA256 -eq (Get-FileHash -path $targetFile -Algorithm SHA256).hash
      }
      elseif ($CVA_MD5 -ne $null) {
        return $CVA_MD5 -eq (Get-FileHash -path $targetFile -Algorithm MD5).hash
      }
      else {
        return $false
      } 
    }

    # When only file is passed and it has valid signature
    return $true
  }

  catch {
    Write-Verbose "During signature check, had exception $_.Exception"
    return $false
  }
}

function Invoke-HPPrivateDeleteCachedItem ([Parameter(Mandatory = $true)] $cab) {
  Invoke-HPPrivateSafeRemove -Path $cab
  Invoke-HPPrivateSafeRemove -Path "$cab.dir" -Recurse
}


function Invoke-HPPrivateSafeRemove {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string[]]$path,
    [Parameter(Mandatory = $false)] [switch]$recurse
  )
  foreach ($p in $path) {
    if (Test-Path $p) {
      Write-Verbose "Removing $p"
      Remove-Item $p -Recurse:$recurse
    }
  }
}

function Invoke-HPPrivateExpandCAB {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)] $cab,
    [Parameter(Mandatory = $true)] $expectedFile
  )
  Write-Verbose "Expanding CAB $cab to $cab.dir"

  $target = "$cab.dir"
  Invoke-HPPrivateSafeRemove -Path $target -Recurse
  Write-Verbose "Expanding $cab to $target"
  $result = New-Item -Force $target -ItemType Directory
  Write-Verbose "Created folder $result"

  $shell = New-Object -ComObject "Shell.Application"
  try {
    if (!$?) { $(throw "unable to create $comObject object") }

    $sourceCab = $shell.Namespace($cab).items()
    $DestinationFolder = $shell.Namespace($target)
    $DestinationFolder.CopyHere($sourceCab)
  }
  finally {
    [System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$shell) | Out-Null
    [System.GC]::Collect()
    [System.GC]::WaitForPendingFinalizers()

  }
  $downloadedOk = Test-Path $expectedFile
  if ($downloadedOk -eq $false) {
    throw "Invalid cab file, did not find $expectedFile in contents"
  }
  return $expectedFile
}


# check if a download is needed, based on file existence and the remote last-modified time
function Test-HPPrivateIsDownloadNeeded {
  [CmdletBinding()]
  param([Parameter(Mandatory = $true)] $url, [Parameter(Mandatory = $true)] $file)

  Write-Verbose "Checking if we need a new copy of $file"

  # $c = [System.Net.ServicePointManager]::SecurityProtocol
  Write-Verbose ("Allowed HTTPS protocols: $c")
  [System.Net.ServicePointManager]::SecurityProtocol = Get-HPPrivateAllowedHttpsProtocols
  $headers = (Invoke-WebRequest -Uri $url -Method HEAD -UseBasicParsing).Headers
  [datetime]$offered = $headers["Last-Modified"]
  Write-Verbose "File on server has timestamp $offered"

  $exists = Test-Path -Path $file -PathType leaf
  if ($exists -eq $false) {
    Write-Verbose "Cached file $file does not exist, will need to download new file"
    $offered
    $true
  }
  else {
    [datetime]$have = (Get-Item $file).CreationTime
    $r = ($have -lt $offered)
    Write-Verbose "Cached file exist and it has timestamp $offered. Need to download: $r"

    $offered
    $r
  }
}


# check if the downloaded xml file is corrupted.
function Test-HPPrivateIsValidXmlFile {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)] $file
  )

  if (-not (Test-Path -Path $file)) {
    Write-Verbose "File $file does not exist."
    return $false
  }

  # Check for Load or Parse errors when loading the XML file.
  $xml = New-Object System.Xml.XmlDocument
  try {
    $xml.Load($file)
    return $true
  }
  catch [System.Xml.XmlException] {
    Write-Verbose "Invalid Xml file $file"
    return $false
  }
}


function Get-HPPrivateUserAgent () {
  "hpcmsl ($($MyInvocation.MyCommand.Module.Name); $($MyInvocation.MyCommand.Module.Version))"
}

function runParallel {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
    [System.Management.Automation.ScriptBlock]$scriptBlock,

    [ValidateRange(1, 16)]
    [Parameter(Mandatory = $false, Position = 1)]
    [int]$maxInstances = [int]$env:NUMBER_OF_PROCESSORS + 1
  )

  $pool = [runspacefactory]::CreateRunspacePool(1, $maxInstances)
  $pool.ApartmentState = "MTA"
  $pool.Open()
  $jobs = New-Object System.Collections.ArrayList
  Write-Progress -Activity "Running tasks" -Id 1 -PercentComplete 0 -CurrentOperation "Initializing"

  try {
    1..$maxInstances | ForEach-Object {
      $psi = [powershell]::Create()
      $psi.RunspacePool = $pool
      $psi.AddScript($scriptBlock).AddArgument($obj) | Out-Null

      $obj = New-Object -TypeName PSObject -Property @{
        ChildID         = $_
        Handle          = $psi.BeginInvoke()
        Instance        = $psi
        Item            = "?"
        PercentComplete = 0
        Status          = "?"
      }
      $jobs.Add($obj) | Out-Null
    }

    $currentcount = 0;
    $totalcount = ($jobs | Measure-Object).Count

    Write-Verbose (?Available runspaces (unused) : { 0 } Where-Object -f $pool.GetAvailableRunspaces())
    $allCompleted = $true

    while ($jobs.Handle.IsCompleted -eq $False) {
      Write-Host "." -NoNewline
      Start-Sleep -Milliseconds 300

      $jobs | ForEach-Object {
        Write-Progress -ParentId 1 -Id (1 + $_.ChildID) -Activity $_.Item -Status $_.Status -PercentComplete $_.PercentComplete
      }
    }
    $return = $jobs | ForEach-Object {
      $_.Instance.EndInvoke($_.Handle)
      $_.Instance.Dispose()
    }
    $jobs.Clear()

  }
  finally {
    if ($pool) {
      $pool.Close();
      $pool.Dispose()
    }
  }
}



function validateWmiResult {
  [CmdletBinding()]
  param([int]$code, [int]$category = 0xff)

  Write-Verbose "Validating error code $code for facility $category"
  switch ($code) {
    0 { }
    0xea { }
    6 { throw [NotSupportedException]"Operation could not be completed. Please ensure this is a supported HP system." }
    5 { throw [ArgumentException]"Method called with invalid parameters." }
    4 { throw [UnauthorizedAccessException]"The caller does not have permissions to perform this operation." }
    default { validateWmiResultInCategory -Category $category -code $code }
  }
}


function validateWmiResultInCategory {
  [CmdletBinding()]
  param([int]$category, [int]$code)

  switch ($category) {
    1 {
      switch ($code) {
        0x40 { throw [NotSupportedException]"This system does not support firmware logs." }
        0x41 { throw [System.TimeoutException]"Call has timed out." }
        default { throw [SystemException]"An unknown error $code has occured." }
      }
    }
    2 {
      switch ($code) {
        0x0b { throw [UnauthorizedAccessException]"The caller does not have permissions to perform this operation." }
        0x0e { throw [UnauthorizedAccessException]"The operation could not be completed, possibly due to a bios password mismatch?" }
        0x0010 { throw [SystemException]"Invalid flash offset." }
        0x0012 { throw [SystemException]"Invalid flash checksum" }
        0x0013 { throw [InvalidOperationException]"Flash-in-progress error" }
        0x0014 { throw [InvalidOperationException]"Flash-in-progress not set" }
        default { throw [SystemException]"An unknown error $code has occured." }
      }
    }
    3 {
      switch ($code) {
        # this facility doesn't define specific codes
        default { throw [SystemException]"An unknown error $code has occured." }
      }
    }
    4 {
      switch ($code) {
        0x0b { throw [UnauthorizedAccessException]"The caller does not have permissions to perform this operation." }
        0x03 { throw [NotSupportedException]"This system does not support HP Secure Platform or a hardware option is missing." }
        0x1c { throw [SystemException]"The request was not accepted by the BIOS." }
        0x1000 { throw [SystemException]"HP Secure Platform is not provisioned." }
        0x1001 { throw [SystemException]"HP Secure Platform is already provisioned." }
        0x1002 { throw [SystemException]"HP Secure Platform is in use. Deprovision all features that use the HP Secure Platform first." }
        default { throw [SystemException]"An unknown error $code has occured." }
      }
    }
    default {
      throw [SystemException]"An unknown error $code has occured."
    }
  }
}

function Test-HPPrivateCustomResult {
  [CmdletBinding()]
  param([int]$result, [int]$mi_result, [int]$category)
  Write-Verbose ("Checking result={0:x8}, mi_result={1:x8}, category={2:x4}" -f $result, $mi_result, $category)
  switch ($result) {
    0 { Write-Verbose ("Operation succeeded.") }
    0x80000711 { validateWmiResult -code $mi_result -Category $category } # E_DFM_FAILED_WITH_EXTENDED_ERROR
    0x80000710 { throw [NotSupportedException]"Current platform does not support this operation." } # E_DFM_FEATURE_NOT_SUPPORTED
    0x8000070b { throw [System.IO.IOException]"Firmware file could not be read." } # E_DFM_FILE_ACCESS_FAILURE
    0x8000070e { throw [InvalidOperationException]"Firmware file is too long for expected flash type." } # E_DFM_FLASH_BUR_INPUT_DATA_TOO_LARGE
    0x80000712 { throw [InvalidOperationException]"The firmware does not mach the target platform." } # E_DFM_WRONG_FLASH_FILE
    0x80000714 { throw [OutOfMemoryException]"A memory allocation failed. The system may be out of memory." } # E_DFM_ALLOC_FAILED
    0x80000715 { throw [InvalidOperationException]"Password length is not valid." } # E_DFM_PASSWORD_SIZE_INVALID
    0x8000071a { throw [System.ArgumentException]"Invalid parameter for HP Sure View API" }
    1392 { throw [System.IO.IOException]"Could not copy the file to the system partition." } # ERROR_FILE_CORRUPT
    234 { Write-Verbose ("Operation succeeded.") } # MORE_DATA
    default { throw [ComponentModel.Win32Exception]$result }
  }

}




function Convert-HPPrivateObjectToBytes {
  [CmdletBinding()]
  param($obj)

  $mem = $null
  $length = 0
  $bytes = $()


  Write-Verbose "Converting object of type $($obj.Gettype()) to byte array"
  try {
    $length = [System.Runtime.InteropServices.Marshal]::SizeOf($obj)
    $bytes = New-Object byte[] $length
    Write-Verbose "Converting object of type $($obj.Gettype()) is $length bytes"
    $mem = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($length)

    [System.Runtime.InteropServices.Marshal]::StructureToPtr($obj, $mem, $true)
    [System.Runtime.InteropServices.Marshal]::Copy($mem, $bytes, 0, $length)
    ($bytes, $length)
  }
  finally {
    # Free the memory we allocated for the struct value
    if ($mem) {
      Write-Verbose "Freeing allocated memory"
      [System.Runtime.InteropServices.Marshal]::FreeHGlobal($mem)
    }
  }
  Write-Verbose "Conversion complete."

}


#region Cryptography

function Get-HPPrivatePublicKeyCoalesce {
  [CmdletBinding()]
  param(
    [System.IO.FileInfo]$file,
    [PSObject]$key
  )

  if ($file) {
    $efile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($file)
    Write-Verbose "Coalescing to FILE PEM $efile"
    $modulus =  New-Object arr4k_t 
    $modulus_size = 4096
    $exponent = 0

    $c = '[X509Utilities]::get_public_key_from_pem' + (Test-OSBitness) + '($efile,[ref]$modulus, [ref]$modulus_size, [ref]$exponent);'
    $result = Invoke-Expression -Command $c
    Test-HPPrivateCustomResult -result $result -mi_result $mi_result -Category 0x04
    New-Object -TypeName PSObject -Property @{
      Modulus  = $modulus.raw[0..($modulus_size - 1)]
      Exponent = $exponent
    }
     
  }
  else {
    Write-Verbose "Coalescing to binary PEM"
    $key
  }

}


function Get-HPPrivateX509CertCoalesce {
  [CmdletBinding()]
  param(
    [System.IO.FileInfo]$file,
    [System.Security.Cryptography.X509Certificates.X509Certificate2]$cert,
    [string]$password
  )
  $param = @{ }
  if ($password) {
    $param.Add("Password", (ConvertTo-SecureString -AsPlainText -Force $password))
  }
  if ($file) {
    $efile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($file)
    Write-Verbose "Coalescing to FILE certificate $efile"
    $param.Add("FileName", $efile)
    Get-HPPrivatePublicKeyCertificateFromPFX @param -Verbose:$VerbosePreference
  }
  else {
    Write-Verbose "Coalescing to binary certificate"
    $key = [System.Security.Cryptography.RSACryptoServiceProvider ]$cert.PublicKey.Key
    $parameters = $key.ExportParameters($false);
    $mod_reversed = $parameters.Modulus
    [array]::Reverse($mod_reversed)
    New-Object -TypeName PSObject -Property @{
      Full        = $Cert
      Certificate = $cert.Export('Cert')
      Modulus     = $mod_reversed
      Exponent    = $parameters.Exponent
    }
  }
}


# get the PK from a PFX file
function Get-HPPrivatePublicKeyCertificateFromPFX {
  [CmdletBinding(DefaultParameterSetName = "FF")]
  param(
    [Parameter(Mandatory = $true, Position = 0)]
    [string]$FileName,

    [Parameter(Mandatory = $false, Position = 1)]
    [securestring]$Password
  )

  $certfile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($FileName)
  if (-not (Test-Path -PathType leaf -Path $certfile)) {
    throw [System.IO.FileNotFoundException] "Certificate file '$certfile' could not be found"
  }
  Write-Verbose "Extracting public key from '$certfile'."

  
  try {
    $cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2
    $cert.Import($certfile, $Password, 'DefaultKeySet')
    $key = [System.Security.Cryptography.RSACryptoServiceProvider ]$cert.PublicKey.Key
    $parameters = $key.ExportParameters($false);

    $mod_reversed = $parameters.Modulus
    [array]::Reverse($mod_reversed)
    New-Object -TypeName PSObject -Property @{
      Full        = $cert
      Certificate = $cert.Export('Cert')
      Modulus     = $mod_reversed
      Exponent    = $parameters.Exponent
    }
  }
  finally {
    #$cert.Dispose();
    #$cert = $null
  }

}

# hash and sign a byte array with the given certificate
function hashAndSign {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true, Position = 0)]
    [byte[]]$data,
    [Parameter(Mandatory = $true, Position = 1)]
    [System.Security.Cryptography.X509Certificates.X509Certificate2]$signer
  )
  $csp = $null
  $hasher = $null
  try {
    $hasher = New-Object System.Security.Cryptography.SHA256Managed
    $hash = $hasher.ComputeHash($data)
    Write-Verbose "Calculated hash $hash"
    # this seems to provide a bit more flexibility
    [System.Security.Cryptography.CspParameters]$cspParams = New-Object System.Security.Cryptography.CspParameters
    [System.Security.Cryptography.RSACryptoServiceProvider]$sn = $signer.PrivateKey
    $cspParams.KeyContainerName = $sn.CspKeyContainerInfo.KeyContainerName

    if ($sn.CspKeyContainerInfo.KeyNumber -eq [System.Security.Cryptography.KeyNumber]::Exchange) { $cspParams.KeyNumber = 1 }
    else { $cspParams.KeyNumber = 2 }

    [System.Security.Cryptography.RSACryptoServiceProvider]$csp = New-Object System.Security.Cryptography.RSACryptoServiceProvider -ArgumentList $cspParams
    $csp.PersistKeyInCsp = $false
    $csp.SignHash($hash, "SHA256");
  }
  finally {
    if ($hasher) { $hasher.Dispose() }
    if ($csp) { $csp.Dispose() }
  }
}

# sign a byte array with a certificate provided in $Filename
function Invoke-HPPrivateSignData {
  [CmdletBinding(DefaultParameterSetName = "FF")]
  param(
    [Parameter(ParameterSetName = "FF", Mandatory = $true, Position = 0)]
    [System.IO.FileInfo]$FileName,

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

    [Parameter(ParameterSetName = "FF")]
    [Parameter(ParameterSetName = "FB")]
    [Parameter(Mandatory = $false, Position = 1)]
    [string]$Password,

    [Parameter(ParameterSetName = "FF")]
    [Parameter(ParameterSetName = "FB")]
    [Parameter(Mandatory = $true, Position = 2)]
    [byte[]]$data
  )
  if ($Password) {
    $pwd = ConvertTo-SecureString -AsPlainText -force $Password
  }
  else {
    $pwd = $null
  }

  $cert = Get-HPPrivateX509CertCoalesce -File $FileName -cert $Certificate -password $pwd
  $result = hashAndSign -data $data -signer $cert.Full
  [array]::Reverse($result)
  $result

}

function Get-HPPrivateHash {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true, Position = 0)] [byte[]]$data, 
    [Parameter(Mandatory = $false, Position = 1)] [string]$algo = "SHA256"
  )
  $cp = [System.Security.Cryptography.HashAlgorithm]::Create($algo)
  try {
    $result = $cp.ComputeHash($data)
  }
  finally {
    $cp.Dispose()
  }
  $result

}

# Downloads files for when OfflineCacheMode is Enable
# If -platform is present : Downloads Advisory Data Files (XXXX_cds.cab) where XXXX is platform ID.
# also downloads the platform List
function Get-HPPrivateOfflineCacheFiles  {
  [CmdletBinding()]
  param(
    [string]$url,
    [string]$filename,
    [System.IO.DirectoryInfo]$cacheDirOffline = [System.IO.Path]::GetTempPath() + "hp",
    [switch]$expand
  )

    $file = Get-HPPrivateTemporaryFileName -filename $filename -cacheDir $cacheDirOffline
    $filename = $filename.replace("cab","xml")
    $downloadedFile = "$file.dir\$filename"
    
    Write-Verbose "Checking if $url is available locally."
    try {
      $result = Test-HPPrivateIsDownloadNeeded -url $url -File $file -Verbose:$VerbosePreference
     }
    catch {
      throw [System.Net.WebException] "Could not find a data file for this platform."
    }

if ($result[1] -eq $true) {
  Write-Verbose "$url is not local, or is out of date, will download."
  Write-Verbose "Cleaning cached data and downloading the data file."
  Invoke-HPPrivateDeleteCachedItem  -cab $file
  Invoke-HPPrivateDownloadFile -url $url -target $file -Verbose:$VerbosePreference
  (Get-Item $file).CreationTime = ($result[0])
  (Get-Item $file).LastWriteTime = ($result[0])
}

if ($expand.IsPresent) {
  # Need to make sure that the expanded data file exists and is not corrupted.
  # Otherwise, expand the cab file.
  if (-not (Test-Path $downloadedFile) -or (-not (Test-HPPrivateIsValidXmlFile -file $downloadedFile)))
  {
    Write-Verbose "Extracting the data file, looking for $downloadedFile."
    $file = Invoke-HPPrivateExpandCAB -cab $file -expectedFile $downloadedFile
  }
  }
  return $downloadedFile
}

# build URL for a remote item
function Get-HPPrivateItemUrl ([int]$number,[string]$ext,[string]$url)
{
  if ($url)
  {
    return "$url/sp$number.$ext"
  }

  [string]$baseNumber = $number.ToString()
  [int] $last3Value = [int]($baseNumber.Substring($baseNumber.Length - 3))
  [int]$blockStart = [int]($baseNumber.Substring(0, $baseNumber.Length - 3))

  [string]$block = ""
  [int]$blockEnd = $blockStart

  if ($last3Value -gt 500)
  {
      $blockEnd += 1
      $block = "$($blockStart)501-$($blockEnd)000"
  }
  else
  {
      if ($last3Value -eq 0)
      {
          $blockStart -= 1
          $block = "$($blockStart)501-$($blockEnd)000"
      }
      else
      {
        $block = "$($blockStart)001-$($blockStart)500"
      }
  }

  return "https://ftp.hp.com/pub/softpaq/sp$block/sp$number.$ext"
} 

#endregion



# SIG # Begin signature block
# MIIcOAYJKoZIhvcNAQcCoIIcKTCCHCUCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDpAJIit4/3cxLM
# w+aHV0eRIjAb7SDZ6YfJ2Ck4YsmR1KCCCo0wggU2MIIEHqADAgECAhAM1s71mz4i
# 3j/UnuaI4vzeMA0GCSqGSIb3DQEBCwUAMHYxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xNTAzBgNV
# BAMTLERpZ2lDZXJ0IFNIQTIgSGlnaCBBc3N1cmFuY2UgQ29kZSBTaWduaW5nIENB
# MB4XDTE5MDQyMjAwMDAwMFoXDTIwMDQyOTEyMDAwMFowdTELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVBhbG8gQWx0bzEQMA4GA1UE
# ChMHSFAgSW5jLjEZMBcGA1UECxMQSFAgQ3liZXJzZWN1cml0eTEQMA4GA1UEAxMH
# SFAgSW5jLjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANEwuTFpw7fQ
# 3Ds5fvexal46Gg9TNMvdiJu7qMqDZnDJNl7ECdEPyLxsioGS7/yomOS9RXdXMJOm
# tyV4/wIPbBaGC8E2tbLTbQQ4IJbgvC+Vc46vbo+sI8YTG6qBICOovFw9VhUNXXEy
# SwHMoBNk8JS8R1slPpJKmNGB10HSatMGaHja0Lbqos0QuEx/tx2OXe+mzepIo66T
# dtSv2MfPy2tcVcXIdiJGn7f4otxoj6T9X7hVIl78r5Y2XWHYtDK8KaV1E/qkiNXK
# 1Xw5S53zv2VsZl6i1LZwt3d1Q9pUmm1AZe2YdhSGvwMP2LYBJGXIBbyLYnxS4HKB
# R7MYZyz7H2kCAwEAAaOCAb8wggG7MB8GA1UdIwQYMBaAFGedDyAJDMyKOuWCRnJi
# /PHMkOVAMB0GA1UdDgQWBBSnSAWgK15kcBLxsg4XNsT7ncH29zAOBgNVHQ8BAf8E
# BAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwbQYDVR0fBGYwZDAwoC6gLIYqaHR0
# cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItaGEtY3MtZzEuY3JsMDCgLqAshipo
# dHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1oYS1jcy1nMS5jcmwwTAYDVR0g
# BEUwQzA3BglghkgBhv1sAwswKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGln
# aWNlcnQuY29tL0NQUzAIBgZngQwBBAEwgYgGCCsGAQUFBwEBBHwwejAkBggrBgEF
# BQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFIGCCsGAQUFBzAChkZodHRw
# Oi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEySGlnaEFzc3VyYW5j
# ZUNvZGVTaWduaW5nQ0EuY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQAD
# ggEBAJQblkFw+UYKYSY2M/CIEpJxZDnf+cDhodKAy+goI3XfExRHhyLu3Gc2ibFB
# Y4wyz/sJSfHehtNPYckXxR9k/FB/GfYtEACug9xXxJ+iLxWUNQ4KPt3bXY/kmDxW
# D1QXJFLbW5Dop3w/K0DL3fxnjOfYCcxsYodbeEiCJprCdNi3zd6x/J8Y35GDbLA5
# p7RfIAzKrmBLPHFGDWr/jWTfwPfUNz6jYJ51m0Ba9j81kzpxNUD0yBIZXBkVvSkx
# A09KxzMSSvxvV9DSqSezQBVgWnl9TbElouYUQwk64i0GzL4lTsphK4rQJJ2uuKtH
# wN4E0ibpm0uIqbLhgk+3ic8fHTIwggVPMIIEN6ADAgECAhALfhCQPDhJD/ovZ5qH
# oae5MA0GCSqGSIb3DQEBCwUAMGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdp
# Q2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKzApBgNVBAMTIkRp
# Z2lDZXJ0IEhpZ2ggQXNzdXJhbmNlIEVWIFJvb3QgQ0EwHhcNMTMxMDIyMTIwMDAw
# WhcNMjgxMDIyMTIwMDAwWjB2MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNl
# cnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTUwMwYDVQQDEyxEaWdp
# Q2VydCBTSEEyIEhpZ2ggQXNzdXJhbmNlIENvZGUgU2lnbmluZyBDQTCCASIwDQYJ
# KoZIhvcNAQEBBQADggEPADCCAQoCggEBALRKXn0HD0HexPV2Fja9cf/PP09zS5zR
# Df5Ky1dYXoUW3QIVVJnwjzwvTQJ4EGjI2DVLP8H3Z86YHK4zuS0dpApUk8SFot81
# sfXxPKezNPtdSMlGyWJEvEiZ6yhJU8M9j8AO3jWY6WJR3z1rQGHuBEHaz6dcVpbR
# +Uy3RISHmGnlgrkT5lW/yJJwkgoxb3+LMqvPa1qfYsQ+7r7tWaRTfwvxUoiKewpn
# JMuQzezSTTRMsOG1n5zG9m8szebKU3QBn2c13jhJLc7tOUSCGXlOGrK1+7t48Elm
# p8/6XJZ1kosactn/UJJTzD7CQzIJGoYTaTz7gTIzMmR1cygmHQgwOwcCAwEAAaOC
# AeEwggHdMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMDMH8GCCsGAQUFBwEBBHMwcTAkBggrBgEFBQcwAYYYaHR0
# cDovL29jc3AuZGlnaWNlcnQuY29tMEkGCCsGAQUFBzAChj1odHRwOi8vY2FjZXJ0
# cy5kaWdpY2VydC5jb20vRGlnaUNlcnRIaWdoQXNzdXJhbmNlRVZSb290Q0EuY3J0
# MIGPBgNVHR8EgYcwgYQwQKA+oDyGOmh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9E
# aWdpQ2VydEhpZ2hBc3N1cmFuY2VFVlJvb3RDQS5jcmwwQKA+oDyGOmh0dHA6Ly9j
# cmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEhpZ2hBc3N1cmFuY2VFVlJvb3RDQS5j
# cmwwTwYDVR0gBEgwRjA4BgpghkgBhv1sAAIEMCowKAYIKwYBBQUHAgEWHGh0dHBz
# Oi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCgYIYIZIAYb9bAMwHQYDVR0OBBYEFGed
# DyAJDMyKOuWCRnJi/PHMkOVAMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSYJhoIAu9j
# ZCvDMA0GCSqGSIb3DQEBCwUAA4IBAQBqDv9+E3wGpUvALoz5U2QJ4rpYkTBQ7Myf
# 4dOoL0hGNhgp0HgoX5hWQA8eur2xO4dc3FvYIA3tGhZN1REkIUvxJ2mQE+sRoQHa
# /bVOeVl1vTgqasP2jkEriqKL1yxRUdmcoMjjTrpsqEfSTtFoH4wCVzuzKWqOaiAq
# ufIAYmS6yOkA+cyk1LqaNdivLGVsFnxYId5KMND66yRdBsmdFretSkXTJeIM8ECq
# XE2sfs0Ggrl2RmkI2DK2gv7jqVg0QxuOZ2eXP2gxFjY4lT6H98fDr516dxnZ3pO1
# /W4r/JT5PbdMEjUsML7ojZ4FcJpIE/SM1ucerDjnqPOtDLd67GftMYIRATCCEP0C
# AQEwgYowdjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcG
# A1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTE1MDMGA1UEAxMsRGlnaUNlcnQgU0hBMiBI
# aWdoIEFzc3VyYW5jZSBDb2RlIFNpZ25pbmcgQ0ECEAzWzvWbPiLeP9Se5oji/N4w
# DQYJYIZIAWUDBAIBBQCgfDAQBgorBgEEAYI3AgEMMQIwADAZBgkqhkiG9w0BCQMx
# DAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkq
# hkiG9w0BCQQxIgQg3jGg3FBa9FJ5oxrZDtSmv6mcWG/L+BmWFJQZfcXAD94wDQYJ
# KoZIhvcNAQEBBQAEggEAbEWccIVbdWWOGY9JjkyEJkUNiSDP7mkkkvQcAmHyqzhP
# xJjgGMd/UlK4npkk01uWX0cxhFN91zu6Siw3kbTPrOhtEMATPqFnvKT3eQxO+LWS
# bAarx5MiZP03jF2ZPyEjCLqyooQeA1xeJIHuoyKmZV2zEzbSYmhmcHTU9xszgX1d
# ZuUC6aOw0N2xVBZH4CTmOLoLBJSyplKGnmrAHZpzl8sCijGEpSlZGLIli2WwPeZM
# 0j9JhctKd4mKrNr2Ioob+w0rLbwvhzuO2sq8/Nu0gblkoz6gMWfPWMOYaZVFnKE5
# WRybXS8aqIQzp/2o14XqDjxQutrhm+0nVDXByZvP56GCDskwgg7FBgorBgEEAYI3
# AwMBMYIOtTCCDrEGCSqGSIb3DQEHAqCCDqIwgg6eAgEDMQ8wDQYJYIZIAWUDBAIB
# BQAweAYLKoZIhvcNAQkQAQSgaQRnMGUCAQEGCWCGSAGG/WwHATAxMA0GCWCGSAFl
# AwQCAQUABCAGhPu/AtwtXlOoULj9vfskfgjS0K7zv/bABNkhPJluWwIRAJgv28au
# DXXTd7X948oWL0MYDzIwMjAwMjE0MjI1MTQxWqCCC7swggaCMIIFaqADAgECAhAE
# zT+FaK52xhuw/nFgzKdtMA0GCSqGSIb3DQEBCwUAMHIxCzAJBgNVBAYTAlVTMRUw
# EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x
# MTAvBgNVBAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBUaW1lc3RhbXBpbmcg
# Q0EwHhcNMTkxMDAxMDAwMDAwWhcNMzAxMDE3MDAwMDAwWjBMMQswCQYDVQQGEwJV
# UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJDAiBgNVBAMTG1RJTUVTVEFNUC1T
# SEEyNTYtMjAxOS0xMC0xNTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
# AOlkNZz6qZhlZBvkF9y4KTbMZwlYhU0w4Mn/5Ts8EShQrwcx4l0JGML2iYxpCAQj
# 4HctnRXluOihao7/1K7Sehbv+EG1HTl1wc8vp6xFfpRtrAMBmTxiPn56/UWXMbT6
# t9lCPqdVm99aT1gCqDJpIhO+i4Itxpira5u0yfJlEQx0DbLwCJZ0xOiySKKhFKX4
# +uGJcEQ7je/7pPTDub0ULOsMKCclgKsQSxYSYAtpIoxOzcbVsmVZIeB8LBKNcA6P
# isrg09ezOXdQ0EIsLnrOnGd6OHdUQP9PlQQg1OvIzocUCP4dgN3Q5yt46r8fcMbu
# QhZTNkWbUxlJYp16ApuVFKMCAwEAAaOCAzgwggM0MA4GA1UdDwEB/wQEAwIHgDAM
# BgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMIIBvwYDVR0gBIIB
# tjCCAbIwggGhBglghkgBhv1sBwEwggGSMCgGCCsGAQUFBwIBFhxodHRwczovL3d3
# dy5kaWdpY2VydC5jb20vQ1BTMIIBZAYIKwYBBQUHAgIwggFWHoIBUgBBAG4AeQAg
# AHUAcwBlACAAbwBmACAAdABoAGkAcwAgAEMAZQByAHQAaQBmAGkAYwBhAHQAZQAg
# AGMAbwBuAHMAdABpAHQAdQB0AGUAcwAgAGEAYwBjAGUAcAB0AGEAbgBjAGUAIABv
# AGYAIAB0AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAgAEMAUAAvAEMAUABTACAAYQBu
# AGQAIAB0AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQAGEAcgB0AHkAIABBAGcAcgBl
# AGUAbQBlAG4AdAAgAHcAaABpAGMAaAAgAGwAaQBtAGkAdAAgAGwAaQBhAGIAaQBs
# AGkAdAB5ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBjAG8AcgBwAG8AcgBhAHQAZQBk
# ACAAaABlAHIAZQBpAG4AIABiAHkAIAByAGUAZgBlAHIAZQBuAGMAZQAuMAsGCWCG
# SAGG/WwDFTAfBgNVHSMEGDAWgBT0tuEgHf4prtLkYaWyoiWyyBc1bjAdBgNVHQ4E
# FgQUVlMPwcYHp03X2G5XcoBQTOTsnsEwcQYDVR0fBGowaDAyoDCgLoYsaHR0cDov
# L2NybDMuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC10cy5jcmwwMqAwoC6GLGh0
# dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3VyZWQtdHMuY3JsMIGFBggr
# BgEFBQcBAQR5MHcwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNv
# bTBPBggrBgEFBQcwAoZDaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lD
# ZXJ0U0hBMkFzc3VyZWRJRFRpbWVzdGFtcGluZ0NBLmNydDANBgkqhkiG9w0BAQsF
# AAOCAQEALoOhRAVKBOO5MlL62YHwGrv4CY0juT3YkqHmRhxKL256PGNuNxejGr9Y
# I7JDnJSDTjkJsCzox+HizO3LeWvO3iMBR+2VVIHggHsSsa8Chqk6c2r++J/BjdEh
# jOQpgsOKC2AAAp0fR8SftApoU39aEKb4Iub4U5IxX9iCgy1tE0Kug8EQTqQk9Eec
# 3g8icndcf0/pOZgrV5JE1+9uk9lDxwQzY1E3Vp5HBBHDo1hUIdjijlbXST9X/Aqf
# I1579JSN3Z0au996KqbSRaZVDI/2TIryls+JRtwxspGQo18zMGBV9fxrMKyh7eRH
# TjOeZ2ootU3C7VuXgvjLqQhsUwm09zCCBTEwggQZoAMCAQICEAqhJdbWMht+QeQF
# 2jaXwhUwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERp
# Z2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMb
# RGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTE2MDEwNzEyMDAwMFoXDTMx
# MDEwNzEyMDAwMFowcjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IElu
# YzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQg
# U0hBMiBBc3N1cmVkIElEIFRpbWVzdGFtcGluZyBDQTCCASIwDQYJKoZIhvcNAQEB
# BQADggEPADCCAQoCggEBAL3QMu5LzY9/3am6gpnFOVQoV7YjSsQOB0UzURB90Pl9
# TWh+57ag9I2ziOSXv2MhkJi/E7xX08PhfgjWahQAOPcuHjvuzKb2Mln+X2U/4Jvr
# 40ZHBhpVfgsnfsCi9aDg3iI/Dv9+lfvzo7oiPhisEeTwmQNtO4V8CdPuXciaC1Tj
# qAlxa+DPIhAPdc9xck4Krd9AOly3UeGheRTGTSQjMF287DxgaqwvB8z98OpH2YhQ
# Xv1mblZhJymJhFHmgudGUP2UKiyn5HU+upgPhH+fMRTWrdXyZMt7HgXQhBlyF/EX
# Bu89zdZN7wZC/aJTKk+FHcQdPK/P2qwQ9d2srOlW/5MCAwEAAaOCAc4wggHKMB0G
# A1UdDgQWBBT0tuEgHf4prtLkYaWyoiWyyBc1bjAfBgNVHSMEGDAWgBRF66Kv9JLL
# gjEtUYunpyGd823IDzASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIB
# hjATBgNVHSUEDDAKBggrBgEFBQcDCDB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUH
# MAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDov
# L2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNy
# dDCBgQYDVR0fBHoweDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0Rp
# Z2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDA6oDigNoY0aHR0cDovL2NybDMuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDBQBgNVHSAESTBH
# MDgGCmCGSAGG/WwAAgQwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNl
# cnQuY29tL0NQUzALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggEBAHGVEulR
# h1Zpze/d2nyqY3qzeM8GN0CE70uEv8rPAwL9xafDDiBCLK938ysfDCFaKrcFNB1q
# rpn4J6JmvwmqYN92pDqTD/iy0dh8GWLoXoIlHsS6HHssIeLWWywUNUMEaLLbdQLg
# cseY1jxk5R9IEBhfiThhTWJGJIdjjJFSLK8pieV4H9YLFKWA1xJHcLN11ZOFk362
# kmf7U2GJqPVrlsD0WGkNfMgBsbkodbeZY4UijGHKeZR+WfyMD+NvtQEmtmyl7odR
# IeRYYJu6DC0rbaLEfrvEJStHAgh8Sa4TtuF8QkIoxhhWz0E0tmZdtnR79VYzIi8i
# NrJLokqV2PWmjlIxggJNMIICSQIBATCBhjByMQswCQYDVQQGEwJVUzEVMBMGA1UE
# ChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYD
# VQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgVGltZXN0YW1waW5nIENBAhAE
# zT+FaK52xhuw/nFgzKdtMA0GCWCGSAFlAwQCAQUAoIGYMBoGCSqGSIb3DQEJAzEN
# BgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMjAwMjE0MjI1MTQxWjArBgsq
# hkiG9w0BCRACDDEcMBowGDAWBBQDJb1QXtqWMC3CL0+gHkwovig0xTAvBgkqhkiG
# 9w0BCQQxIgQg1965ZC99q/SICT1w2VfN48Ysni06Qbp5Ey2HT2PzkNUwDQYJKoZI
# hvcNAQEBBQAEggEAR5wqSmWS/KFVOk7lfGipEBXtwpNjumcCSCmwgtcIWMv+E2Hp
# 7+EbSxhfjCpEaLDnMItG1waHcn/2IrOzVKXTYbkgl7a68QiOYEEmIuRXz4KZGLEC
# fu+BOuMTRRcdAwR/8EX1gR8x5X4bKVDc6pc4Cf41DCPV+NxRUiY1BkVuNfrPtiR/
# +zpk3qoCkMBsvOTbrA2Ri9UijEtWTJ/DZ+6wpKNQSoxzT9Cv4JilkDvNgCsZztQG
# vdlxxVSZwbw1dQBkQWXbu8h+C9bfI4isIN8CttoyuNVOlW4UvO0JnU7fnsGp4BeO
# KSL6QM/hVW4sV9VYsnsWCzEHU7X3rltPG9Mhtw==
# SIG # End signature block