HP.Softpaq.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
#requires -Modules "HP.Private","HP.Firmware"
Add-Type -AssemblyName System.IO.Compression.FileSystem

<#
.SYNOPSIS
  Retrieve SoftPaq metadata (CVA) can retrieve a CVA or an alternate server.
 
.DESCRIPTION
  Get a SoftPaq metadata file (CVA) from the location specified by the URL parameter. If URL is not specified, the SoftPaq metadata is downloaded can retrieve a CVA via HTTPS.
 
.PARAMETER Number
  The SoftPaq number for which to retrieve the metadata. Do not include prefix such as SP or extension such as .exe, specify the SoftPaq number only.
 
.PARAMETER Url
  Specify an alternate location for the SoftPaq URL. This URL must be http, https, or ftp. The SoftPaq CVAs are expected to be at the location pointed to by this URL. If not specified, ftp.hp.com is used via HTTPS protocol.
 
.PARAMETER MaxRetries
  Maximum number of retries allowed to obtain an exclusive lock to downloaded files. This is relevant only when files are downloaded into a shared directory and multiple processes may be reading or writing from the same location.
 
  Current default value is 10 retries, and each retry includes a 30 second pause, which means the maximum time the default value will wait for an exclusive logs is 300 seconds or 5 minutes.
 
.EXAMPLE
  Get-SoftpaqMetadata -Number 1234 | Out-SoftpaqField -field Title
 
.LINK
  [Get-Softpaq](Get-Softpaq)
 
.LINK
  [Get-SoftpaqMetadataFile](Get-SoftpaqMetadataFile)
 
.LINK
  [Get-SoftpaqList](Get-SoftpaqList)
 
.LINK
  [Out-SoftpaqField](Out-SoftpaqField)
 
.LINK
  [Clear-SoftpaqCache](Clear-SoftpaqCache)
#>

function Get-SoftpaqMetadata {
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/Get-SoftpaqMetadata")]
  param(
    [ValidatePattern('^([Ss][Pp])*([0-9]{3,9})((\.[Ee][Xx][Ee]|\.[Cc][Vv][Aa])*)$')]
    [Parameter(Position = 0,Mandatory = $true)] [string]$Number,
    [Parameter(Position = 1,Mandatory = $false)] [string]$Url,
    [Parameter(Position = 2,Mandatory = $false)] [int]$MaxRetries = 0
  )

  $no = [int]$number.ToLower().TrimStart("sp").trimend(".exe").trimend('cva')
  [System.Net.ServicePointManager]::SecurityProtocol = Get-HPPrivateAllowedHttpsProtocols
  $loc = Get-HPPrivateItemUrl $no "cva" $url
  Get-HPPrivateReadINI -File $loc -Verbose:$VerbosePreference -maxRetries $maxRetries
}


<#
.SYNOPSIS
  Retrieve SoftPaq (EXE) can retrieve a CVA or an alternate server.
 
.DESCRIPTION
  This function downloads a SoftPaq from a the default download server (ftp.hp.com) or an alternate server.
  When using the default location, the download is performed over HTTPS, otherwise the protocol is dictated by the URL parameter.
 
  If a SoftPaq is either unavailable to download or has been obsoleted on the server, Get-Softpaq will display a warning that the SoftPaq could not be retrieved from ftp.hp.com.
 
  Get-Softpaq functionality is not supported in WinPE.
 
.PARAMETER Number
  The SoftPaq number for which to retrieve the metadata. Do not include prefix such as SP or extension such as .exe, specify the SoftPaq number only.
 
.PARAMETER SaveAs
  Provide a specific name for the saved SoftPaq, otherwise it is inferred based on the remote name or the SoftPaq metadata if -FriendlyName is specified.
 
.PARAMETER FriendlyName
  Create a friendly name for the downloaded SoftPaq, based on the SoftPaq number and title.
 
.PARAMETER Quiet
  Suppress non-errors such as download progress and other messages.
 
.PARAMETER Overwrite
  This parameter controls the overwrite behavior. Options may be "no" to not overwrite existing files, "yes" to force overwrite, and "skip" to skip existing files without an error. Default is "no" if overwrite is not specified.
 
.PARAMETER Action
  Perform a specific action after download. The action may be "install" or "silentinstall". Silentinstall information is retrieved from the SoftPaq metadata (CVA) file.
 
.PARAMETER Extract
  Extracts files from a specified zipped archive file to a specified destination folder.
 
  Note:
    By default, files are extracted into a new sub-folder relative to the downloaded SoftPaq executable.
 
.PARAMETER DestinationPath
  Specifies the path to the folder in which you want to save extracted files. Enter the path to a folder, but do not specify a file name or file name extension.
 
  Note:
    By default, the files are extracted into a new sub-folder relative to the downloaded executable.
 
.PARAMETER Url
  Specify an alternate location for the SoftPaq URL. This URL must be http, https, or ftp. The SoftPaqs are expected to be at the location pointed to by this URL. If not specified, ftp.hp.com is used via HTTPS protocol.
 
.PARAMETER KeepInvalidSigned
  Get-Softpaq function performs an Authenticode signature check after a download, and the default is to delete any downloaded file with invalid or missing signature. Use this flag to keep the file that failed the signature.
 
.PARAMETER MaxRetries
  Maximum number of retries allowed to obtain an exclusive lock to downloaded files. This is relevant only when files are downloaded into a shared directory and multiple processes may be reading or writing from the same location.
 
  Current default value is 10 retries, and each retry includes a 30 second pause, which means the maximum time the default value will wait for an exclusive logs is 300 seconds or 5 minutes.
 
.EXAMPLE
    Get-Softpaq -Number 1234
 
.EXAMPLE
    Get-Softpaq -Number 1234 -Extract -DestinationPath "c:\staging\drivers"
 
.LINK
  [Get-SoftpaqMetadata](Get-SoftpaqMetadata)
 
.LINK
  [Get-SoftpaqMetadataFile](Get-SoftpaqMetadataFile)
 
.LINK
  [Get-SoftpaqList](Get-SoftpaqList)
 
.LINK
  [Out-SoftpaqField](Out-SoftpaqField)
 
.LINK
  [Clear-SoftpaqCache](Clear-SoftpaqCache)
 
#>

function Get-Softpaq {
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/Get-Softpaq",DefaultParameterSetName = "DownloadParams")]
  param(
    [Parameter(ParameterSetName = "DownloadParams")]
    [Parameter(Position = 0,Mandatory = $true)]
    [string]$Number,

    [Parameter(ParameterSetName = "DownloadParams")]
    [Parameter(Position = 1,Mandatory = $false)]
    [string]$SaveAs,

    [Parameter(ParameterSetName = "DownloadParams")]
    [Parameter(Position = 2,Mandatory = $false)]
    [switch]$FriendlyName,

    [Parameter(ParameterSetName = "DownloadParams")]
    [Parameter(Position = 3,Mandatory = $false)]
    [switch]$Quiet,

    [Parameter(ParameterSetName = "DownloadParams")]
    [ValidateSet("no","yes","skip")]
    [Parameter(Position = 4,Mandatory = $false)]
    [string]$Overwrite = "no",

    [Parameter(Position = 5,Mandatory = $false,ParameterSetName = "DownloadParams")]
    [Parameter(Position = 5,Mandatory = $false,ParameterSetName = "InstallParams")]
    [ValidateSet("install","silentinstall")]
    [string]$Action,

    [Parameter(ParameterSetName = "DownloadParams")]
    [Parameter(Position = 6,Mandatory = $false)]
    [string]$Url,

    [Parameter(ParameterSetName = "DownloadParams")]
    [Parameter(Position = 7,Mandatory = $false)]
    [switch]$KeepInvalidSigned,

    [Parameter(ParameterSetName = "DownloadParams")]
    [Parameter(Position = 8,Mandatory = $false)]
    [int]$MaxRetries = 0,

    [Parameter(Mandatory = $false,ParameterSetName = "DownloadParams")]
    [Parameter(Mandatory = $false,ParameterSetName = "ExtractParams")]
    [switch]$Extract,

    [Parameter(Mandatory = $false,ParameterSetName = "DownloadParams")]
    [Parameter(Mandatory = $false,ParameterSetName = "ExtractParams")]
    [ValidatePattern('^[a-zA-Z]:\\')]
    [string]$DestinationPath
  )

  if ((Test-WinPE) -and ($action)) {
    throw [NotSupportedException]"Softpaq installation is not supported in WinPE"
  }

  [System.Net.ServicePointManager]::SecurityProtocol = Get-HPPrivateAllowedHttpsProtocols
  $no = [int]$number.ToLower().TrimStart("sp").trimend(".exe")

  if ($keepInvalidSigned.IsPresent) { $keepInvalid = $true }
  else { $keepInvalid = $false }

  if ($quiet.IsPresent) { $progress = -not $quiet }
  else { $progress = $true }

  $loc = Get-HPPrivateItemUrl -Number $no -Ext "exe" -url $url
  $target = $null
  $root = $null

  if ($friendlyName.IsPresent -or $action) {
    # get SoftPaq metadata
    try { $root = Get-SoftpaqMetadata $no -url $url -maxRetries $maxRetries }
    catch {
      if ($progress -eq $true) {
        Write-Host -ForegroundColor Magenta "(Warning) Could not retrieve CVA file metadata for sp$no."
        Write-Host -ForegroundColor Magenta $_.Exception.Message
      }
    }
  }

  # build the filename
  if (!$saveAs) {
    if ($friendlyName.IsPresent)
    {
      Write-Verbose "Need to get CVA data to determine Friendly Name for SoftPaq file"
      $target = getfriendlyFileName -Number $no -info $root -Verbose:$VerbosePreference
      $target = "$target.exe"
    }

    else { $target = "sp$no.exe" }
  }
  else { $target = $saveAs }

  $targetFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($target)
  Write-Verbose "TargetFile: $targetFile"

  try
  {
    Invoke-HPPrivateDownloadFile -url $loc -Target $targetFile -progress $progress -NoClobber $overwrite -Verbose:$VerbosePreference -maxRetries $maxRetries
  }
  catch
  {
    Write-Host -ForegroundColor Magenta "(Warning) Could not retrieve $loc."
    Write-Host -ForegroundColor Magenta $_.Exception.Message
    throw $_.Exception
  }

  # check digital signatures
  $signed = Get-HPPrivateCheckSignature -File $targetFile -Verbose:$VerbosePreference -Progress:(-not $Quiet.IsPresent)

  if ($signed -eq $false) {
    switch ($keepInvalid) {
      $true {
        if ($progress -eq $true) {
          Write-Host -ForegroundColor Magenta "(Warning): File $targetFile has an invalid or missing signature"
          return
        }
      }
      $false {
        Invoke-HPPrivateSafeRemove -Path $targetFile -Verbose:$VerbosePreference
        throw [System.BadImageFormatException]"File $targetFile has invalid or missing signature and will be deleted."
        return
      }
    }
  }
  else {
    if ($progress -eq $true) {
      Write-Verbose "Digital signature is valid."
    }
  }

  # perform requested action
  if ($action -ne $null)
  {
    if ($Extract.IsPresent) {
      $PostInstallAction = "extract"
      if (!$DestinationPath) {
        $DestinationPath = Join-Path -Path $(Get-Location) -ChildPath $target.trimend(".exe")
      }
      if ($DestinationPath -match [regex]::Escape([System.Environment]::SystemDirectory)) {
        throw 'Windows System32 is not a valid destination path.'
      }
      $workingPath = $(Get-HPPrivateTempPath)
      $workingPath = Join-Path -Path $workingPath -ChildPath $target
      Write-Verbose "Copying downloaded SoftPaq to temporary working directory $workingPath"
      Copy-Item -Path $targetFile -Destination $workingPath -Force
      Invoke-PostDownloadSoftpaqAction -downloadedFile $workingPath -Action $PostInstallAction -Number $number -info $root -Destination $DestinationPath -Verbose:$VerbosePreference
      Write-Verbose "Remove SoftPaq from the temporary working directory $workingPath"
      Remove-Item -Path $workingPath -Force
    }
    else {
      $PostInstallAction = $action
      Invoke-PostDownloadSoftpaqAction -downloadedFile $targetFile -Action $PostInstallAction -Number $number -info $root -Destination $DestinationPath -Verbose:$VerbosePreference
    }
  }
}

<#
.SYNOPSIS
  Retrieve SoftPaq metadata (CVA) can retrieve a CVA or an alternate server.
 
.DESCRIPTION
  Use this command to download a SoftPaq from a specified location. By default, the SoftPaq is retrieved from HP.COM via HTTPS.
 
.PARAMETER Number
  The SoftPaq number for which to retrieve the metadata. Do not include prefix such as SP or extension such as .exe.
 
.PARAMETER SaveAs
  Provide a specific name for the saved SoftPaq metadata, otherwise it is inferred based on the remote name or the metadata if -friendlyName is specified.
 
.PARAMETER FriendlyName
  Create a friendly name for the downloaded SoftPaq, based on the SoftPaq number and title.
 
.PARAMETER Quiet
  Suppress non-errors such as download progress and other messages.
 
.PARAMETER Overwrite
  This parameter controls the overwrite behavior. Options may be "no" to not overwrite existing files, "yes" to force overwrite, and "skip" to skip existing files without an error. Default is "no" if overwrite is not specified.
 
.PARAMETER MaxRetries
  Maximum number of retries allowed to obtain an exclusive lock to downloaded files. This is relevant only when files are downloaded into a shared directory and multiple processes may be reading or writing from the same location.
 
  Current default value is 10 retries, and each retry includes a 30 second pause, which means the maximum time the default value will wait for an exclusive logs is 300 seconds or 5 minutes.
 
.PARAMETER url
  An alternate URL where to look for the SoftPaq
 
.EXAMPLE
  Get-SoftpaqMetadataFile -Number 1234
 
.LINK
  [Get-SoftpaqMetadata](Get-SoftpaqMetadata)
 
.LINK
  [Get-Softpaq](Get-Softpaq)
 
.LINK
  [Get-SoftpaqList](Get-SoftpaqList)
 
.LINK
  [Out-SoftpaqField](Out-SoftpaqField)
 
.LINK
  [Clear-SoftpaqCache](Clear-SoftpaqCache)
 
#>

function Get-SoftpaqMetadataFile {
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/Get-SoftpaqMetadataFile")]
  param(
    [ValidatePattern('^([Ss][Pp])*([0-9]{3,9})((\.[Ee][Xx][Ee]|\.[Cc][Vv][Aa])*)$')]
    [Parameter(Position = 0,Mandatory = $true)]
    [string]$Number,
    [Parameter(Position = 1,Mandatory = $false)]
    [string]$SaveAs,
    [Parameter(Position = 2,Mandatory = $false)]
    [switch]$FriendlyName,
    [Parameter(Position = 3,Mandatory = $false)]
    [switch]$Quiet,
    [ValidateSet("No","Yes","Skip")]
    [Parameter(Position = 4,Mandatory = $false)]
    [string]$Overwrite = "No",
    [Parameter(Position = 5,Mandatory = $false)]
    [string]$Url,
    [Parameter(Position = 6,Mandatory = $false)]
    [int]$MaxRetries = 0
  )

  [System.Net.ServicePointManager]::SecurityProtocol = Get-HPPrivateAllowedHttpsProtocols
  $no = [int]$number.ToLower().TrimStart("sp").trimend(".exe").trimend('cva')

  if ($quiet.IsPresent) { $progress = -not $quiet }
  else { $progress = $true }

  $loc = Get-HPPrivateItemUrl -Number $no -Ext "cva" -url $url

  $target = $null

  # get SoftPaq metadata. We don't need this step unless we get friendly name
  if ($friendlyName.IsPresent) {
    Write-Verbose "Need to get CVA data to determine Friendly Name for CVA file"
    try { $root = Get-SoftpaqMetadata $number -url $url -maxRetries $maxRetries }
    catch {
      if ($progress -eq $true) {
        Write-Host -ForegroundColor Magenta "(Warning) Could not retrieve CVA file metadata"
        Write-Host -ForegroundColor Magenta $_.Exception.Message
      }
      $root = $null
    }
  }

  # build the filename
  if (!$saveAs) {
    if ($friendlyName.IsPresent) {
      Write-Verbose "Need to get CVA data to determine Friendly Name for CVA file"
      $target = getfriendlyFileName -Number $no -info $root -Verbose:$VerbosePreference
      $target = "$target.cva"
    }
    else { $target = "sp$no.cva" }
  }
  else { $target = $saveAs }

  # download the file
  $targetFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($target)
  Invoke-HPPrivateDownloadFile -url $loc -Target $targetFile -progress $progress -NoClobber $overwrite -Verbose:$VerbosePreference -maxRetries $maxRetries -skipSignatureCheck
}

<#
.SYNOPSIS
  Filter a loaded SoftPaq metadata
 
.DESCRIPTION
  Extract the specified field from the SoftPaq metadata. Some of the currently supported fields are:
 
  * Title
  * Description
  * SilentInstall
  * Number
  * Platforms
  * PlatformIDs
  * Version
  * SoftPaqSHA256
  * SoftPaqMD5
 
.PARAMETER Field
  The predefined filter to retrieve
 
.PARAMETER InputObject
  Data coming from the pipeline
 
.EXAMPLE
  $mysoftpaq | Out-SoftpaqField -Field Title
 
.LINK
  [Get-SoftpaqMetadata](Get-SoftpaqMetadata)
 
.LINK
  [Get-Softpaq](Get-Softpaq)
 
.LINK
  [Get-SoftpaqList](Get-SoftpaqList)
 
.LINK
  [Get-Softpaq](Get-Softpaq)
 
.LINK
  [Clear-SoftpaqCache](Clear-SoftpaqCache)
#>

function Out-SoftpaqField {
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/Out-SoftpaqField")]
  param(
    [ValidateSet("Install","SilentInstall","Title","Description","Number","Platforms","PlatformIDs","SoftPaqSHA256","SoftPaqMD5","Version")]
    [Parameter(Mandatory = $True)]
    [string]$Field,

    [ValidateNotNullOrEmpty()]
    [Parameter(ValueFromPipeline = $True,Mandatory = $True)]
    [Alias('In')]
    $InputObject
  )

  begin {
    if (!$mapper.contains($field)) {
      throw [InvalidOperationException]"Field '$field' is not supported as a filter"
    }
  }
  process
  {
    $result = descendNodesAndGet $InputObject -Field $field
    if ($mapper[$field] -match "%KeyValues\(.*\)$") {

      $pattern = $mapper[$field] -match "\((.*)\)"
      if ($pattern[0]) {

        # Need to narrow it down to PlatformIDs otherwise Platforms will be shown in UpperCase too.
        if ($Field -eq "PlatformIDs") {
          $result = $result[$result.keys -match $matches[1]].ToUpper() | Get-Unique
          return $result -replace "^0X",''
        }
        else {
          return $result[$result.keys -match $matches[1]] | Get-Unique
        }
      }
    }
    return $result
  }
  end {}
}

<#
.SYNOPSIS
    Clear the SoftPaq cache.
 
.DESCRIPTION
 
  Clear the cache used for downloading SoftPaq database files.
 
  This is normally not needed as part of normal operation, the cache doesn't grow significantly over time, and is also cleared
  by normal operations that flush the user's temporary directory.
 
  The function is only intended as a debugging function.
 
 
 
.EXAMPLE
    Clear-SoftpaqCache
 
.PARAMETER cacheDir
    Custom location for caching data files. If not specified, the user-specific TEMP directory is used.
 
 
.LINK
  [Get-SoftpaqMetadata](Get-SoftpaqMetadata)
 
.LINK
  [Get-Softpaq](Get-Softpaq)
 
.LINK
  [Get-SoftpaqList](Get-SoftpaqList)
 
.LINK
  [Get-Softpaq](Get-Softpaq)
 
.LINK
  [Out-SoftpaqField](Out-SoftpaqField)
 
.NOTES
    This command removes the cached files from the user temporary directory. It cannot be used to clear the cache
  when the data files are stored inside a repository structure. Custom cache locations (other than the default)
  must be specified via the cacheDir folder.
 
#>

function Clear-SoftpaqCache {
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/Clear-SoftpaqCache")]
  param(
    [Parameter(Mandatory = $false)]
    [System.IO.DirectoryInfo]$CacheDir
  )
  $cacheDir = Get-HPPrivateCacheDirPath ($cacheDir)
  Invoke-HPPrivateSafeRemove -Path $cacheDir -Recurse
}

<#
.SYNOPSIS
  Get a list of SoftPaqs for a platform
 
.DESCRIPTION
  This function gets a list of latest SoftPaqs for a specified platform ID, or the current computer.
 
  Get-SoftpaqList functionality is not supported in WinPE.
 
.PARAMETER Platform
  The platform ID for which to retrieve the list of SoftPaqs. If not available, the current platform ID is used.
 
.PARAMETER Bitness
  The platform bitness (32 or 64). If not available, the current platform bitness is used.
 
.PARAMETER Os
  Filter for the specified OS. The OS may be "win10" or "win11". If not specified, current platform OS is used.
 
.PARAMETER OsVer
  Filter for the specified OS Version. This is a string value, specifying the target OS Version (e.g. "1809", "1903", "1909", "2004", "2009", "21H1", "21H2", "22H2" etc). If the parameter is not specified, the current operating system version will be assumed, which may not be what is intended.
 
.PARAMETER Category
  The category field filters to a specific category of SoftPaqs. It must be one (or more) of "Bios", "Firmware", "Driver", "Software", "OS", "Manageability", "Diagnostic", "Utility", "Driverpack", "Dock", "UWPPack".
 
.PARAMETER ReleaseType
  Filter to specified release type. The release type must be one (or more) of "Critical", "Recommended", "Routine".
 
.PARAMETER ReferenceUrl
  Specify an alternate location for the HP Image Assistant (HPIA) Reference files. This URL must be http, https, or ftp. The Reference files are expected to be at the location pointed to by this URL inside a directory named after the platform ID you want a SoftPaq list for. If not specified, ftp.hp.com is used via HTTPS protocol.
 
  For example, If you want to point to 83b2 Win10 OSVer 2009 reference files, Get-SoftpaqList will try to find them in this directory structure: $ReferenceUrl/83b2/83b2_64_10.0.2009.cab
 
.PARAMETER SoftpaqUrl
  Specify an alternate location for the SoftPaq URL. This URL must be http, https, or ftp. The SoftPaqs are expected to be at the location pointed to by this URL. If not specified, ftp.hp.com is used via HTTPS protocol.
 
.PARAMETER Quiet
  Suppress non-error progress messages
 
.PARAMETER Download
  Download matching SoftPaqs
 
.PARAMETER DownloadMetadata
  Also download CVA files (metadata) for matching SoftPaqs
 
.PARAMETER DownloadNotes
  Also download note files (human readable info files) for matching SoftPaqs
 
.PARAMETER DownloadDirectory
  Specify a directory for the downloaded files
 
.PARAMETER FriendlyName
  If specified, retrieve the SoftPaq metadata and create a friendly file name based on the SoftPaq title. Applies if -download is specified.
 
.PARAMETER Overwrite
  This parameter controls the overwrite behavior. Options may be "no" to not overwrite existing files, "yes" to force overwrite, and "skip" to skip existing files without an error. Default is "no" if overwrite is not specified.
 
.PARAMETER Format
  Display results in a specified format (Json, XML, or CSV). If not specified, results are returned as PowerShell objects
 
.PARAMETER Characteristic
  Specify additional filter characteristics for the SoftPaq list. The characteristic must be one (or more) of "SSM", "DPB", "UWP".
 
.PARAMETER CacheDir
  Custom location for caching data files. If not specified, the user-specific TEMP directory is used.
 
.PARAMETER MaxRetries
  Maximum number of retries allowed to obtain an exclusive lock to downloaded files. This is relevant only when files are downloaded into a shared directory and multiple processes may be reading or writing from the same location.
 
  Current default value is 10 retries, and each retry includes a 30 second pause, which means the maximum time the default value will wait for an exclusive logs is 300 seconds or 5 minutes.
 
.PARAMETER PreferLTSC
  If specified and if data file exists, retrieves the LTSB/LTSC Reference file for the entered Platform. If data file doesn't exist, falls back to regular Reference file for that platform.
 
.PARAMETER AddHttps
  If specified, prepends https tag to the following SoftPaq attributes: url, ReleaseNotes and Metadata
 
.EXAMPLE
  Get-SoftpaqList -Download
 
.EXAMPLE
  Get-SoftpaqList -Bitness 64 -Os win10 -OsVer 1903
 
.EXAMPLE
  Get-SoftpaqList -Platform 83b2 -Os win10 -OsVer "21H1"
 
.EXAMPLE
  Get-SoftpaqList -Platform 8444 -Category Diagnostic -Format json
 
.EXAMPLE
  Get-SoftpaqList -Category Driverpack
 
.EXAMPLE
  Get-SoftpaqList -ReleaseType Critical -Characteristic SSM
 
.EXAMPLE
  Get-SoftpaqList -Platform 83b2 -Category Dock,Firmware -ReleaseType Routine,Critical
 
.EXAMPLE
  Get-SoftpaqList -Platform 2216 -Category Driverpack -Os win10 -OsVer 1607 -PreferLTSC
 
.LINK
  [Get-SoftpaqMetadata](Get-SoftpaqMetadata)
 
.LINK
  [Get-Softpaq](Get-Softpaq)
 
.LINK
  [Clear-SoftpaqCache](Clear-SoftpaqCache)
 
.LINK
  [Get-Softpaq](Get-Softpaq)
 
.LINK
  [Out-SoftpaqField](Out-SoftpaqField)
 
.NOTES
  The response is a record set composed of zero or more SoftPaq records. The definition of a record is as follows:
 
  | Field | Description |
  |---------------|-------------|
  | Id | The SoftPaq identifier |
  | Name | The SoftPaq name (title) |
  | Category | The SoftPaq category |
  | Version | The SoftPaq version |
  | Vendor | The author of the SoftPaq |
  | ReleaseType | The SoftPaq release type |
  | SSM | This flag indicates this SoftPaq support unattended silent install |
  | DPB | This flag indicates this SoftPaq is included in driver pack builds |
  | Url | The SoftPaq download URL |
  | ReleaseNotes | The URL to a human-readable rendering of the SoftPaq release notes |
  | Metadata | The URL to the SoftPaq metadata (CVA) file |
  | MD5 | (where available) The MD5 value for this SoftPaq |
  | Size | The SoftPaq size, in bytes |
  | ReleaseDate | The date the SoftPaq was published |
  | UWP | (where available) This flag indicates this SoftPaq contains Universal Windows Platform applications |
 
#>

function Get-SoftpaqList {
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/Get-SoftpaqList",DefaultParameterSetName = "ViewParams")]
  param(

    [ValidatePattern("^[a-fA-F0-9]{4}$")]
    [Parameter(ParameterSetName = "DownloadParams")]
    [Parameter(Position = 0,Mandatory = $false,ParameterSetName = "ViewParams")] [string]$Platform,

    [ValidateSet(32,64)]
    [Parameter(ParameterSetName = "DownloadParams")]
    [Parameter(Position = 1,Mandatory = $false,ParameterSetName = "ViewParams")] [int]$Bitness,

    [ValidateSet($null,"win7","win8","win8.1","win81","win10","win11")]
    [Parameter(ParameterSetName = "DownloadParams")]
    [Parameter(Position = 2,Mandatory = $false,ParameterSetName = "ViewParams")] [string]$Os,

    [ValidateSet("1809","1903","1909","2004","2009","21H1","21H2","22H2")]
    [Parameter(ParameterSetName = "DownloadParams")]
    [Parameter(Position = 3,Mandatory = $false,ParameterSetName = "ViewParams")] [string]$OsVer,

    [Parameter(ParameterSetName = "DownloadParams")]
    [Alias('Url')]
    [Parameter(Position = 4,Mandatory = $false,ParameterSetName = "ViewParams")] [string]$ReferenceUrl = "https://hpia.hpcloud.hp.com/ref",

    [Parameter(ParameterSetName = "DownloadParams")]
    [Parameter(Position = 5,Mandatory = $false,ParameterSetName = "ViewParams")] [switch]$Quiet,

    [Parameter(ParameterSetName = "DownloadParams")]
    [ValidateSet("XML","Json","CSV")]
    [Parameter(Position = 6,ParameterSetName = "ViewParams")] [string]$Format,

    [Parameter(Position = 7,ParameterSetName = "DownloadParams")] [string]$DownloadDirectory,

    [Alias("downloadSoftpaq","downloadPackage")]
    [Parameter(Position = 8,ParameterSetName = "DownloadParams")] [switch]$Download,

    [Alias("downloadCva")]
    [Parameter(Position = 9,ParameterSetName = "DownloadParams")] [switch]$DownloadMetadata,
    [Parameter(Position = 10,ParameterSetName = "DownloadParams")] [switch]$DownloadNotes,
    [Parameter(Position = 11,ParameterSetName = "DownloadParams")] [switch]$FriendlyName,

    [ValidateSet("No","Yes","Skip")]
    [Parameter(ParameterSetName = "DownloadParams")]
    [Parameter(Position = 12,Mandatory = $false,ParameterSetName = "ViewParams")] [string]$Overwrite = "No",


    [ValidateSet("BIOS","Firmware","Driver","Software","OS","Manageability","Diagnostic","Utility","Driverpack","Dock","UWPPack")]
    [Parameter(ParameterSetName = "DownloadParams")]
    [Parameter(Position = 13,ParameterSetName = "ViewParams")] [string[]]$Category = $null,


    [ValidateSet("Critical","Recommended","Routine")]
    [Parameter(ParameterSetName = "DownloadParams")]
    [Parameter(Position = 14,ParameterSetName = "ViewParams")] [string[]]$ReleaseType = $null,


    [ValidateSet("SSM","DPB","UWP")]
    [Parameter(ParameterSetName = "DownloadParams")]
    [Parameter(Position = 15,ParameterSetName = "ViewParams")] [string[]]$Characteristic = $null,


    [Parameter(ParameterSetName = "DownloadParams")]
    [Parameter(Position = 16,ParameterSetName = "ViewParams")] [System.IO.DirectoryInfo]$CacheDir,

    [Parameter(ParameterSetName = "DownloadParams")]
    [Parameter(Position = 17,Mandatory = $false,ParameterSetName = "ViewParams")] [int]$MaxRetries = 0,

    [Alias("PreferLTSB")]
    [Parameter(ParameterSetName = "DownloadParams")]
    [Parameter(Position = 18,Mandatory = $false,ParameterSetName = "ViewParams")] [switch]$PreferLTSC,

    [Parameter(ParameterSetName = "DownloadParams")]
    [Parameter(Position = 19,Mandatory = $false,ParameterSetName = "ViewParams")] [string]$SoftpaqUrl,

    [Parameter(ParameterSetName = "DownloadParams")]
    [Parameter(Position = 20,Mandatory = $false,ParameterSetName = "ViewParams")] [switch]$AddHttps
  )

  if (Test-WinPE) {
    throw [NotSupportedException]"This operation is not supported in WinPE"
  }

  [System.Net.ServicePointManager]::SecurityProtocol = Get-HPPrivateAllowedHttpsProtocols
  $ver = ""
  $progress = $true
  $cacheDir = Get-HPPrivateCacheDirPath ($cacheDir)

  if ($quiet.IsPresent) { $progress = -not $quiet }
  if (-not $platform) { $platform = Get-HPDeviceProductID }
  $platform = $platform.ToLower()
  if ($OsVer) { $OsVer = $OsVer.ToLower() }

  if (!$bitness) {
    $bitness = Get-HPPrivateCurrentOsBitness
  }
  if (!$os) {
    $os = Get-HPPrivateCurrentOs
  }


  if (([System.Environment]::OSVersion.Version.Major -eq 10) -and $OsVer) {

    try {
      # try converting OsVer to int
      $OSVer = [int]$OsVer

      if ($OSVer -gt 2009 -or $OSVer -lt 1507) {
        throw "Unsupported operating system version"
      }
    }
    catch {
      if (!($OSVer -match '[0-9]{2}[hH][0-9]')) {
        throw "Unsupported operating system version"
      }
    }
  }

  # determine OSVer for Win10 if OSVer is not passed
  if (([System.Environment]::OSVersion.Version.Major -eq 10) -and (!$osver))
  {
    Write-Verbose "need to determine OSVer"
    $osver = GetCurrentOSVer
  }

  switch ($os)
  {
    "win10" { $ver = "10.0." + $osver.ToString() }
    "win11" { $ver = "11.0." + $osver.ToString() }
    "win81" { $ver = "6.3" }
    "win8.1" { $ver = "6.3" }
    "win8" { $ver = "6.2" }
    "win7" { $ver = "6.1" }
    default { throw [NotSupportedException]"Unsupported operating system: " + $_ }
  }

  $fn = "$($platform)_$($bitness)_$($ver)"
  #Write-Host -ForegroundColor Magenta $fn
  $result = $null
  $LTSCExists = $false
  $ReferenceFallbackUrL = "https://ftp.hp.com/pub/caps-softpaq/cmit/imagepal/ref"

  if ($PreferLTSC.IsPresent) {
    $qurl = "$ReferenceUrl/$platform/$fn.e.cab"
    $qfile = Get-HPPrivateTemporaryFileName -FileName "$fn.e.cab" -cacheDir $cacheDir
    $downloadedFile = "$qfile.dir\$fn.e.xml"
    $try_on_ftp = $false
    try {
      $result = Test-HPPrivateIsDownloadNeeded -url $qurl -File $qfile -Verbose:$VerbosePreference
      if ($result[1] -eq $true) {
        Write-Verbose "Trying to download $qurl from AWS Server..."
      }
      $LTSCExists = $true
    }
    catch {
      Write-Verbose "HTTP request to $qurl failed: $($_.Exception.Message)"
      $try_on_ftp = $true
    }

    if ($try_on_ftp) {
      try {
        Write-Verbose "$qurl not found on AWS Server. Trying to download it from FTP Server..."
        $qurl = "$ReferenceFallbackUrl/$platform/$fn.e.cab"
        $result = Test-HPPrivateIsDownloadNeeded -url $qurl -File $qfile -Verbose:$VerbosePreference
        if ($result[1] -eq $true) {
          $LTSCExists = $true
        }
      }
      catch {
        Write-Verbose "HTTP request to $qurl failed: $($_.Exception.Message)"
        if (-not $quiet -or $result[1] -eq $false) {
          Write-Host -ForegroundColor Magenta "LTSB/LTSC data file doesn't exists for platform $platform ($os $osver)"
          Write-Host -ForegroundColor Cyan "Getting the regular (non-LTSB/LTSC) data file for this platform"
        }
      }
    }
  }

  # LTSB(C) file doesn't exists, fall back to regular Ref file
  if ((-not $PreferLTSC.IsPresent) -or ($PreferLTSC.IsPresent -and ($LTSCExists -eq $false))) {
    $qurl = "$ReferenceUrl/$platform/$fn.cab"
    $qfile = Get-HPPrivateTemporaryFileName -FileName "$fn.cab" -cacheDir $cacheDir
    $downloadedFile = "$qfile.dir\$fn.xml"
    $try_on_ftp = $false
    try {
      $result = Test-HPPrivateIsDownloadNeeded -url $qurl -File $qfile -Verbose:$VerbosePreference
      if ($result[1] -eq $true) {
        Write-Verbose "Trying to download $qurl from AWS Server..."
      }
    }
    catch {
      Write-Verbose "HTTP request to $qurl failed: $($_.Exception.Message)"
      $try_on_ftp = $true
    }

    if ($try_on_ftp) {
      try {
        Write-Verbose "$qurl not found on AWS Server. Trying to download it from FTP Server..."
        $qurl = "$ReferenceFallbackUrl/$platform/$fn.cab"
        $result = Test-HPPrivateIsDownloadNeeded -url $qurl -File $qfile -Verbose:$VerbosePreference
      }
      catch {
        Write-Verbose "HTTP request to $qurl failed: $($_.Exception.Message)"
        if (-not $quiet -or $result[1] -eq $false) {
          Write-Host -ForegroundColor Magenta $_.Exception.Message
        }
        throw [System.Net.WebException]"Could not find a data file for this platform."
      }
    }
  }

  if ($result[1] -eq $true) {
    Write-Verbose "Cleaning cached data and downloading the data file."
    Invoke-HPPrivateDeleteCachedItem -cab $qfile
    Invoke-HPPrivateDownloadFile -url $qurl -Target $qfile -progress $progress -NoClobber $overwrite -Verbose:$VerbosePreference -maxRetries $maxRetries
    (Get-Item $qfile).CreationTime = ($result[0])
    (Get-Item $qfile).LastWriteTime = ($result[0])
  }

  # 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 and looking for $downloadedFile."
    $file = Invoke-HPPrivateExpandCAB -cab $qfile -expectedFile $downloadedFile
  }

  Write-Verbose "Reading XML document $downloadedFile"
  [xml]$data = Get-Content $downloadedFile
  Write-Verbose "Parsing the document"

  $d = Select-Xml -Xml $data -XPath "//ImagePal/Solutions/UpdateInfo"

  $results = $d.Node | ForEach-Object {
    if (($null -ne $releaseType) -and ($_.ReleaseType -notin $releaseType)) { return }
    if (-not (matchCategory -cat $_.Category -allowed $category -EQ $true)) { return }
    if ("ContentTypes" -in $_.PSObject.Properties.Name) { $ContentTypes = $_.ContentTypes } else { $ContentTypes = $null }
    if (($null -ne $characteristic) -and (-not (matchAllCharacteristic $characteristic -SSM $_.SSMCompliant -DPB $_.DPBCompliant -UWP $ContentTypes))) { return }
    if ($AddHttps.IsPresent) {
      $objUrl = "https://$($_.url)"
      $objReleaseNotes = "https://$($_.ReleaseNotesUrl)"
      $objMetadata = "https://$($_.CvaUrl)"
    }
    else {
      $objUrl = $_.url
      $objReleaseNotes = $_.ReleaseNotesUrl
      $objMetadata = $_.CvaUrl
    }

    $pso = [pscustomobject]@{
      id = $_.id
      Name = $_.Name
      Category = $_.Category
      Version = $_.Version.TrimStart("0.")
      Vendor = $_.Vendor
      ReleaseType = $_.ReleaseType
      SSM = $_.SSMCompliant
      DPB = $_.DPBCompliant
      url = $objUrl
      ReleaseNotes = $objReleaseNotes
      Metadata = $objMetadata
      MD5 = $_.MD5
      Size = $_.Size
      ReleaseDate = $_.DateReleased
      UWP = $(if ("ContentTypes" -in $_.PSObject.Properties.Name) { $true } else { $false })
    }
    $pso



    if ($download.IsPresent) {
      [int]$id = $pso.id.ToLower().Replace("sp","")
      if ($friendlyName.IsPresent) {
        Write-Verbose "Need to get CVA data to determine Friendly Name for download file"
        $target = getfriendlyFileName -Number $pso.id.ToLower().TrimStart("sp") -From $pso.Name -Verbose:$VerbosePreference
      }
      else { $target = $pso.id }

      if ($downloadDirectory) { $target = "$downloadDirectory\$target" }
      else {
        $cwd = Convert-Path .
        $target = "$cwd\$target"
      }

      if ($downloadMetadata.IsPresent)
      {
        $loc = Get-HPPrivateItemUrl -Number $Id -Ext "cva" -url $SoftpaqUrl
        Invoke-HPPrivateDownloadFile -url $loc -Target "$target.cva" -progress $progress -NoClobber $overwrite -Verbose:$VerbosePreference -skipSignatureCheck -maxRetries $maxRetries
      }

      $loc = Get-HPPrivateItemUrl -Number $Id -Ext "exe" -url $SoftpaqUrl

      Invoke-HPPrivateDownloadFile -url $loc -Target "$target.exe" -progress $progress -NoClobber $overwrite -Verbose:$VerbosePreference -maxRetries $maxRetries

      if ($downloadNotes.IsPresent)
      {
        $loc = Get-HPPrivateItemUrl -Number $Id -Ext "html" -url $SoftpaqUrl
        Invoke-HPPrivateDownloadFile -url $loc -Target "$target.htm" -progress $progress -NoClobber $overwrite -Verbose:$VerbosePreference -skipSignatureCheck -maxRetries $maxRetries
      }
    }
  }

  $result = $results | Select-Object * -Unique
  switch ($format)
  {
    "xml" { $result | ConvertTo-Xml -As String }
    "json" { $result | ConvertTo-Json }
    "csv" { $result | ConvertTo-Csv -NoTypeInformation }
    default { return $result }
  }
}

<#
.SYNOPSIS
  Install the latest version of HP Image Assistant ([HPIA](https://ftp.hp.com/pub/caps-softpaq/cmit/HPIA.html))
 
.DESCRIPTION
  This function figures out the latest version of HPIA and downloads the SoftPaq. If -Extract is not used, the SoftPaq is only downloaded.
 
.PARAMETER Extract
  Extracts SoftPaq content to a specified destination folder.
 
  Note: By default, files are extracted into a new sub-folder relative to the downloaded SoftPaq executable.
 
.PARAMETER DestinationPath
  Specifies the path to the folder in which you want to save extracted files.
  Enter the path to a folder, but do not specify a file name or file name extension.
 
  Note: By default, the files are extracted into a new sub-folder relative to the downloaded executable.
 
.PARAMETER Source
  This parameter is currently reserved for internal use only.
 
.PARAMETER CacheDir
  Custom location for caching data files. If not specified, the user-specific TEMP directory is used.
 
.PARAMETER MaxRetries
  Maximum number of retries allowed to obtain an exclusive lock to downloaded files. This is relevant only when files are downloaded into a shared directory and multiple processes may be reading or writing from the same location.
 
  Current default value is 10 retries, and each retry includes a 30 second pause, which means the maximum time the default value will wait for an exclusive logs is 300 seconds or 5 minutes.
 
.PARAMETER Quiet
  Suppress non-errors such as download progress and other messages.
 
.EXAMPLE
  Install-HPImageAssistant
 
.EXAMPLE
  Install-HPImageAssistant -Quiet
 
.EXAMPLE
  Install-HPImageAssistant -Extract -DestinationPath "c:\staging\hpia"
 
.LINK
  [Get-Softpaq](Get-Softpaq)
 
.LINK
  [Get-SoftpaqMetadataFile](Get-SoftpaqMetadataFile)
 
.LINK
  [Get-SoftpaqList](Get-SoftpaqList)
 
.LINK
  [Clear-SoftpaqCache](Clear-SoftpaqCache)
#>

function Install-HPImageAssistant {
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/Install-HPImageAssistant")]
  param(
    [Parameter(Position = 0,Mandatory = $false,ParameterSetName = "ExtractParams")]
    [switch]$Extract,

    [Parameter(Position = 1,Mandatory = $false,ParameterSetName = "ExtractParams")]
    [ValidatePattern('^[a-zA-Z]:\\')]
    [string]$DestinationPath,

    [Parameter(Position = 2,Mandatory = $false)]
    [System.IO.DirectoryInfo]$CacheDir,

    [Parameter(Position = 3,Mandatory = $false)]
    [int]$MaxRetries = 0,

    [Parameter(Position = 4,Mandatory = $false)]
    [string]$Source = "https://hpia.hpcloud.hp.com/HPIAMsg.cab",

    [Parameter(Position = 5,Mandatory = $false)]
    [switch]$Quiet
  )

  if ($quiet.IsPresent) { $progress = -not $quiet }
  else { $progress = $true }

  $cacheDir = Get-HPPrivateCacheDirPath ($cacheDir)

  $fallbackSource = "https://ftp.hp.com/pub/caps-softpaq/cmit/imagepal/HPIAMsg.cab"

  $sourceFile = Get-HPPrivateTemporaryFileName -FileName "HPIAMsg.cab" -cacheDir $cacheDir
  $downloadedFile = "$sourceFile.dir\HPIAMsg.xml"

  $try_on_ftp = $false
  try {
    $result = Test-HPPrivateIsDownloadNeeded -url $source -File $sourceFile -Verbose:$VerbosePreference
    if ($result[1] -eq $true) {
      Write-Verbose "Trying to download $source from AWS Server..."
    }
  }
  catch {
    $try_on_ftp = $true
  }

  if ($try_on_ftp) {
    try {
      Write-Verbose "$source not found on AWS Server. Trying to download it from FTP Server..."
      $source = $fallbackSource
      $result = Test-HPPrivateIsDownloadNeeded -url $source -File $sourceFile -Verbose:$VerbosePreference
      if ($result[1] -eq $true) {
        Write-Verbose "Trying to download $source from FTP Server..."
      }
    }
    catch {
      if ($result[1] -eq $false) {
        Write-Host -ForegroundColor Magenta "data file not found"
      }
    }
  }

  if ($result[1] -eq $true) {
    Write-Verbose "Cleaning cached data and downloading the data file."
    Invoke-HPPrivateDeleteCachedItem -cab $sourceFile
    Invoke-HPPrivateDownloadFile -url $source -Target $sourceFile -progress $progress -Verbose:$VerbosePreference -maxRetries $maxRetries
  }

  Write-Verbose "Downloaded file is : $downloadedFile"
  # 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 $sourceFile -expectedFile $downloadedFile
    Write-Verbose $file
  }

  Write-Verbose "Reading XML document $downloadedFile"
  [xml]$data = Get-Content $downloadedFile
  Write-Verbose "Parsing the document"

  # Getting the SoftPaq information
  $SoftpaqVersion = $data.ImagePal.HPIALatest.Version
  $SoftpaqUrl = $data.ImagePal.HPIALatest.SoftpaqURL
  $Softpaq = $SoftpaqUrl.Split('/')[-1]
  $SoftpaqExtractedFolderName = $Softpaq.ToLower().trimend(".exe")

  $TargetFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Softpaq)

  Write-Verbose "SoftPaq Version: $SoftpaqVersion"
  Write-Verbose "SoftPaq URL: $SoftpaqUrl"

  $params = @{
    url = $SoftpaqUrl
    Target = $TargetFile
    MaxRetries = $MaxRetries
    progress = $progress
  }

  try {
    Invoke-HPPrivateDownloadFile @params
    Write-Verbose "Successfully downloaded SoftPaq at $TargetFile"
    # if Extract and Destination location is specified, proceed to extract the SoftPaq
    if ($Extract) {
      if (!$DestinationPath) {
        $DestinationPath = Join-Path -Path $(Get-Location) -ChildPath $SoftpaqExtractedFolderName
      }
      if ($DestinationPath -match [regex]::Escape([System.Environment]::SystemDirectory)) {
        throw 'Windows System32 is not a valid destination path.'
      }
      $workingPath = $(Get-HPPrivateTempPath)
      $workingPath = Join-Path -Path $workingPath -ChildPath $Softpaq
      Write-Verbose "Copying downloaded SoftPaq to temporary working directory $workingPath"
      Copy-Item -Path $TargetFile -Destination $workingPath -Force
      Invoke-PostDownloadSoftpaqAction -downloadedFile $workingPath -Action "Extract" -Destination $DestinationPath
      Write-Verbose "SoftPaq self-extraction finished at $DestinationPath"
      Write-Verbose "Remove SoftPaq from the temporary working directory $workingPath"
      Remove-Item -Path $workingPath -Force
    }
    Write-Verbose "Success"
  }
  catch {
    if (-not $Quiet) {
      Write-Host -ForegroundColor Magenta $_.Exception.Message
    }
    throw $_.Exception
  }
}



# private functionality below

function matchCategory ([string]$cat,[string[]]$allowed)
{
  if ($allowed -eq $null) { return $true }
  if ($cat.StartsWith("Driver") -eq $true) { return $allowed -eq "driver" }
  if ($cat.StartsWith("Operating System -") -eq $true) { return $allowed -eq "os" }
  if ($cat.StartsWith("Manageability - Driver Pack") -eq $true) { return $allowed -eq "driverpack" }
  if ($cat.StartsWith("Manageability - UWP Pack") -eq $true) { return $allowed -eq "UWPPack" }
  if ($cat.StartsWith("Manageability -") -eq $true) { return $allowed -eq "manageability" }
  if ($cat.StartsWith("Utility -") -eq $true) { return $allowed -eq "utility" }
  if (($cat.StartsWith("Dock -") -eq $true) -or ($cat -eq "Docks")) { return $allowed -eq "dock" }
  if (($cat -eq "BIOS") -or ($cat.StartsWith("BIOS -") -eq $true)) { return $allowed -eq "BIOS" }
  if ($cat -eq "firmware") { return $allowed -eq "firmware" }
  if ($cat -eq "diagnostic") { return $allowed -eq "diagnostic" }
  else {
    return $allowed -eq "software"
  }
  return $false
}

function matchAllCharacteristic ([string[]]$targetedCharacteristic,[string]$SSM,[string]$DPB,[string]$UWP)
{
  if ($targetedCharacteristic -eq $null) { return $true }
  if ($targetedCharacteristic.Count -eq 0) { return $true }

  $ContainsAllCharacteristic = $true

  foreach ($characteristic in $targetedCharacteristic)
  {
    switch ($characteristic.trim().ToLower()) {
      "ssm"
      {
        if ($SSM.trim().ToLower() -eq "false") { $ContainsAllCharacteristic = $false }
      }
      "dpb"
      {
        if ($DPB.trim().ToLower() -eq "false") { $ContainsAllCharacteristic = $false }
      }
      "uwp"
      {
        if ($UWP.trim().ToLower() -ne "uwp") { $ContainsAllCharacteristic = $false }
      }
    }
  }
  return $ContainsAllCharacteristic
}



function Release-Ref ($ref) {
  [System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$ref) | Out-Null
  [System.GC]::Collect()
  [System.GC]::WaitForPendingFinalizers()
}

# create a friendly name from SoftPaq metadata (CVA)
function getfriendlyFileName
{
  [CmdletBinding()]
  param(
    [int]$number,
    $info,
    [string]$from
  )

  try {
    $title = "sp$number"

    #if title was passed in, we use it
    if ($from) { $title = $from }

    #else if object was passed in, we use it
    elseif ($info -ne $null) { $title = ($info | Out-SoftpaqField Title) }

    #else use a default
    else { $title = "(No description available)" }

    $pass1 = removeInvalidCharacters $title
    $pass2 = $pass1.trim()
    $pass3 = $pass2 -replace ('\s+','_')
    return $number.ToString("sp######") + "-" + $pass3
  }
  catch {
    Write-Host -ForegroundColor Magenta "Could not determine friendly name so using SoftPaq number."
    Write-Host -ForegroundColor Magenta $_.Exception.Message
    return "sp$number"
  }
}

# remove invalid characters from a filename
function removeInvalidCharacters ([string]$Name) {
  $invalidChars = [IO.Path]::GetInvalidFileNameChars() -join ''
  $re = "[{0}]" -f [regex]::Escape($invalidChars)
  return ($Name -replace $re)
}

#shortcuts to various sections of CVA file
$mapper = @{
  "Install" = "Install Execution|Install";
  "SilentInstall" = "Install Execution|SilentInstall";
  "Number" = "Softpaq|SoftpaqNumber";
  "Title" = "Software Title|%lang";
  "Description" = "%lang.Software Description|_body";
  "Platforms" = "System Information|%KeyValues(^SysName.*$)";
  "PlatformIDs" = "System Information|%KeyValues(^SysId.*$)";
  "SoftPaqSHA256" = "Softpaq|SoftPaqSHA256";
  "SoftPaqMD5" = "Softpaq|SoftPaqMD5";
  "Version" = "General|VendorVersion";
};

#ISO to CVA language mapper
$lang_mapper = @{
  "en" = "us";
};


# navigate a CVA structure
function descendNodesAndGet ($root,$field,$lang = "en")
{
  $f1 = $mapper[$field].Replace("%lang",$lang_mapper[$lang])
  $f = $f1.Split("|")
  $node = $root

  foreach ($c in $f) {
    if ($c -match "^%KeyValues\(.*\)$") { return $node }
    if ($c -match "^%Keys\(.*\)$") { return $node }
    $node = $node[$c]
  }
  $node
}

function New-HPPrivateSoftPaqListManifest {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)]
    [PSCustomObject[]]$Softpaqs,

    [Parameter(Mandatory = $false)]
    [AllowEmptyString()]
    $Name,

    [ValidateSet('win10', 'win11')]
    [string]$Os,

    [Parameter(Mandatory = $false)]
    [string]$OSVer,

    [Parameter(Mandatory = $false)]
    [ValidateSet('JSON','XML')]
    $Format = 'JSON'
  )

  $manifest = [PSCustomObject]@{
    Date = $(Get-Date -Format s)
    Name = $Name
    Os = $Os
    OsVer = $OSVer
    SoftPaqs = @($Softpaqs)
  }

  switch ($Format) {
    'XML' { $result = ConvertTo-Xml -InputObject $manifest -As String -Depth 2 -NoTypeInformation }
    'JSON' { $result = ConvertTo-Json -InputObject $manifest }
  }

  return $result
}

<#
.SYNOPSIS
  Create a Driver Pack for the list of specified SoftPaqs
 
.DESCRIPTION
  This function retrieves the specified SoftPaqs to build a Driver Pack in the following formats:
 
  - NoCompressedFile - Folder containing all the drivers
  - ZIP - All drivers compressed in a ZIP file
  - WIM - All drivers packed in a Windows Imaging Format
 
.PARAMETER Softpaqs
  List of SoftPaqs to be included in the Driver Pack. This parameter can be specified by piping the output of Get-SoftpaqList to this function.
 
.PARAMETER Os
  Filter for the specified OS. The OS may be "win10" or "win11". If not specified, current platform OS is used.
 
.PARAMETER OsVer
  Filter for the specified OS Version. This is a string value, specifying the target OS Version (e.g. "1809", "1903", "1909", "2004", "2009", "21H1", "21H2", "22H2" etc). If the parameter is not specified, the current operating system version will be assumed, which may not be what is intended.
 
.PARAMETER Format
  Specify the output format (NoCompressedFile, ZIP, or WIM).
 
.PARAMETER Path
  This parameter allows specifying an absolute path for the Driver Pack directory. The current directory is used by default if none is specified.
 
.PARAMETER Name
  Specify a custom name for the Driver Pack. e.g. DP880D
 
.PARAMETER Overwrite
  Force overwriting any existing file with the same name during driver pack creation.
 
.EXAMPLE
  Get-SoftpaqList -platform 880D -os 'win10' -osver '21H2' | New-HPBuildDriverPack -Os Win10 -OsVer 21H1 -Name 'DP880D'
 
.EXAMPLE
  Get-SoftpaqList -platform 880D -os 'win10' -osver '21H2' | New-HPBuildDriverPack -Format Zip -Os Win10 -OsVer 21H1 -Name 'DP880D'
 
.EXAMPLE
  Get-SoftpaqList -platform 880D -os 'win10' -osver '21H2' | ?{$_.DPB -Like 'true' -and $_.id -notin @('sp137116') -and $_.name -notmatch 'AMD|USB'} | New-HPBuildDriverPack -Path 'C:\MyDriverPack' -Format Zip -Os Win10 -OsVer 21H1 -Name 'DP880D'
 
.NOTES
  - Admin privilege is required.
  - Running this command in PowerShell ISE is not supported and may produce inconsistent results.
#>

function New-HPBuildDriverPack {
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/New-HPBuildDriverPack")]
  param(
    [ValidateNotNullOrEmpty()]
    [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 1)]
    [array]$Softpaqs,

    [Parameter(Mandatory = $false, Position = 2)]
    [ValidateSet('win10', 'win11')]
    [string]$Os,

    [ValidateSet("1809","1903","1909","2004","2009","21H1","21H2","22H2")] # keep in sync with the SoftPaq module
    [Parameter(Mandatory = $false, Position = 3)]
    [string]$OSVer,

    [Parameter(Mandatory = $false, Position = 4)]
    [System.IO.DirectoryInfo]$Path,

    [Parameter(Mandatory = $false, Position = 5)]
    [ValidateSet('wim','zip','NoCompressedFile')]
    $Format = 'NoCompressedFile',

    [Parameter(Mandatory = $true, Position = 6)]
    [ValidatePattern("^\w{1,20}$")]
    [string]$Name,

    [Parameter(Mandatory = $false, Position = 7)]
    [switch]$Overwrite
  )
  BEGIN {
    $softpaqsArray = @()
  }
  PROCESS {
    $softpaqsArray += $Softpaqs
  }
  END {
    if (!$Os) {
      $Os = Get-HPPrivateCurrentOs
    }
  
    if (!$OsVer) {
      $revision = (GetCurrentOSVer).ToUpper()
      if ($revision -notin "1809","1903","1909","2004","2009","21H1","21H2","22H2") {
        throw "OSVer $revision currently not supported"
      }
      $OsVer = $revision
    }

    # ZIP and WIM format requires admin privilege
    if (-not (Test-IsElevatedAdmin)) {
      throw [System.Security.AccessControl.PrivilegeNotHeldException]"elevated administrator"
    }

    [System.IO.DirectoryInfo]$cwd = (Get-Location).Path
    if (-not $Path) {
      $Path = $cwd
    }

    $downloadPath = Get-HPPrivateTempFilePath
    $finalPath = Join-Path -Path $Path.FullName -ChildPath $Name

    if ($Format -eq 'NoCompressedFile') {
      if ([System.IO.Directory]::Exists($finalPath)) {
        if ($Overwrite.IsPresent) {
          Write-Verbose "$finalPath already exists, overwriting the directory"
          Remove-Item -LiteralPath $finalPath -Force -Recurse
        }
        else {
          # find new name that doesn't exist
          $existingFileIncrement = 0
          Get-ChildItem -Path "$($finalPath)_*" -Directory | Where-Object {
            if ($_.BaseName -Match '_([0-9]+)$') {
              [int]$i = [int]($Matches[1])
              if ($i -gt $existingFileIncrement) {
                $existingFileIncrement = $i
              }
            }
          }
          $existingFileIncrement += 1
          $finalPath = "$($finalPath)_$($existingFileIncrement)"
        }
      }
      $workingPath = $finalPath
    }
    else {
      $workingPath = Get-HPPrivateTempFilePath
    }

    Write-Verbose "Working directory: $workingPath"

    if ($PSVersionTable.PSEdition -eq 'Desktop' -and -not $(Test-HPPrivateIsLongPathSupported)) {
      Write-Verbose "Unicode paths are required"
      if (Test-HPPrivateIsRunningOnISE) {
        Write-Warning 'Running this command in PowerShell ISE is not supported and may produce inconsistent results.'
      }
      $finalPath = Get-HPPrivateUnicodePath -Path $finalPath
      $workingPath = Get-HPPrivateUnicodePath -Path $workingPath
      $downloadPath = Get-HPPrivateUnicodePath -Path $downloadPath
    }

    if ($Format -eq 'NoCompressedFile' -and [System.IO.Directory]::Exists($finalPath)) {
      Write-Verbose "$finalPath already exists, deleting the directory"
      Remove-Item -Path "$finalPath\*" -Recurse -Force -ErrorAction Ignore
      Remove-Item -Path $finalPath -Recurse -Force -ErrorAction Ignore
    }

    if (-not [System.IO.Directory]::Exists($Path)) {
      throw "The absolute path specified to a directory does not exist: $Path"
    }

    Write-Verbose "Creating directory: $workingPath"
    [System.IO.Directory]::CreateDirectory($workingPath) | Out-Null
    if (-not [System.IO.Directory]::Exists($workingPath)) {
      throw "An error occurred while creating directory $workingPath"
    }

    Write-Verbose "Creating downloadPath: $downloadPath"
    [System.IO.Directory]::CreateDirectory($downloadPath) | Out-Null
    if (-not [System.IO.Directory]::Exists($downloadPath)) {
      throw "An error occurred while creating directory $downloadPath"
    }

    $manifestPath = [IO.Path]::Combine($workingPath, 'manifest')
    Write-Verbose "Creating manifest file: $manifestPath.json"
    New-HPPrivateSoftPaqListManifest -Softpaqs $softpaqsArray -Name $Name -Os $Os -OsVer $OsVer -Format Json | Out-File -LiteralPath "$manifestPath.json"
    Write-Verbose "Creating manifest file: $manifestPath.xml"
    New-HPPrivateSoftPaqListManifest -Softpaqs $softpaqsArray -Name $Name -Os $Os -OsVer $OsVer -Format XML | Out-File -LiteralPath "$manifestPath.xml"

    foreach ($ientry in $softpaqsArray) {
      Write-Verbose "Processing $($ientry.id)"
      try {
        $metadata = Get-SoftpaqMetadata $ientry.id -MaxRetries 3
      }
      catch {
        Write-Verbose $_.Exception.Message
        Write-Warning "$($ientry.id) metadata was not found or the SoftPaq is Obsolete. This will not be included in the package."
        continue
      }
      
      if ($metadata.ContainsKey('Devices_INFPath')) {
        # fix folder naming issue when softpaq name contains '/',(ex. "Intel TXT/ACM" driver)
        $downloadFilePath = [IO.Path]::Combine($downloadPath, "$($ientry.id).exe")
        Write-Verbose "Downloading SoftPaq $downloadFilePath"
        try {
          Get-Softpaq -Number $ientry.id -SaveAs $downloadFilePath -MaxRetries 3
        }
        catch {
          Write-Verbose $_.Exception.Message
          Write-Warning "$($ientry.id) was not found or the SoftPaq is Obsolete. This will not be included in the package."
          continue
        }
        Write-Verbose "Setting current dir to $($downloadPath)"
        Set-Location -LiteralPath $downloadPath
        $extractFolderName = $ientry.id
        Write-Verbose "Extracting SoftPaq $downloadFilePath to .\$extractFolderName"
        try {
          Start-Process -Wait $downloadFilePath -ArgumentList "-e -f `".\$extractFolderName`"","-s"
        }
        catch {
          Set-Location $cwd
          throw
        }
        Set-Location $cwd

        $OsId = if ($Os -eq 'Win11') { 'W11' } else { 'WT64' }
        $fullInfPathName = "$($OsId)_$($OSVer.ToUpper())_INFPath"
        if ($metadata.Devices_INFPath.ContainsKey($fullInfPathName)) {
          $infPathName = $fullInfPathName
        }
        else {
          # fallback to generic inf path name
          $infPathName = "$($OsId)_INFPath"
        }
        if ($metadata.Devices_INFPath.ContainsKey($infPathName)) {
          Write-Verbose "$infPathName selected"
          $infPaths = $($metadata.Devices_INFPath[$infPathName])
          $finalExtractFolderName = $ientry.id
          $destinationPath = [IO.Path]::Combine($workingPath, $finalExtractFolderName)
          $extractPath = [IO.Path]::Combine($downloadPath, $extractFolderName)
          [System.IO.Directory]::CreateDirectory($destinationPath) | Out-Null
          foreach ($infPath in $infPaths) {
            $infPath = $infPath.TrimStart('.\')
            $absoluteInfPath = [IO.Path]::Combine($extractPath, $infPath)
            Write-Verbose "Copying $absoluteInfPath to $destinationPath"
            Copy-Item $absoluteInfPath $destinationPath -Force -Recurse
          }
        }
        else {
          Write-Warning "INF path $fullInfPathName missing on $($ientry.id) metadata. This will not be included in the package."
        }
      }
      else {
        Write-Warning "$($ientry.id) is not Driver Pack Builder (DPB) compliant. This will not be included in the package."
      }
    }
    Write-Verbose "Removing temporary files $($downloadPath)"
    Remove-Item -Path "$downloadPath\*" -Recurse -Force -ErrorAction Ignore
    Remove-Item -Path $downloadPath -Recurse -Force -ErrorAction Ignore

    switch ($Format) {
      'zip' {
        Write-Verbose "Compressing driver pack to $($Format): $workingPath.zip"
        [System.IO.Compression.ZipFile]::CreateFromDirectory($workingPath, "$workingPath.zip")
        Remove-Item -Path "$workingPath\*" -Recurse -Force -ErrorAction Ignore
        Remove-Item -Path $workingPath -Recurse -Force -ErrorAction Ignore
        if ([System.IO.File]::Exists("$finalPath.$Format")) {
          if ($Overwrite.IsPresent) {
            Write-Verbose "$finalPath.zip already exists, overwriting the file"
            Remove-Item -LiteralPath "$($finalPath).$Format" -Force
          }
          else {
            # find new name that doesn't exist
            $existingFileIncrement = 0
            Get-ChildItem -Path "$($finalPath)_*.$Format" -File | Where-Object {
              if ($_.BaseName -Match '_([0-9]+)$') {
                [int]$i = [int]($Matches[1])
                if ($i -gt $existingFileIncrement) {
                  $existingFileIncrement = $i
                }
              }
            }
            $existingFileIncrement += 1
            $finalPath = "$($finalPath)_$($existingFileIncrement)"
          }
        }
        [System.IO.File]::Move("$workingPath.$Format", "$finalPath.$Format")
        $resultFile = [System.IO.FileInfo]"$finalPath.$Format"
      }
      'wim' {
        Write-Verbose "Compressing driver pack to $($Format): $workingPath.$Format"
        if ([System.IO.File]::Exists("$workingPath.$Format")) {
          # New-WindowsImage will not override existing file
          Remove-Item -LiteralPath "$($workingPath).$Format" -Force
        }
        New-WindowsImage -CapturePath $workingPath -ImagePath "$workingPath.$Format" -CompressionType Max `
          -LogPath $([IO.Path]::Combine($(Get-HPPrivateTempPath), 'DISM.log')) -Name $Name | Out-Null
        Remove-Item -Path "$workingPath\*" -Recurse -Force -ErrorAction Ignore
        Remove-Item -Path $workingPath -Recurse -Force -ErrorAction Ignore

        if ([System.IO.File]::Exists("$finalPath.$Format")) {
          if ($Overwrite.IsPresent) {
            Write-Verbose "$finalPath.wim already exists, overwriting the file"
            Remove-Item -LiteralPath "$($finalPath).$Format" -Force
          }
          else {
            # find new name that doesn't exist
            $existingFileIncrement = 0
            Get-ChildItem -Path "$finalPath*.$Format" | Where-Object {
              if ($_.BaseName -Match '_([0-9]+)$') {
                [int]$i = [int]($Matches[1])
                if ($i -gt $existingFileIncrement) {
                  $existingFileIncrement = $i
                }
              }
            }
            $existingFileIncrement += 1
            $finalPath = "$($finalPath)_$($existingFileIncrement)"
          }
        }
        [System.IO.File]::Move("$workingPath.$Format", "$finalPath.$Format")
        $resultFile = [System.IO.FileInfo]"$finalPath.$Format"
      }
      default {
        $resultFile = [System.IO.DirectoryInfo]$finalPath
      }
    }
    $resultFile
    Write-Host "`nDriver Pack created at $($resultFile.FullName)"
  }
}

function Remove-HPPrivateSoftpaqEntries {
  [CmdletBinding()]
param(
  [Parameter(Mandatory = $true)] $pFullSoftpaqList,
      [Parameter(Mandatory = $true)] [array]$pUnselectList,
      [Parameter(Mandatory = $true)] [boolean]$pUnselectListAsArg
)

  $l_DPBList = @() # list of drivers that will be selected from the full list
  $l_Unselected = @() # list of drivers that were unselected (to display)
  for ($i=0;$i -lt $pFullSoftpaqList.Count; $i++ ) {
      $iUnselectMatched = $null
      # see if the entries contain Softpaqs by name or ID, and remove from list
      foreach ( $iList in $pUnselectList ) { 
          if ( ($pFullSoftpaqList[$i].name -match $iList) -or ($pFullSoftpaqList[$i].id -like $iList) ) { 
              $iUnselectMatched = $true ; $l_Unselected += $pFullSoftpaqList[$i]
              break
          } 
      }
      if ( -not $iUnselectMatched ) { $l_DPBList += $pFullSoftpaqList[$i] }
  }

  if ($l_Unselected.Count -gt 0) {
    Write-Host "Unselected drivers: "
    foreach ( $iun in $l_Unselected ) {
      Write-Host "`t$($iun.id) $($iun.Name) [$($iun.Category)] $($iun.Version) $($iun.ReleaseDate)"
    }
  }

  , $l_DPBList
}

function Remove-HPPrivateOlderSoftpaqEntries {
  [CmdletBinding()]
param(
  [Parameter(Mandatory = $true)] $pFullSoftpaqList
  )
  Write-Host "Removing superseded entries (-RemoveOlder switch option)"
  #############################################################################
  # 1. get a list of Softpaqs with multiple entries
  $l_TmpList = @()
  foreach ( $iEntry in $pFullSoftpaqList ) {
      foreach ( $i in $pFullSoftpaqList ) {    # search for entries that are same names as $iEntry
          if ( ($i.name -match $iEntry.name) -and (-not ($i.id -match $iEntry.id)) -and ($iEntry -notin $l_TmpList)) {
              $l_TmpList += $iEntry         # found an softpaq name with multiple versions
          }
      } # foreach ( $i in $pFullSoftpaqList )
  } # foreach ( $iEntry in $pFullSoftpaqList )
  if ($l_TmpList.Count -gt 0) {
    Write-Host "These drivers have multiple SoftPaqs (have superseded entries)"
    foreach ( $iun in $l_TmpList ) {
      Write-Host "`t$($iun.id) $($iun.Name) [$($iun.Category)] $($iun.Version)"
    }
  }

  #############################################################################
  # 2. from the $lTmpList list, find the latest (highest sp number softpaq) of each
  $l_FinalTmpList = @()
  foreach ( $iEntry in $l_TmpList ) {
      foreach ( $i in $l_TmpList ) {
        if ($l_FinalTmpList.Count -eq 0 -or
              ($i.name -match $iEntry.name -and
              $i.id -notmatch $iEntry.id -and
              [int]$i.id.substring(2) -lt [int]$iEntry.id.substring(2) -and
              $iEntry.name -notin $l_FinalTmpList.name)) {
            $l_FinalTmpList += $iEntry
        } # if ...
      } # foreach ( $i in $l_TmpList )
  } # foreach ( $iEntry in $l_TmpList )
  if ($l_FinalTmpList.Count -gt 0) {
    Write-Host "These SoftPaqs are good - higher SP numbers"
    foreach ( $iun in $l_FinalTmpList ) {
      Write-Host "`t$($iun.id) $($iun.Name) [$($iun.Category)] $($iun.Version)"
    }
  }
  #############################################################################
  # 3. lastly, remove superseeded drivers from main driver pack list
  $l_FinalDPBList = @()
  foreach ( $iEntry in $pFullSoftpaqList ) {
    if ( $l_TmpList.Count -eq 0 -or ($iEntry.name -notin $l_TmpList.name) -or ($iEntry.id -in $l_FinalTmpList.id) ) {
      if ($l_FinalDPBList.Count -eq 0 -or $iEntry.name -notin $l_FinalDPBList.name) { $l_FinalDPBList += $iEntry }
    }
  } # foreach ( $iEntry in $lDPBList )

  , $l_FinalDPBList           # return list of Softpaqs without the superseded Softpaqs
}

<#
.SYNOPSIS
  Create a Driver Pack for the specified platform
 
.DESCRIPTION
  This function retrieves SoftPaqs for the specified platform to build a Driver Pack in the following formats:
 
  - NoCompressedFile - Folder containing all the drivers
  - ZIP - All drivers compressed in a ZIP file
  - WIM - All drivers packed in a Windows Imaging Format
 
.PARAMETER Platform
  The platform ID for which to retrieve the SoftPaqs. If not available, the current platform ID is used.
 
.PARAMETER Os
  Filter for the specified OS. The OS may be "win10" or "win11". If not specified, current platform OS is used.
 
.PARAMETER OsVer
  Filter for the specified OS Version. This is a string value, specifying the target OS Version (e.g. "1809", "1903", "1909", "2004", "2009", "21H1", "21H2", "22H2" etc). If the parameter is not specified, the current operating system version will be assumed, which may not be what is intended.
 
.PARAMETER Format
  Specify the output format (NoCompressedFile, ZIP, or WIM).
 
.PARAMETER WhatIf
  When specified, the Driver Pack is not created, but the list of SoftPaqs that would be included in the Driver Pack is presented.
 
.PARAMETER RemoveOlder
  When specified, older versions of the same SoftPaq are not included in the Driver Pack.
 
.PARAMETER UnselectList
  List of SoftPaq numbers and SoftPaq names to not include in the Driver Pack. A partial name can be specified. e.g. "Docks","USB","sp123456"
 
.PARAMETER Path
  This parameter allows specifying an absolute path for the Driver Pack directory. The current directory is used by default if none is specified.
 
.PARAMETER Url
  Specify an alternate location for the HP Image Assistant (HPIA) Reference files. This URL must be http, https, or ftp. The Reference files are expected to be at the location pointed to by this URL inside a directory named after the platform ID you want a SoftPaq list for. If not specified, ftp.hp.com is used via HTTPS protocol.
 
  For example, If you want to point to 83b2 Win10 OSVer 2009 reference files, New-HPDriverPack will try to find them in this directory structure: $ReferenceUrl/83b2/83b2_64_10.0.2009.cab
 
.PARAMETER Overwrite
  Force overwriting any existing file with the same name during driver pack creation.
 
.EXAMPLE
  New-HPDriverPack -WhatIf
 
.EXAMPLE
  New-HPDriverPack -Platform 880D -OS 'win10' -OSVer '21H2' -Path 'C:\MyDriverPack' -Unselectlist 'sp96688','AMD','USB' -RemoveOlder -WhatIf
 
.EXAMPLE
  New-HPDriverPack -Platform 880D -OS 'win10' -OSVer '21H2' -Path 'C:\MyDriverPack' -Unselectlist 'sp96688','AMD','USB' -RemoveOlder -Format Zip
 
.NOTES
  - Admin privilege is required.
  - Running this command in PowerShell ISE is not supported and may produce inconsistent results.
#>

function New-HPDriverPack {
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/New-HPDriverPack",SupportsShouldProcess=$true)]
  param(
    [Parameter(Mandatory = $false, Position = 1)]
    [ValidatePattern("^[a-fA-F0-9]{4}$")]
    [string]$Platform,

    [Parameter(Mandatory = $false, Position = 2 )]
    [ValidateSet('win10', 'win11')]
    [string]$Os,

    [ValidateSet("1809","1903","1909","2004","2009","21H1","21H2","22H2")] # keep in sync with the SoftPaq module
    [Parameter(Mandatory = $false, Position = 3 )]
    [string]$OSVer,

    [Parameter(Mandatory = $false, Position = 4 )]
    [System.IO.DirectoryInfo]$Path,

    [Parameter(Mandatory = $false, Position = 5 )]
    [array]$UnselectList,

    [Parameter(Mandatory = $false, Position = 6 )]
    [switch]$RemoveOlder = $false,

    [Parameter( Mandatory = $false, Position = 7 )]
    [ValidateSet('NoCompressedFile','zip','wim')]
    [string]$Format='NoCompressedFile',

    [Parameter(Mandatory = $false, Position = 8)]
    [string]$Url,

    [Parameter(Mandatory = $false, Position = 9)]
    [switch]$Overwrite
  )

  # 7zip and Win format require admin privilege
  if (-not (Test-IsElevatedAdmin)) {
    throw [System.Security.AccessControl.PrivilegeNotHeldException]"elevated administrator"
  }

  if (!$Platform) {
    $Platform = Get-HPDeviceProductID
  }

  if (!$Os) {
    $Os = Get-HPPrivateCurrentOs
  }

  if (!$OsVer) {
    $revision = (GetCurrentOSVer).ToUpper()
    if ($revision -notin "1809","1903","1909","2004","2009","21H1","21H2","22H2") {
      throw "OSVer $revision currently not supported"
    }
    $OsVer = $revision
  }

  $bitness = 64

  Write-Host "Creating Driver Pack for Platform $Platform, $Os-$OsVer $($bitness)b"

  $params = @{
    Platform = $Platform
    Os = $Os
    OsVer = $OsVer
    Bitness = $bitness
    MaxRetries = 3
  }
  if ($Url) {
    $params.Url = $Url
  }

  try {
    [array]$lFullDPBList = Get-SoftpaqList @params -Verbose:$VerbosePreference -AddHttps | Where-Object { ($_.DPB -like 'true') }
  }
  catch {
    Write-Host "SoftPaq list not available for the platform or OS specified"
    throw $_.Exception.Message
  }

  # remove any Softpaqs matching names in $UnselectList from the returned list
  if ($UnselectList -and $UnselectList.Count -gt 0) {
      $UnselectListAsArgument = $PSBoundParameters.ContainsKey("UnselectList")
      [array]$DPBList = Remove-HPPrivateSoftpaqEntries -pFullSoftpaqList $lFullDPBList -pUnselectList $UnselectList -pUnselectListAsArg $UnselectListAsArgument
  }
  else {
    [array]$DPBList = $lFullDPBList
  }

  # remove any Softpaqs matching names in $UnselectList from the returned list
  if ($RemoveOlder) {
      $FinalListofSoftpaqs = Remove-HPPrivateOlderSoftpaqEntries -pFullSoftpaqList $DPBList
      [array]$DPBList = $FinalListofSoftpaqs
  }

  if ($DPBList.Count -eq 0) {
    Write-Host "Final list of SoftPaqs is empty, no Driver Pack created"
  }
  else {
    Write-Host "Final list of SoftPaqs for Driver Pack"
    foreach ($iFinal in $DPBList) {
      Write-Host "`t$($iFinal.id) $($iFinal.Name) [$($iFinal.Category)] $($iFinal.Version) $($iFinal.ReleaseDate)"
    }
  }

  # show which selected drivers contain UWP/appx applications (UWP = true)
  $UWPList = @($DPBList | Where-Object { $_.UWP -like 'true' })
  if ($UWPList -and $UWPList.Count -gt 0) {
    Write-Host 'The following selected Drivers contain UWP/appx Store apps'
    foreach ($iUWP in $UWPList) {
      Write-Host "`t$($iUWP.id) $($iUWP.Name) [$($iUWP.Category)] $($iUWP.Version) $($iUWP.ReleaseDate)"
    }
  }

  # create the driver pack
  if ($pscmdlet.ShouldProcess($Platform)) {
    if ($DPBList.Count -gt 0) {
      $params = @{
        Softpaqs = $DPBList
        Name = "DP$Platform"
        Format = $Format
        Os = $Os
        OsVer = $OSVer
      }
      if ($Path) {
        $params.Path = $Path
      }
      return New-HPBuildDriverPack @params -Overwrite:$Overwrite
    }
  }
}
# SIG # Begin signature block
# MIIuAgYJKoZIhvcNAQcCoIIt8zCCLe8CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCOK6ALMvTXuoaD
# WAD2Cn180xwyi337K0sFKVoRjwqPT6CCE2wwggXAMIIEqKADAgECAhAP0bvKeWvX
# +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
# Dm0fS5OgMYIZ7DCCGegCAQEwfTBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT
# aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExAhAI+qTPsJ3byDJ7SsgX0LBU
# MA0GCWCGSAFlAwQCAQUAoHwwEAYKKwYBBAGCNwIBDDECMAAwGQYJKoZIhvcNAQkD
# MQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJ
# KoZIhvcNAQkEMSIEIGlXuy6V3QbmT5SCh3wduqS8ANZXI78Zx5R8/oCBHAckMA0G
# CSqGSIb3DQEBAQUABIIBgEXUbwI8ltSPMIDynjGGA3zIWSIPTDuFTyvKouWrWNj4
# S3sfIF5j3+526XT/tymDk1hviKLZ57wKlMQtYvl0uIIEvXxTwsgvwb8ASGSyJ8oU
# DSSKegRwmMvFj5mrDqvNOjGuUBAt0yMh5TIwHkTAf0I3OH7gbxYFVCmw950rOxeI
# H3lOeCY5qfEKcIN1FzCQ75KDSYKg+Tb1D1SFhDQIG4ONFTFrWPn6tyX+WUX5Ako2
# fH1WTVaJPilrDh21gZfyffSubOEA1QBOgd1smNH1LlFnxY3eTlG75AJSVZAMapry
# 6LsOyUEKODTC8dhijGZjO1MGiJXlrhku0kRDu3PNJ5iTIWdgG/24KP95Tcq0J1PT
# 2yDGCcW6hlw3rIXBaySFnQyccLF7K3p+iD1tFbV+wku8VRgT4IVFeG3pQrpQVqvI
# MwGejRygviBidnx06Sb7gm9bWu68bl9a9j6nSeuGriqHOk3pqj1aE2ihojiq5X/Q
# pWpiGVUoBoEfkL5JhEmu+qGCF0Iwghc+BgorBgEEAYI3AwMBMYIXLjCCFyoGCSqG
# SIb3DQEHAqCCFxswghcXAgEDMQ8wDQYJYIZIAWUDBAIBBQAwdgYLKoZIhvcNAQkQ
# AQSgZwRlMGMCAQEGCWCGSAGG/WwHATAxMA0GCWCGSAFlAwQCAQUABCB15aQZqLMJ
# h/oiVsv3X9d3Fi43RvX8EY2yeCVYqHUvbgIPM7e+8LwpYX1Id0gEfVL2GA8yMDIy
# MDkwNzE3MzUxNVqgghMNMIIGxjCCBK6gAwIBAgIQCnpKiJ7JmUKQBmM4TYaXnTAN
# BgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs
# IEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEy
# NTYgVGltZVN0YW1waW5nIENBMB4XDTIyMDMyOTAwMDAwMFoXDTMzMDMxNDIzNTk1
# OVowTDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMSQwIgYD
# VQQDExtEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMiAtIDIwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQC5KpYjply8X9ZJ8BWCGPQz7sxcbOPgJS7SMeQ8QK77
# q8TjeF1+XDbq9SWNQ6OB6zhj+TyIad480jBRDTEHukZu6aNLSOiJQX8Nstb5hPGY
# Pgu/CoQScWyhYiYB087DbP2sO37cKhypvTDGFtjavOuy8YPRn80JxblBakVCI0Fa
# +GDTZSw+fl69lqfw/LH09CjPQnkfO8eTB2ho5UQ0Ul8PUN7UWSxEdMAyRxlb4pgu
# j9DKP//GZ888k5VOhOl2GJiZERTFKwygM9tNJIXogpThLwPuf4UCyYbh1RgUtwRF
# 8+A4vaK9enGY7BXn/S7s0psAiqwdjTuAaP7QWZgmzuDtrn8oLsKe4AtLyAjRMruD
# +iM82f/SjLv3QyPf58NaBWJ+cCzlK7I9Y+rIroEga0OJyH5fsBrdGb2fdEEKr7mO
# CdN0oS+wVHbBkE+U7IZh/9sRL5IDMM4wt4sPXUSzQx0jUM2R1y+d+/zNscGnxA7E
# 70A+GToC1DGpaaBJ+XXhm+ho5GoMj+vksSF7hmdYfn8f6CvkFLIW1oGhytowkGvu
# b3XAsDYmsgg7/72+f2wTGN/GbaR5Sa2Lf2GHBWj31HDjQpXonrubS7LitkE956+n
# GijJrWGwoEEYGU7tR5thle0+C2Fa6j56mJJRzT/JROeAiylCcvd5st2E6ifu/n16
# awIDAQABo4IBizCCAYcwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwFgYD
# VR0lAQH/BAwwCgYIKwYBBQUHAwgwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZI
# AYb9bAcBMB8GA1UdIwQYMBaAFLoW2W1NhS9zKXaaL3WMaiCPnshvMB0GA1UdDgQW
# BBSNZLeJIf5WWESEYafqbxw2j92vDTBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8v
# Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0MDk2U0hBMjU2
# VGltZVN0YW1waW5nQ0EuY3JsMIGQBggrBgEFBQcBAQSBgzCBgDAkBggrBgEFBQcw
# AYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFgGCCsGAQUFBzAChkxodHRwOi8v
# Y2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0MDk2U0hB
# MjU2VGltZVN0YW1waW5nQ0EuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQANLSN0ptH1
# +OpLmT8B5PYM5K8WndmzjJeCKZxDbwEtqzi1cBG/hBmLP13lhk++kzreKjlaOU7Y
# hFmlvBuYquhs79FIaRk4W8+JOR1wcNlO3yMibNXf9lnLocLqTHbKodyhK5a4m1Wp
# Gmt90fUCCU+C1qVziMSYgN/uSZW3s8zFp+4O4e8eOIqf7xHJMUpYtt84fMv6XPfk
# U79uCnx+196Y1SlliQ+inMBl9AEiZcfqXnSmWzWSUHz0F6aHZE8+RokWYyBry/J7
# 0DXjSnBIqbbnHWC9BCIVJXAGcqlEO2lHEdPu6cegPk8QuTA25POqaQmoi35komWU
# EftuMvH1uzitzcCTEdUyeEpLNypM81zctoXAu3AwVXjWmP5UbX9xqUgaeN1Gdy4b
# esAzivhKKIwSqHPPLfnTI/KeGeANlCig69saUaCVgo4oa6TOnXbeqXOqSGpZQ65f
# 6vgPBkKd3wZolv4qoHRbY2beayy4eKpNcG3wLPEHFX41tOa1DKKZpdcVazUOhdbg
# LMzgDCS4fFILHpl878jIxYxYaa+rPeHPzH0VrhS/inHfypex2EfqHIXgRU4SHBQp
# WMxv03/LvsEOSm8gnK7ZczJZCOctkqEaEf4ymKZdK5fgi9OczG21Da5HYzhHF1tv
# E9pqEG4fSbdEW7QICodaWQR2EaGndwITHDCCBq4wggSWoAMCAQICEAc2N7ckVHzY
# R6z9KGYqXlswDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCVVMxFTATBgNVBAoT
# DERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UE
# AxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MB4XDTIyMDMyMzAwMDAwMFoXDTM3
# MDMyMjIzNTk1OVowYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ
# bmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2
# IFRpbWVTdGFtcGluZyBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
# AMaGNQZJs8E9cklRVcclA8TykTepl1Gh1tKD0Z5Mom2gsMyD+Vr2EaFEFUJfpIjz
# aPp985yJC3+dH54PMx9QEwsmc5Zt+FeoAn39Q7SE2hHxc7Gz7iuAhIoiGN/r2j3E
# F3+rGSs+QtxnjupRPfDWVtTnKC3r07G1decfBmWNlCnT2exp39mQh0YAe9tEQYnc
# fGpXevA3eZ9drMvohGS0UvJ2R/dhgxndX7RUCyFobjchu0CsX7LeSn3O9TkSZ+8O
# pWNs5KbFHc02DVzV5huowWR0QKfAcsW6Th+xtVhNef7Xj3OTrCw54qVI1vCwMROp
# VymWJy71h6aPTnYVVSZwmCZ/oBpHIEPjQ2OAe3VuJyWQmDo4EbP29p7mO1vsgd4i
# FNmCKseSv6De4z6ic/rnH1pslPJSlRErWHRAKKtzQ87fSqEcazjFKfPKqpZzQmif
# tkaznTqj1QPgv/CiPMpC3BhIfxQ0z9JMq++bPf4OuGQq+nUoJEHtQr8FnGZJUlD0
# UfM2SU2LINIsVzV5K6jzRWC8I41Y99xh3pP+OcD5sjClTNfpmEpYPtMDiP6zj9Ne
# S3YSUZPJjAw7W4oiqMEmCPkUEBIDfV8ju2TjY+Cm4T72wnSyPx4JduyrXUZ14mCj
# WAkBKAAOhFTuzuldyF4wEr1GnrXTdrnSDmuZDNIztM2xAgMBAAGjggFdMIIBWTAS
# BgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBS6FtltTYUvcyl2mi91jGogj57I
# bzAfBgNVHSMEGDAWgBTs1+OC0nFdZEzfLmc/57qYrhwPTzAOBgNVHQ8BAf8EBAMC
# AYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwdwYIKwYBBQUHAQEEazBpMCQGCCsGAQUF
# BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQQYIKwYBBQUHMAKGNWh0dHA6
# Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3J0
# MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdp
# Q2VydFRydXN0ZWRSb290RzQuY3JsMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCG
# SAGG/WwHATANBgkqhkiG9w0BAQsFAAOCAgEAfVmOwJO2b5ipRCIBfmbW2CFC4bAY
# LhBNE88wU86/GPvHUF3iSyn7cIoNqilp/GnBzx0H6T5gyNgL5Vxb122H+oQgJTQx
# Z822EpZvxFBMYh0MCIKoFr2pVs8Vc40BIiXOlWk/R3f7cnQU1/+rT4osequFzUNf
# 7WC2qk+RZp4snuCKrOX9jLxkJodskr2dfNBwCnzvqLx1T7pa96kQsl3p/yhUifDV
# inF2ZdrM8HKjI/rAJ4JErpknG6skHibBt94q6/aesXmZgaNWhqsKRcnfxI2g55j7
# +6adcq/Ex8HBanHZxhOACcS2n82HhyS7T6NJuXdmkfFynOlLAlKnN36TU6w7HQhJ
# D5TNOXrd/yVjmScsPT9rp/Fmw0HNT7ZAmyEhQNC3EyTN3B14OuSereU0cZLXJmvk
# OHOrpgFPvT87eK1MrfvElXvtCl8zOYdBeHo46Zzh3SP9HSjTx/no8Zhf+yvYfvJG
# nXUsHicsJttvFXseGYs2uJPU5vIXmVnKcPA3v5gA3yAWTyf7YGcWoWa63VXAOimG
# sJigK+2VQbc61RWYMbRiCQ8KvYHZE/6/pNHzV9m8BPqC3jLfBInwAM1dwvnQI38A
# C+R2AibZ8GV2QqYphwlHK+Z/GqSFD/yYlvZVVCsfgPrA8g4r5db7qS9EFUrnEw4d
# 2zc4GqEr9u3WfPwwggWNMIIEdaADAgECAhAOmxiO+dAt5+/bUOIIQBhaMA0GCSqG
# SIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
# GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFz
# c3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBaFw0zMTExMDkyMzU5NTla
# MGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsT
# EHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9v
# dCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8
# MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauy
# efLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34Lz
# B4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+x
# embud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhA
# kHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1Lyu
# GwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2
# PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37A
# lLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD7
# 6GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/
# ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXA
# j6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIBNjAPBgNVHRMBAf8EBTAD
# AQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzAfBgNVHSMEGDAWgBRF
# 66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMCAYYweQYIKwYBBQUHAQEE
# bTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYB
# BQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3Vy
# ZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0aHR0cDovL2NybDMuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDARBgNVHSAECjAI
# MAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0NcVec4X6CjdBs9thbX979X
# B72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnovLbc47/T/gLn4offyct4k
# vFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65ZyoUi0mcudT6cGAxN3J0TU
# 53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFWjuyk1T3osdz9HNj0d1pc
# VIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPFmCLBsln1VWvPJ6tsds5v
# Iy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9ztwGpn1eqXijiuZQxggN2
# MIIDcgIBATB3MGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5j
# LjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBU
# aW1lU3RhbXBpbmcgQ0ECEAp6SoieyZlCkAZjOE2Gl50wDQYJYIZIAWUDBAIBBQCg
# gdEwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0y
# MjA5MDcxNzM1MTVaMCsGCyqGSIb3DQEJEAIMMRwwGjAYMBYEFIUI84ZRXLPTB322
# tLfAfxtKXkHeMC8GCSqGSIb3DQEJBDEiBCAAoUU4OC95sM0RnwAb3KN/knYoSPsi
# Jk6nfq49BgCeFzA3BgsqhkiG9w0BCRACLzEoMCYwJDAiBCCdppAVw0nGwYl4Rbo1
# gq1wyI+kKTvbar6cK9JTknnmOzANBgkqhkiG9w0BAQEFAASCAgCA5qjZg7EAJjd/
# EIyf4ANfhYKMaUUgqvAHQ4L2DwOpIAEd1EY+SYEIR/XZ6fpDagAoxc/Tc5W54+GY
# bTdsHXw+ds2Rs5cSD0pd76la7zR2XZ87Sl/R+RbZRb4XZaP8+6f74DdvZ41jEX7p
# E4rT+mDW6tWbFyFEBziCFW8f/ZdYY0d07Fs3ZC7M/kA0GEleYGYCIioizKtCWbwm
# WkiV21qX4nh6aUVzhX2185cvMPurd0nsA9B45B7vALyIv4z8VoLuYNKbcFFtQXtk
# sJEKGaifYP9AoQMGeWfCmMXrLY03x04qRspL94ni2rLN7d6INQdamGUuPFOHY66K
# 9sw0ts9c8jkcERbgKQkwSV7Cqv6iWX324RVYgGCEvkRLnfUuF0h2PSmY+JgQNkuC
# 9C3oSjYJSkQFx+Bl4E5IEHKZ3A8KX4HXmoSzoBL24Ozz81ukwN3yKU98jeGSoO2z
# YDRkSrJCULQJlLA5JvBo6lWXVH0uizMhBqeTWC00OhzGt431A+oGEDYSPwxF+iJz
# OYxcz/xyHh8wgybIoMCmWfks/rWyV5LTWuTlBG29vQ7/ZFsFFMtdMXphUoC5emZ5
# XZT9A1K7XZIC26lNf1JJAZVijKv5MK3ITt9jEgWw9AvM2aAsWM3vKDFS6+BNZKH8
# D7MdW4lGbz7a2832RPd5WzFHe5TNfA==
# SIG # End signature block