public/Get-LnvUpdatesRepo.ps1
|
function Get-LnvUpdatesRepo { <# .SYNOPSIS Get the latest Lenovo updates for specified machine types and store in an Update Retriever style local repository. .DESCRIPTION For instances where Update Retriever cannot be used to create the local repository or where full automation of the repository creation is desired. This cmdlet can be customized and executed on a regular basis to get the latest update packages. .PARAMETER MachineTypes Mandatory: False Data type: String Must be a string of machine type ids separated with commas and surrounded by double quotes (e.g., "21DD,21DE,21DF"). Although multiple machine types can be specified, it is recommended to keep the list small to reduce download times for all updates. If no value is specified, the machine type of the device running the script will be used. .PARAMETER WindowsVersion Mandatory: False Data type: String Must be a string of '10' or '11'. The default if no value is specified will be determined by the OS of the device the script is running on. .PARAMETER PackageTypes Mandatory: False Data type: String Must be a string of Package Type integers separated by commas and surrounded by single quotes. The possible values are: 1: Application 2: Driver 3: BIOS 4: Firmware The default if no value is specified will be all package types. .PARAMETER RebootTypes Mandatory: False Data type: String Must be a string of integers, separated by commas, representing the different boot types and surrounded by single quotes: 0: No reboot required 1: Forces a reboot (not recommended in a task sequence) 3: Requires a reboot (but does not initiate it) 4: Forces a shutdown (not used much anymore) 5: Delayed forced reboot (used by many firmware updates) The default if no value is specified will be all RebootTypes. .PARAMETER RepositoryPath Mandatory: True Data type: string Must be a fully qualified path to the folder where the local repository will be saved. Can use single quotes for literal paths or double quotes if path contains variables. .PARAMETER RT5toRT3 Mandatory: False Data type: Switch Specify this parameter if you want to convert Reboot Type 5 (Delayed Forced Reboot) packages to be Reboot Type 3 (Requires Reboot). Only do this in task sequence scenarios where a Restart can be performed after the Thin Installer task. Use the -noreboot parameter on the Thin Installer command line to suppress reboot to allow the task sequence to control the restart. NOTE: This parameter can only be used when Thin Installer will be processing the updates in the repository. This parameter cannot be specified with -CloudRepo. .PARAMETER PackageList Mandatory: False Data type: String Specify a list of updates by their package IDs which can be obtained through Update Retriever. One or more updates can be specified, separated by a comma. .PARAMETER CloudRepo Mandatory: False Data type: Switch Switch to use Lenovo CDN for package hosting. Cannot be used with RT5toRT3. .PARAMETER DeltaUpdate Mandatory: False Data type: Switch Downloads only new/changed packages since the last run. For the initial repository creation, it is recommended to run this cmdlet without the -DeltaUpdate parameter while specifying all Machine Type Models. Moving forward, the -DeltaUpdate parameter can be used to only download new updates since the last run. This will significantly reduce the download time and network traffic. If introducing a new Machine Type, it is recommended to append it to the original Machine Types list that was used when initially creating the repository, while also specifying the -DeltaUpdate parameter. .INPUTS None. Does not accept pipeline input. .OUTPUTS Creates repository folder structure with: - Package files in subfolders - database.xml catalog file - database.xsd schema file - Log file with operation details .EXAMPLE # Initial repository creation Get-LnvUpdatesRepo.ps1 -RepositoryPath 'C:\Repository' -MachineTypes "21DD,21DE,21DF" -PackageTypes "2,3,4" -RebootTypes "3,5" -RT5toRT3 .EXAMPLE # Add new machine type to existing repo Get-LnvUpdatesRepo.ps1 -RepositoryPath 'C:\Repository' -MachineTypes "21DD,21DE,21DF,21DG" -PackageTypes "2,3,4" -RebootTypes "3,5" -RT5toRT3 -DeltaUpdate .EXAMPLE Get-LnvUpdatesRepo.ps1 -RepositoryPath 'C:\Repository' -PackageTypes "1,2" -RebootTypes "0,3" .EXAMPLE Get-LnvUpdatesRepo.ps1 -RepositoryPath 'Z:\21DD' -PackageTypes "1,2,3" -RebootTypes "0,3,5" -RT5toRT3 .NOTES #> #region Function parameters param ( # Repository path for storing update files and metadata [Parameter(Mandatory = $true)] [System.String] $RepositoryPath, # Machine types to process (4-characters) [Parameter(Mandatory = $false)] [ValidateScript({ $mtArray = $_ -split ',' | ForEach-Object { $_.Trim() } $mtArray | ForEach-Object { if ($_ -notmatch '^[A-Z0-9]{4}$') { throw "Machine type '$_' is invalid. Must be 4 characters of letters and numbers only." } } return $true })] $MachineTypes, [Parameter(Mandatory = $false)] [ValidateSet("10", "11")] [System.String] $WindowsVersion, [Parameter(Mandatory = $false)] [System.String] $PackageTypes, [Parameter(Mandatory = $false)] [System.String] $RebootTypes, [Parameter(Mandatory = $false)] [System.String] $Severities, [Parameter(Mandatory = $false)] [System.Management.Automation.SwitchParameter] $RT5toRT3, [Parameter(Mandatory = $false)] [System.String] $PackageList, [Parameter(Mandatory = $false)] [System.Management.Automation.SwitchParameter] $CloudRepo, [Parameter(Mandatory = $false)] [System.Management.Automation.SwitchParameter] $DeltaUpdate ) #endregion #region Begin block begin { #region Variable Initialization $script:MachineTypesArray = $null $script:rt = @() $script:pt = @() $script:sv = @() $script:Packages = @() # Set RebootTypes if (-not [string]::IsNullOrWhiteSpace($RebootTypes)) { $script:rt = $RebootTypes.Split(',') } else { $script:rt = @('0', '1', '3', '4', '5') } # Set PackageTypes if (-not [string]::IsNullOrWhiteSpace($PackageTypes)) { $script:pt = $PackageTypes.Split(',') } else { $script:pt = @('1', '2', '3', '4') } # Set Severities if (-not [string]::IsNullOrWhiteSpace($Severities)) { $script:sv = $Severities.Split(',') } else { $script:sv = @('0', '1', '2', '3') } # Set PackageList if (-not [string]::IsNullOrWhiteSpace($PackageList)) { $PackageList = $PackageList.Replace(' ', '') $script:Packages = $PackageList.Split(',') } #endregion #region XML Schema Definition $dbxsd_text = @" <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"> <xs:element name="Database"> <xs:complexType> <xs:sequence> <xs:element name="Package" type="PackageType" minOccurs="0" maxOccurs="unbounded"/> </xs:sequence> <xs:attribute name="version" use="required"> <xs:simpleType> <xs:restriction base="xs:string"/> </xs:simpleType> </xs:attribute> <xs:attribute name="cloud" type="xs:string" use="optional"/> </xs:complexType> </xs:element> <xs:element name="FileName"> <xs:simpleType> <xs:restriction base="xs:string"/> </xs:simpleType> </xs:element> <xs:element name="LocalPath"> <xs:simpleType> <xs:restriction base="xs:string"/> </xs:simpleType> </xs:element> <xs:element name="Mode"> <xs:simpleType> <xs:restriction base="xs:string"/> </xs:simpleType> </xs:element> <xs:complexType name="PackageType"> <xs:sequence> <xs:element ref="FileName"/> <xs:element ref="Version"/> <xs:element ref="ReleaseDate"/> <xs:element ref="Size"/> <xs:element ref="URL"/> <xs:element ref="Mode"/> <xs:element ref="Type"/> <xs:element ref="Status"/> <xs:element ref="PreviousStatus"/> <xs:element ref="LocalPath"/> <xs:element ref="Severity"/> <xs:element ref="DisplayLicense"/> <xs:element name="SystemCompatibility" type="SystemCompatibilityType"/> </xs:sequence> <xs:attribute name="id" use="required"> <xs:simpleType> <xs:restriction base="xs:string"/> </xs:simpleType> </xs:attribute> <xs:attribute name="name" use="required"> <xs:simpleType> <xs:restriction base="xs:string"/> </xs:simpleType> </xs:attribute> <xs:attribute name="description" use="required"> <xs:simpleType> <xs:restriction base="xs:string"/> </xs:simpleType> </xs:attribute> </xs:complexType> <xs:element name="PreviousStatus"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="Active"/> <xs:enumeration value="Archived"/> <xs:enumeration value="Test"/> <xs:enumeration value="Draft"/> <xs:enumeration value="Hidden"/> <xs:enumeration value="Default"/> <xs:enumeration value="None"/> </xs:restriction> </xs:simpleType> </xs:element> <xs:element name="ReleaseDate"> <xs:simpleType> <xs:restriction base="xs:string"/> </xs:simpleType> </xs:element> <xs:element name="Size"> <xs:simpleType> <xs:restriction base="xs:long"/> </xs:simpleType> </xs:element> <xs:element name="Status"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="Active"/> <xs:enumeration value="Archived"/> <xs:enumeration value="Test"/> <xs:enumeration value="Draft"/> <xs:enumeration value="Hidden"/> <xs:enumeration value="Default"/> </xs:restriction> </xs:simpleType> </xs:element> <xs:element name="Severity"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="Critical"/> <xs:enumeration value="Recommended"/> <xs:enumeration value="Optional"/> <xs:enumeration value="Default"/> </xs:restriction> </xs:simpleType> </xs:element> <xs:element name="DisplayLicense"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="Display"/> <xs:enumeration value="NotDisplay"/> <xs:enumeration value="Default"/> </xs:restriction> </xs:simpleType> </xs:element> <xs:complexType name="SystemType"> <xs:attribute name="mtm" use="required"> <xs:simpleType> <xs:restriction base="xs:string"/> </xs:simpleType> </xs:attribute> <xs:attribute name="os" use="required"> <xs:simpleType> <xs:restriction base="xs:string"/> </xs:simpleType> </xs:attribute> </xs:complexType> <xs:complexType name="SystemCompatibilityType"> <xs:sequence> <xs:element name="System" type="SystemType" minOccurs="0" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> <xs:element name="Type"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="Quest"/> <xs:enumeration value="Local"/> </xs:restriction> </xs:simpleType> </xs:element> <xs:element name="URL"> <xs:simpleType> <xs:restriction base="xs:string"/> </xs:simpleType> </xs:element> <xs:element name="Version"> <xs:simpleType> <xs:restriction base="xs:string"/> </xs:simpleType> </xs:element> </xs:schema> "@ #endregion #region Logging functions function Write-LnvLog { Param( [Parameter(Mandatory = $true)] [ValidateSet("Information", "Warning", "Error")] [string]$Level, [Parameter(Mandatory = $true)] [string]$Message ) $TimeStamp = (Get-Date).ToString("yyyy-MM-ddTHH:mm:ss") $LogLine = "[LNV_{0}_{1}]: {2}" -f $Level.ToUpper(), $TimeStamp, $Message Out-File -FilePath "$LogPath" -InputObject $LogLine -Append -NoClobber return $LogLine } # Set logging paths $TimeStamp = Get-Date -Format "ddMMyyHHmmss" $LogFileName = "Get-LnvUpdatesRepo$(if ($DeltaUpdate) { '-Delta' })_$TimeStamp.log" $LogPath = Join-Path -Path $RepositoryPath -ChildPath $LogFileName # Remove the log file if DeltaUpdate is specified if ($DeltaUpdate) { if (Test-Path -Path $LogPath) { try { Remove-Item -Path $LogPath -Force -ErrorAction SilentlyContinue } catch { Write-Output "Failed to remove delta log file: $LogPath. Error: $_" } } } #endregion #region OS Version detection if (-not $WindowsVersion) { $WinVersion = (Get-CimInstance -Namespace root/cimv2 -ClassName Win32_OperatingSystem).Version switch -Regex ($WinVersion) { '10.0.1' { $script:OS = "Win10" $script:OSName = "Windows 10" } '10.0.2' { $script:OS = "Win11" $script:OSName = "Windows 11" } default { Write-LnvLog -Level Warning -Message "Unknown Windows version: $WinVersion" } } } else { switch ($WindowsVersion) { '10' { $script:OS = "Win10" $script:OSName = "Windows 10" } '11' { $script:OS = "Win11" $script:OSName = "Windows 11" } default { Write-LnvLog -Level Warning -Message "Unknown Windows version: $WindowsVersion" } } } #endregion } #endregion #region Process block process { try { # Confirm Parameters if ($CloudRepo -and $RT5toRT3) { throw "The -CloudRepo switch cannot be used with the -RT5toRT3 parameter" } if ($RepositoryPath) { $result = $RepositoryPath -match "^((?:~?\/)|(?:(?:\\\\\?\\)?[a-zA-Z]+\:))(?:\/?(.*))?$" if ($result -ne $True) { Write-LnvLog -Level Error -Message "RepositoryPath parameter must be a properly formatted and fully qualified path to an existing folder where the local repository resides." return } } if (-not $MachineTypes) { if ((Get-CimInstance -Namespace root/cimv2 -ClassName Win32_ComputerSystemProduct).Vendor.ToLower() -ne 'lenovo') { Write-LnvLog -Level Warning "This script is only supported on Lenovo commercial PC products." return } $TrimmedMachineType = (Get-CimInstance -Namespace root/cimv2 -ClassName Win32_ComputerSystemProduct).Name.Substring(0, 4).ToUpper().Trim() $script:MachineTypesArray = @($TrimmedMachineType) } else { # Split the input string and clean up each value $script:MachineTypesArray = $MachineTypes -split ',' | ForEach-Object { $_.Trim().ToUpper() } | Where-Object { $_ } } if ($script:MachineTypesArray.Length -eq 0) { Write-LnvLog -Level Warning -Message "MachineTypes parameter must contain at least one four character machine type of a Lenovo PC." return } <# What to do if repository folder already exists #TODO Add parameter to configure this behavior Comment and uncomment lines in the if clause below to achieve desired behavior. Check if the repository folder exists #> $RepositoryFolderExists = Test-Path -Path $RepositoryPath if ($RepositoryFolderExists) { if (-not $DeltaUpdate) { # Repopulate each time with the latest content Remove-Item $RepositoryPath -Recurse -ErrorAction SilentlyContinue } else { Write-LnvLog -Level Information -Message "DeltaUpdate is enabled. Existing repository content will not be removed." # Remove only database.xml and database.xsd files $DatabaseFiles = @("database.xml", "database.xsd") foreach ($File in $DatabaseFiles) { $FilePath = Join-Path -Path $RepositoryPath -ChildPath $File if (Test-Path -Path $FilePath) { Remove-Item $FilePath -Force -ErrorAction SilentlyContinue Write-LnvLog -Level Information -Message "Removed $FilePath for DeltaUpdate." } } } } # 1. Prepare repository location New-Item -ItemType Directory -Path $RepositoryPath -Force | Out-Null $RepositoryFolderExists = Test-Path -Path $RepositoryPath if (-not $RepositoryFolderExists) { Write-LnvLog -Level Error -Message "Failed to create folder at the following path $RepositoryPath" return } #region Database # 1.1 Create database.xsd file [System.XML.XMLDocument]$dbxsd = New-Object -TypeName System.Xml.XmlDocument $dbxsd.LoadXml($dbxsd_text) $DatabaseXsdPath = Join-Path -Path $RepositoryPath -ChildPath "database.xsd" $dbxsd.Save($DatabaseXsdPath) <# 1.2 Create an XML document object to contain database.xml Array of severities to translate integer into string #> $Severities = @("None", "Critical", "Recommended", "Optional") # Initialize dbxml [System.XML.XMLDocument]$dbxml = New-Object -TypeName System.Xml.XmlDocument # Add the XML declaration $xmldecl = $dbxml.CreateXmlDeclaration("1.0", "UTF-8", $null) # Create the root element <Database> [System.XML.XMLElement]$dbxmlRoot = $dbxml.CreateElement("Database") $dbxml.InsertBefore($xmldecl, $dbxml.DocumentElement) | Out-Null $dbxml.AppendChild($dbxmlRoot) | Out-Null # Add the 'version' attribute to the <Database> element $dbxmlRoot.SetAttribute("version", "301") | Out-Null # Conditionally add the 'cloud' attribute if ($CloudRepo) { $dbxmlRoot.SetAttribute("cloud", "True") | Out-Null } #endregion # 2. Download the updates catalog from https://download.lenovo.com/catalog/<mt>_<os>.xml # Calculate progress percentages $script:mtcount = $script:MachineTypesArray.Count $script:mtportion = (100 / $script:mtcount) # Percentage per machine type $UrlTypeTable = @{} $Counter = 1 foreach ($mt in $script:MachineTypesArray) { Write-Progress -Activity "Gathering packages to evaluate" -Status $mt -PercentComplete ($counter * $script:mtportion) if ($mt.Length -eq 4) { $CatalogUrl = "https://download.lenovo.com/catalog/$mt`_$script:OS.xml" $Catalog = Get-LnvXmlFilePvt -Url $CatalogUrl if (-not $catalog) { Write-LnvLog -Level Error -Message "Failed to download the updates catalog from $catalogUrl. Check that $mt is a valid machine type." return } # 2.1. Get URLs for package descriptors $Updates = @{} $PackagesUrls = $catalog.packages.package.location foreach ($Url in $PackagesUrls) { $FileName = $Url.Substring($Url.LastIndexOf("/") + 1) $SeparatorIndex = $FileName.IndexOf('.') $PackageID = $FileName.Substring(0, $SeparatorIndex - 3) $Updates.Add($PackageID, $Url) } $PackagesCount = $Updates.Count Write-LnvLog -Level Information -Message("Found packages for $($mt): $($PackagesCount)") if ($Updates.Count -eq 0) { Write-LnvLog -Level Error -Message "No updates found in the updates catalog" return } if ($Updates.Count -ne 0) { # For each package, get package descriptor XML foreach ($Item in $Updates.GetEnumerator()) { $Url = $Item.Value if ($UrlTypeTable.Keys -contains $Url) { $UrlTypeTable[$url] += $mt } else { $UrlTypeTable[$url] = @($mt) } } } } else { Write-LnvLog -Level Warning -Message "Skipping $mt as it is not a valid machine type." } } Write-Progress -Completed -Activity "Gathering packages to evaluate" $Counter = 1 foreach ($Url in $UrlTypeTable.Keys) { Write-Progress -Activity "Evaluating packages" -Id 1 -PercentComplete ($Counter / $UrlTypeTable.Keys.Count * 100.0) # Download package descriptor XML to this subfolder try { [xml] $PkgXML = Get-LnvXmlFilePvt -Url $Url } catch { Write-LnvLog -Level Error -Message "Failed to download the package descriptor from $Url" continue } # Get real package ID from XML attribute try { $PackageId = $PkgXml.Package.id } catch { Write-LnvLog -Level Error -Message "Could not find package ID in package descriptor from $Url" continue } Write-Progress -ParentId 1 -Activity "Evaluating package" -Status $PackageId -PercentComplete 0 # Filter by PackageId if PackageList specified and filter by Package Type and Reboot Type if ( (($script:Packages.Count -eq 0) -or ($script:Packages.Contains($PackageId))) -and ($script:rt -contains $PkgXML.Package.Reboot.type) -and ($script:pt -contains $PkgXML.Package.PackageType.type) -and ($script:sv -contains $PkgXML.Package.Severity.type) ) { <# Save package xml Create a subfolder using package ID as the folder name #> $PackagePath = Join-Path -Path $RepositoryPath -ChildPath $PackageId if ($DeltaUpdate) { if (-not (Test-Path -Path $PackagePath)) { Write-LnvLog -Level Information -Message "New update found. Creating folder at the following path $RepositoryPath$PackageId" } } # Create the directory regardless of $DeltaUpdate New-Item -ItemType Directory -Force $PackagePath | Out-Null if (-not (Test-Path -Path $PackagePath)) { Write-LnvLog -Level Error -Message "Failed to create folder at the following path $RepositoryPath\$PackageId" continue } } else { $Counter++ continue } Write-LnvLog -Level Information -Message "Getting $PackageID..." # Gather data needed for dbxml $__packageID = $pkgXML.Package.id $__name = $pkgXML.Package.name $__description = $pkgXML.Package.Title.Desc.InnerText $__filename = $url.SubString($url.LastIndexOf('/') + 1) $__version = $pkgXML.Package.version $__releasedate = $pkgXML.Package.ReleaseDate #$__size = $pkgXML.Package.Files.Installer.File.Size $__size = 0 $__url = $url.SubString(0, $url.LastIndexOf('/') + 1) $__localRepositoryPath = [IO.Path]::Combine($RepositoryPath, $__packageID, $__filename) $__localpath = [IO.Path]::Combine("\", $__packageID, $__filename) #$__severity = $severities[$pkgXML.Package.Severity.type] switch ($pkgXML.Package.Severity.type) { 0 { $__severity = "Default" } 1 { $__severity = "Critical" } 2 { $__severity = "Recommended" } 3 { $__severity = "Optional" } default { $__severity = "Default" } } #region Package Processing try { # Validate XML exists and check DeltaUpdate conditions if ($DeltaUpdate -and (Test-Path -Path $__localRepositoryPath)) { <# .DESCRIPTION Handles existing XML files when DeltaUpdate is enabled: - Checks if RT5toRT3 changes were already applied - Validates XML versions - Updates or preserves existing files based on conditions #> $existingXml = [xml](Get-Content -Path $__localRepositoryPath) if ($RT5toRT3 -and $pkgXML.Package.Reboot.type -eq '5') { # Check if reboot type was already changed to 3 if ($existingXml.Package.Reboot.type -eq '3') { Write-LnvLog -Level Information -Message "Skipping download for $PackageID - RT5toRT3 change already applied." $PkgXML = $existingXml # Use existing XML for database entry } else { # Download and modify XML (New-Object System.Net.WebClient).DownloadFile($Url, $__localRepositoryPath) $xml = [xml](Get-Content -Path $__localRepositoryPath) $xml.Package.Reboot.type = '3' $xml.Save($__localRepositoryPath) $PkgXML = $xml } } elseif ($existingXml.Package.version -eq $pkgXML.Package.version) { Write-LnvLog -Level Information -Message "Skipping download for $PackageID - XML is current." $PkgXML = $existingXml # Use existing XML for database entry } else { # Download new XML if versions don't match (New-Object System.Net.WebClient).DownloadFile($Url, $__localRepositoryPath) } } else { # Download new XML if file doesn't exist (New-Object System.Net.WebClient).DownloadFile($Url, $__localRepositoryPath) # Apply RT5toRT3 if needed if (($RT5toRT3) -and ($PkgXML.Package.Reboot.type -eq '5')) { Write-LnvLog -Level Information -Message "Changing reboot type for $packageID" $xml = [xml](Get-Content -Path $__localRepositoryPath) $xml.Package.Reboot.type = '3' $xml.Save($__localRepositoryPath) $PkgXML = $xml } } } catch { Write-LnvLog -Level Error -Message "Failed to save xml at the following path $__localRepositoryPath : $_" continue } #endregion <# Load package descriptor XML and download each of the files referenced under the <Files> tag. Note that the files will be located at the same relative path as the package descriptor XML on https://download.lenovo.com/... #> $FileNameElements = @() $InstallerFile = @() $ReadmeFile = @() $ExternalFiles = @() $InstallerFile = $PkgXML.GetElementsByTagName("Files").GetElementsByTagName("Installer").GetElementsByTagName("File") try { $ReadmeFile = $PkgXML.GetElementsByTagName("Files").GetElementsByTagName("Readme").GetElementsByTagName("File") } catch { Write-LnvLog -Level Information -Message "No readme file specified." # Continue on exception: No readme file specified. } Write-Progress -ParentId 1 -Activity "Evaluating package" -Status $PackageId -PercentComplete 33 try { $externalFiles = $pkgXML.GetElementsByTagName("Files").GetElementsByTagName("External").GetElementsByTagName("File") } catch { Write-LnvLog -Level Information -Message "No external detection files specified." # Continue on exception: No external detection files specified. } Write-Progress -ParentId 1 -Activity "Evaluating package" -Status $PackageId -PercentComplete 66 if ($ReadmeFile) { $FileNameElements += $ReadmeFile } if ($ExternalFiles) { $FileNameElements += $ExternalFiles } $FileNameElements += $InstallerFile foreach ($Element in $FileNameElements) { try { $FileName = $Element.GetElementsByTagName("Name").InnerText $ExpectedFileSize = $Element.GetElementsByTagName("Size").InnerText $ExpectedFileCRC = $Element.GetElementsByTagName("CRC").InnerText $FileUrl = "$__url/$Filename" $FileDestinationPath = [IO.Path]::Combine($RepositoryPath, $__packageID, $Filename) # Skip download if CloudRepo is enabled if ($CloudRepo) { Write-LnvLog -Level Information -Message "CloudRepo enabled. Skipping download for $Filename." continue } # Validate existing files if DeltaUpdate is enabled if ($DeltaUpdate -and (Test-Path -Path $FileDestinationPath)) { try { Write-LnvLog -Level Information -Message "DeltaUpdate is enabled. Validating existing file at $FileDestinationPath." #Check file size and CRC and delete the folder if they are not equal $actualFileCRC = $(Get-FileHash -Path $FileDestinationPath -Algorithm SHA256).Hash $actualFileSize = $(Get-Item -Path $FileDestinationPath).Length if ($actualFileCRC -eq $ExpectedFileCRC -and $ExpectedFileSize -eq $actualFileSize) { Write-LnvLog -Level Information -Message "File $Filename is valid. Skipping download." continue } else { Write-LnvLog -Level Warning -Message "File $Filename is invalid or outdated. It will be re-downloaded." Remove-Item -Path $FileDestinationPath -Force -ErrorAction SilentlyContinue } } catch { Write-LnvLog -Level Error -Message "Failed to validate or remove existing file $FileDestinationPath. Error: $_" # Optionally, continue to download the file even if validation fails } } else { # Download the file Write-LnvLog -Level Information -Message "Downloading $Filename ($([math]::round($ExpectedFileSize / 1MB, 2)) MB)." $fileDownloadParams = @{ Url = $FileUrl DestinationPath = $FileDestinationPath ExpectedFileSize = $ExpectedFileSize ExpectedFileCRC = $ExpectedFileCRC } $fileDownloadResult = Get-LnvFilePvt @fileDownloadParams if (-not $fileDownloadResult) { throw "Failed to download or validate the file $Filename." } Write-LnvLog -Level Information -Message "Downloaded $Filename successfully." $__size += $ExpectedFileSize } } catch { Write-LnvLog -Level Error -Message "Error processing file $($FileName): $_" Write-LnvLog -Level Warning -Message "Deleting package $__packageID due to errors." $PackageFolder = [IO.Path]::Combine($RepositoryPath, $__packageID) Remove-Item $PackageFolder -Recurse -ErrorAction SilentlyContinue $Counter++ continue } } Write-LnvLog -Level Information -Message "$($__packageID) complete." Write-Progress -ParentId 1 -Activity "Evaluating package" -Status $PackageId -PercentComplete 100 #region Database XML creation # Build xml elements for dbxml Write-LnvLog -Level Information -Message "Creating database entry for $__packageID" # Package root element $_package = $dbxml.CreateElement("Package") $_package.SetAttribute("id", $__packageID) | Out-Null $_package.SetAttribute("name", $__name) | Out-Null $_package.SetAttribute("description", $__description) | Out-Null # Package metadata elements $sub1 = $dbxml.CreateElement("FileName") $sub1text = $dbxml.CreateTextNode($__filename) $sub1.AppendChild($sub1text) | Out-Null $sub2 = $dbxml.CreateElement("Version") $sub2text = $dbxml.CreateTextNode($__version) $sub2.AppendChild($sub2text) | Out-Null $sub3 = $dbxml.CreateElement("ReleaseDate") # Format release date for database (MM/DD/YYYY) $dateparts = $__releasedate.Split('-') $urdate = "{0}/{1}/{2}" -f $dateparts[1].TrimStart('0'), $dateparts[2], $dateparts[0] $sub3text = $dbxml.CreateTextNode($urdate) $sub3.AppendChild($sub3text) | Out-Null $sub4 = $dbxml.CreateElement("Size") $sub4text = $dbxml.CreateTextNode($__size) $sub4.AppendChild($sub4text) | Out-Null $sub5 = $dbxml.CreateElement("URL") $sub5text = $dbxml.CreateTextNode($__url) $sub5.AppendChild($sub5text) | Out-Null $sub6 = $dbxml.CreateElement("Mode") $sub6text = $dbxml.CreateTextNode("") $sub6.AppendChild($sub6text) | Out-Null $sub7 = $dbxml.CreateElement("Type") $sub7text = $dbxml.CreateTextNode("Quest") $sub7.AppendChild($sub7text) | Out-Null $sub8 = $dbxml.CreateElement("Status") $sub8text = $dbxml.CreateTextNode("Active") $sub8.AppendChild($sub8text) | Out-Null $sub9 = $dbxml.CreateElement("PreviousStatus") $sub9text = $dbxml.CreateTextNode("None") $sub9.AppendChild($sub9text) | Out-Null $sub10 = $dbxml.CreateElement("LocalPath") $sub10text = $dbxml.CreateTextNode($__localpath) $sub10.AppendChild($sub10text) | Out-Null $sub11 = $dbxml.CreateElement("Severity") $sub11text = $dbxml.CreateTextNode($__severity) $sub11.AppendChild($sub11text) | Out-Null $sub12 = $dbxml.CreateElement("DisplayLicense") $sub12text = $dbxml.CreateTextNode("NotDisplay") $sub12.AppendChild($sub12text) | Out-Null # System compatability $sub13 = $dbxml.CreateElement("SystemCompatibility") foreach ($mt in $UrlTypeTable[$url]) { $sub13sub = $dbxml.CreateElement("System") $sub13sub.SetAttribute("mtm", $mt) $sub13sub.SetAttribute("os", $script:OSName) $sub13.AppendChild($sub13sub) | Out-Null } #endregion #Set details for the update and populate database.xml $_package.AppendChild($sub1) | Out-Null $_package.AppendChild($sub2) | Out-Null $_package.AppendChild($sub3) | Out-Null $_package.AppendChild($sub4) | Out-Null $_package.AppendChild($sub5) | Out-Null $_package.AppendChild($sub6) | Out-Null $_package.AppendChild($sub7) | Out-Null $_package.AppendChild($sub8) | Out-Null $_package.AppendChild($sub9) | Out-Null $_package.AppendChild($sub10) | Out-Null $_package.AppendChild($sub11) | Out-Null $_package.AppendChild($sub12) | Out-Null $_package.AppendChild($sub13) | Out-Null $dbxml.LastChild.AppendChild($_package) | Out-Null $Counter++ } } catch { Write-LnvLog -Level Error -Message "Unexpected error occurred:`n $_" return } } #endregion #region End block end { # Only try to save database.xml if we have valid data if ($null -ne $dbxml -and $null -ne $dbxml.LastChild) { $DatabaseXmlPath = Join-Path -Path $RepositoryPath -ChildPath "database.xml" $dbxml.Save($DatabaseXmlPath) Write-Progress -Completed -Activity "Evaluating packages" if (-not $DeltaUpdate) { Write-LnvLog -Level Information -Message "Update packages downloaded." } else { Write-LnvLog -Level Information -Message "Delta update packages downloaded." } } } #endregion } # SIG # Begin signature block # MIItugYJKoZIhvcNAQcCoIItqzCCLacCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUR+tuE+4i4vxgFIeTIku40AEu # B2uggibcMIIFjTCCBHWgAwIBAgIQDpsYjvnQLefv21DiCEAYWjANBgkqhkiG9w0B # AQwFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD # VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk # IElEIFJvb3QgQ0EwHhcNMjIwODAxMDAwMDAwWhcNMzExMTA5MjM1OTU5WjBiMQsw # CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu # ZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQw # ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz # 7MKnJS7JIT3yithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS # 5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7 # bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfI # SKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jH # trHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14 # Ztk6MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2 # h4mXaXpI8OCiEhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt # 6zPZxd9LBADMfRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPR # iQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ER # ElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4K # Jpn15GkvmB0t9dmpsh3lGwIDAQABo4IBOjCCATYwDwYDVR0TAQH/BAUwAwEB/zAd # BgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wHwYDVR0jBBgwFoAUReuir/SS # y4IxLVGLp6chnfNtyA8wDgYDVR0PAQH/BAQDAgGGMHkGCCsGAQUFBwEBBG0wazAk # BggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUFBzAC # hjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURS # b290Q0EuY3J0MEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0 # LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwEQYDVR0gBAowCDAGBgRV # HSAAMA0GCSqGSIb3DQEBDAUAA4IBAQBwoL9DXFXnOF+go3QbPbYW1/e/Vwe9mqyh # hyzshV6pGrsi+IcaaVQi7aSId229GhT0E0p6Ly23OO/0/4C5+KH38nLeJLxSA8hO # 0Cre+i1Wz/n096wwepqLsl7Uz9FDRJtDIeuWcqFItJnLnU+nBgMTdydE1Od/6Fmo # 8L8vC6bp8jQ87PcDx4eo0kxAGTVGamlUsLihVo7spNU96LHc/RzY9HdaXFSMb++h # UD38dglohJ9vytsgjTVgHAIDyyCwrFigDkBjxZgiwbJZ9VVrzyerbHbObyMt9H5x # aiNrIv8SuFQtJ37YOtnwtoeW/VvRXKwYw02fc7cBqZ9Xql4o4rmUMIIFkDCCA3ig # AwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG # EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl # cnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMw # ODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UE # ChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYD # VQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqGSIb3DQEBAQUA # A4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEppz1Y # q3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lX # FllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxe # TsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbu # yntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I # 9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/CNdaSaTC5qmg # Z92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtmmnTK3kse # 5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKy # Ebe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwh # HbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/ # Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwID # AQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4E # FgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQADggIBALth2X2p # bL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY # ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdN # Oj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4 # i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJ # EVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9NcCOGDErcgdLM # MpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N0XWs0Mr7QbhD # parTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb/UdK # Dd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP # 0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLS # oCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9T # dSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+MIIGsDCCBJigAwIBAgIQCK1AsmDS # nEyfXs2pvZOu2TANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQGEwJVUzEVMBMGA1UE # ChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYD # VQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjEwNDI5MDAwMDAwWhcN # MzYwNDI4MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs # IEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWduaW5n # IFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A # MIICCgKCAgEA1bQvQtAorXi3XdU5WRuxiEL1M4zrPYGXcMW7xIUmMJ+kjmjYXPXr # NCQH4UtP03hD9BfXHtr50tVnGlJPDqFX/IiZwZHMgQM+TXAkZLON4gh9NH1MgFcS # a0OamfLFOx/y78tHWhOmTLMBICXzENOLsvsI8IrgnQnAZaf6mIBJNYc9URnokCF4 # RS6hnyzhGMIazMXuk0lwQjKP+8bqHPNlaJGiTUyCEUhSaN4QvRRXXegYE2XFf7JP # hSxIpFaENdb5LpyqABXRN/4aBpTCfMjqGzLmysL0p6MDDnSlrzm2q2AS4+jWufcx # 4dyt5Big2MEjR0ezoQ9uo6ttmAaDG7dqZy3SvUQakhCBj7A7CdfHmzJawv9qYFSL # ScGT7eG0XOBv6yb5jNWy+TgQ5urOkfW+0/tvk2E0XLyTRSiDNipmKF+wc86LJiUG # soPUXPYVGUztYuBeM/Lo6OwKp7ADK5GyNnm+960IHnWmZcy740hQ83eRGv7bUKJG # yGFYmPV8AhY8gyitOYbs1LcNU9D4R+Z1MI3sMJN2FKZbS110YU0/EpF23r9Yy3IQ # KUHw1cVtJnZoEUETWJrcJisB9IlNWdt4z4FKPkBHX8mBUHOFECMhWWCKZFTBzCEa # 6DgZfGYczXg4RTCZT/9jT0y7qg0IU0F8WD1Hs/q27IwyCQLMbDwMVhECAwEAAaOC # AVkwggFVMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFGg34Ou2O/hfEYb7 # /mF7CIhl9E5CMB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9PMA4GA1Ud # DwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDAzB3BggrBgEFBQcBAQRrMGkw # JAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggrBgEFBQcw # AoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJv # b3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGlnaWNlcnQu # Y29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwHAYDVR0gBBUwEzAHBgVngQwB # AzAIBgZngQwBBAEwDQYJKoZIhvcNAQEMBQADggIBADojRD2NCHbuj7w6mdNW4AIa # pfhINPMstuZ0ZveUcrEAyq9sMCcTEp6QRJ9L/Z6jfCbVN7w6XUhtldU/SfQnuxaB # RVD9nL22heB2fjdxyyL3WqqQz/WTauPrINHVUHmImoqKwba9oUgYftzYgBoRGRjN # YZmBVvbJ43bnxOQbX0P4PpT/djk9ntSZz0rdKOtfJqGVWEjVGv7XJz/9kNF2ht0c # sGBc8w2o7uCJob054ThO2m67Np375SFTWsPK6Wrxoj7bQ7gzyE84FJKZ9d3OVG3Z # XQIUH0AzfAPilbLCIXVzUstG2MQ0HKKlS43Nb3Y3LIU/Gs4m6Ri+kAewQ3+ViCCC # cPDMyu/9KTVcH4k4Vfc3iosJocsL6TEa/y4ZXDlx4b6cpwoG1iZnt5LmTl/eeqxJ # zy6kdJKt2zyknIYf48FWGysj/4+16oh7cGvmoLr9Oj9FpsToFpFSi0HASIRLlk2r # REDjjfAVKM7t8RhWByovEMQMCGQ8M4+uKIw8y4+ICw2/O/TOHnuO77Xry7fwdxPm # 5yg/rBKupS8ibEH5glwVZsxsDsrFhsP2JjMMB0ug0wcCampAMEhLNKhRILutG4UI # 4lkNbcoFUCvqShyepf2gpx8GdOfy1lKQ/a+FSCH5Vzu0nAPthkX0tGFuv2jiJmCG # 6sivqf6UHedjGzqGVnhOMIIGtDCCBJygAwIBAgIQDcesVwX/IZkuQEMiDDpJhjAN # BgkqhkiG9w0BAQsFADBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQg # SW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2Vy # dCBUcnVzdGVkIFJvb3QgRzQwHhcNMjUwNTA3MDAwMDAwWhcNMzgwMTE0MjM1OTU5 # WjBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNV # BAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hB # MjU2IDIwMjUgQ0ExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtHgx # 0wqYQXK+PEbAHKx126NGaHS0URedTa2NDZS1mZaDLFTtQ2oRjzUXMmxCqvkbsDpz # 4aH+qbxeLho8I6jY3xL1IusLopuW2qftJYJaDNs1+JH7Z+QdSKWM06qchUP+AbdJ # gMQB3h2DZ0Mal5kYp77jYMVQXSZH++0trj6Ao+xh/AS7sQRuQL37QXbDhAktVJMQ # bzIBHYJBYgzWIjk8eDrYhXDEpKk7RdoX0M980EpLtlrNyHw0Xm+nt5pnYJU3Gmq6 # bNMI1I7Gb5IBZK4ivbVCiZv7PNBYqHEpNVWC2ZQ8BbfnFRQVESYOszFI2Wv82wnJ # RfN20VRS3hpLgIR4hjzL0hpoYGk81coWJ+KdPvMvaB0WkE/2qHxJ0ucS638ZxqU1 # 4lDnki7CcoKCz6eum5A19WZQHkqUJfdkDjHkccpL6uoG8pbF0LJAQQZxst7VvwDD # jAmSFTUms+wV/FbWBqi7fTJnjq3hj0XbQcd8hjj/q8d6ylgxCZSKi17yVp2NL+cn # T6Toy+rN+nM8M7LnLqCrO2JP3oW//1sfuZDKiDEb1AQ8es9Xr/u6bDTnYCTKIsDq # 1BtmXUqEG1NqzJKS4kOmxkYp2WyODi7vQTCBZtVFJfVZ3j7OgWmnhFr4yUozZtqg # PrHRVHhGNKlYzyjlroPxul+bgIspzOwbtmsgY1MCAwEAAaOCAV0wggFZMBIGA1Ud # EwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFO9vU0rp5AZ8esrikFb2L9RJ7MtOMB8G # A1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9PMA4GA1UdDwEB/wQEAwIBhjAT # BgNVHSUEDDAKBggrBgEFBQcDCDB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGG # GGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDovL2Nh # Y2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcnQwQwYD # VR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0 # VHJ1c3RlZFJvb3RHNC5jcmwwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9 # bAcBMA0GCSqGSIb3DQEBCwUAA4ICAQAXzvsWgBz+Bz0RdnEwvb4LyLU0pn/N0IfF # iBowf0/Dm1wGc/Do7oVMY2mhXZXjDNJQa8j00DNqhCT3t+s8G0iP5kvN2n7Jd2E4 # /iEIUBO41P5F448rSYJ59Ib61eoalhnd6ywFLerycvZTAz40y8S4F3/a+Z1jEMK/ # DMm/axFSgoR8n6c3nuZB9BfBwAQYK9FHaoq2e26MHvVY9gCDA/JYsq7pGdogP8HR # trYfctSLANEBfHU16r3J05qX3kId+ZOczgj5kjatVB+NdADVZKON/gnZruMvNYY2 # o1f4MXRJDMdTSlOLh0HCn2cQLwQCqjFbqrXuvTPSegOOzr4EWj7PtspIHBldNE2K # 9i697cvaiIo2p61Ed2p8xMJb82Yosn0z4y25xUbI7GIN/TpVfHIqQ6Ku/qjTY6hc # 3hsXMrS+U0yy+GWqAXam4ToWd2UQ1KYT70kZjE4YtL8Pbzg0c1ugMZyZZd/BdHLi # Ru7hAWE6bTEm4XYRkA6Tl4KSFLFk43esaUeqGkH/wyW4N7OigizwJWeukcyIPbAv # jSabnf7+Pu0VrFgoiovRDiyx3zEdmcif/sYQsfch28bZeUz2rtY/9TCA6TD8dC3J # E3rYkrhLULy7Dc90G6e8BlqmyIjlgp2+VqsS9/wQD7yFylIz0scmbKvFoW2jNrbM # 1pD2T7m3XDCCBu0wggTVoAMCAQICEAqA7xhLjfEFgtHEdqeVdGgwDQYJKoZIhvcN # AQELBQAwaTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEw # PwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IFRpbWVTdGFtcGluZyBSU0E0MDk2 # IFNIQTI1NiAyMDI1IENBMTAeFw0yNTA2MDQwMDAwMDBaFw0zNjA5MDMyMzU5NTla # MGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UE # AxMyRGlnaUNlcnQgU0hBMjU2IFJTQTQwOTYgVGltZXN0YW1wIFJlc3BvbmRlciAy # MDI1IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDQRqwtEsae0Oqu # YFazK1e6b1H/hnAKAd/KN8wZQjBjMqiZ3xTWcfsLwOvRxUwXcGx8AUjni6bz52fG # Tfr6PHRNv6T7zsf1Y/E3IU8kgNkeECqVQ+3bzWYesFtkepErvUSbf+EIYLkrLKd6 # qJnuzK8Vcn0DvbDMemQFoxQ2Dsw4vEjoT1FpS54dNApZfKY61HAldytxNM89PZXU # P/5wWWURK+IfxiOg8W9lKMqzdIo7VA1R0V3Zp3DjjANwqAf4lEkTlCDQ0/fKJLKL # kzGBTpx6EYevvOi7XOc4zyh1uSqgr6UnbksIcFJqLbkIXIPbcNmA98Oskkkrvt6l # PAw/p4oDSRZreiwB7x9ykrjS6GS3NR39iTTFS+ENTqW8m6THuOmHHjQNC3zbJ6nJ # 6SXiLSvw4Smz8U07hqF+8CTXaETkVWz0dVVZw7knh1WZXOLHgDvundrAtuvz0D3T # +dYaNcwafsVCGZKUhQPL1naFKBy1p6llN3QgshRta6Eq4B40h5avMcpi54wm0i2e # PZD5pPIssoszQyF4//3DoK2O65Uck5Wggn8O2klETsJ7u8xEehGifgJYi+6I03Uu # T1j7FnrqVrOzaQoVJOeeStPeldYRNMmSF3voIgMFtNGh86w3ISHNm0IaadCKCkUe # 2LnwJKa8TIlwCUNVwppwn4D3/Pt5pwIDAQABo4IBlTCCAZEwDAYDVR0TAQH/BAIw # ADAdBgNVHQ4EFgQU5Dv88jHt/f3X85FxYxlQQ89hjOgwHwYDVR0jBBgwFoAU729T # SunkBnx6yuKQVvYv1Ensy04wDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoG # CCsGAQUFBwMIMIGVBggrBgEFBQcBAQSBiDCBhTAkBggrBgEFBQcwAYYYaHR0cDov # L29jc3AuZGlnaWNlcnQuY29tMF0GCCsGAQUFBzAChlFodHRwOi8vY2FjZXJ0cy5k # aWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRUaW1lU3RhbXBpbmdSU0E0MDk2 # U0hBMjU2MjAyNUNBMS5jcnQwXwYDVR0fBFgwVjBUoFKgUIZOaHR0cDovL2NybDMu # ZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0VGltZVN0YW1waW5nUlNBNDA5 # NlNIQTI1NjIwMjVDQTEuY3JsMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCGSAGG # /WwHATANBgkqhkiG9w0BAQsFAAOCAgEAZSqt8RwnBLmuYEHs0QhEnmNAciH45PYi # T9s1i6UKtW+FERp8FgXRGQ/YAavXzWjZhY+hIfP2JkQ38U+wtJPBVBajYfrbIYG+ # Dui4I4PCvHpQuPqFgqp1PzC/ZRX4pvP/ciZmUnthfAEP1HShTrY+2DE5qjzvZs7J # IIgt0GCFD9ktx0LxxtRQ7vllKluHWiKk6FxRPyUPxAAYH2Vy1lNM4kzekd8oEARz # FAWgeW3az2xejEWLNN4eKGxDJ8WDl/FQUSntbjZ80FU3i54tpx5F/0Kr15zW/mJA # xZMVBrTE2oi0fcI8VMbtoRAmaaslNXdCG1+lqvP4FbrQ6IwSBXkZagHLhFU9HCrG # /syTRLLhAezu/3Lr00GrJzPQFnCEH1Y58678IgmfORBPC1JKkYaEt2OdDh4GmO0/ # 5cHelAK2/gTlQJINqDr6JfwyYHXSd+V08X1JUPvB4ILfJdmL+66Gp3CSBXG6IwXM # ZUXBhtCyIaehr0XkBoDIGMUG1dUtwq1qmcwbdUfcSYCn+OwncVUXf53VJUNOaMWM # ts0VlRYxe5nK+At+DI96HAlXHAL5SlfYxJ7La54i71McVWRP66bW+yERNpbJCjyC # YG2j+bdpxo/1Cy4uPcU3AWVPGrbn5PhDBf3Froguzzhk++ami+r3Qrx5bIbY3TVz # giFI7Gq3zWcwggdWMIIFPqADAgECAhAFfARoFtaNDha0m0G6FxiVMA0GCSqGSIb3 # DQEBCwUAMGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFB # MD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5 # NiBTSEEzODQgMjAyMSBDQTEwHhcNMjYwNTE0MDAwMDAwWhcNMjcwNjE3MjM1OTU5 # WjBeMQswCQYDVQQGEwJVUzEXMBUGA1UECBMOTm9ydGggQ2Fyb2xpbmExFDASBgNV # BAcTC01vcnJpc3ZpbGxlMQ8wDQYDVQQKEwZMZW5vdm8xDzANBgNVBAMTBkxlbm92 # bzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALVacqHydjmqo6bF2tHw # cETPkOZLgPv1tJeIerpLcfmxLoX40GsUiY5l96B13bcfKjKBJQvHsj5D08RfGnif # K+sGsAX8xCgaYNLxq0COKn51GgLLMNgzlE4rR/8mUMuWK2fKXs24dmn7teE2+e+2 # dz/GfHXgrAIPvIr4PN+dZAFeoj6/wfpLLHZXtQLxKVmtDFc7gQZkM6Z8o0HrH2eX # 2MVNQwbZZWvSQhqRN66/LMcXxbLP8SAb6nKRCExflbI5i+MpEq+xOdiJxkP5dC5s # rQ31JGLDdhbuAcUJEdXATzgP1pT9is3uWZm3fro71Kvfa0XLwBZWR2ut8sQ6KUhd # 0Nsmd5c0f2PiD2uTd9mDWHQ34bu9mDunZaeWZrIlUP9MJ8TMM82ao/4tjFNK5m3T # hZUbCGwoepenXq8yjbKMKqHHvcoJY0SmkAIlUzWoRBbRC+uN0TwcR048sZDPo+ZC # gdONLPrjnsIU+NxuhfDMnj4UYbkRbtvnJ+U0O4Eu+ajRS87Li+jN2jHrABvAUXtP # J7DkkxTTfaspzlfxueoqVKcGLnu4PUAthKg3g4hleGgeo7s+krEDAzyvdBnY+b8U # dRH/BCxDr7G5ys/bCMPEzcUFT5LrrVBHO6N2U65iYkamzEo4okrrCNFDJPZC1G/p # NleTeiIadSPTT0tJGPK+nDo1AgMBAAGjggIDMIIB/zAfBgNVHSMEGDAWgBRoN+Dr # tjv4XxGG+/5hewiIZfROQjAdBgNVHQ4EFgQU7R8eucYUNncCkcWYWfCltSt/mxYw # PgYDVR0gBDcwNTAzBgZngQwBBAEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5k # aWdpY2VydC5jb20vQ1BTMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEF # BQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1odHRwOi8vY3JsMy5kaWdpY2VydC5j # b20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEzODQyMDIx # Q0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0 # VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5jcmwwgZQG # CCsGAQUFBwEBBIGHMIGEMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy # dC5jb20wXAYIKwYBBQUHMAKGUGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9E # aWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNIQTM4NDIwMjFDQTEu # Y3J0MAkGA1UdEwQCMAAwDQYJKoZIhvcNAQELBQADggIBAC0PiOF1B8Qdp2pcfb3P # 6MuzaeTUw77bfMSCcnwY8xFtkyRRp9H6X4ZaSTdAQge3d6NG7OcHnGQ3FKraZqT3 # ugjNEXvH1MXr8SNgFb7t7TyKDK0RmRK9idsycLJfRsmbj0FSTL12jqrzyHrQqkTq # KaDqYfIVan7VxP2On8gq3/OT368z1YJbRgfu/rNpXoYpXTKSfxaIXNE7rEiUwu+T # vi8wgfLqC/a7ujC8vbYRWgaHHsz3bjFVN/h1p78adds2GdxPzYFoS12+JKq2cKGe # Ma5Hbr2YQfHsbpQlzZglB3mGCci+uhN5YGaHleo3Ohd0BHci32RFOA1xdD8fS9TD # pGRMGLKF9z5WTmiGZVR4x54Qwtf4clbA5V0P9d4ppmLZJKdeaHd/JKONVJ9nYV1H # NA52CgvgRWpUc+jxnPL5A3J9qIvUXp98ZQifEyHBFX2VqWTdu9MldzklRo/eAT1k # MUVheRAcd32yLDiFvn7lp2nasJs8Hh+6bUxfHKbKBrNrw7CmcTDzFK119BkykWS0 # vL7xsbCEbavLIhW/ZqEjWA9k/v2FS2wbBQ9YOC8F3magnslxWlZWsym3Dxdm0Hr1 # WKHbnFcBscMiBQ+fhLDAqIG8tPPnNhc7QhOOlxQlbfEDh8QmQbUo2H9hJJEGiHJS # VwuAKbcWembFyAgLV02ASxS7MYIGSDCCBkQCAQEwfTBpMQswCQYDVQQGEwJVUzEX # MBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0 # ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExAhAFfARo # FtaNDha0m0G6FxiVMAkGBSsOAwIaBQCgeDAYBgorBgEEAYI3AgEMMQowCKACgACh # AoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAM # BgorBgEEAYI3AgEVMCMGCSqGSIb3DQEJBDEWBBRzawm2k+ZdIbjlfgFAtP9AYfLq # UjANBgkqhkiG9w0BAQEFAASCAgAKLJhCfICEMz3pjeOts6VhI9ChuwSIbhCB+++J # Q8/Lif2W9nV9xwLfiqxY32q5h908ucaGPW+qoIlH8S1sGTtbfmT3olGVI1scpia/ # YwL5pkSQN/hSUFhzZqMDlC+V1Bgf8EzCWGFvaYQIE6igF00sOQL0JT4n9gmykdWV # ESUd/FLF1lHE3zzGd1toO7O1VP3sTOt92gmDcSouXw3vFzUpKAlCB4QL9Oef1o5s # yw5Ajaz0GccBvV5uUwCw313I8+o8r0h3i/HH36Q540EiAWBYU1VGfAhgTaAYb9BS # rK23MotosJeMcEemfcll8/b7t9c1v0Q+t6eW9f9/5WKCSGiHeRJMt19/t85DWuby # Ls/3RL6SIP23Ot5wSQcmGo0AXCmW/oB53yZcM+oVoaSgmYnfXWiG3i619+1J3mMe # u4j3uv+zb24At2CBw+z0X6yoE5qhwzVbr/WSMFxfSmN1Oyf4zrFtlzTM5YTSYZoe # JZvtof9Qm6yACYJuJc8a1xYfRSjj7tdRnSGTEYhce6bRDvWGplQYbAG4ydGJRMBB # J7ank5D0CMLfkhs9vKHN1YtZatFStqmMD1QF/SjuF5MXCVSI9C0S0sFBeSwaolzk # u0cLbxwsYsIFF9ia1XbCvZcqGxGrw6rO1pg98vMT4aEUKFD/is+N8lP5Q69mEXlY # aioivqGCAyYwggMiBgkqhkiG9w0BCQYxggMTMIIDDwIBATB9MGkxCzAJBgNVBAYT # AlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQg # VHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYgMjAyNSBDQTEC # EAqA7xhLjfEFgtHEdqeVdGgwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMx # CwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0yNjA2MDkxNzIxMzRaMC8GCSqG # SIb3DQEJBDEiBCD4XuehzrG4iHSPxyup2UpaSa7fwMKmkSHVfDkv7OxEUTANBgkq # hkiG9w0BAQEFAASCAgC90dPIjVwlwY5J7v7BGj4jJ/0EyVAciRKkAzMGI96FB2Jk # duomqGK5+EhJvhkPoBhiYsr12XCAm/t4fhizd9paNnGoRS8DV4s65SpXFO+u/mNW # FcrzKx+llsgoZHvXz61dJSNQulm5E7UxhAGScJtZ8ZRmbcU1s6oS8tKl8FPUfMat # MAOImeUPhBFJww6yRB408so1X8L1Gltyp9W7uEwifG4KTyqEuROu03Nwl98TsYcQ # xJvAADHpR46kB6uclIv6LNudCDSytdOWV109MgQT/vW1v5L3bsA3Y4w6qczcqDrw # IQqvKraJZvNgXiMk/k1UIS3tmeYNGG/S1ag51wlvbUL67Hg6NxLdsg9HnNKUKh8u # 47AFxJUhMysCBIViDpG8uE29EXmoJaZe0VsDSosIoqQ1XGQ5YIs99S/QGgE7y1+i # dOUYFxfqm2IkgkzxSyS2VX1PEcJeklsKUMUo0Kpy4TAqVAVY/mlDQ35vM1z4vprj # U/5PXdlTlZHsNA6IBVIybLFxMKAHn0uZk6qfxIenCcgBbxP7Hu+/PwZ0Aa5NLqMF # 8qf1T+TNwu9Ukyl+Q+RTbXspMEFLUmLEDgbwyq9QdVD+qz/p+897alhbDeBmgRru # XIdV/JVr3eXPxejxTIdkzpEaD67JaS4UkkU62XTTIP/rT9JQJqjeLmxAXM2YqA== # SIG # End signature block |