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 |