HP.Repo.psm1

# 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.



Set-StrictMode -Version 3.0
#requires -Modules "HP.Private","HP.Softpaq"

$RepositoryType = @"
 
public enum ErrorHandling {
  Fail = 0,
  LogAndContinue = 1
};
 
public class SoftpaqRepositoryFile {
    public class SoftpaqRepositoryFilter {
            public string platform;
            public string operatingSystem;
            public string category;
            public string releaseType;
            public string characteristic;
    };
 
    public class NotificationConfiguration {
            public string server;
            public int port;
            public bool tls;
            public string[] addresses;
            public string username;
            public string password;
            public string from;
            public string fromname;
    };
     
    public class Configuration {
        public ErrorHandling OnRemoteFileNotFound;
    public int ExclusiveLockMaxRetries;
    public string OfflineCacheMode;
        }
 
 
    public string DateCreated;
    public string DateLastModified;
    public string CreatedBy;
    public string ModifiedBy;
    public SoftpaqRepositoryFilter[] Filters;
    public NotificationConfiguration Notifications;
    public Configuration Settings;
 
 
}
"@


$REPOFILE = ".repository/repository.json"
$LOGFILE = ".repository/activity.log"

Add-Type -TypeDefinition $RepositoryType

function getCurrentOsver
{
  [string](Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name ReleaseID | Select-Object ReleaseID).ReleaseId
}

# print a bare error
function err ([string]$str,[boolean]$withLog = $true)
{
  [console]::ForegroundColor = 'red'
  [console]::Error.WriteLine($str)
  [console]::ResetColor()

  if ($withLog) { log ("Error - $str") }
}

# convert a date object to an 8601 string
function iso8601DateString ($date)
{
  $date.ToString("yyyy-MM-dd'T'HH:mm:ss.fffK",[System.Globalization.CultureInfo]::InvariantCulture)
}

# get current user name
function getUserName ()
{
  try {
    [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
  }
  catch {
    return $env:username
  }
}

# check if a file exists
function fileExists ($file)
{
  Test-Path $file -PathType Leaf

}

# load a json object
function loadJson ($file)
{
  try {
    [softpaqrepositoryfile]$result = Get-Content -Raw -Path $file | ConvertFrom-Json
    return $result
  }
  catch
  {
    err ("Could not parse '$file' $($_.Exception.Message)")
    return $Null
  }
}

# load a repository definition file
function loadRepository ()
{
  Write-Verbose "loading $REPOFILE"
  $inRepo = fileExists ($REPOFILE)
  if ($inRepo -eq $false) {
    err "This directory is not a repository." $false
    return $false,$null
  }

  $repo = loadJson ($REPOFILE)
  if ($repo -eq $null)
  {
    err ("Could not initialize the repository: $($_.Exception.Message)")
    return $false,$null
  }

  if ($repo.Filters -eq $Null) { $repo.Filters = @() }

  if (-not $repo.Settings) {
    $repo.Settings = New-Object SoftpaqRepositoryFile+Configuration
  }

  if (-not $repo.Settings.OnRemoteFileNotFound) {
    $repo.Settings.OnRemoteFileNotFound = [ErrorHandling]::Fail
  }

  if (-not $repo.Settings.ExclusiveLockMaxRetries) {
    $repo.Settings.ExclusiveLockMaxRetries = 10
  }

  if (-not $repo.Settings.OfflineCacheMode) {
    $repo.Settings.OfflineCacheMode = "Disable"
  }

  foreach($filter in $repo.Filters)
  {
      if ($filter.characteristic -eq $null)
      {
          $filter.characteristic = "*"
      }
  }

  if ($repo.Notifications -eq $null) {
    $repo.Notifications = New-Object SoftpaqRepositoryFile+NotificationConfiguration
    $repo.Notifications.port = 25
    $repo.Notifications.tls = $false
    $repo.Notifications.username = ""
    $repo.Notifications.password = ""
    $repo.Notifications.from = "softpaq-repo-sync@$($env:userdnsdomain)"
    $repo.Notifications.fromname = "Softpaq Repository Notification"
  }

  Write-Verbose "load success"
  return $true,$repo
}

# download a softpaq, optionally checking existing softpaqs. Note that CVAs are always
# downloaded since there is no reliable way to check their consistency.

function downloadSoftpaq ($cmd,[int]$maxRetries = 10)
{
  $download_file = $true  
  $filename = "sp" + $cmd.number + ".exe"
  $CVAname = "sp" + $cmd.number + ".cva"
 
    # downloading the CVA
    Write-Verbose ("Downloading CVA $($cmd.number)")
    log (" sp$($cmd.number) - Downloading CVA file.")
    Get-SoftpaqMetadataFile @cmd -maxRetries $maxRetries
    log (" sp$($cmd.number) - Done downloading CVA file.") 
  
    if (fileExists ($filename) -EQ $true) {
      Write-Verbose "Checking signature for existing file $filename"
      if (Get-HPPrivateCheckSignature -File $filename -CVAfile $CVAname -Verbose:$VerbosePreference) {
        Write-Host -ForegroundColor Magenta "File $filename already exists and passes signature check. Will not re-download."
        log (" sp$($cmd.number) - Already exists. Will not re-download.")
        $download_file = $false
      }      
      else {
        Write-Verbose ("Need to re-download file '$filename'")
      }
    }
    else {
      Write-Verbose("Need to download file '$filename'")
    }


    if ($download_file -eq $true) {     
      try{
        log (" sp$($cmd.number) - Downloading EXE file.")
        Get-Softpaq @cmd -maxRetries $maxRetries -overwrite yes
        log (" sp$($cmd.number) - Done downloading EXE file.")
      }
      catch {
        Write-Host -ForegroundColor Magenta "File sp$($cmd.number) has invalid or missing signature and will be deleted."
        log (" sp$($cmd.number) has invalid or missing signature and will be deleted.")
        log (" sp$($cmd.number) - Re-Downloading EXE file.")
        Get-Softpaq @cmd -maxRetries $maxRetries
        log (" sp$($cmd.number) - Done downloading EXE file.")
      }
  }
}

# write a repository definition file
function writeRepositoryFile ($obj)
{
  $now = Get-Date
  $obj.DateLastModified = iso8601DateString ($now)
  $obj.ModifiedBy = getUserName
  Write-Verbose "Writing repository file to $REPOFILE"
  $obj | ConvertTo-Json | Out-File -Force $REPOFILE
}

# check if a filter exists in a repo object
function filterExists ($repo,$f)
{
  $c = getFilters $repo $f
  return ($c -ne $null)
}

# get a list f filters in a repo, matching exact parameters
function getFilters ($repo,$f)
{
  if ($repo.Filters.Count -eq 0) { return $null }
  $repo.Filters | Where-Object {
    $_.platform -eq $f.platform -and
    $_.operatingSystem -eq $f.operatingSystem -and
    $_.category -eq $f.category -and
    $_.releaseType -eq $f.releaseType
  }
}

# get a list of filters in a repo, considering empty parameters as wildcards
function getFiltersWild ($repo,$f)
{
  if ($repo.Filters.Count -eq 0) { return $null }


  $repo.Filters | Where-Object {
    $_.platform -eq $f.platform -and
    (
      $_.operatingSystem -eq $f.operatingSystem -or
      $f.operatingSystem -eq "*" -or
      ($f.operatingSystem -eq "win10:*" -and $_.operatingSystem.startsWith("win10"))
    ) -and
    ($_.category -eq $f.category -or $f.category -eq "*") -and
    ($_.releaseType -eq $f.releaseType -or $f.releaseType -eq "*") -and
    ($_.characteristic -eq $f.characteristic -or $f.characteristic -eq "*")
  }
}

# write a log entry to the .repository/activity.log
function log ([string[]]$entryText)
{
  $now = Get-Date
  $date = iso8601DateString ($now)
  $who = getUserName


  foreach ($line in $entryText)
  {
    $entry = "[" + $date + "] " + $who + " - " + $line
    $entry.Trim() | Out-File -Append $LOGFILE
    Write-Verbose ($entry)
  }

}

# touch a file (change its date if exists, or create it if it doesn't.
function touchFile ($file)
{
  if (Test-Path $file) { (Get-ChildItem $file).LastWriteTime = Get-Date }
  else { Write-Output $null > $file }
}


# remove all marks from the repository
function flushMarks ()
{
  Write-Verbose "Removing all marks"
  Remove-Item ".repository\mark\*" -Include "*.mark"
}


# send a notification email
function send ($subject,$body,$html = $true)
{
  $n = Get-RepositoryNotificationConfiguration
  if ((-not $n) -or ($n.server -eq "")) {
    Write-Verbose ("Notifications are not configured")
    return
  }

  try {
    if ($n.addresses -eq $null -or $n.addresses.Count -eq 0)
    {
      Write-Verbose ("Notifications has no recipients defined")
      return
    }
    log ("Sending a notification email")

    $params = @{}
    $params.To = $n.addresses
    $params.SmtpServer = $n.server
    $params.port = $n.port
    $params.UseSsl = $n.tls
    $params.from = "$($n.fromname) <$($n.from)>"
    $params.Subject = $subject
    $params.Body = $body
    $params.BodyAsHtml = $html

    Write-Verbose ("server: $($params.SmtpServer)")
    Write-Verbose ("port: $($params.Port)")

    if ([string]::IsNullOrEmpty($n.username) -eq $false)
    {
      try {
        [securestring]$read = ConvertTo-SecureString -string $n.password
        $params.Credential = New-Object System.Management.Automation.PSCredential ($n.username,$read)
        if ($params.Credential -eq $null) {
          log ("Could not build credential object from username and password")
          return;
        }
      }
      catch {
        err ("Failed to build credential object from username and password: $($_.Exception.Message)")
        return
      }
    }
    Send-MailMessage @params -ErrorAction Stop
  }
  catch {
    err ("Could not send email: $($_.Exception.Message)")
    return
  }
  Write-Verbose ("Send complete.")
}

<#
.SYNOPSIS
    Initialize a repository in the current directory.
 
.DESCRIPTION
    This command initializes a directory to be used as a repository. It creates a .repository folder in the current directory,
    which contains the definition of the .repository and all its settings.
 
    In order to uninitalize a directory, simple remove the .repository folder.
 
    After initializing a repository, you must add at least one filter to define the content that this repository will receive.
 
    If the directory already contains a repository, the command will fail.
 
.EXAMPLE
    Initialize-Repository
 
.LINK
    [Add-RepositoryFilter](Add-RepositoryFilter)
 
.LINK
    [Remove-RepositoryFilter](Remove-RepositoryFilter)
 
.LINK
    [Get-RepositoryInfo](Get-RepositoryInfo)
 
.LINK
    [Invoke-RepositorySync](Invoke-RepositorySync)
 
.LINK
    [Invoke-RepositoryCleanup](Invoke-RepositoryCleanup)
 
.LINK
    [Set-RepositoryNotificationConfiguration](Set-RepositoryNotificationConfiguration)
 
.LINK
    [Clear-RepositoryNotificationConfiguration](Clear-RepositoryNotificationConfiguration)
 
.LINK
    [Get-RepositoryNotificationConfiguration](Get-RepositoryNotificationConfiguration)
 
.LINK
    [Show-RepositoryNotificationConfiguration](Show-RepositoryNotificationConfiguration)
 
.LINK
    [Add-RepositorySyncFailureRecipient](Add-RepositorySyncFailureRecipient)
 
.LINK
    [Remove-RepositorySyncFailureRecipient](Remove-RepositorySyncFailureRecipient)
 
.LINK
    [Test-RepositoryNotificationConfiguration](Test-RepositoryNotificationConfiguration)
 
.LINK
    [Get-RepositoryConfiguration](Get-RepositoryConfiguration)
 
.LINK
    [Set-RepositoryConfiguration](Set-RepositoryConfiguration)
#>

function Initialize-Repository
{
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/Initialize%E2%80%90Repository")]
  param()

  if (fileExists ($REPOFILE)) {
    err "This directory is already initialized as a repository."
    return
  }
  $now = Get-Date
  $newRepositoryFile = New-Object SoftpaqRepositoryFile

  $newRepositoryFile.Settings = New-Object SoftpaqRepositoryFile+Configuration
  $newRepositoryFile.Settings.OnRemoteFileNotFound = [ErrorHandling]::Fail
  $newRepositoryFile.Settings.ExclusiveLockMaxRetries = 10
  $newRepositoryFile.Settings.OfflineCacheMode = "Disable"

  $newRepositoryFile.DateCreated = iso8601DateString ($now)
  $newRepositoryFile.CreatedBy = getUserName

  try {
    New-Item -ItemType directory -Path .repository | Out-Null
    writeRepositoryFile $newRepositoryFile
    New-Item -ItemType directory -Path ".repository/mark" | Out-Null
  }
  catch {
    err ("Could not initialize the repository: $($_.Exception.Message)")
    return
  }
  log "Repository initialized successfully."
  Write-Host "Ok."
}


<#
.SYNOPSIS
    Add a filter to the repository.
 
.DESCRIPTION
 
    This function adds a filter to a repository, which was previously initialized by Initialize-Repository.
 
    The repository can contain one or more filters, and the effective filtering will be the sum of all filters defined.
 
 
.PARAMETER platform
   Specifies the given platform as a platform to include in this repository. This is a platform ID, a 4-digit hexadecimal number, as obtained by Get?HPDeviceProductID.
 
 
.PARAMETER os
    Specifies the operating system to be include in this repository. The field must be one of "win7", "win8", "win8.1", "win10". If this parameter is not specified, all operating system associated with the specified platform will be included.
 
.PARAMETER osver
    For windows 10 only, specify the target build (e.g. 1709, 1803, etc). If the parameter is not specified, current operating system build number
    will be assumed, which may not be what is intended.
 
    For windows versions other than windows 10, this switch is silently ignored.
 
.PARAMETER category
    Specifies the softpaq category to be include in this repository. The category must be one (or more) of "bios", "firmware", "driver", "software", "os", "manageability", "diagnostic", "utility", "driverpack", "dock".
    If this parameter is not specified, all categories are included.
 
.PARAMETER releaseType
    Specifies the softpaq release type to be include in this repository. The release type must be one (or more) of "critical", "recommended", "routine". If this parameter is not specified, all release types are included.
 
.PARAMETER characteristic
    Specifies the softpaq characteristic to be include in this repository. The characteristic must be one of "ssm", "dpb". If this parameter is not specified, all characteristics are included.
 
.EXAMPLE
    Add-RepositoryFilter -platform 1234 -os win10
 
.LINK
    [Initialize-Repository](Initialize-Repository)
 
.LINK
    [Remove-RepositoryFilter](Remove-RepositoryFilter)
 
.LINK
    [Get-RepositoryInfo](Get-RepositoryInfo)
 
.LINK
    [Invoke-RepositoryCleanup](Invoke-RepositoryCleanup)
 
.LINK
    [Invoke-RepositorySync](Invoke-RepositorySync)
 
.LINK
    [Set-RepositoryNotificationConfiguration](Set-RepositoryNotificationConfiguration)
 
.LINK
    [Clear-RepositoryNotificationConfiguration](Clear-RepositoryNotificationConfiguration)
 
.LINK
    [Get-RepositoryNotificationConfiguration](Get-RepositoryNotificationConfiguration)
 
.LINK
    [Show-RepositoryNotificationConfiguration](Show-RepositoryNotificationConfiguration)
 
.LINK
    [Add-RepositorySyncFailureRecipient](Add-RepositorySyncFailureRecipient)
 
.LINK
    [Remove-RepositorySyncFailureRecipient](Remove-RepositorySyncFailureRecipient)
 
.LINK
    [Test-RepositoryNotificationConfiguration](Test-RepositoryNotificationConfiguration)
 
.LINK
    [Get-HPDeviceProductID](Get-HPDeviceProductID)
#>

function Add-RepositoryFilter
{
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/Add%E2%80%90RepositoryFilter")]
  param(
    [ValidatePattern("^[a-fA-F0-9]{4}$")]
    [Parameter(Position = 0,Mandatory = $true)] [string]$platform,

    [ValidateSet("win7","win8","win8.1","win81","win10","*")] # keep in sync with the softpaq module
    [string[]]
    [Parameter(Position = 1)] $os = "*",

    [ValidateRange(1507,9999)]
    [Parameter(Position = 1)] [int]$osver,

    [ValidateSet("bios","firmware","driver","software","os","manageability","diagnostic","utility","driverpack","dock","*")] # keep in sync with the softpaq module
    [string[]]
    [Parameter(Position = 2)] $category = "*",

    [ValidateSet("critical","recommended","routine","*")] # keep in sync with the softpaq module
    [string[]]
    [Parameter(Position = 3)] $releaseType = "*",
  
    [ValidateSet("ssm","dpb","*")] # keep in sync with the softpaq module
    [string[]]
    [Parameter(Position = 4)] $characteristic = "*"
  )

  try {
    $c = loadRepository
    if ($c[0] -eq $false) { return }
    $repo = $c[1]

    $newFilter = New-Object SoftpaqRepositoryFile+SoftpaqRepositoryFilter
    $newFilter.platform = $platform

    $newFilter.operatingSystem = $os
    if (-not $osver)
    {
      $osver = getCurrentOsver
    }
    if ($os -eq "win10") { $newFilter.operatingSystem = "win10:$osver" }

    $newFilter.category = $category
    $newFilter.releaseType = $releaseType
    $newFilter.characteristic = $characteristic

    # silently ignore if the filter is already in the repo
    $exists = filterExists $repo $newFilter
    if (!$exists) {
      $repo.Filters += $newFilter
      writeRepositoryFile ($repo)
      log "Added filter $platform {{ os='$os', category='$category', release='$releaseType', characteristic='$characteristic' }}"
    }
    else
    {
      Write-Verbose ("Silently ignoring this filter, exact match is already in repository")
    }
    Write-Host "Ok."
  }
  catch
  {
    err ("Could not add filter to repository: $($_.Exception.Message)")
  }
}


<#
.SYNOPSIS
    Remove one or more filters from the repository.
     
 
.DESCRIPTION
    This function modifies the repository to remove filters from the repository definition.
 
    If an optional parameter is not specified, it will be considered a wildcard and match any value. Therefore this command may
    result in multiple filters being deleted.
 
 
.PARAMETER platform
    The platform to remove. This is a 4-digit hex number, and can be obtained via Get-HPDeviceProductID
 
.PARAMETER os
    An optional parameter to narrow down the filter to a specific OS for the specified platform. If not specified, all OS will be matched.
 
 
.PARAMETER osver
    For windows 10 only, specify the target build (e.g. 1709, 1803, etc). If the parameter is not specified all windows 10 filters will match.
 
    For windows versions other than windows 10, this switch is silently ignored.
 
.PARAMETER category
    An optional parameter to narrow down the filter to a specific category for the specified platform. If not specified, all categories will be matched.
 
.PARAMETER releaseType
    An optional parameter to narrow down the filter to a specific release type for the specified platform. If not specified, all release types will be matched.
 
.PARAMETER characteristic
    An optional parameter to narrow down the filter to a specific characteristic for the specified platform. If not specified, all characteristics will be matched.
 
.EXAMPLE
    Remove-RepositoryFilter -platform 1234
 
     
.LINK
    [Initialize-Repository](Initialize-Repository)
 
.LINK
    [Add-RepositoryFilter](Add-RepositoryFilter)
 
.LINK
    [Get-RepositoryInfo](Get-RepositoryInfo)
 
.LINK
    [Invoke-RepositoryCleanup](Invoke-RepositoryCleanup)
 
.LINK
    [Invoke-RepositorySync](Invoke-RepositorySync)
 
.LINK
    [Set-RepositoryNotificationConfiguration](Set-RepositoryNotificationConfiguration)
 
.LINK
    [Clear-RepositoryNotificationConfiguration](Clear-RepositoryNotificationConfiguration)
 
.LINK
    [Get-RepositoryNotificationConfiguration](Get-RepositoryNotificationConfiguration)
 
.LINK
    [Show-RepositoryNotificationConfiguration](Show-RepositoryNotificationConfiguration)
 
.LINK
    [Add-RepositorySyncFailureRecipient](Add-RepositorySyncFailureRecipient)
 
.LINK
    [Remove-RepositorySyncFailureRecipient](Remove-RepositorySyncFailureRecipient)
 
.LINK
    [Get-HPDeviceProductID](Get-HPDeviceProductID)
 
.LINK
    [Test-RepositoryNotificationConfiguration](Test-RepositoryNotificationConfiguration)
#>

function Remove-RepositoryFilter
{
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/Remove%E2%80%90RepositoryFilter")]
  param(
    [ValidatePattern("^[a-fA-F0-9]{4}$")]
    [Parameter(Position = 0,Mandatory = $true)] [string]$platform,

    [ValidateSet("win7","win8","win8.1","win81","win10","*")] # keep in sync with the softpaq module
    [string[]]
    [Parameter(Position = 1)] $os = "*",

    [ValidateRange(1507,9999)]
    [Parameter(Position = 1)] [int]$osver,

    [ValidateSet("bios","firmware","driver","software","os","manageability","diagnostic","utility","driverpack","dock","*")] # keep in sync with the softpaq module
    [string[]]
    [Parameter(Position = 2)] $category = "*",

    [ValidateSet("critical","recommended","routine","*")] # keep in sync with the softpaq module
    [string[]]
    [Parameter(Position = 3)] $releaseType = "*",

    [Parameter(Position = 4,Mandatory = $false)] [switch]$yes = $false,

    [ValidateSet("ssm", "dpb", "*")] # keep in sync with the softpaq module
    [string[]]
    [Parameter(Position = 5)] $characteristic = "*"
  )

  try {
    $c = loadRepository
    if ($c[0] -eq $false) { return }

    $newFilter = New-Object SoftpaqRepositoryFile+SoftpaqRepositoryFilter
    $newFilter.platform = $platform
    $newFilter.operatingSystem = $os

    if ($os -eq "win10") {
      if ($osver) { $newFilter.operatingSystem = "win10:$osver" }
      else { $newFilter.operatingSystem = "win10:*" }
    }

    $newFilter.category = $category
    $newFilter.releaseType = $releaseType
    $newFilter.characteristic = $characteristic

    $todelete = getFiltersWild $c[1] $newFilter
    if ($todelete -eq $null) {
      Write-Verbose ("No matching filter to delete")
      Write-Host ("Ok.")
      return
    }

    if ($yes -ne $true) {
      Write-Host "The following filters will be deleted:" -ForegroundColor Cyan
      $todelete | ConvertTo-Json -Depth 2 | Write-Host -ForegroundColor Cyan
      $answer = Read-Host "Enter 'y' to continue: "
      if ($answer -ne "y") {
        Write-Host 'Aborted.'
        return }
    }

    $c[1].Filters = $c[1].Filters | Where-Object { $todelete -notcontains $_ }
    writeRepositoryFile $c[1]

    Write-Host ("Ok.")
    foreach ($f in $todelete) {
      log "Removed filter $($f.platform) { os='$($f.operatingSystem)', category='$($f.category)', release='$($f.releaseType), characteristic='$($f.characteristic)' }"
    }
  }
  catch
  {
    err ("Could not remove filter from repository: $($_.Exception.Message)")
  }
}


<#
.SYNOPSIS
    Show the current repository definition.
 
.DESCRIPTION
    Get the repository definition as an object. This command must be run inside an initialized repository.
     
.EXAMPLE
    $myrepository = Get-RepositoryInfo
     
     
.LINK
    [Initialize-Repository](Initialize-Repository)
 
.LINK
    [Add-RepositoryFilter](Add-RepositoryFilter)
 
.LINK
    [Remove-RepositoryFilter](Remove-RepositoryFilter)
 
.LINK
    [Invoke-RepositorySync](Invoke-RepositorySync)
 
.LINK
    [Invoke-RepositoryCleanup](Invoke-RepositoryCleanup)
 
.LINK
    [Set-RepositoryNotificationConfiguration](Set-RepositoryNotificationConfiguration)
 
.LINK
    [Clear-RepositoryNotificationConfiguration](Clear-RepositoryNotificationConfiguration)
 
.LINK
    [Get-RepositoryNotificationConfiguration](Get-RepositoryNotificationConfiguration)
 
.LINK
    [Show-RepositoryNotificationConfiguration](Show-RepositoryNotificationConfiguration)
 
.LINK
    [Add-RepositorySyncFailureRecipient](Add-RepositorySyncFailureRecipient)
 
.LINK
    [Remove-RepositorySyncFailureRecipient](Remove-RepositorySyncFailureRecipient)
 
.LINK
    [Test-RepositoryNotificationConfiguration](Test-RepositoryNotificationConfiguration)
#>

function Get-RepositoryInfo ()
{
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/Get%E2%80%90RepositoryInfo")]
  param()

  try {
    $c = loadRepository
    if ($c[0] -eq $false) { return }
    $c[1]
  }
  catch
  {
    err ("Could not get repository info: $($_.Exception.Message)")
  }
}


<#
.SYNOPSIS
    Synchronize repository
     
.DESCRIPTION
    This command performs a synchronization of a repository, by downloading latest softpaqs associated with
    the repository filters.
 
    The command may be scheduled via task manager to run on schedule. Define a notification email via Set-RepositoryNotificationConfiguration
    to receive any failure notifications during unattended operation.
 
This command may be followed by Invoke-RepositoryCleanup to remove any obsolete softpaqs from the repository.
   
Invoke-RepositorySync functionality is not supported in WinPE.
 
.PARAMETER quiet
    Suppress progress messages during operation.
 
.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 'skip' if overwrite is
    not specified.
 
    Note that specifying 'no' is of very limited use for this particular cmdlet, and normally not a good choice.
 
.EXAMPLE
    Invoke-RepositorySync -quiet
 
.LINK
    [Initialize-Repository](Initialize-Repository)
 
.LINK
    [Add-RepositoryFilter](Add-RepositoryFilter)
 
.LINK
    [Remove-RepositoryFilter](Remove-RepositoryFilter)
 
.LINK
    [Get-RepositoryInfo](Get-RepositoryInfo)
 
 
.LINK
    [Invoke-RepositoryCleanup](Invoke-RepositoryCleanup)
 
 
.LINK
    [Set-RepositoryNotificationConfiguration](Set-RepositoryNotificationConfiguration)
 
.LINK
    [Clear-RepositoryNotificationConfiguration](Clear-RepositoryNotificationConfiguration)
 
.LINK
    [Get-RepositoryNotificationConfiguration](Get-RepositoryNotificationConfiguration)
 
.LINK
    [Show-RepositoryNotificationConfiguration](Show-RepositoryNotificationConfiguration)
 
.LINK
    [Add-RepositorySyncFailureRecipient](Add-RepositorySyncFailureRecipient)
 
.LINK
    [Remove-RepositorySyncFailureRecipient](Remove-RepositorySyncFailureRecipient)
 
.LINK
    [Test-RepositoryNotificationConfiguration](Test-RepositoryNotificationConfiguration)
#>

function Invoke-RepositorySync
{
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/Invoke%E2%80%90RepositorySync")]
  param(
    [Parameter(Position = 0,Mandatory = $false)] [switch]$quiet = $false
  )

  try {
    $cwd = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath((Get-Location))

    $cacheDir = Join-Path -Path $cwd -ChildPath ".repository"
    $cacheDirOffline = $cacheDir + "\cache\offline"
    $repo = loadRepository

    if ($repo[0] -eq $false) { return }

    $filters = $repo[1].Filters
    if ($filters.Count -eq 0) {
      Write-Verbose "Repository has no filters defined, terminating."
      Write-Host ("Ok.")
      return
    }

    $platformGroups = $filters | Group-Object -Property platform
    $normalized = @()

    foreach ($pobj in $platformGroups)
    {

      $items = $pobj.Group

      if ($items | Where-Object -Property operatingSystem -EQ -Value "*") {
        $items | ForEach-Object { $_.operatingSystem = "*" }
      }

      if ($items | Where-Object -Property category -EQ -Value "*") {
        $items | ForEach-Object { $_.category = "*" }
      }

      if ($items | Where-Object -Property releaseType -EQ -Value "*") {
        $items | ForEach-Object { $_.releaseType = "*" }
      }

      if ($items | Where-Object -Property characteristic -EQ -Value "*") {
        $items | ForEach-Object { $_.characteristic = "*" }
      }

         Write-Verbose $

      $normalized += $items | sort -Unique -Property operatingSystem,category,releaseType,characteristic
    }

    $softpaqlist = @()
    log "Repository sync has started"
    $cmd = @{}

    # build the list of softpaqs to download
    foreach ($c in $normalized) {
      Write-Verbose ($c | Format-List | Out-String)

      if (Get-HPDeviceDetails -platform $c.platform)
      {
      $cmd.platform = $c.platform.ToLower()
      $cmd.quiet = $quiet
      $cmd.verbose = $VerbosePreference

      Write-Verbose ("Working on a rule for platform $($cmd.platform)")

      if ($c.operatingSystem.startsWith("win10:"))
      {
        $split = $c.operatingSystem -split ':'
        $cmd.os = $split[0]
        $cmd.osver = $split[1]
      }
      elseif ($c.operatingSystem -eq "win10")
      {
        $cmd.os = "win10"
        $cmd.osver = getCurrentOsver
      }
      elseif ($c.operatingSystem -ne "*")
      {
        $cmd.os = $c.operatingSystem
        #$cmd.osver = $null
      }


      if ($c.characteristic -ne "*") 
      { 
          $cmd.characteristic = $c.characteristic.ToUpper() 
          Write-Verbose "Filter-characteristic:$($cmd.characteristic)"
      }

      if ($c.releaseType -ne "*") 
      { 
          $cmd.releaseType = $c.releaseType.Split() 
          Write-Verbose "Filter-releaseType:$($cmd.releaseType)"
      }
      if ($c.category -ne "*") 
      { 
          $cmd.category = $c.category.Split() 
          Write-Verbose "Filter-category:$($cmd.category)"
      }

      log "Reading the softpaq list for platform $($cmd.platform)"
      $results = Get-SoftpaqList @cmd -cacheDir $cacheDir -maxRetries $repo[1].Settings.ExclusiveLockMaxRetries
      log "softpaq list for platform $($cmd.platform) created"
      $softpaqlist += $results


      $OfflineCacheMode = $repo[1].settings.OfflineCacheMode
      if ($OfflineCacheMode -eq "Enable"){

        $baseurl = "https://ftp.hp.com/pub/caps-softpaq/cmit/imagepal/ref/"
        $url = $baseurl + "platformList.cab"
        $filename = "platformList.cab"
        Write-Verbose "Trying to download PlatformList..."
        $PlatformList = Get-HPPrivateOfflineCacheFiles -url $url -filename $filename -cacheDirOffline $cacheDirOffline -expand
        Write-Verbose "Finish downloading PlatformList - $PlatformList"

        $url = $baseurl + "$($cmd.platform)/$($cmd.platform)_cds.cab"
        $cacheDirAdvisory = $cacheDirOffline + "\$($cmd.platform)"
        $filename = "$($cmd.platform)_cds.cab"
        Write-Verbose "Trying to download Advisory Data Files..."
        try {
          $AdvisoryFile = Get-HPPrivateOfflineCacheFiles -url $url -filename $filename -cacheDirOffline $cacheDirAdvisory -expand
          Write-Verbose "Finish downloading Advisory Data Files - $AdvisoryFile"
        }
        catch {
          $exception = $_.Exception
          switch ($repo[1].Settings.OnRemoteFileNotFound) {
            "LogAndContinue" {
            [string]$data = formatSyncErrorMessageAsHtml $exception
            log ($data -split "`n")
            send "Softpaq repository synchronization error" $data
          }
          # "Fail"
          default  {
          throw $exception
          }
        }
      }
        
        $url = "https://ftp.hp.com/pub/caps-softpaq/cmit/imagepal/kb/common/latest.cab"
        $cacheDirKb = $cacheDirOffline + "\kb\common"
        $filename = "latest.cab"
        Write-Verbose "Trying to download Knowledge Base..."
        $KnowledgeBase = Get-HPPrivateOfflineCacheFiles -url $url -filename $filename -cacheDirOffline $cacheDirKb 
        Write-Verbose "Finish downloading Knowledge Base - $KnowledgeBase"
      }
    }
    else {
      Write-Host -ForegroundColor Cyan "Platform $($c.platform) doesn't exist. Please add a valid platform."
      log "Platform $($c.platform) in not valid."
      }
  }

    Write-Verbose ("Done with the list, repository is $($softpaqlist.Count) softpaqs.")
    Write-Verbose ("Flushing the list of markers")
    flushMarks
    Write-Verbose ("Writing new marks")

    foreach ($sp in $softpaqList) {
      $number = $sp.id.ToLower().TrimStart("sp")
      touchFile (".repository/mark/" + $number + ".mark")
    }

    Write-Verbose ("Starting download")
    $cmd = @{}
    $cmd.quiet = $quiet
    $cmd.verbose = $VerbosePreference

    log "Download has started for $($softpaqlist.Count) softpaqs."
    foreach ($sp in $softpaqlist)
    {
      $cmd.number = $sp.id.ToLower().TrimStart("sp")
      Write-Verbose "Working on data for softpaq $($cmd.number)"
      try {
        log "Start downloading files for sp$($cmd.number)."
        downloadSoftpaq $cmd -maxRetries $repo[1].Settings.ExclusiveLockMaxRetries
     
      if($OfflineCacheMode -eq "Enable"){
        log (" sp$($cmd.number) - Downloading Release Notes.")
        $ReleaseNotesurl = Get-HPPrivateItemUrl $cmd.number "html" 
        $target = "sp$($cmd.number).html"
        $targetfile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($target)
        Invoke-HPPrivateDownloadFile -url $ReleaseNotesurl -target $targetfile
        log (" sp$($cmd.number) - Done Downloading Release Notes.")
      }
      log "Finish downloading files for sp$($cmd.number)."
      }
      catch {
        $exception = $_.Exception
      
        switch ($repo[1].Settings.OnRemoteFileNotFound)
        {
          "LogAndContinue" {
            [string]$data = formatSyncErrorMessageAsHtml $exception
            log ($data -split "`n")
            send "Softpaq repository synchronization error" $data
          }
          # "Fail"
          default  {
            throw $exception
          }
        }
      }
    }

    log "Repository sync has ended"
    Write-Host "Ok."
  }
  catch
 {
    #Write-Output $_.Exception | format-list -force
    err "Repository synchronization failed: $($_.Exception.Message)" $true
    [string]$data = formatSyncErrorMessageAsHtml $_.Exception
    log ($data -split "`n")
    send "Softpaq repository synchronization error" $data
 }
}

function formatSyncErrorMessageAsHtml ($exception)
{
  [string]$data = "An error occurred during softpaq synchronization.`n`n";
  $data += "The error was: <em>$($exception.Message)</em>`n"
  $data += "`nDetails:`n<pre>"
  $data += "<hr/>"
  $data += ($exception | Format-List -Force | Out-String)
  $data += "</pre>"
  $data += "<hr/>"
  $data
}


<#
 
 
.SYNOPSIS
    Cleanup repository
     
.DESCRIPTION
    Use Invoke-RepositoryCleanup to remove softpaqs from repository that are obsolete. These may be softpaqs that have been replaced
    by newer versions, or that no longer match the active repository filters.
 
.EXAMPLE
    Invoke-RepositoryCleanup
 
.LINK
    [Initialize-Repository](Initialize-Repository)
 
.LINK
    [Add-RepositoryFilter](Add-RepositoryFilter)
 
.LINK
    [Remove-RepositoryFilter](Remove-RepositoryFilter)
 
.LINK
    [Get-RepositoryInfo](Get-RepositoryInfo)
 
.LINK
    [Invoke-RepositorySync](Invoke-RepositorySync)
 
.LINK
    [Set-RepositoryNotificationConfiguration](Set-RepositoryNotificationConfiguration)
 
.LINK
    [Clear-RepositoryNotificationConfiguration](Clear-RepositoryNotificationConfiguration)
 
.LINK
    [Get-RepositoryNotificationConfiguration](Get-RepositoryNotificationConfiguration)
 
.LINK
    [Show-RepositoryNotificationConfiguration](Show-RepositoryNotificationConfiguration)
 
.LINK
    [Add-RepositorySyncFailureRecipient](Add-RepositorySyncFailureRecipient)
 
.LINK
    [Remove-RepositorySyncFailureRecipient](Remove-RepositorySyncFailureRecipient)
 
.LINK
    [Test-RepositoryNotificationConfiguration](Test-RepositoryNotificationConfiguration)
 
#>

function Invoke-RepositoryCleanup
{
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/Invoke%E2%80%90RepositoryCleanup")]
  param()

  log ("Beginning repository cleanup")
  $deleted = 0

  try {
    Get-ChildItem "." -File | ForEach-Object {
      $name = $_.Name.ToLower().TrimStart("sp").Split('.')[0]
      if ($name -ne $null) {
        if (!(Test-Path ".repository/mark/$name.mark" -PathType Leaf))
        {
          Write-Verbose ("Deleting orphaned file $($_.Name)")
          Remove-Item $_.Name
          $deleted++
        }
      }
    }
    log ("Completed repository cleanup, deleted $deleted files.")
    Write-Host "Ok."
  }
  catch {
    err ("Could not clean repository: $($_.Exception.Message)")
  }
}


<#
.SYNOPSIS
    Set the repository notification configuration.
 
.DESCRIPTION
    This function defines a notification SMTP server (and optionally, port) for an email server to be used to send
    failure notifications during unattended synchronization via Invoke-RepositorySync.
 
    One or more recipients can then be added via Add-RepositorySyncFailureRecipient.
 
    The directory must have been initialized via Initialize?Repository.
 
    The function must be invoked inside a directory initialized as a repository.
 
 
.PARAMETER server
    The server name (or IP) for the outgoing mail (SMTP) server
 
.PARAMETER port
    Specifies a port for the SMTP server. If not provided, the default IANA-assigned port 25 will be used.
 
.PARAMETER tls
    specifies whether to use SSL/TLS. The value may be "true", "false", or "auto". Auto
    will automatically set SSL to true when the port is changed to a value different than 25. By default, TLS is false.
 
.PARAMETER user
    Specifies the SMTP server username for authenticated SMTP servers. If username is not specified, connection will be made without authentication.
 
.PARAMETER password
    Specifies the SMTP server password for authenticated SMTP servers.
     
.PARAMETER from
    Specifies the email address from which the notification will appear to originate. Note that in servers may accept emails from specified
    domains only, or in some cases may require the email address to match the username.
 
.PARAMETER fromname
    Specifies the from address display name.
 
.PARAMETER removecredentials
    Removes the SMTP server credentials without removing the entire mail server configuration.
 
.EXAMPLE
    Set-RepositoryNotificationConfiguration smtp.mycompany.com
 
.LINK
    [Initialize-Repository](Initialize-Repository)
 
.LINK
    [Add-RepositoryFilter](Add-RepositoryFilter)
 
.LINK
    [Remove-RepositoryFilter](Remove-RepositoryFilter)
 
.LINK
    [Get-RepositoryInfo](Get-RepositoryInfo)
 
.LINK
    [Invoke-RepositorySync](Invoke-RepositorySync)
 
.LINK
    [Invoke-RepositoryCleanup](Invoke-RepositoryCleanup)
 
.LINK
    [Clear-RepositoryNotificationConfiguration](Clear-RepositoryNotificationConfiguration)
 
.LINK
    [Get-RepositoryNotificationConfiguration](Get-RepositoryNotificationConfiguration)
 
.LINK
    [Show-RepositoryNotificationConfiguration](Show-RepositoryNotificationConfiguration)
 
.LINK
    [Add-RepositorySyncFailureRecipient](Add-RepositorySyncFailureRecipient)
 
.LINK
    [Remove-RepositorySyncFailureRecipient](Remove-RepositorySyncFailureRecipient)
 
.LINK
    [Test-RepositoryNotificationConfiguration](Test-RepositoryNotificationConfiguration)
 
#>

function Set-RepositoryNotificationConfiguration
{
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/Set%E2%80%90RepositoryNotificationConfiguration")]
  param(
    [Parameter(Position = 0,Mandatory = $false)]
    [string]
    [ValidatePattern("^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$")]
    $server = $null,

    [Parameter(Position = 1,Mandatory = $false)]
    [ValidateRange(1,65535)]
    [int]
    $port = 0,

    [Parameter(Position = 2,Mandatory = $false)]
    [string]
    [ValidateSet('true','false','auto')]
    $tls = $null,

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

    [Parameter(Position = 4,Mandatory = $false)]
    [string]
    $password = $null,

    [Parameter(Position = 5,Mandatory = $false)]
    [string]
    [ValidatePattern("^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$")]
    $from = $null,

    [Parameter(Position = 6,Mandatory = $false)]
    [string]
    $fromname = $null,

    [Parameter(Position = 7,Mandatory = $false)]
    [switch]
    $removeCredentials
  )

  Write-Verbose "Beginning notification configuration update"

  if ($removeCredentials.IsPresent -and ([string]::IsNullOrEmpty($username) -eq $false -or [string]::IsNullOrEmpty($password) -eq $false))
  {
    err ("-removeCredentials may not be specified with -username or -password")
    return
  }

  try {
    $c = loadRepository
    if ($c[0] -eq $false) { return }

    Write-Verbose "Applying configuration"
    if ([string]::IsNullOrEmpty($server) -eq $false) {
      Write-Verbose ("Setting SMTP Server to: $server")
      $c[1].Notifications.server = $server
    }

    if ($port -ne 0) {
      Write-Verbose ("Setting SMTP Server port to: $port")
      $c[1].Notifications.port = $port
    }

    if ([string]::IsNullOrEmpty($username) -eq $false) {
      Write-Verbose ("Setting SMTP server credential(username) to: $username")
      $c[1].Notifications.username = $username
    }

    if ([string]::IsNullOrEmpty($password) -eq $false) {
      Write-Verbose ("Setting SMTP server credential(password) to: (redacted)")
      $c[1].Notifications.password = ConvertTo-SecureString $password -Force -asPlainText | ConvertFrom-SecureString
    }


    if ($removeCredentials.IsPresent -eq $true)
    {
      Write-Verbose ("Clearing credentials from notification configuration")
      $c[1].Notifications.username = $null
      $c[1].Notifications.password = $null
    }

    switch ($tls)
    {
      "auto" {
        if ($port -ne 25) { $c[1].Notifications.tls = $true }
        else { $c[1].Notifications.tls = $false }
        Write-Verbose ("SMTP server SSL autocalculated to: $($c[1].Notifications.tls)")
      }

      "true" {
        $c[1].Notifications.tls = $true
        Write-Verbose ("Setting SMTP SSL to: $($c[1].Notifications.tls)")
      }
      "false" {
        $c[1].Notifications.tls = $false
        Write-Verbose ("Setting SMTP SSL to: $($c[1].Notifications.tls)")
      }
    }


    if ([string]::IsNullOrEmpty($from) -eq $false) {
      Write-Verbose ("Setting Mail from adress to: $from")
      $c[1].Notifications.from = $from }
    if ([string]::IsNullOrEmpty($fromname) -eq $false) {
      Write-Verbose ("Setting Mail from displayname to: $fromname")
      $c[1].Notifications.fromname = $fromname }

    writeRepositoryFile $c[1]
    Write-Host ("Ok.")
    log ("Updated notification configuration")
  }
  catch {
    err ("Failed to modify repository configuration: $($_.Exception.Message)")
  }

}

<#
.SYNOPSIS
    Clear the repository notification configuration
 
.DESCRIPTION
    This function removes notification configuration from repository, in effect turning off notifications.
 
    The directory must have been initialized via Initialize-Repository and notification configuration must have been defined via Set-RepositoryNotificationConfiguration
 
    The function must be invoked inside a directory initialized as a repository.
 
 
.LINK
    [Initialize-Repository](Initialize-Repository)
 
.LINK
    [Add-RepositoryFilter](Add-RepositoryFilter)
 
.LINK
    [Remove-RepositoryFilter](Remove-RepositoryFilter)
 
.LINK
    [Get-RepositoryInfo](Get-RepositoryInfo)
 
.LINK
    [Invoke-RepositorySync](Invoke-RepositorySync)
 
.LINK
    [Invoke-RepositoryCleanup](Invoke-RepositoryCleanup)
 
.LINK
    [Set-RepositoryNotificationConfiguration](Set-RepositoryNotificationConfiguration)
 
.LINK
    [Get-RepositoryNotificationConfiguration](Get-RepositoryNotificationConfiguration)
 
.LINK
    [Show-RepositoryNotificationConfiguration](Show-RepositoryNotificationConfiguration)
 
.LINK
    [Add-RepositorySyncFailureRecipient](Add-RepositorySyncFailureRecipient)
 
.LINK
    [Remove-RepositorySyncFailureRecipient](Remove-RepositorySyncFailureRecipient)
 
.LINK
    [Test-RepositoryNotificationConfiguration](Test-RepositoryNotificationConfiguration)
 
.EXAMPLE
    Clear-RepositoryNotificationConfiguration
 
#>

function Clear-RepositoryNotificationConfiguration ()
{
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/Clear%E2%80%90RepositoryNotificationConfiguration")]
  param()
  log "Clearing notification configuration"
  try {
    $c = loadRepository
    if ($c[0] -eq $false) { return }
    $c[1].Notifications = $null
    writeRepositoryFile $c[1]
    Write-Host ("Ok.")
  }
  catch {
    err ("Failed to modify repository configuration: $($_.Exception.Message)")
  }

}



<#
.SYNOPSIS
    Get an object representing the current notification configuration
 
.DESCRIPTION
    This function retrieves the current notification configuration as an object.
 
    The directory must have been initialized via Initialize-Repository and notification configuration must have been defined via Set-RepositoryNotificationConfiguration
 
    The function must be invoked inside a directory initialized as a repository.
 
 
.LINK
    [Initialize-Repository](Initialize-Repository)
 
.LINK
    [Add-RepositoryFilter](Add-RepositoryFilter)
 
.LINK
    [Remove-RepositoryFilter](Remove-RepositoryFilter)
 
.LINK
    [Get-RepositoryInfo](Get-RepositoryInfo)
 
.LINK
    [Invoke-RepositorySync](Invoke-RepositorySync)
 
.LINK
    [Invoke-RepositoryCleanup](Invoke-RepositoryCleanup)
 
.LINK
    [Set-RepositoryNotificationConfiguration](Set-RepositoryNotificationConfiguration)
 
.LINK
    [Clear-RepositoryNotificationConfiguration](Clear-RepositoryNotificationConfiguration)
 
.LINK
    [Show-RepositoryNotificationConfiguration](Show-RepositoryNotificationConfiguration)
 
.LINK
    [Add-RepositorySyncFailureRecipient](Add-RepositorySyncFailureRecipient)
 
.LINK
    [Test-RepositoryNotificationConfiguration](Test-RepositoryNotificationConfiguration)
 
.EXAMPLE
    $config = Get-RepositoryNotificationConfiguration
 
 
#>

function Get-RepositoryNotificationConfiguration ()
{
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/Get%E2%80%90RepositoryNotificationConfiguration")]
  param()

  $c = loadRepository
  if ($c[0] -eq $false -or $c[1].Notifications -eq $null)
  {
    return $null
  }
  return $c[1].Notifications
}


<#
.SYNOPSIS
    Display the current notification configuration to screen
 
 
.DESCRIPTION
    This function retrieves the current notification configuration as user-friendly screen output.
 
    The directory must have been initialized via Initialize-Repository and notification configuration must have been defined via Set-RepositoryNotificationConfiguration
 
    The function must be invoked inside a directory initialized as a repository.
 
.LINK
    [Initialize-Repository](Initialize-Repository)
 
.LINK
    [Add-RepositoryFilter](Add-RepositoryFilter)
 
.LINK
    [Remove-RepositoryFilter](Remove-RepositoryFilter)
 
.LINK
    [Get-RepositoryInfo](Get-RepositoryInfo)
 
.LINK
    [Invoke-RepositorySync](Invoke-RepositorySync)
 
.LINK
    [Invoke-RepositoryCleanup](Invoke-RepositoryCleanup)
 
.LINK
    [Set-RepositoryNotificationConfiguration](Set-RepositoryNotificationConfiguration)
 
.LINK
    [Clear-RepositoryNotificationConfiguration](Clear-RepositoryNotificationConfiguration)
 
.LINK
    [Get-RepositoryNotificationConfiguration](Get-RepositoryNotificationConfiguration)
 
.LINK
    [Add-RepositorySyncFailureRecipient](Add-RepositorySyncFailureRecipient)
 
.LINK
    [Test-RepositoryNotificationConfiguration](Test-RepositoryNotificationConfiguration)
 
.EXAMPLE
    Show-RepositoryNotificationConfiguration
#>

function Show-RepositoryNotificationConfiguration ()
{
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/Show%E2%80%90RepositoryNotificationConfiguration")]
  param()

  try {
    $c = Get-RepositoryNotificationConfiguration
    if ($c -eq $null)
    {
      err ("Notifications are not configured.")
      return
    }

    if (![string]::IsNullOrEmpty($c.username)) {
      Write-Host "Notification server: smtp://$($c.username):<password-redacted>@$($c.server):$($c.port)"
    }
    else {
      Write-Host "Notification server: smtp://$($c.server):$($c.port)"
    }
    Write-Host "Email will arrive from $($c.from) with name `"$($c.fromname)`""

    if ($c.addresses -eq $null -or $c.addresses.Count -eq 0)
    {
      Write-Host "There are no recipients configured"
      return
    }
    foreach ($r in $c.addresses)
    {
      Write-Host "Recipient: $r"
    }
  }
  catch {
    err ("Failed to read repository configuration: $($_.Exception.Message)")
  }

}

<#
.SYNOPSIS
    Add a recipient to be notified of failures
 
.DESCRIPTION
    This function adds an email address to the repository. On failures, notifications will be sent to this email address.
 
    The directory must have been initialized via Initialize?Repository and notification configured via Set-RepositoryNotificationConfiguration.
 
    The function must be invoked inside a directory initialized as a repository.
 
.PARAMETER to
    The email address to add
 
.LINK
    [Initialize-Repository](Initialize-Repository)
 
.LINK
    [Add-RepositoryFilter](Add-RepositoryFilter)
 
.LINK
    [Remove-RepositoryFilter](Remove-RepositoryFilter)
 
.LINK
    [Get-RepositoryInfo](Get-RepositoryInfo)
 
.LINK
    [Invoke-RepositorySync](Invoke-RepositorySync)
 
.LINK
    [Invoke-RepositoryCleanup](Invoke-RepositoryCleanup)
 
.LINK
    [Set-RepositoryNotificationConfiguration](Set-RepositoryNotificationConfiguration)
 
.LINK
    [Clear-RepositoryNotificationConfiguration](Clear-RepositoryNotificationConfiguration)
 
.LINK
    [Get-RepositoryNotificationConfiguration](Get-RepositoryNotificationConfiguration)
 
.LINK
    [Show-RepositoryNotificationConfiguration](Show-RepositoryNotificationConfiguration)
 
.LINK
    [Remove-RepositorySyncFailureRecipient](Remove-RepositorySyncFailureRecipient)
 
.LINK
    [Test-RepositoryNotificationConfiguration](Test-RepositoryNotificationConfiguration)
 
.EXAMPLE
    Add-RepositorySyncFailureRecipient -to someone@mycompany.com
 
#>

function Add-RepositorySyncFailureRecipient ()
{
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/Add%E2%80%90RepositorySyncFailureRecipient")]
  param(
    [Parameter(Position = 0,Mandatory = $true)]
    [ValidatePattern("^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$")]
    [string]
    $to
  )

  log "Adding '$to' as a recipient."
  try {
    $c = loadRepository
    if ($c[0] -eq $false) { return }

    if ($c[1].Notifications -eq $null) {
      err ("Notifications are not configured")
      return
    }


    if ($c[1].Notifications.addresses -eq $null) {
      $c[1].Notifications.addresses = $()
    }


    $c[1].Notifications.addresses += $to.Trim()
    $c[1].Notifications.addresses = $c[1].Notifications.addresses | sort -Unique
    writeRepositoryFile ($c[1] | sort -Unique)
    Write-Host ("Ok.")
  }
  catch {
    err ("Failed to modify repository configuration: $($_.Exception.Message)")
  }

}

<#
.SYNOPSIS
    Remove a recipient from notification list for the current repository.
 
 
.DESCRIPTION
    This function removes an email address as a recipient for synchronization failure messages.
 
    The directory must have been initialized via Initialize?Repository and notification configured via Set-RepositoryNotificationConfiguration.
 
    The function must be invoked inside a directory initialized as a repository.
 
.PARAMETER to
    The email address to remove
 
.LINK
    [Initialize-Repository](Initialize-Repository)
 
.LINK
    [Add-RepositoryFilter](Add-RepositoryFilter)
 
.LINK
    [Remove-RepositoryFilter](Remove-RepositoryFilter)
 
.LINK
    [Get-RepositoryInfo](Get-RepositoryInfo)
 
.LINK
    [Invoke-RepositorySync](Invoke-RepositorySync)
 
.LINK
    [Invoke-RepositoryCleanup](Invoke-RepositoryCleanup)
 
.LINK
    [Set-RepositoryNotificationConfiguration](Set-RepositoryNotificationConfiguration)
 
.LINK
    [Clear-RepositoryNotificationConfiguration](Clear-RepositoryNotificationConfiguration)
 
.LINK
    [Get-RepositoryNotificationConfiguration](Get-RepositoryNotificationConfiguration)
 
.LINK
    [Show-RepositoryNotificationConfiguration](Show-RepositoryNotificationConfiguration)
 
.LINK
    [Remove-RepositorySyncFailureRecipient](Remove-RepositorySyncFailureRecipient)
 
.LINK
    [Test-RepositoryNotificationConfiguration](Test-RepositoryNotificationConfiguration)
 
.EXAMPLE
    Remove-RepositorySyncFailureRecipient -to someone@mycompany.com
 
#>

function Remove-RepositorySyncFailureRecipient
{
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/Remove%E2%80%90RepositorySyncFailureRecipient")]
  param(
    [Parameter(Position = 0,Mandatory = $true)]
    [ValidatePattern("^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$")]
    [string]
    $to
  )
  log "Removing '$to' as a recipient."
  try {
    $c = loadRepository
    if ($c[0] -eq $false) { return }

    if ($c[1].Notifications -eq $null) {
      err ("Notifications are not configured")
      return
    }


    if ($c[1].Notifications.addresses -eq $null) {
      $c[1].Notifications.addresses = $()
    }

    $c[1].Notifications.addresses = $c[1].Notifications.addresses | Where-Object { $_ -ne $to.Trim() } | sort -Unique
    writeRepositoryFile ($c[1] | sort -Unique)
    Write-Host ("Ok.")
  }
  catch {
    err ("Failed to modify repository configuration: $($_.Exception.Message)")
  }
}


<#
.SYNOPSIS
    Test the email notification configuration by sending a test email
 
.DESCRIPTION
    This function sends a test email using the current repository configuration and reports
    any errors associated with the send process. It is intended for debugging the email server configuration.
 
.LINK
    [Initialize-Repository](Initialize-Repository)
 
.LINK
    [Add-RepositoryFilter](Add-RepositoryFilter)
 
.LINK
    [Remove-RepositoryFilter](Remove-RepositoryFilter)
 
.LINK
    [Get-RepositoryInfo](Get-RepositoryInfo)
 
.LINK
    [Invoke-RepositorySync](Invoke-RepositorySync)
 
.LINK
    [Invoke-RepositoryCleanup](Invoke-RepositoryCleanup)
 
.LINK
    [Set-RepositoryNotificationConfiguration](Set-RepositoryNotificationConfiguration)
 
.LINK
    [Clear-RepositoryNotificationConfiguration](Clear-RepositoryNotificationConfiguration)
 
.LINK
    [Get-RepositoryNotificationConfiguration](Get-RepositoryNotificationConfiguration)
 
.LINK
    [Show-RepositoryNotificationConfiguration](Show-RepositoryNotificationConfiguration)
 
.LINK
    [Remove-RepositorySyncFailureRecipient](Remove-RepositorySyncFailureRecipient)
 
.EXAMPLE
    Test-RepositoryNotificationConfiguration
 
#>

function Test-RepositoryNotificationConfiguration
{
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/Test%E2%80%90RepositoryNotificationConfiguration")]
  param()

  log ("test email started")
  send "Repository Failure Notification (Test only)" "No content." -html $false
  Write-Host ("Ok.")
}

<#
.SYNOPSIS
    Set repository configuration values
 
.DESCRIPTION
    This function sets various configuration options that control synchronization behavior.
 
.PARAMETER setting
    The setting to configure from 'OnRemoteFileNotFound' and 'OfflineCacheMode'.
 
.PARAMETER value
    The new value of the setting for OnRemoteFileNotFound. It can be from 'Fail' (default) and 'LogAndContinue'.
 
.PARAMETER cachevalue
  The new cachevalue of the setting for OfflineCacheMode. It can be from 'Disable' (default) and 'Enable'.
 
.LINK
  [Initialize-Repository](Initialize-Repository)
   
.LINK
  [Get-RepositoryConfiguration](Get-RepositoryConfiguration)
     
.NOTES
    Current event handlers supported:
 
  - _OnRemoteFileNotFound_ - indicates what should happen if an expected softpaq is not found
  on the remote site. The default is 'Fail' in which case the process will stop. Setting to 'LogAndContinue'
  will log the error, but the repository synchronization will continue.
 
 
#>

function Set-RepositoryConfiguration
{
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/Set%E2%80%90RepositoryConfiguration")]
  param(
    [ValidateSet('OnRemoteFileNotFound','OfflineCacheMode')]
    [Parameter(ParameterSetName = "ErrorHandler",Position = 0,Mandatory = $true)]
    [Parameter(ParameterSetName = "CacheMode",Position = 0,Mandatory = $true)]
    [string]$setting,

    [Parameter(ParameterSetName = "ErrorHandler",Position = 1,Mandatory = $true)]
    [ErrorHandling]$value,

    [ValidateSet('Enable','Disable')]
    [Parameter(ParameterSetName = "CacheMode",Position = 1,Mandatory = $true)]
    [string]$cachevalue
  )
  $c = loadRepository
  if ($c[0] -eq $false) { return }
  if ($setting -eq "OnRemoteFileNotFound") {
    if(($value -eq "Fail") -or ($value -eq "LogAndContinue")){
      $c[1].Settings. "${setting}" = $value
      writeRepositoryFile $c[1]
      Write-Host ("Ok.")
    }
    else {
      Write-Host -ForegroundColor Magenta "Enter valid value for $setting."
    }
  }
  elseif ($setting -eq "OfflineCacheMode") {
    if ($cachevalue) {
      $c[1].Settings. "${setting}" = $cachevalue
      writeRepositoryFile $c[1]
      Write-Host ("Ok.")
    }
    else {
      Write-Host -ForegroundColor Magenta "Enter valid cachevalue for $setting."
    }
  }
}


<#
.SYNOPSIS
    Get repository configuration values
 
.DESCRIPTION
    This function get various configuration options that control synchronization behavior.
 
.PARAMETER setting
    The setting to retrieve
 
.LINK
    [Set-RepositoryConfiguration](Set-RepositoryConfiguration)
 
.LINK
  [Initialize-Repository](Initialize-Repository)
 
 
#>

function Get-RepositoryConfiguration
{
  [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/Get%E2%80%90RepositoryConfiguration")]
  param(
    [Parameter(Position = 0,Mandatory = $true)]
    [string]
    [ValidateSet('OnRemoteFileNotFound','OfflineCacheMode')]
    $setting
  )

  $c = loadRepository
  if ($c[0] -eq $false) { return }
  $c[1].Settings. "${setting}"
}



Export-ModuleMember -Function 'Add-*'
Export-ModuleMember -Function 'Get-*'
Export-ModuleMember -Function 'Set-*'
Export-ModuleMember -Function 'Invoke-*'
Export-ModuleMember -Function 'Remove-*'
Export-ModuleMember -Function 'Show-*'
Export-ModuleMember -Function 'Clear-*'
Export-ModuleMember -Function 'Initialize-*'
Export-ModuleMember -Function 'Test-*'




# SIG # Begin signature block
# MIIcOAYJKoZIhvcNAQcCoIIcKTCCHCUCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCX1cfR3cb2IA0P
# QqFTMPpQv+Lnk128K16Fkj4qm6lGfqCCCo0wggU2MIIEHqADAgECAhAM1s71mz4i
# 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
# hkiG9w0BCQQxIgQgE6/fQITfv4ncwJvZnm81N8d+0SbtDS2etI2loQT+LlwwDQYJ
# KoZIhvcNAQEBBQAEggEAfQh11SglZndXMd5m/Mig6gXPYwcQxsjluXXPh4jkPtxh
# 02Asb1qRbvw6h4Z+K4oOYBb45ng4B/hQQQRwZrWSiAPn3sSwRMkWh/xQvalEQZyL
# 4ejSlQ4icWu0Byrh/WPqWsnOcnDyhiQGZFdNfeWn3idRxLCfv4wHMzg1LvMoPs+w
# N4KJB7L9wfrJCE/aves3AP51NvHWsrjw0YlpyA8rM8K5b82KuTi1BJ3cgFuTXQA/
# 2uMrMm3EHF5zTzG7b56WUD/NCEOkMyX0BfEMtgb2N5ZpHjNB8T+kBO+6c670azBy
# QAhDbmISl1HEXLNbLmXQbeLZNy/gEuaQG4yumXe5oaGCDskwgg7FBgorBgEEAYI3
# AwMBMYIOtTCCDrEGCSqGSIb3DQEHAqCCDqIwgg6eAgEDMQ8wDQYJYIZIAWUDBAIB
# BQAweAYLKoZIhvcNAQkQAQSgaQRnMGUCAQEGCWCGSAGG/WwHATAxMA0GCWCGSAFl
# AwQCAQUABCC892D1XiS0Vdu7KlEelIXEj9EzO1tAdTtp7cttXukrJQIRANrAmDtp
# 8O99+aHIik9VMxEYDzIwMjAwMjE0MjI1NDA3WqCCC7swggaCMIIFaqADAgECAhAE
# 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
# BgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMjAwMjE0MjI1NDA3WjArBgsq
# hkiG9w0BCRACDDEcMBowGDAWBBQDJb1QXtqWMC3CL0+gHkwovig0xTAvBgkqhkiG
# 9w0BCQQxIgQgIt7DK02Ot8cwZNcEwPP4CjcN0zVzahuQxrunsB4DfGAwDQYJKoZI
# hvcNAQEBBQAEggEApAvGx7Xq1GS7KzZ0/ktukbLIpTJIVjkMJcbE5V3FTBNgn70c
# bHO6pVuQSoiOi2stypmX37+io+kGw/2QpKWTOeP5imsOjW+ec7PJGfks8lbk6mO6
# t3jo4avv5g40ddUGI1cFxisu0i1ewRpk7WR0ioMT2hTXdGH0q/6tAE4e1VtLthFi
# SJZeXyI2eC47gWbLLctbJ62T5c/xDN/jRdjYayXYE4q4lAc4MNJBImNHzlXU46HV
# hairUSZ6mo02I55AqpwXZWO0BLO/W2Q3hgQO+ZXdGuHxoFMVmlBJQ6E7kUZQhA6Q
# sBo8uyoPgR4yE47FLuf9ao96UqLFRMYGGv9KYw==
# SIG # End signature block