HP.Repo.psm1
# # Copyright 2018-2021 HP Development Company, L.P. # All Rights Reserved. # # NOTICE: All information contained herein is, and remains the property of HP Development Company, L.P. # # The intellectual and technical concepts contained herein are proprietary to HP Development Company, L.P # and may be covered by U.S. and Foreign Patents, patents in process, and are protected by # trade secret or copyright law. Dissemination of this information or reproduction of this material # is strictly forbidden unless prior written permission is obtained from HP Development Company, L.P. Set-StrictMode -Version 5.1 #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 RepositoryReport; } 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 # print a bare error function err { [CmdletBinding()] param( [string]$str, [boolean]$withLog = $true ) [console]::ForegroundColor = 'red' [console]::Error.WriteLine($str) [console]::ResetColor() if ($withLog) { Write-LogError -Message $str -Component "HP.Repo" -File $LOGFILE } } # convert a date object to an 8601 string function ISO8601DateString { [CmdletBinding()] param( [DateTime]$Date ) $Date.ToString("yyyy-MM-dd'T'HH:mm:ss.fffffff",[System.Globalization.CultureInfo]::InvariantCulture) } # get current user name function GetUserName () { [CmdletBinding()] param() try { [System.Security.Principal.WindowsIdentity]::GetCurrent().Name } catch { return $env:username } } # check if a file exists function FileExists { [CmdletBinding()] param( [string]$File ) Test-Path $File -PathType Leaf } # load a json object function LoadJson { [CmdletBinding()] param( [string]$File ) try { $PS7Mark = "PS7Mark" $rawData = (Get-Content -Raw -Path $File) -replace '("DateLastModified": ")([^"]+)(")', ('$1' + $PS7Mark + '$2' + $PS7Mark + '$3') [SoftpaqRepositoryFile]$result = $rawData | ConvertFrom-Json $result.DateLastModified = $result.DateLastModified -replace $PS7Mark, "" return $result } catch { err ("Could not parse '$File' $($_.Exception.Message)") return $Null } } # load a repository definition file function LoadRepository { [CmdletBinding()] param() Write-Verbose "loading $REPOFILE" $inRepo = FileExists -File $REPOFILE if (-not $inRepo) { throw [System.Management.Automation.ItemNotFoundException]"Directory '$(Get-Location)' is not a repository." } $repo = LoadJson -File $REPOFILE if (-not $repo -eq $null) { err ("Could not initialize the repository: $($_.Exception.Message)") return $false,$null } if (-not $repo.Filters) { $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" } if (-not $repo.Settings.RepositoryReport) { $repo.Settings.RepositoryReport = "CSV" } foreach ($filter in $repo.Filters) { if (-not $filter.characteristic ) { $filter.characteristic = "*" } } if (-not $repo.Notifications ) { $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 { [CmdletBinding()] param( $DownloadSoftpaqCmd, [int]$maxRetries = 10 ) $download_file = $true $filename = "sp" + $DownloadSoftpaqCmd.number + ".exe" $CVAname = "sp" + $DownloadSoftpaqCmd.number + ".cva" # downloading the CVA Write-Verbose ("Downloading CVA $($DownloadSoftpaqCmd.number)") Log (" sp$($DownloadSoftpaqCmd.number).cva - Downloading CVA file.") Get-SoftpaqMetadataFile @DownloadSoftpaqCmd -MaxRetries $maxRetries Log (" sp$($DownloadSoftpaqCmd.number).cva - Done downloading CVA file.") if (FileExists -File $filename) { Write-Verbose "Checking signature for existing file $filename" if (Get-HPPrivateCheckSignature -File $filename -CVAfile $CVAname -Verbose:$VerbosePreference -progress:(-not $DownloadSoftpaqCmd.Quiet)) { if (-not $DownloadSoftpaqCmd.Quiet) { Write-Host -ForegroundColor Magenta "File $filename already exists and passes signature check. Will not redownload." } Log (" sp$($DownloadSoftpaqCmd.number).exe - Already exists. Will not redownload.") $download_file = $false } else { Write-Verbose ("Need to redownload file '$filename'") } } else { Write-Verbose ("Need to download file '$filename'") } if ($download_file -eq $true) { try { Log (" sp$($DownloadSoftpaqCmd.number).exe - Downloading EXE file.") Get-Softpaq @DownloadSoftpaqCmd -MaxRetries $maxRetries -Overwrite yes # check post-download integrity if (-not (Get-HPPrivateCheckSignature -File $filename -CVAfile $CVAname -Verbose:$VerbosePreference -progress:(-not $DownloadSoftpaqCmd.Quiet))) { Remove-Item -Path $filename -Force -Verbose:$VerbosePreference Remove-Item -Path $CVAName -Force -Verbose:$VerbosePreference $msg = "File $filename failed integrity check and has been deleted, will retry download next sync" if (-not $DownloadSoftpaqCmd.Quiet) { Write-Host -ForegroundColor Magenta $msg } Write-LogWarning -Message $msg -Component "HP.Repo" -File $LOGFILE } Log (" sp$($DownloadSoftpaqCmd.number).exe - Done downloading EXE file.") } catch { Write-Host -ForegroundColor Magenta "File sp$($DownloadSoftpaqCmd.number).exe has invalid or missing signature and will be deleted." Log (" sp$($DownloadSoftpaqCmd.number).exe has invalid or missing signature and will be deleted.") Log (" sp$($DownloadSoftpaqCmd.number).exe - Redownloading EXE file.") Get-Softpaq @DownloadSoftpaqCmd -MaxRetries $maxRetries Log (" sp$($DownloadSoftpaqCmd.number).exe - Done downloading EXE file.") } } } # write a repository definition file function WriteRepositoryFile { [CmdletBinding()] param($obj) $now = Get-Date $obj.DateLastModified = ISO8601DateString -Date $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 { [CmdletBinding()] param($repo, $f) $c = getFilters $repo $f return ($null -ne $c) } # get a list of filters in a repo, matching exact parameters function GetFilters { [CmdletBinding()] param($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 -and $_.characteristic -eq $f.characteristic } } # get a list of filters in a repo, considering empty parameters as wildcards function GetFiltersWild { [CmdletBinding()] param($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 { [CmdletBinding()] param([string[]]$entryText) foreach ($line in $entryText) { if (-not $line) { $line = " " } Write-LogInfo -Message $line -Component "HP.Repo" -File $LOGFILE } } # touch a file (change its date if exists, or create it if it doesn't. function TouchFile { [CmdletBinding()] param([string] $File) if (Test-Path $File) { (Get-ChildItem $File).LastWriteTime = Get-Date } else { Write-Output $null > $File } } # remove all marks from the repository function FlushMarks { [CmdletBinding()] param() Write-Verbose "Removing all marks" Remove-Item ".repository\mark\*" -Include "*.mark" } # send a notification email function Send { [CmdletBinding()] param( $subject, $body, $html = $true ) $n = Get-RepositoryNotificationConfiguration if ((-not $n) -or (-not $n.server)) { Write-Verbose ("Notifications are not configured") return } try { if ((-not $n.addresses) -or (-not $n.addresses.Count)) { 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 -AsPlainText -Force $params.Credential = New-Object System.Management.Automation.PSCredential ($n.username,$read) if (-not $params.Credential) { 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 un-initalize 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 -File $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.Settings.RepositoryReport = "CSV" $newRepositoryFile.DateCreated = ISO8601DateString -Date $now $newRepositoryFile.CreatedBy = GetUserName try { New-Item -ItemType directory -Path .repository | Out-Null WriteRepositoryFile -obj $newRepositoryFile New-Item -ItemType directory -Path ".repository/mark" | Out-Null } catch { err ("Could not initialize the repository: $($_.Exception.Message)") return } Log "Repository initialized successfully." } <# .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 OS Version (e.g. 1709, 1803, 2009 etc). Starting from 21H1 release, "xxHx" format is expected. If the parameter is not specified, current operating system version 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", "UWPPack". 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", "uwp". If this parameter is not specified, all characteristics are included. .EXAMPLE Add-RepositoryFilter -Platform 1234 -Os win10 -OsVer 2009 .EXAMPLE Add-RepositoryFilter -Platform 1234 -Os win10 -OsVer "21H1" .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 = "*", [Parameter(Position = 1)] [string]$OsVer, [ValidateSet("Bios","Firmware","Driver","Software","Os","Manageability","Diagnostic","Utility","Driverpack","Dock","UWPPack","*")] # 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","UWP","*")] # keep in sync with the softpaq module [string[]] [Parameter(Position = 4)] $Characteristic = "*" ) $c = LoadRepository try { 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 ($OsVer) { $OsVer = $OsVer.ToLower() } 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 -obj $repo if($OsVer -and $Os -ne '*') { Log "Added filter $Platform {{ os='$Os', osver='$OsVer', category='$Category', release='$ReleaseType', characteristic='$Characteristic' }}" } else {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-Verbose "Repository Filter Added." } 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 OS Version (e.g. 1709, 1803, 2009 etc). If the parameter is not specified all windows 10 filters will match. Starting from 21H1 release, "xxHx" format is expected. 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. .PARAMETER Yes An optional parameter to provide. If not specified, script will ask for confirmation before deleting a filter. If specified, script will go ahead and delete the filter without confirming. .EXAMPLE Remove-RepositoryFilter -Platform 1234 .EXAMPLE Remove-RepositoryFilter -Platform 1234 -Os win10 -OsVer "21H1" .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 = "*", [Parameter(Position = 1)] [string]$OsVer, [ValidateSet("Bios","Firmware","Driver","Software","Os","Manageability","Diagnostic","Utility","Driverpack","Dock","UWPPack","*")] # 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","UWP","*")] # keep in sync with the softpaq module [string[]] [Parameter(Position = 5)] $Characteristic = "*" ) $c = LoadRepository try { 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 (-not $todelete) { Write-Verbose ("No matching filter to delete") return } if (-not $Yes.IsPresent) { 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 -obj $c[1] 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() $c = LoadRepository try { if (-not $c[0]) { return } $c[1] } catch { err ("Could not get repository info: $($_.Exception.Message)") } } <# .SYNOPSIS Synchronize repository and generate a report with repository content information .DESCRIPTION This command performs a synchronization of a repository, by downloading latest SoftPaqs associated with the repository filters. At the end of each sync, it create a repository report in a format (default .CSV) set via Set-RepositoryConfiguration. 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 ) $repo = LoadRepository try { $cwd = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath((Get-Location)) $cacheDir = Join-Path -Path $cwd -ChildPath ".repository" $cacheDirOffline = $cacheDir + "\cache\offline" $reportDir = $cacheDir # return if repository is not initialized if ($repo[0] -eq $false) { return } # return if repository is initialized but no filters added $filters = $repo[1].Filters if ($filters.Count -eq 0) { Write-Verbose "Repository has no filters defined, terminating." 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 = "*" } } $normalized += $items | sort -Unique -Property operatingSystem,category,releaseType,characteristic } $softpaqlist = @() Log "Repository sync has started" $softpaqListCmd = @{} # build the list of SoftPaqs to download foreach ($c in $normalized) { Write-Verbose ($c | Format-List | Out-String) if (Get-HPDeviceDetails -Platform $c.platform) { $softpaqListCmd.platform = $c.platform.ToLower() $softpaqListCmd.Quiet = $Quiet $softpaqListCmd.verbose = $VerbosePreference Write-Verbose ("Working on a rule for platform $($softpaqListCmd.platform)") if ($c.operatingSystem.startsWith("win10:")) { $split = $c.operatingSystem -split ':' $softpaqListCmd.os = $split[0] $softpaqListCmd.osver = $split[1] } elseif ($c.operatingSystem -eq "win10") { $softpaqListCmd.os = "win10" $softpaqListCmd.osver = GetCurrentOSVer } elseif ($c.operatingSystem -ne "*") { $softpaqListCmd.os = $c.operatingSystem #$softpaqListCmd.osver = $null } if ($c.characteristic -ne "*") { $softpaqListCmd.characteristic = $c.characteristic.ToUpper().Split() Write-Verbose "Filter-characteristic:$($softpaqListCmd.characteristic)" } if ($c.releaseType -ne "*") { $softpaqListCmd.releaseType = $c.releaseType.Split() Write-Verbose "Filter-releaseType:$($softpaqListCmd.releaseType)" } if ($c.category -ne "*") { $softpaqListCmd.category = $c.category.Split() Write-Verbose "Filter-category:$($softpaqListCmd.category)" } Log "Reading the softpaq list for platform $($softpaqListCmd.platform)" $results = Get-SoftpaqList @softpaqListCmd -cacheDir $cacheDir -MaxRetries $repo[1].Settings.ExclusiveLockMaxRetries Log "softpaq list for platform $($softpaqListCmd.platform) created" $softpaqlist += $results $OfflineCacheMode = $repo[1].Settings.OfflineCacheMode if ($OfflineCacheMode -eq "Enable") { # keep the download order of PlatformList, Advisory data and Knowledge Base as is to maintain unit tests $baseurl = "https://hpia.hpcloud.hp.com/ref/" $url = $baseurl + "platformList.cab" $filename = "platformList.cab" Write-Verbose "Trying to download PlatformList..." try { $PlatformList = Get-HPPrivateOfflineCacheFiles -url $url -FileName $filename -cacheDirOffline $cacheDirOffline -Expand Write-Verbose "Finish downloading PlatformList - $PlatformList" } catch { Write-Verbose "Trying to download PlatformList from FTP..." $url = "https://ftp.hp.com/pub/caps-softpaq/cmit/imagepal/ref/platformList.cab" $PlatformList = Get-HPPrivateOfflineCacheFiles -url $url -FileName $filename -cacheDirOffline $cacheDirOffline -Expand if (-not $PlatformList) { $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 } } } } # download Advisory data $url = $baseurl + "$($softpaqListCmd.platform)/$($softpaqListCmd.platform)_cds.cab" $cacheDirAdvisory = $cacheDirOffline + "\$($softpaqListCmd.platform)" $filename = "$($softpaqListCmd.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 { Write-Verbose "Trying to download AdvisoryData from FTP..." $baseurl = "https://ftp.hp.com/pub/caps-softpaq/cmit/imagepal/ref/" $url = $baseurl + "$($softpaqListCmd.platform)/$($softpaqListCmd.platform)_cds.cab" #$cacheDirAdvisory = $cacheDirOffline + "\$($softpaqListCmd.platform)" #$filename = "$($softpaqListCmd.platform)_cds.cab" $AdvisoryFile = Get-HPPrivateOfflineCacheFiles -url $url -FileName $filename -cacheDirOffline $cacheDirAdvisory -Expand Write-Verbose "Finish downloading Advisory Data Files - $AdvisoryFile" if (-not $AdvisoryFile) { $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 } } } } # download Knowledge Base $url = "https://hpia.hpcloud.hp.com/kb/common/latest.cab" $cacheDirKb = $cacheDirOffline + "\kb\common" $filename = "latest.cab" Write-Verbose "Trying to download Knowledge Base..." try { $KnowledgeBase = Get-HPPrivateOfflineCacheFiles -url $url -FileName $filename -cacheDirOffline $cacheDirKb Write-Verbose "Finish downloading Knowledge Base - $KnowledgeBase" } catch { Write-Verbose "Trying to download Knowledge Base from FTP..." $url = "https://ftp.hp.com/pub/caps-softpaq/cmit/imagepal/kb/common/latest.cab" #$cacheDirKb = $cacheDirOffline + "\kb\common" #$filename = "latest.cab" $KnowledgeBase = Get-HPPrivateOfflineCacheFiles -url $url -FileName $filename -cacheDirOffline $cacheDirKb Write-Verbose "Finish downloading Knowledge Base - $KnowledgeBase" if (-not $KnowledgeBase) { $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 } } } } } } else { Write-Host -ForegroundColor Cyan "Platform $($c.platform) doesn't exist. Please add a valid platform." Write-LogWarning "Platform $($c.platform) in not valid, and is was skipped." } } Write-Verbose ("Done with the list, repository is $($softpaqlist.Count) softpaqs.") [array]$softpaqlist = @($softpaqlist | Sort-Object -Unique -Property Id) Write-Verbose ("After trimming duplicates, we have $($softpaqlist.Count) softpaqs.") Write-Verbose ("Flushing the list of markers") FlushMarks Write-Verbose ("Writing new marks") # generate .mark file for each SoftPaq to be downloaded foreach ($sp in $softpaqList) { $number = $sp.id.ToLower().TrimStart("sp") TouchFile -File ".repository/mark/$number.mark" } Write-Verbose ("Starting download") $downloadCmd = @{} $downloadCmd.Quiet = $quiet $downloadCmd.verbose = $VerbosePreference Log "Download has started for $($softpaqlist.Count) softpaqs." foreach ($sp in $softpaqlist) { $downloadCmd.number = $sp.id.ToLower().TrimStart("sp") Write-Verbose "Working on data for softpaq $($downloadCmd.number)" try { log "Start downloading files for sp$($downloadCmd.number)." DownloadSoftpaq $downloadCmd -MaxRetries $repo[1].Settings.ExclusiveLockMaxRetries -Verbose:$VerbosePreference if ($OfflineCacheMode -eq "Enable") { log (" sp$($downloadCmd.number).html - Downloading Release Notes.") $ReleaseNotesurl = Get-HPPrivateItemUrl $downloadCmd.number "html" $target = "sp$($downloadCmd.number).html" $targetfile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($target) Invoke-HPPrivateDownloadFile -url $ReleaseNotesurl -Target $targetfile log (" sp$($downloadCmd.number).html - Done Downloading Release Notes.") } log "Finish downloading files for sp$($downloadCmd.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-Verbose "Repository Sync has ended." Log "Repository Report creation started" Write-Verbose "Repository Report creation started." try { # get the configuration set for repository report if any $RepositoryReport = $repo[1].Settings.RepositoryReport if ($RepositoryReport) { $Format = $RepositoryReport New-RepositoryReport -Format $Format -RepositoryPath "$cwd" -OutputFile "$cwd\.repository\Contents.$Format" Log "Repository Report created as Contents.$Format" Write-Verbose "Repository Report created as Content.$Format." } } catch [System.IO.FileNotFoundException] { Write-Verbose "No data available to create Repository Report as directory '$(Get-Location)' does not contain any CVA files." Log "No data available to create Repository Report as directory '$(Get-Location)' does not contain any CVA files." } catch { Write-Verbose "Error in creating Repository Report" Log "Error in creating Repository Report." } } catch { 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() $repo = LoadRepository log ("Beginning repository cleanup") $deleted = 0 try { Get-ChildItem "." -File | ForEach-Object { $name = $_.Name.ToLower().TrimStart("sp").Split('.')[0] if ($name -ne $null) { if (-not (Test-Path ".repository/mark/$name.mark" -PathType Leaf)) { Write-Verbose "Deleting orphaned file $($_.Name)" Remove-Item $_.Name $deleted++ } #else { # Write-Verbose "Softpaq $($_.Name) is still needed." #} } } log ("Completed repository cleanup, deleted $deleted files.") } 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 UserName 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 } $c = LoadRepository try { if (-not $c[0]) { 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) { Write-Verbose ("Setting SMTP Server port to: $Port") $c[1].Notifications.port = $Port } if (-not [string]::IsNullOrEmpty($UserName)) { Write-Verbose ("Setting SMTP server credential(username) to: $UserName") $c[1].Notifications.username = $UserName } if (-not [string]::IsNullOrEmpty($Password) ) { Write-Verbose ("Setting SMTP server credential(password) to: (redacted)") $c[1].Notifications.password = ConvertTo-SecureString $Password -Force -asPlainText | ConvertFrom-SecureString } if ($RemoveCredentials.IsPresent) { 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 auto-calculated 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 (-not [string]::IsNullOrEmpty($From)) { Write-Verbose ("Setting Mail from address to: $From") $c[1].Notifications.from = $From } if (-not [string]::IsNullOrEmpty($FromName)) { Write-Verbose ("Setting Mail from displayname to: $FromName") $c[1].Notifications.fromname = $FromName } WriteRepositoryFile -obj $c[1] 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" $c = LoadRepository try { if (-not $c[0]) { return } $c[1].Notifications = $null WriteRepositoryFile -obj $c[1] Write-Verbose ("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 ((-not $c[0]) -or (-not $c[1].Notifications)) { 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 (-not $c) { err ("Notifications are not configured.") return } if (-not [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 ((-not $c.addresses) -or (-not $c.addresses.Count)) { 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." $c = LoadRepository try { if (-not $c[0]) { return } if (-not $c[1].Notifications) { err ("Notifications are not configured") return } if (-not $c[1].Notifications.addresses) { $c[1].Notifications.addresses = $() } $c[1].Notifications.addresses += $To.Trim() $c[1].Notifications.addresses = $c[1].Notifications.addresses | Sort-Object -Unique WriteRepositoryFile -obj ($c[1] | Sort-Object -Unique) } 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." $c = LoadRepository try { if ($c[0] -eq $false) { return } if (-not $c[1].Notifications) { err ("Notifications are not configured") return } if (-not $c[1].Notifications.addresses) { $c[1].Notifications.addresses = $() } $c[1].Notifications.addresses = $c[1].Notifications.addresses | Where-Object { $_ -ne $To.Trim() } | Sort-Object -Unique WriteRepositoryFile -obj ($c[1] | Sort-Object -Unique) } 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-Verbose ("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', 'OfflineCacheMode' and 'RepositoryReport'. .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'. .PARAMETER Format The new value of the setting for RepositoryReport. It can be from 'CSV' (Default) ,'JSon', 'XML' and 'ExcelCSV'. .LINK [Initialize-Repository](Initialize-Repository) .LINK [Get-RepositoryConfiguration](Get-RepositoryConfiguration) .Example Set-RepositoryConfiguration -Setting OnRemoteFileNotFound -Value LogAndContinue .Example Set-RepositoryConfiguration -Setting OfflineCacheMode -CacheValue Enable .Example Set-RepositoryConfiguration -Setting RepositoryReport -Format CSV .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. Current Repository Report functionality: Invoke-RepositorySync will create a report containing contents of the Repository in a format provided by RepositoryFormat Setting. Use RepositoryReport Setting with -Format to set the format in which the you would like the report to be generated. Default format is CSV. Report will be generated with a name Contents.<format> e.g. Contents.CSV inside the .repository directory To create a report outside the repository use New-RepositoryReport function. #> function Set-RepositoryConfiguration { [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/Set%E2%80%90RepositoryConfiguration")] param( [ValidateSet('OnRemoteFileNotFound','OfflineCacheMode','RepositoryReport')] [Parameter(ParameterSetName = "ErrorHandler",Position = 0,Mandatory = $true)] [Parameter(ParameterSetName = "CacheMode",Position = 0,Mandatory = $true)] [Parameter(ParameterSetName = "ReportHandler",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, [ValidateSet('CSV','JSon','XML','ExcelCSV')] [Parameter(ParameterSetName = "ReportHandler",Position = 1,Mandatory = $true)] [string]$Format ) $c = LoadRepository if (-not $c[0]) { return } if ($Setting -eq "OnRemoteFileNotFound") { if (($Value -eq "Fail") -or ($Value -eq "LogAndContinue")) { $c[1].Settings. "${Setting}" = $Value WriteRepositoryFile -obj $c[1] Write-Verbose ("Ok.") } else { Write-Host -ForegroundColor Magenta "Enter valid Value for $Setting." Write-LogWarning "Enter valid Value for $Setting." } } elseif ($Setting -eq "OfflineCacheMode") { if ($CacheValue) { $c[1].Settings. "${Setting}" = $CacheValue WriteRepositoryFile -obj $c[1] Write-Verbose ("Ok.") } else { Write-Host -ForegroundColor Magenta "Enter valid CacheValue for $Setting." Write-LogWarning "Enter valid CacheValue for $Setting." } } elseif ($Setting -eq "RepositoryReport") { if($Format) { $c[1].Settings. "${Setting}" = $Format WriteRepositoryFile -obj $c[1] Write-Verbose ("Ok.") } else { Write-Host -ForegroundColor Magenta "Enter valid Format for $Setting." Write-LogWarning "Enter valid Format for $Setting." } } } <# .SYNOPSIS Get repository configuration values .DESCRIPTION This function get various configuration options that control synchronization behavior. .PARAMETER setting The setting to retrieve .Example Get-RepositoryConfiguration -Setting OfflineCacheMode .Example Get-RepositoryConfiguration -Setting OnRemoteFileNotFound .Example Get-RepositoryConfiguration -Setting RepositoryReport .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','RepositoryReport')] $Setting ) $c = LoadRepository if (-not $c[0]) { return } $c[1].Settings. "${Setting}" } <# .SYNOPSIS Create a report from a repository directory .DESCRIPTION This function scans a repository (or any directory containing CVAs and EXEs) and creates a report in one of the supported formats. Currently the supported formats are: - XML - Return an XML object - JSON - Return a JSON document - CSV - Return a CSV document - ExcelCSV - Return a CSV document containing an Excel hint that defines comma as a default separator. Use this only if you plan on opening the CSV file with excel. If a format is not specified, the function will return PowerShell objects to the pipeline. .PARAMETER Format Specify the output format (CSV, JSON, or XML). If not specified, the function will return PowerShell objects. .PARAMETER RepositoryPath By default, the function assumes the repository is in the current directory. This parameter allows specifying a different location for the repository. .PARAMETER OutputFile Specify an output file for the function. Can only be specified together with "Format". .EXAMPLE New-RepositoryReport -Format JSON -RepositoryPath c:\myrepository\softpaqs -OutputFile c:\repository\today.json .NOTES The function currently supports scenarios where the Softpaq executable is stored under the format sp<softpaq-number>.exe. #> function New-RepositoryReport { [CmdletBinding(HelpUri = "https://developers.hp.com/hp-client-management/doc/New%E2%80%90RepositoryReport")] param( [Parameter(Position = 0,Mandatory = $false)] [ValidateSet('CSV','JSon','XML','ExcelCSV')] [string]$Format, [Parameter(Position = 1,Mandatory = $false)] [System.IO.DirectoryInfo]$RepositoryPath = '.', [Parameter(Position = 2,Mandatory = $false)] [System.IO.FileInfo]$OutputFile ) if ($OutputFile -and -not $format) { throw "OutputFile parameter requires a Format specifier" } $cvaList = Get-ChildItem -Path $RepositoryPath -Filter '*.cva' if (-not $cvaList -or -not $cvaList.Length) { throw [System.IO.FileNotFoundException]"Directory '$(Get-Location)' does not contain CVA files." } Write-Verbose "Processing $($cvaList.Length) CVAs" $results = $cvaList | ForEach-Object { $cva = Get-HPPrivateReadINI $_.FullName try { $exe = Get-ChildItem -Path ($cva.Softpaq.SoftpaqNumber.Trim() + ".exe") -ErrorAction stop } catch [System.Management.Automation.ItemNotFoundException]{ $exe = $null } [PSCustomObject]@{ Softpaq = $cva.Softpaq.SoftpaqNumber Vendor = $cva.General.VendorName Title = $cva. "Software Title".US Type = if ($Cva.General.category.Contains("-")) { $Cva.General.category.Substring(0,$Cva.General.category.IndexOf('-')).Trim() } else { $Cva.General.category } Version = "$($cva.General.Version) Rev.$($cva.General.Revision)" Downloaded = if ($exe) { $exe.CreationTime } else { "" } Size = if ($exe) { "$($exe.Length)" } else { "" } } } switch ($format) { "CSV" { $r = $results | ConvertTo-Csv -NoTypeInformation } "ExcelCSV" { $r = $results | ConvertTo-Csv -NoTypeInformation $r = [string[]]"sep=," + $r } "JSon" { $r = $results | ConvertTo-Json } "XML" { $r = $results | ConvertTo-Xml -NoTypeInformation } default { return $results } } if ($OutputFile) { if ($format -eq "xml") { $r = $r.OuterXML } $r | Out-File -FilePath $OutputFile -Encoding utf8 } else { $r } } # SIG # Begin signature block # MIIakgYJKoZIhvcNAQcCoIIagzCCGn8CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAbbFeYYPsEASR1 # QX0PsyDuuHrMPoY0bUWFbEQcRhY64aCCCm8wggUwMIIEGKADAgECAhAECRgbX9W7 # ZnVTQ7VvlVAIMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV # BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xMzEwMjIxMjAwMDBa # Fw0yODEwMjIxMjAwMDBaMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lD # ZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EwggEiMA0GCSqGSIb3 # DQEBAQUAA4IBDwAwggEKAoIBAQD407Mcfw4Rr2d3B9MLMUkZz9D7RZmxOttE9X/l # qJ3bMtdx6nadBS63j/qSQ8Cl+YnUNxnXtqrwnIal2CWsDnkoOn7p0WfTxvspJ8fT # eyOU5JEjlpB3gvmhhCNmElQzUHSxKCa7JGnCwlLyFGeKiUXULaGj6YgsIJWuHEqH # CN8M9eJNYBi+qsSyrnAxZjNxPqxwoqvOf+l8y5Kh5TsxHM/q8grkV7tKtel05iv+ # bMt+dDk2DZDv5LVOpKnqagqrhPOsZ061xPeM0SAlI+sIZD5SlsHyDxL0xY4PwaLo # LFH3c7y9hbFig3NBggfkOItqcyDQD2RzPJ6fpjOp/RnfJZPRAgMBAAGjggHNMIIB # yTASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAK # BggrBgEFBQcDAzB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9v # Y3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGln # aWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDCBgQYDVR0fBHow # eDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJl # ZElEUm9vdENBLmNybDA6oDigNoY0aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0Rp # Z2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDBPBgNVHSAESDBGMDgGCmCGSAGG/WwA # AgQwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAK # BghghkgBhv1sAzAdBgNVHQ4EFgQUWsS5eyoKo6XqcQPAYPkt9mV1DlgwHwYDVR0j # BBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8wDQYJKoZIhvcNAQELBQADggEBAD7s # DVoks/Mi0RXILHwlKXaoHV0cLToaxO8wYdd+C2D9wz0PxK+L/e8q3yBVN7Dh9tGS # dQ9RtG6ljlriXiSBThCk7j9xjmMOE0ut119EefM2FAaK95xGTlz/kLEbBw6RFfu6 # r7VRwo0kriTGxycqoSkoGjpxKAI8LpGjwCUR4pwUR6F6aGivm6dcIFzZcbEMj7uo # +MUSaJ/PQMtARKUT8OZkDCUIQjKyNookAv4vcn4c10lFluhZHen6dGRrsutmQ9qz # sIzV6Q3d9gEgzpkxYz0IGhizgZtPxpMQBvwHgfqL2vmCSfdibqFT+hKUGIUukpHq # aGxEMrJmoecYpJpkUe8wggU3MIIEH6ADAgECAhAFUi3UAAgCGeslOwtVg52XMA0G # CSqGSIb3DQEBCwUAMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ # bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0 # IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EwHhcNMjEwMzIyMDAwMDAw # WhcNMjIwMzMwMjM1OTU5WjB1MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZv # cm5pYTESMBAGA1UEBxMJUGFsbyBBbHRvMRAwDgYDVQQKEwdIUCBJbmMuMRkwFwYD # VQQLExBIUCBDeWJlcnNlY3VyaXR5MRAwDgYDVQQDEwdIUCBJbmMuMIIBIjANBgkq # hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtJ+rYUkseHcrB2M/GyomCEyKn9tCyfb+ # pByq/Jyf5kd3BGh+/ULRY7eWmR2cjXHa3qBAEHQQ1R7sX85kZ5sl2ukINGZv5jEM # 04ERNfPoO9+pDndLWnaGYxxZP9Y+Icla09VqE/jfunhpLYMgb2CuTJkY2tT2isWM # EMrKtKPKR5v6sfhsW6WOTtZZK+7dQ9aVrDqaIu+wQm/v4hjBYtqgrXT4cNZSPfcj # 8W/d7lFgF/UvUnZaLU5Z/+lYbPf+449tx+raR6GD1WJBAzHcOpV6tDOI5tQcwHTo # jJklvqBkPbL+XuS04IUK/Zqgh32YZvDnDohg0AEGilrKNiMes5wuAQIDAQABo4IB # xDCCAcAwHwYDVR0jBBgwFoAUWsS5eyoKo6XqcQPAYPkt9mV1DlgwHQYDVR0OBBYE # FD4tECf7wE2l8kA6HTvOgkbo33MvMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAK # BggrBgEFBQcDAzB3BgNVHR8EcDBuMDWgM6Axhi9odHRwOi8vY3JsMy5kaWdpY2Vy # dC5jb20vc2hhMi1hc3N1cmVkLWNzLWcxLmNybDA1oDOgMYYvaHR0cDovL2NybDQu # ZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1jcy1nMS5jcmwwSwYDVR0gBEQwQjA2 # BglghkgBhv1sAwEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5kaWdpY2VydC5j # b20vQ1BTMAgGBmeBDAEEATCBhAYIKwYBBQUHAQEEeDB2MCQGCCsGAQUFBzABhhho # dHRwOi8vb2NzcC5kaWdpY2VydC5jb20wTgYIKwYBBQUHMAKGQmh0dHA6Ly9jYWNl # cnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJBc3N1cmVkSURDb2RlU2lnbmlu # Z0NBLmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQBZca1CZfgn # DucOwEDZk0RXqb8ECXukFiih/rPQ+T5Xvl3bZppGgPnyMyQXXC0fb94p1socJzJZ # fn7rEQ4tHxL1vpBvCepB3Jq+i3A8nnJFHSjY7aujglIphfGND97U8OUJKt2jwnni # EgsWZnFHRI9alEvfGEFyFrAuSo+uBz5oyZeOAF0lRqaRht6MtGTma4AEgq6Mk/iP # LYIIZ5hXmsGYWtIPyM8Yjf//kLNPRn2WeUFROlboU6EH4ZC0rLTMbSK5DV+xL/e8 # cRfWL76gd/qj7OzyJR7EsRPg92RQUC4RJhCrQqFFnmI/K84lPyHRgoctAMb8ie/4 # X6KaoyX0Z93PMYIPeTCCD3UCAQEwgYYwcjELMAkGA1UEBhMCVVMxFTATBgNVBAoT # DERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UE # AxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2lnbmluZyBDQQIQBVIt # 1AAIAhnrJTsLVYOdlzANBglghkgBZQMEAgEFAKB8MBAGCisGAQQBgjcCAQwxAjAA # MBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgor # BgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCD5qbGmFV/KYUGVmY5Xxzv3zeJ0Vz5W # KoOpDqDH7E+2mTANBgkqhkiG9w0BAQEFAASCAQBrQ05aoO3gM2WvbI3Q4hJrQSg1 # aJVBL722pdyZr67vBG85ZVL9DWhyhrsW7WOdXLyB24jnV5VIyroNmQ5kkBHiiHVs # WLLf+Is2Qsyul010+CrNckavAgjrsjPsJMYdu/GAnoS+gPyMZHe5z6n0huG+cFk7 # ORnEYUIrqwx5X+qfaRx3kcVHDB5wet/qSkHsA9HHyG6r3GazJIUHjm5Ucfz6hgPv # cPBjMzOy87bG1ZYcfq+HggN4NOunIqufkiCLbuuYWMQTvA2Z9LFqmsbI6e7+rqp8 # sjmAAK+R4ANUttzleLzcAakXk8apAoiqEkicdGGK6xVi5RPiQAW17g8492xcoYIN # RTCCDUEGCisGAQQBgjcDAwExgg0xMIINLQYJKoZIhvcNAQcCoIINHjCCDRoCAQMx # DzANBglghkgBZQMEAgEFADB4BgsqhkiG9w0BCRABBKBpBGcwZQIBAQYJYIZIAYb9 # bAcBMDEwDQYJYIZIAWUDBAIBBQAEIGIyaJUBLBEscgaYdQT36thSOVC0oI1uDXQp # uyBs70IAAhEA++sBOrmpVXW2IJjlsQOP+RgPMjAyMTA0MTkxNzA0MzVaoIIKNzCC # BP4wggPmoAMCAQICEA1CSuC+Ooj/YEAhzhQA8N0wDQYJKoZIhvcNAQELBQAwcjEL # MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 # LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElE # IFRpbWVzdGFtcGluZyBDQTAeFw0yMTAxMDEwMDAwMDBaFw0zMTAxMDYwMDAwMDBa # MEgxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEgMB4GA1UE # AxMXRGlnaUNlcnQgVGltZXN0YW1wIDIwMjEwggEiMA0GCSqGSIb3DQEBAQUAA4IB # DwAwggEKAoIBAQDC5mGEZ8WK9Q0IpEXKY2tR1zoRQr0KdXVNlLQMULUmEP4dyG+R # awyW5xpcSO9E5b+bYc0VkWJauP9nC5xj/TZqgfop+N0rcIXeAhjzeG28ffnHbQk9 # vmp2h+mKvfiEXR52yeTGdnY6U9HR01o2j8aj4S8bOrdh1nPsTm0zinxdRS1LsVDm # QTo3VobckyON91Al6GTm3dOPL1e1hyDrDo4s1SPa9E14RuMDgzEpSlwMMYpKjIjF # 9zBa+RSvFV9sQ0kJ/SYjU/aNY+gaq1uxHTDCm2mCtNv8VlS8H6GHq756WwogL0sJ # yZWnjbL61mOLTqVyHO6fegFz+BnW/g1JhL0BAgMBAAGjggG4MIIBtDAOBgNVHQ8B # Af8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDBB # BgNVHSAEOjA4MDYGCWCGSAGG/WwHATApMCcGCCsGAQUFBwIBFhtodHRwOi8vd3d3 # LmRpZ2ljZXJ0LmNvbS9DUFMwHwYDVR0jBBgwFoAU9LbhIB3+Ka7S5GGlsqIlssgX # NW4wHQYDVR0OBBYEFDZEho6kurBmvrwoLR1ENt3janq8MHEGA1UdHwRqMGgwMqAw # oC6GLGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3VyZWQtdHMuY3Js # MDKgMKAuhixodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1hc3N1cmVkLXRz # LmNybDCBhQYIKwYBBQUHAQEEeTB3MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5k # aWdpY2VydC5jb20wTwYIKwYBBQUHMAKGQ2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0 # LmNvbS9EaWdpQ2VydFNIQTJBc3N1cmVkSURUaW1lc3RhbXBpbmdDQS5jcnQwDQYJ # KoZIhvcNAQELBQADggEBAEgc3LXpmiO85xrnIA6OZ0b9QnJRdAojR6OrktIlxHBZ # vhSg5SeBpU0UFRkHefDRBMOG2Tu9/kQCZk3taaQP9rhwz2Lo9VFKeHk2eie38+dS # n5On7UOee+e03UEiifuHokYDTvz0/rdkd2NfI1Jpg4L6GlPtkMyNoRdzDfTzZTlw # S/Oc1np72gy8PTLQG8v1Yfx1CAB2vIEO+MDhXM/EEXLnG2RJ2CKadRVC9S0yOIHa # 9GCiurRS+1zgYSQlT7LfySmoc0NR2r1j1h9bm/cuG08THfdKDXF+l7f0P4TrweOj # SaH6zqe/Vs+6WXZhiV9+p7SOZ3j5NpjhyyjaW4emii8wggUxMIIEGaADAgECAhAK # oSXW1jIbfkHkBdo2l8IVMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAlVTMRUw # EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x # JDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xNjAxMDcx # MjAwMDBaFw0zMTAxMDcxMjAwMDBaMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxE # aWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMT # KERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBUaW1lc3RhbXBpbmcgQ0EwggEiMA0G # CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC90DLuS82Pf92puoKZxTlUKFe2I0rE # DgdFM1EQfdD5fU1ofue2oPSNs4jkl79jIZCYvxO8V9PD4X4I1moUADj3Lh477sym # 9jJZ/l9lP+Cb6+NGRwYaVX4LJ37AovWg4N4iPw7/fpX786O6Ij4YrBHk8JkDbTuF # fAnT7l3ImgtU46gJcWvgzyIQD3XPcXJOCq3fQDpct1HhoXkUxk0kIzBdvOw8YGqs # LwfM/fDqR9mIUF79Zm5WYScpiYRR5oLnRlD9lCosp+R1PrqYD4R/nzEU1q3V8mTL # ex4F0IQZchfxFwbvPc3WTe8GQv2iUypPhR3EHTyvz9qsEPXdrKzpVv+TAgMBAAGj # ggHOMIIByjAdBgNVHQ4EFgQU9LbhIB3+Ka7S5GGlsqIlssgXNW4wHwYDVR0jBBgw # FoAUReuir/SSy4IxLVGLp6chnfNtyA8wEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNV # HQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgweQYIKwYBBQUHAQEEbTBr # MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUH # MAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJ # RFJvb3RDQS5jcnQwgYEGA1UdHwR6MHgwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2lj # ZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwOqA4oDaGNGh0dHA6 # Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmww # UAYDVR0gBEkwRzA4BgpghkgBhv1sAAIEMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v # d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUA # A4IBAQBxlRLpUYdWac3v3dp8qmN6s3jPBjdAhO9LhL/KzwMC/cWnww4gQiyvd/Mr # HwwhWiq3BTQdaq6Z+CeiZr8JqmDfdqQ6kw/4stHYfBli6F6CJR7Euhx7LCHi1lss # FDVDBGiy23UC4HLHmNY8ZOUfSBAYX4k4YU1iRiSHY4yRUiyvKYnleB/WCxSlgNcS # R3CzddWThZN+tpJn+1Nhiaj1a5bA9FhpDXzIAbG5KHW3mWOFIoxhynmUfln8jA/j # b7UBJrZspe6HUSHkWGCbugwtK22ixH67xCUrRwIIfEmuE7bhfEJCKMYYVs9BNLZm # XbZ0e/VWMyIvIjayS6JKldj1po5SMYICTTCCAkkCAQEwgYYwcjELMAkGA1UEBhMC # VVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0 # LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIFRpbWVzdGFt # cGluZyBDQQIQDUJK4L46iP9gQCHOFADw3TANBglghkgBZQMEAgEFAKCBmDAaBgkq # hkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTIxMDQxOTE3 # MDQzNVowKwYLKoZIhvcNAQkQAgwxHDAaMBgwFgQU4deCqOGRvu9ryhaRtaq0lKYk # m/MwLwYJKoZIhvcNAQkEMSIEIPYgf5FVOpzr3kFphmSjrHyQjFM6Jx7xkxHWbINj # snvBMA0GCSqGSIb3DQEBAQUABIIBAAJomdKw1pvU7yEoaXUUuG5bOWCsIxXWsEn/ # pF2/f6htgYXRjpKPg+HQs4EW1TAZsf/Wq5CNCDiRCzy3FOvpOkJQIFJbwYEhdJgG # KARotk0Y9BBkMQJqeLrrQHatjcd7bun4054ZyIRQuzMjmcdRtr3PgyEmrOF5vcyJ # I9Jhyc3g8fjDepiTGi+k1W14cSyjU24280SYiL4F8+UW/VpRQQdXjEsKGZZmlT0I # l4pb2mr/GVTDaGZYYfjnypbUf37jZbL4gG3uhuyeubVkTXunkw4Y4nn9pX+Q8AON # kTyZHQMB4vcAcwz7QlQbdWFP5hDGjmWlGEq2Cy6bgBjRo/gdaxQ= # SIG # End signature block |