UnifiStockTracker.psm1
function Write-Color { <# .SYNOPSIS Write-Color is a wrapper around Write-Host delivering a lot of additional features for easier color options. .DESCRIPTION Write-Color is a wrapper around Write-Host delivering a lot of additional features for easier color options. It provides: - Easy manipulation of colors, - Logging output to file (log) - Nice formatting options out of the box. - Ability to use aliases for parameters .PARAMETER Text Text to display on screen and write to log file if specified. Accepts an array of strings. .PARAMETER Color Color of the text. Accepts an array of colors. If more than one color is specified it will loop through colors for each string. If there are more strings than colors it will start from the beginning. Available colors are: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White .PARAMETER BackGroundColor Color of the background. Accepts an array of colors. If more than one color is specified it will loop through colors for each string. If there are more strings than colors it will start from the beginning. Available colors are: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White .PARAMETER StartTab Number of tabs to add before text. Default is 0. .PARAMETER LinesBefore Number of empty lines before text. Default is 0. .PARAMETER LinesAfter Number of empty lines after text. Default is 0. .PARAMETER StartSpaces Number of spaces to add before text. Default is 0. .PARAMETER LogFile Path to log file. If not specified no log file will be created. .PARAMETER DateTimeFormat Custom date and time format string. Default is yyyy-MM-dd HH:mm:ss .PARAMETER LogTime If set to $true it will add time to log file. Default is $true. .PARAMETER LogRetry Number of retries to write to log file, in case it can't write to it for some reason, before skipping. Default is 2. .PARAMETER Encoding Encoding of the log file. Default is Unicode. .PARAMETER ShowTime Switch to add time to console output. Default is not set. .PARAMETER NoNewLine Switch to not add new line at the end of the output. Default is not set. .PARAMETER NoConsoleOutput Switch to not output to console. Default all output goes to console. .EXAMPLE Write-Color -Text "Red ", "Green ", "Yellow " -Color Red,Green,Yellow .EXAMPLE Write-Color -Text "This is text in Green ", "followed by red ", "and then we have Magenta... ", "isn't it fun? ", "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan .EXAMPLE Write-Color -Text "This is text in Green ", "followed by red ", "and then we have Magenta... ", "isn't it fun? ", "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan -StartTab 3 -LinesBefore 1 -LinesAfter 1 .EXAMPLE Write-Color "1. ", "Option 1" -Color Yellow, Green Write-Color "2. ", "Option 2" -Color Yellow, Green Write-Color "3. ", "Option 3" -Color Yellow, Green Write-Color "4. ", "Option 4" -Color Yellow, Green Write-Color "9. ", "Press 9 to exit" -Color Yellow, Gray -LinesBefore 1 .EXAMPLE Write-Color -LinesBefore 2 -Text "This little ","message is ", "written to log ", "file as well." ` -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt" -TimeFormat "yyyy-MM-dd HH:mm:ss" Write-Color -Text "This can get ","handy if ", "want to display things, and log actions to file ", "at the same time." ` -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt" .EXAMPLE Write-Color -T "My text", " is ", "all colorful" -C Yellow, Red, Green -B Green, Green, Yellow Write-Color -t "my text" -c yellow -b green Write-Color -text "my text" -c red .EXAMPLE Write-Color -Text "Testuję czy się ładnie zapisze, czy będą problemy" -Encoding unicode -LogFile 'C:\temp\testinggg.txt' -Color Red -NoConsoleOutput .NOTES Understanding Custom date and time format strings: https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings Project support: https://github.com/EvotecIT/PSWriteColor Original idea: Josh (https://stackoverflow.com/users/81769/josh) #> [alias('Write-Colour')] [CmdletBinding()] param ( [alias ('T')] [String[]]$Text, [alias ('C', 'ForegroundColor', 'FGC')] [ConsoleColor[]]$Color = [ConsoleColor]::White, [alias ('B', 'BGC')] [ConsoleColor[]]$BackGroundColor = $null, [alias ('Indent')][int] $StartTab = 0, [int] $LinesBefore = 0, [int] $LinesAfter = 0, [int] $StartSpaces = 0, [alias ('L')] [string] $LogFile = '', [Alias('DateFormat', 'TimeFormat')][string] $DateTimeFormat = 'yyyy-MM-dd HH:mm:ss', [alias ('LogTimeStamp')][bool] $LogTime = $true, [int] $LogRetry = 2, [ValidateSet('unknown', 'string', 'unicode', 'bigendianunicode', 'utf8', 'utf7', 'utf32', 'ascii', 'default', 'oem')][string]$Encoding = 'Unicode', [switch] $ShowTime, [switch] $NoNewLine, [alias('HideConsole')][switch] $NoConsoleOutput ) if (-not $NoConsoleOutput) { $DefaultColor = $Color[0] if ($null -ne $BackGroundColor -and $BackGroundColor.Count -ne $Color.Count) { Write-Error "Colors, BackGroundColors parameters count doesn't match. Terminated." return } if ($LinesBefore -ne 0) { for ($i = 0; $i -lt $LinesBefore; $i++) { Write-Host -Object "`n" -NoNewline } } if ($StartTab -ne 0) { for ($i = 0; $i -lt $StartTab; $i++) { Write-Host -Object "`t" -NoNewline } } if ($StartSpaces -ne 0) { for ($i = 0; $i -lt $StartSpaces; $i++) { Write-Host -Object ' ' -NoNewline } } if ($ShowTime) { Write-Host -Object "[$([datetime]::Now.ToString($DateTimeFormat))] " -NoNewline } if ($Text.Count -ne 0) { if ($Color.Count -ge $Text.Count) { if ($null -eq $BackGroundColor) { for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewline } } else { for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewline } } } else { if ($null -eq $BackGroundColor) { for ($i = 0; $i -lt $Color.Length ; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewline } for ($i = $Color.Length; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -NoNewline } } else { for ($i = 0; $i -lt $Color.Length ; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewline } for ($i = $Color.Length; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -BackgroundColor $BackGroundColor[0] -NoNewline } } } } if ($NoNewLine -eq $true) { Write-Host -NoNewline } else { Write-Host } if ($LinesAfter -ne 0) { for ($i = 0; $i -lt $LinesAfter; $i++) { Write-Host -Object "`n" -NoNewline } } } if ($Text.Count -and $LogFile) { $TextToFile = "" for ($i = 0; $i -lt $Text.Length; $i++) { $TextToFile += $Text[$i] } $Saved = $false $Retry = 0 Do { $Retry++ try { if ($LogTime) { "[$([datetime]::Now.ToString($DateTimeFormat))] $TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop -WhatIf:$false } else { "$TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop -WhatIf:$false } $Saved = $true } catch { if ($Saved -eq $false -and $Retry -eq $LogRetry) { Write-Warning "Write-Color - Couldn't write to log file $($_.Exception.Message). Tried ($Retry/$LogRetry))" } else { Write-Warning "Write-Color - Couldn't write to log file $($_.Exception.Message). Retrying... ($Retry/$LogRetry)" } } } Until ($Saved -eq $true -or $Retry -ge $LogRetry) } } function Get-UnifiStock { <# .SYNOPSIS Get the stock status of Ubiquiti products from their online store. .DESCRIPTION Get the stock status of Ubiquiti products from their online store. .PARAMETER Store The store to check for stock. Valid values are: Europe, USA, UK If you want to use a different store you can use Get-UnifiStockLegacy for other countries. This is because the legacy store has a different format for the JSON data, and are not yet migrated to new "look" .PARAMETER Collection Which collection to list. .EXAMPLE Get-UnifiStock -Store USA -Collection AccessoriesCabling, CableBox | Sort-Object -Property Name | Format-Table .EXAMPLE Get-UnifiStock -Store Europe -Collection HostingAndGatewaysCloud,DreamMachine, DreamRouter | Sort-Object -Property Name | Format-Table .EXAMPLE Get-UnifiStock -Store Europe | Sort-Object -Property Name | Format-Table .NOTES General notes #> [cmdletbinding()] param( [ValidateSet('Europe', 'USA', 'UK')] [Parameter(Mandatory)][string] $Store, [ValidateSet( 'AccessoriesCabling', 'AccessPointMounting', 'AccessPointSkins', 'CableBox', 'CablePatch', 'CableSFP', 'CameraEnhancers', 'CameraSecurityBulletDSLR', 'CameraSecurityBulletHighPerformance', 'CameraSecurityBulletStandard', 'CameraSecurityCompactPoEWired', 'CameraSecurityCompactWiFiConnected', 'CameraSecurityDome360', 'CameraSecurityDomeSlim', 'CameraSecurityDoorAccessAccessories', 'CameraSecurityDoorAccessReaders', 'CameraSecurityDoorAccessStarterKit', 'CameraSecurityInteriorDesign', 'CameraSecurityNVRLargeScale', 'CameraSecurityNVRMidScale', 'CameraSecurityPTZ', 'CameraSecuritySpecialChime', 'CameraSecuritySpecialSensor', 'CameraSecuritySpecialViewport', 'CameraSecuritySpecialWiFiDoorbell', 'CameraSkins', 'DesktopStands', 'DeviceMounting', 'DreamMachine', 'DreamRouter', 'HDDStorage', 'HostingAndGatewaysCloud', 'HostingAndGatewaysLargeScale', 'HostingAndGatewaysSmallScale', 'InstallationsRackmount', 'InternetBackup', 'NewIntegrationsAVDisplayMounting', 'NewIntegrationsAVGiantPoETouchscreens', 'NewIntegrationsPhoneATA', 'NewIntegrationsPhoneCompact', 'NewIntegrationsPhoneExecutive', 'PoEAndPower', 'PoEPower', 'PowerTechPowerRedundancy', 'PowerTechUninterruptiblePoE', 'SwitchingEnterpriseAggregation', 'SwitchingEnterprisePoE', 'SwitchingProEthernet', 'SwitchingProPoE', 'SwitchingStandardEthernet', 'SwitchingStandardPoE', 'SwitchingUtility10GbpsEthernet', 'SwitchingUtilityMini', 'SwitchingUtilityPoE', 'WiFiBuildingBridge10Gigabit', 'WiFiFlagshipCompact', 'WiFiFlagshipHighCapacity', 'WiFiInWallOutletMesh', 'WiFiMan', 'WiFiOutdoorFlexible', 'WiFiFlagshipLongRange', 'WiFiOutdoorLongRange', "NewIntegrationsMobileRouting", "CameraSecurityDoorAccessHub", "NewIntegrationsAVDigitalSignage", "SwitchingUtilityIndustrial", "SwitchingUtilityIndoorOutdoor", "SwitchingEnterprise10GbpsEthernet", "SwitchingUtilityHiPowerPoE", "WiFiMegaCapacity", "PowerTechUninterruptiblePower", "PowerTechPowerDistribution", "CameraSecuritySpecialFloodlight", "DreamWall", "NewIntegrationsEVCharging", "CloudKeyRackMount", "AmpliFiMesh", "AmpliFiAlien", "WiFiInWallCompact", "WiFiInWallHighCapacity", "AccessPointAntennas", "CameraSecurityBulletEnhancedAI", "WiFiBuildingBridgeGigabit", # organizational collections "Cabling", "AccessPointMounting", "AccessPointSkins", "CableSFP", "CameraEnhancers", "CameraSkins", "DeviceMounting", "DisplayMounting", "HostingAndGatewaysCloud", "HostingAndGatewaysLargeScale", "HostingAndGatewaysSmallScale", "PoEAndPower", "PoEPower", "WiFiManager", "InternetBackup", "PowerRedundancy", "ProEthernetSwitching", "StandardEthernetSwitching", "StandardPoESwitching", "10GbpsEthernetSwitching", "PoESwitching", "FlagshipCompactWiFi", "FlagshipHighCapacityWiFi", "InWallHighCapacityWiFi", "OutdoorFlexibleWiFi", "UICare" )][string[]] $Collection ) $Stores = @{ Europe = 'eu' USA = 'us' UK = 'uk' } $StoreLinks = @{ Europe = 'https://eu.store.ui.com/eu/en' USA = 'https://store.ui.com/us/en' UK = 'https://uk.store.ui.com/uk/en' } $Accessories = @{ "uisp-accessories-cabling" = "AccessoriesCabling" "unifi-accessory-tech-access-point-mounting" = "AccessPointMounting" "unifi-accessory-tech-access-point-skins" = "AccessPointSkins" "unifi-accessory-tech-cable-box" = "CableBox" "unifi-accessory-tech-cable-patch" = "CablePatch" "unifi-accessory-tech-cable-sfp" = "CableSFP" "unifi-accessory-tech-camera-enhancers" = "CameraEnhancers" "unifi-accessory-tech-camera-skins" = "CameraSkins" "unifi-accessory-tech-desktop-stands" = "DesktopStands" "unifi-accessory-tech-device-mounting" = "DeviceMounting" "unifi-accessory-tech-hdd-storage" = "HDDStorage" "unifi-accessory-tech-hosting-and-gateways-cloud" = "HostingAndGatewaysCloud" "unifi-accessory-tech-hosting-and-gateways-large-scale" = "HostingAndGatewaysLargeScale" "unifi-accessory-tech-hosting-and-gateways-small-scale" = "HostingAndGatewaysSmallScale" "unifi-accessory-tech-installations-rackmount" = "InstallationsRackmount" "unifi-accessory-tech-poe-and-power" = "PoEAndPower" "unifi-accessory-tech-poe-power" = "PoEPower" "unifi-accessory-tech-wifiman" = "WiFiMan" "unifi-camera-security-bullet-dslr" = "CameraSecurityBulletDSLR" "unifi-camera-security-bullet-high-performance" = "CameraSecurityBulletHighPerformance" "unifi-camera-security-bullet-standard" = "CameraSecurityBulletStandard" "unifi-camera-security-compact-poe-wired" = "CameraSecurityCompactPoEWired" "unifi-camera-security-compact-wifi-connected" = "CameraSecurityCompactWiFiConnected" "unifi-camera-security-dome-360" = "CameraSecurityDome360" "unifi-camera-security-dome-slim" = "CameraSecurityDomeSlim" "unifi-camera-security-door-access-accessories" = "CameraSecurityDoorAccessAccessories" "unifi-camera-security-door-access-readers" = "CameraSecurityDoorAccessReaders" "unifi-camera-security-door-access-starter-kit" = "CameraSecurityDoorAccessStarterKit" "unifi-camera-security-interior-design" = "CameraSecurityInteriorDesign" "unifi-camera-security-nvr-large-scale" = "CameraSecurityNVRLargeScale" "unifi-camera-security-nvr-mid-scale" = "CameraSecurityNVRMidScale" "unifi-camera-security-ptz" = "CameraSecurityPTZ" "unifi-camera-security-special-chime" = "CameraSecuritySpecialChime" "unifi-camera-security-special-sensor" = "CameraSecuritySpecialSensor" "unifi-camera-security-special-viewport" = "CameraSecuritySpecialViewport" "unifi-camera-security-special-wifi-doorbell" = "CameraSecuritySpecialWiFiDoorbell" "unifi-dream-machine" = "DreamMachine" "unifi-dream-router" = "DreamRouter" "unifi-internet-backup" = "InternetBackup" "unifi-new-integrations-av-display-mounting" = "NewIntegrationsAVDisplayMounting" "unifi-new-integrations-av-giant-poe-touchscreens" = "NewIntegrationsAVGiantPoETouchscreens" "unifi-new-integrations-phone-ata" = "NewIntegrationsPhoneATA" "unifi-new-integrations-phone-compact" = "NewIntegrationsPhoneCompact" "unifi-new-integrations-phone-executive" = "NewIntegrationsPhoneExecutive" "unifi-power-tech-power-redundancy" = "PowerTechPowerRedundancy" "unifi-power-tech-uninterruptible-poe" = "PowerTechUninterruptiblePoE" "unifi-switching-enterprise-aggregation" = "SwitchingEnterpriseAggregation" "unifi-switching-enterprise-power-over-ethernet" = "SwitchingEnterprisePoE" "unifi-switching-pro-ethernet" = "SwitchingProEthernet" "unifi-switching-pro-power-over-ethernet" = "SwitchingProPoE" "unifi-switching-standard-ethernet" = "SwitchingStandardEthernet" "unifi-switching-standard-power-over-ethernet" = "SwitchingStandardPoE" "unifi-switching-utility-10-gbps-ethernet" = "SwitchingUtility10GbpsEthernet" "unifi-switching-utility-mini" = "SwitchingUtilityMini" "unifi-switching-utility-poe" = "SwitchingUtilityPoE" "unifi-wifi-building-bridge-10-gigabit" = "WiFiBuildingBridge10Gigabit" "unifi-wifi-flagship-compact" = "WiFiFlagshipCompact" "unifi-wifi-flagship-high-capacity" = "WiFiFlagshipHighCapacity" "unifi-wifi-inwall-outlet-mesh" = "WiFiInWallOutletMesh" "unifi-wifi-outdoor-flexible" = "WiFiOutdoorFlexible" "unifi-wifi-flagship-long-range" = "WiFiFlagshipLongRange" "unifi-wifi-outdoor-long-range" = "WiFiOutdoorLongRange" "unifi-new-integrations-mobile-routing" = "NewIntegrationsMobileRouting" "unifi-camera-security-door-access-hub" = "CameraSecurityDoorAccessHub" "unifi-new-integrations-av-digital-signage" = "NewIntegrationsAVDigitalSignage" "unifi-switching-utility-industrial" = "SwitchingUtilityIndustrial" "unifi-switching-utility-indoor-outdoor" = "SwitchingUtilityIndoorOutdoor" "unifi-switching-enterprise-10-gbps-ethernet" = "SwitchingEnterprise10GbpsEthernet" "unifi-switching-utility-hi-power-poe" = "SwitchingUtilityHiPowerPoE" "unifi-wifi-mega-capacity" = "WiFiMegaCapacity" "unifi-power-tech-uninterruptible-power" = "PowerTechUninterruptiblePower" "unifi-power-tech-power-distribution" = "PowerTechPowerDistribution" "unifi-camera-security-special-floodlight" = "CameraSecuritySpecialFloodlight" "unifi-dream-wall" = "DreamWall" "unifi-new-integrations-ev-charging" = "NewIntegrationsEVCharging" "cloud-key-rack-mount" = "CloudKeyRackMount" "amplifi-mesh" = "AmpliFiMesh" "amplifi-alien" = "AmpliFiAlien" "unifi-wifi-inwall-compact" = "WiFiInWallCompact" "unifi-wifi-inwall-high-capacity" = "WiFiInWallHighCapacity" "unifi-accessory-tech-access-point-antennas" = "AccessPointAntennas" "unifi-camera-security-bullet-enhanced-ai" = "CameraSecurityBulletEnhancedAI" "unifi-wifi-building-bridge-gigabit" = "WiFiBuildingBridgeGigabit" "accessories-cabling" = "Cabling" "accessory-tech-access-point-mounting" = "AccessPointMounting" "accessory-tech-access-point-skins" = "AccessPointSkins" "accessory-tech-cable-sfp" = "CableSFP" "accessory-tech-camera-enhancers" = "CameraEnhancers" "accessory-tech-camera-skins" = "CameraSkins" "accessory-tech-device-mounting" = "DeviceMounting" "accessory-tech-display-mounting" = "DisplayMounting" "accessory-tech-hosting-and-gateways-cloud" = "HostingAndGatewaysCloud" "accessory-tech-hosting-and-gateways-large-scale" = "HostingAndGatewaysLargeScale" "accessory-tech-hosting-and-gateways-small-scale" = "HostingAndGatewaysSmallScale" "accessory-tech-poe-and-power" = "PoEAndPower" "accessory-tech-poe-power" = "PoEPower" "accessory-tech-wifiman" = "WiFiManager" "internet-backup" = "InternetBackup" "power-tech-power-redundancy" = "PowerRedundancy" "switching-pro-ethernet" = "ProEthernetSwitching" "switching-standard-ethernet" = "StandardEthernetSwitching" "switching-standard-power-over-ethernet" = "StandardPoESwitching" "switching-utility-10-gbps-ethernet" = "10GbpsEthernetSwitching" "switching-utility-poe" = "PoESwitching" "wifi-flagship-compact" = "FlagshipCompactWiFi" "wifi-flagship-high-capacity" = "FlagshipHighCapacityWiFi" "wifi-inwall-high-capacity" = "InWallHighCapacityWiFi" "wifi-outdoor-flexible" = "OutdoorFlexibleWiFi" "ui-care" = "UICare" } $UrlStore = $Stores[$Store] $UrlStoreLink = $StoreLinks[$Store] $ProgressPreference = 'SilentlyContinue' try { Write-Verbose -Message "Get-UnifiStock - Getting Unifi products" $invokeRestMethodSplat = @{ UseBasicParsing = $true Uri = "https://ecomm.svc.ui.com/graphql" Method = 'Post' ContentType = "application/json" } $Limit = 250 $Offset = 0 $Total = 1 $Products = while ($offset -lt $total) { $Body = [ordered] @{ operationName = "GetProductsForLandingPagePro" variables = @{ input = @{ limit = $Limit offset = $Offset filter = @{ storeId = "$UrlStore" language = "en" line = "Unifi" } } } query = @" query GetProductsForLandingPagePro(`$input: StorefrontProductListInput!) { storefrontProducts(input: `$input) { pagination { limit offset total __typename } items { ...LandingProProductFragment __typename } __typename } } fragment LandingProProductFragment on StorefrontProduct { id title shortTitle name slug collectionSlug organizationalCollectionSlug shortDescription tags { name __typename } gallery { ...ImageOnlyGalleryFragment __typename } options { id title values { id title __typename } __typename } variants { id sku status title galleryItemIds isEarlyAccess optionValueIds displayPrice { ...MoneyFragment __typename } hasPurchaseHistory __typename } __typename } fragment ImageOnlyGalleryFragment on Gallery { id items { id data { __typename ... on Asset { id mimeType url height width __typename } } __typename } type __typename } fragment MoneyFragment on Money { amount currency __typename } "@ } $invokeRestMethodSplat.Body = $Body | ConvertTo-Json -Depth 10 $Output = Invoke-RestMethod @invokeRestMethodSplat $CurrentProducts = $Output.data.storefrontProducts.items $CurrentProducts $Pagination = $Output.data.storefrontProducts.pagination $total = $pagination.total $offset += $limit } } catch { Write-Color -Text "Unable to get Unifi products. Error: $($_.Exception.Message)" -Color Red return } if ($Products) { Write-Verbose -Message "Get-UnifiStock - Got $($Products.Count) products" $UnifiProducts = foreach ($Product in $Products) { foreach ($Variant in $Product.variants) { if ($Product.collectionSlug) { $Category = $Accessories[$Product.collectionSlug] } elseif ($Product.organizationalCollectionSlug) { $Category = $Accessories[$Product.organizationalCollectionSlug] } else { $Category = 'Unknown' } if ($Collection) { if ($Category -notin $Collection) { continue } } [PSCustomObject] @{ Name = $Product.title ShortName = $Product.shortTitle Available = $Variant.status -eq 'AVAILABLE' Category = $Category Collection = $Product.collectionSlug OrganizationalCollectionSlug = $Product.organizationalCollectionSlug SKU = $Variant.sku SKUName = $Variant.title EarlyAccess = $Variant.isEarlyAccess ProductUrl = "$UrlStoreLink/collections/$($Product.collectionSlug)/products/$($Product.slug)" } } } $UnifiProducts } } function Get-UnifiStockLegacy { <# .SYNOPSIS Get the stock status of Ubiquiti products from their online store. .DESCRIPTION Get the stock status of Ubiquiti products from their online store. .PARAMETER Store The store to check for stock. Valid values are: Europe, USA, Brazil, India, Japan, Taiwan, Signapore, Mexico, China .PARAMETER Collection Which collection to list. .EXAMPLE Get-UnifiStock -Store USA -Collection Protect, ProtectAccessories, ProtectNVR | Sort-Object -Property Name | Format-Table .EXAMPLE Get-UnifiStock -Store Europe -Collection Protect, NetworkWifi | Sort-Object -Property Name | Format-Table .EXAMPLE Get-UnifiStock -Store Europe | Sort-Object -Property Name | Format-Table .NOTES General notes #> [cmdletbinding()] param( [ValidateSet('Brazil', 'India', 'Japan', 'Taiwan', 'Signapore', 'Mexico', 'China')] [Parameter(Mandatory)] [string] $Store, [ValidateSet( 'EarlyAccess', 'EarlyAccessConnect', 'EarlyAccessDoorAccess', 'EarlyAccessSmartpower', 'EarlyAccessUispFiber', 'EarlyAccessUispWired', 'EarlyAccessUispWireless', 'EarlyAccessUnifiNetworkHost', 'NetworkHost', 'NetworkOS', 'NetworkRoutingOffload', 'NetworkRoutingSwitching', 'NetworkSmartPower', 'NetworkSwitching', 'NetworkWifi', 'OperatorAirmaxAndLtu', 'OperatorIspInfrastructure', 'Protect', 'ProtectAccessories', 'ProtectNVR', 'UnifiAccessories', 'UnifiConnect', 'UnifiDoorAccess', 'UnifiPhoneSystem' )] [string[]] $Collection ) $Stores = @{ Europe = 'https://eu.store.ui.com' USA = 'https://store.ui.com' Brazil = 'https://br.store.ui.com' India = 'https://store-ui.in' Japan = 'https://jp.store.ui.com' Taiwan = 'https://tw.store.ui.com' Singapore = 'https://sg.store.ui.com' Mexico = 'https://mx.store.ui.com' China = 'https://store.ui.com.cn' } $Collections = @{ Protect = 'unifi-protect' ProtectNVR = 'unifi-protect-nvr' ProtectAccessories = 'unifi-protect-accessories' NetworkOS = 'unifi-network-unifi-os-consoles' NetworkRoutingSwitching = 'unifi-network-routing-switching' NetworkSmartPower = 'unifi-network-smartpower' NetworkRoutingOffload = 'unifi-network-routing-offload' NetworkHost = 'unifi-network-host' NetworkSwitching = 'unifi-network-switching' NetworkWifi = 'unifi-network-wireless' UnifiAccessories = 'unifi-accessories' EarlyAccess = 'early-access' EarlyAccessDoorAccess = 'early-access-door-access' EarlyAccessConnect = 'early-access-connect' EarlyAccessSmartpower = 'early-access-smartpower' EarlyAccessUispFiber = 'early-access-uisp-fiber' EarlyAccessUispWired = 'early-access-uisp-wired' EarlyAccessUispWireless = 'early-access-uisp-wireless' EarlyAccessUnifiNetworkHost = 'early-access-unifi-network-host' UnifiConnect = 'unifi-connect' UnifiDoorAccess = 'unifi-door-access' OperatorAirmaxAndLtu = 'operator-airmax -and -ltu' OperatorIspInfrastructure = 'operator-isp-infrastructure' UnifiPhoneSystem = 'unifi-phone-system' } $UrlStore = $Stores[$Store] if (-not $Collection) { $Collection = $Collections.Keys } foreach ($Category in $Collection) { $UrlCollection = $Collections[$Category] $Url = "$UrlStore/collections/$UrlCollection" $UrlProducts = "$Url/products.json" $ProgressPreference = 'SilentlyContinue' try { Write-Verbose -Message "Get-UnifiStock - Getting $UrlProducts" $Output = Invoke-WebRequest -Uri $UrlProducts -ErrorAction Stop -Verbose:$false } catch { Write-Color -Text "Unable to get $UrlProducts. Error: $($_.Exception.Message)" -Color Red return } if ($Output) { $OutputJSON = $Output.Content | ConvertFrom-Json $UnifiProducts = foreach ($Product in $OutputJSON.products) { foreach ($Variant in $Product.variants) { try { $DateCreated = [DateTime]::Parse($Variant.created_at) } catch { Write-Verbose -Message "Unable to parse date: $($Variant.created_at). Skipping" $DateCreated = $null } try { $DateUpdated = [DateTime]::Parse($Variant.updated_at) } catch { Write-Verbose -Message "Unable to parse date: $($Variant.updated_at). Skipping" $DateUpdated = $null } [PSCustomObject] @{ Name = $Product.title Available = $Variant.available Category = $Category Price = $Variant.price SKU = $Variant.sku SKUName = $Variant.title Created = $DateCreated Updated = $DateUpdated ProductUrl = "$Url/products/$($Product.handle)" Tags = $Product.tags } } } $UnifiProducts } } } function Wait-UnifiStock { <# .SYNOPSIS When run waits for the specified SKU or Product to be in stock in Ubiquiti's online store. .DESCRIPTION When run waits for the specified SKU or Product to be in stock in Ubiquiti's online store. Once the product is in stock the function will play a beep, read which product is in stock and open a browser to specific product page. .PARAMETER ProductName One or more products to wait for to be in stock with search by it's Name .PARAMETER ProductSKU One or more products to wait for to be in stock with search by it's SKU .PARAMETER Store The store to check for stock. Valid values are Europe, USA and UK. If you want to use a different store you can use Wait-UnifiStockLegacy for other countries. This is because the legacy store has a different format for the JSON data, and are not yet migrated to new "look" .PARAMETER Seconds The number of seconds to wait between checks. Default is 60 seconds. .PARAMETER DoNotOpenWebsite If specified the website will not be opened when the product is in stock. .PARAMETER DoNotPlaySound If specified the sound will not be played when the product is in stock. .PARAMETER DoNotUseBeep If specified the beep will not be played when the product is in stock. .EXAMPLE Wait-UnifiStock -ProductSKU 'UDR-EU' -ProductName 'Switch Flex XG' -Seconds 60 -Store Europe .EXAMPLE Wait-UnifiStock -ProductName 'UniFi6 Mesh', 'G4 Doorbell Pro', 'Camera G4 Pro', 'Test' -Seconds 60 -Store Europe .EXAMPLE Wait-UnifiStock -ProductName 'UniFi6 Mesh', 'G4 Doorbell Pro', 'Camera G4 Pro', 'Test' -Seconds 60 -DoNotUseBeep -Store Europe .NOTES General notes #> [cmdletBinding()] param( [string[]] $ProductName, [string[]] $ProductSKU, [parameter(Mandatory)][ValidateSet('Europe', 'USA', 'UK')][string] $Store, [int] $Seconds = 60, [switch] $DoNotOpenWebsite, [switch] $DoNotPlaySound, [switch] $DoNotUseBeep ) $Cache = [ordered] @{} $CurrentStock = Get-UnifiStock -Store $Store foreach ($Product in $CurrentStock) { $Cache[$Product.Name] = $Product $Cache[$Product.SKU] = $Product } [Array] $ApplicableProducts = @( foreach ($Name in $ProductName) { $Found = $false foreach ($StockName in $CurrentStock.Name) { if ($StockName -like "$Name") { $StockName $found = $true } } if (-not $Found) { Write-Color -Text "Product Name '$Name' not found in stock. Ignoring" -Color Red } } foreach ($Name in $ProductSKU) { if ($Name -in $CurrentStock.SKU) { $Name } else { Write-Color -Text "Product SKU '$Name' not found in stock. Ignoring" -Color Red } } ) $ApplicableProducts = $ApplicableProducts | Sort-Object -Unique if ($ApplicableProducts.Count -eq 0) { Write-Color -Text "No products requested by user not found on list of available products. Exiting" -Color Red return } Write-Color -Text "Setting up monitoring for ", ($ApplicableProducts -join ", ") -Color Yellow, Green $Count = 0 Do { if ($Count -ne 0) { Start-Sleep -Seconds $Seconds } Write-Color -Text "Checking stock..." -Color Yellow $CurrentResults = Get-UnifiStock -Store $Store | Where-Object { $_.Name -in $ApplicableProducts -or $_.SKU -in $ApplicableProducts } | Sort-Object -Property Name Write-Color -Text "Checking stock... Done, sleeping for $Seconds seconds" -Color Green $Count++ } While ($CurrentResults.Available -notcontains $true) foreach ($Product in $CurrentResults | Where-Object { $_.Available -eq $true }) { Write-Color -Text "Product ", $($Product.Name), " is in stock! ", "SKU: $($Product.SKU)" -Color Yellow, Green, Yellow, Green if (-not $DoNotOpenWebsite) { Start-Process $Product.ProductUrl } if (-not $DoNotPlaySound) { try { $Voice = New-Object -ComObject Sapi.spvoice -ErrorAction Stop } catch { Write-Color -Text "Failed to create voice object. Error: $($_.Exception.Message)" -Color Red } if ($Voice) { $voice.rate = 0 try { $null = $voice.speak("Hey,there is stock available for $($Product.Name)") } catch { Write-Color -Text "Failed to speak. Error: $($_.Exception.Message)" -Color Red } } } if (-not $DoNotUseBeep) { [console]::beep(500, 300) } } } function Wait-UnifiStockLegacy { <# .SYNOPSIS When run waits for the specified SKU or Product to be in stock in Ubiquiti's online store. .DESCRIPTION When run waits for the specified SKU or Product to be in stock in Ubiquiti's online store. Once the product is in stock the function will play a beep, read which product is in stock and open a browser to specific product page. .PARAMETER ProductName One or more products to wait for to be in stock with search by it's Name .PARAMETER ProductSKU One or more products to wait for to be in stock with search by it's SKU .PARAMETER Store The store to check for stock. Valid values are Brazil, India, Japan, Taiwan, Signapore, Mexico, China If you want EU/USA store you can use Wait-UnifiStock for those. .PARAMETER Seconds The number of seconds to wait between checks. Default is 60 seconds. .PARAMETER DoNotOpenWebsite If specified the website will not be opened when the product is in stock. .PARAMETER DoNotPlaySound If specified the sound will not be played when the product is in stock. .PARAMETER DoNotUseBeep If specified the beep will not be played when the product is in stock. .EXAMPLE Wait-UnifiStockLegacy -ProductSKU 'UDR-EU' -ProductName 'Switch Flex XG' -Seconds 60 -Store Brazil .EXAMPLE Wait-UnifiStockLegacy -ProductName 'UniFi6 Mesh', 'G4 Doorbell Pro', 'Camera G4 Pro', 'Test' -Seconds 60 -Store Brazil .EXAMPLE Wait-UnifiStockLegacy -ProductName 'UniFi6 Mesh', 'G4 Doorbell Pro', 'Camera G4 Pro', 'Test' -Seconds 60 -DoNotUseBeep -Store Brazil .NOTES General notes #> [cmdletBinding()] param( [string[]] $ProductName, [string[]] $ProductSKU, [parameter(Mandatory)][ValidateSet('Brazil', 'India', 'Japan', 'Taiwan', 'Signapore', 'Mexico', 'China')][string] $Store, [int] $Seconds = 60, [switch] $DoNotOpenWebsite, [switch] $DoNotPlaySound, [switch] $DoNotUseBeep ) $Cache = [ordered] @{} $CurrentStock = Get-UnifiStockLegacy -Store $Store foreach ($Product in $CurrentStock) { $Cache[$Product.Name] = $Product $Cache[$Product.SKU] = $Product } [Array] $ApplicableProducts = @( foreach ($Name in $ProductName) { $Found = $false foreach ($StockName in $CurrentStock.Name) { if ($StockName -like "$Name") { $StockName $found = $true } } if (-not $Found) { Write-Color -Text "Product Name '$Name' not found in stock. Ignoring" -Color Red } } foreach ($Name in $ProductSKU) { if ($Name -in $CurrentStock.SKU) { $Name } else { Write-Color -Text "Product SKU '$Name' not found in stock. Ignoring" -Color Red } } ) if ($ApplicableProducts.Count -eq 0) { Write-Color -Text "No products requested by user not found on list of available products. Exiting" -Color Red return } $Collections = @( foreach ($Product in $ApplicableProducts) { $Cache[$Product].Category } ) | Select-Object -Unique Write-Color -Text "Setting up monitoring for ", ($ApplicableProducts -join ", ") -Color Yellow, Green $Count = 0 Do { if ($Count -ne 0) { Start-Sleep -Seconds $Seconds } Write-Color -Text "Checking stock..." -Color Yellow $CurrentResults = Get-UnifiStockLegacy -Store $Store -Collection $Collections | Where-Object { $_.Name -in $ApplicableProducts -or $_.SKU -in $ApplicableProducts } | Sort-Object -Property Name Write-Color -Text "Checking stock... Done, sleeping for $Seconds seconds" -Color Green $Count++ } While ($CurrentResults.Available -notcontains $true) foreach ($Product in $CurrentResults | Where-Object { $_.Available -eq $true }) { Write-Color -Text "Product ", $($Product.Name), " is in stock! ", "SKU: $($Product.SKU)" -Color Yellow, Green, Yellow, Green if (-not $DoNotOpenWebsite) { Start-Process $Product.ProductUrl } if (-not $DoNotPlaySound) { try { $Voice = New-Object -ComObject Sapi.spvoice -ErrorAction Stop } catch { Write-Color -Text "Failed to create voice object. Error: $($_.Exception.Message)" -Color Red } if ($Voice) { $voice.rate = 0 try { $null = $voice.speak("Hey,there is stock available for $($Product.Name)") } catch { Write-Color -Text "Failed to speak. Error: $($_.Exception.Message)" -Color Red } } } if (-not $DoNotUseBeep) { [console]::beep(500, 300) } } } Export-ModuleMember -Function @('Get-UnifiStock', 'Get-UnifiStockLegacy', 'Wait-UnifiStock', 'Wait-UnifiStockLegacy') -Alias @() -Cmdlet "*" # SIG # Begin signature block # MIItsQYJKoZIhvcNAQcCoIItojCCLZ4CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBvxQT0xm1oc1xN # CX7l0wq5jMz1dVeNfQjdxZ1zrC1VsqCCJrQwggWNMIIEdaADAgECAhAOmxiO+dAt # 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV # BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa # Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD # ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E # MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy # unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF # xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1 # 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB # MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR # WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6 # nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB # YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S # UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x # q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB # NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP # TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC # AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp # Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0 # aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB # LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc # Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov # Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy # oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW # juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF # mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z # twGpn1eqXijiuZQwggWQMIIDeKADAgECAhAFmxtXno4hMuI5B72nd3VcMA0GCSqG # SIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx # GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy # dXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8MCIw # aTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauyefLK # EdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34LzB4Tm # dDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+xembu # d8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhAkHnD # eMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1LyuGwN1 # XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2PVld # QnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37AlLTS # YW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD76GSm # M9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/ybzT # QRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXAj6Kx # fgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD # VR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzANBgkq # hkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNkaA9Wz3eucPn9mkqZucl4 # XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjSPMFDQK4dUPVS/JA7u5iZ # aWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK7VB6fWIhCoDIc2bRoAVg # X+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eBcg3AFDLvMFkuruBx8lbk # apdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp5aPNoiBB19GcZNnqJqGL # FNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msgdDDS4Dk0EIUhFQEI6FUy # 3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vriRbgjU2wGb2dVf0a1TD9u # KFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ79ARj6e/CVABRoIoqyc54 # zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5nLGbsQAe79APT0JsyQq8 # 7kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3i0objwG2J5VT6LaJbVu8 # aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0HEEcRrYc9B9F1vM/zZn4w # ggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVT # MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1 # c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqG # SIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbS # g9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9 # /UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXn # HwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0 # VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4f # sbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUmcJgmf6AaRyBD40Nj # gHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0 # QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvv # mz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T # /jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk # 42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXcheMBK9Rp6103a50g5r # mQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4E # FgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5n # P+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcG # CCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu # Y29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln # aUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8v # Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNV # HSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIB # AH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxp # wc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIl # zpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQ # cAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfe # Kuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+j # Sbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJsh # IUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6 # OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDw # N7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR # 81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2 # VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIGsDCCBJigAwIBAgIQ # CK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQGEwJVUzEV # MBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t # MSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjEwNDI5MDAw # MDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln # aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT # aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMIICIjANBgkqhkiG9w0BAQEF # AAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5WRuxiEL1M4zrPYGXcMW7xIUmMJ+k # jmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJPDqFX/IiZwZHMgQM+TXAkZLON4gh9 # NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXzENOLsvsI8IrgnQnAZaf6mIBJNYc9 # URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bqHPNlaJGiTUyCEUhSaN4QvRRXXegY # E2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTCfMjqGzLmysL0p6MDDnSlrzm2q2AS # 4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaDG7dqZy3SvUQakhCBj7A7CdfHmzJa # wv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urOkfW+0/tvk2E0XLyTRSiDNipmKF+w # c86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7ADK5GyNnm+960IHnWmZcy740hQ83eR # Gv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4R+Z1MI3sMJN2FKZbS110YU0/EpF2 # 3r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlNWdt4z4FKPkBHX8mBUHOFECMhWWCK # ZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0IU0F8WD1Hs/q27IwyCQLMbDwMVhEC # AwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFGg34Ou2 # O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9P # MA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDAzB3BggrBgEFBQcB # AQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggr # BgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1 # c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGln # aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwHAYDVR0gBBUwEzAH # BgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcNAQEMBQADggIBADojRD2NCHbuj7w6 # mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcTEp6QRJ9L/Z6jfCbVN7w6XUhtldU/ # SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WTauPrINHVUHmImoqKwba9oUgYftzY # gBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9ntSZz0rdKOtfJqGVWEjVGv7XJz/9 # kNF2ht0csGBc8w2o7uCJob054ThO2m67Np375SFTWsPK6Wrxoj7bQ7gzyE84FJKZ # 9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0HKKlS43Nb3Y3LIU/Gs4m6Ri+kAew # Q3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL6TEa/y4ZXDlx4b6cpwoG1iZnt5Lm # Tl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+16oh7cGvmoLr9Oj9FpsToFpFSi0HA # SIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8M4+uKIw8y4+ICw2/O/TOHnuO77Xr # y7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrFhsP2JjMMB0ug0wcCampAMEhLNKhR # ILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy1lKQ/a+FSCH5Vzu0nAPthkX0tGFu # v2jiJmCG6sivqf6UHedjGzqGVnhOMIIGwjCCBKqgAwIBAgIQBUSv85SdCDmmv9s/ # X+VhFjANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln # aUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5 # NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTIzMDcxNDAwMDAwMFoXDTM0MTAx # MzIzNTk1OVowSDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu # MSAwHgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMzCCAiIwDQYJKoZIhvcN # AQEBBQADggIPADCCAgoCggIBAKNTRYcdg45brD5UsyPgz5/X5dLnXaEOCdwvSKOX # ejsqnGfcYhVYwamTEafNqrJq3RApih5iY2nTWJw1cb86l+uUUI8cIOrHmjsvlmbj # aedp/lvD1isgHMGXlLSlUIHyz8sHpjBoyoNC2vx/CSSUpIIa2mq62DvKXd4ZGIX7 # ReoNYWyd/nFexAaaPPDFLnkPG2ZS48jWPl/aQ9OE9dDH9kgtXkV1lnX+3RChG4PB # uOZSlbVH13gpOWvgeFmX40QrStWVzu8IF+qCZE3/I+PKhu60pCFkcOvV5aDaY7Mu # 6QXuqvYk9R28mxyyt1/f8O52fTGZZUdVnUokL6wrl76f5P17cz4y7lI0+9S769Sg # LDSb495uZBkHNwGRDxy1Uc2qTGaDiGhiu7xBG3gZbeTZD+BYQfvYsSzhUa+0rRUG # FOpiCBPTaR58ZE2dD9/O0V6MqqtQFcmzyrzXxDtoRKOlO0L9c33u3Qr/eTQQfqZc # ClhMAD6FaXXHg2TWdc2PEnZWpST618RrIbroHzSYLzrqawGw9/sqhux7UjipmAmh # cbJsca8+uG+W1eEQE/5hRwqM/vC2x9XH3mwk8L9CgsqgcT2ckpMEtGlwJw1Pt7U2 # 0clfCKRwo+wK8REuZODLIivK8SgTIUlRfgZm0zu++uuRONhRB8qUt+JQofM604qD # y0B7AgMBAAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAW # BgNVHSUBAf8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglg # hkgBhv1sBwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8wHQYDVR0O # BBYEFKW27xPn783QZKHVVqllMaPe1eNJMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6 # Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEy # NTZUaW1lU3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUF # BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6 # Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZT # SEEyNTZUaW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIBAIEa1t6g # qbWYF7xwjU+KPGic2CX/yyzkzepdIpLsjCICqbjPgKjZ5+PF7SaCinEvGN1Ott5s # 1+FgnCvt7T1IjrhrunxdvcJhN2hJd6PrkKoS1yeF844ektrCQDifXcigLiV4JZ0q # BXqEKZi2V3mP2yZWK7Dzp703DNiYdk9WuVLCtp04qYHnbUFcjGnRuSvExnvPnPp4 # 4pMadqJpddNQ5EQSviANnqlE0PjlSXcIWiHFtM+YlRpUurm8wWkZus8W8oM3NG6w # QSbd3lqXTzON1I13fXVFoaVYJmoDRd7ZULVQjK9WvUzF4UbFKNOt50MAcN7MmJ4Z # iQPq1JE3701S88lgIcRWR+3aEUuMMsOI5ljitts++V+wQtaP4xeR0arAVeOGv6wn # LEHQmjNKqDbUuXKWfpd5OEhfysLcPTLfddY2Z1qJ+Panx+VPNTwAvb6cKmx5Adza # ROY63jg7B145WPR8czFVoIARyxQMfq68/qTreWWqaNYiyjvrmoI1VygWy2nyMpqy # 0tg6uLFGhmu6F/3Ed2wVbK6rr3M66ElGt9V/zLY4wNjsHPW2obhDLN9OTH0eaHDA # dwrUAuBcYLso/zjlUlrWrBciI0707NMX+1Br/wd3H3GXREHJuEbTbDJ8WC9nR2Xl # G3O2mflrLAZG70Ee8PBf4NvZrZCARK+AEEGKMIIHXzCCBUegAwIBAgIQB8JSdCgU # otar/iTqF+XdLjANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UE # ChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQg # Q29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMB4XDTIzMDQxNjAw # MDAwMFoXDTI2MDcwNjIzNTk1OVowZzELMAkGA1UEBhMCUEwxEjAQBgNVBAcMCU1p # a2/FgsOzdzEhMB8GA1UECgwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMSEwHwYD # VQQDDBhQcnplbXlzxYJhdyBLxYJ5cyBFVk9URUMwggIiMA0GCSqGSIb3DQEBAQUA # A4ICDwAwggIKAoICAQCUmgeXMQtIaKaSkKvbAt8GFZJ1ywOH8SwxlTus4McyrWmV # OrRBVRQA8ApF9FaeobwmkZxvkxQTFLHKm+8knwomEUslca8CqSOI0YwELv5EwTVE # h0C/Daehvxo6tkmNPF9/SP1KC3c0l1vO+M7vdNVGKQIQrhxq7EG0iezBZOAiukNd # GVXRYOLn47V3qL5PwG/ou2alJ/vifIDad81qFb+QkUh02Jo24SMjWdKDytdrMXi0 # 235CN4RrW+8gjfRJ+fKKjgMImbuceCsi9Iv1a66bUc9anAemObT4mF5U/yQBgAuA # o3+jVB8wiUd87kUQO0zJCF8vq2YrVOz8OJmMX8ggIsEEUZ3CZKD0hVc3dm7cWSAw # 8/FNzGNPlAaIxzXX9qeD0EgaCLRkItA3t3eQW+IAXyS/9ZnnpFUoDvQGbK+Q4/bP # 0ib98XLfQpxVGRu0cCV0Ng77DIkRF+IyR1PcwVAq+OzVU3vKeo25v/rntiXCmCxi # W4oHYO28eSQ/eIAcnii+3uKDNZrI15P7VxDrkUIc6FtiSvOhwc3AzY+vEfivUkFK # RqwvSSr4fCrrkk7z2Qe72Zwlw2EDRVHyy0fUVGO9QMuh6E3RwnJL96ip0alcmhKA # BGoIqSW05nXdCUbkXmhPCTT5naQDuZ1UkAXbZPShKjbPwzdXP2b8I9nQ89VSgQID # AQABo4ICAzCCAf8wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYD # VR0OBBYEFHrxaiVZuDJxxEk15bLoMuFI5233MA4GA1UdDwEB/wQEAwIHgDATBgNV # HSUEDDAKBggrBgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1odHRwOi8vY3Js # My5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQw # OTZTSEEzODQyMDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQuZGlnaWNlcnQu # Y29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAy # MUNBMS5jcmwwPgYDVR0gBDcwNTAzBgZngQwBBAEwKTAnBggrBgEFBQcCARYbaHR0 # cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMIGUBggrBgEFBQcBAQSBhzCBhDAkBggr # BgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBo # dHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2Rl # U2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqG # SIb3DQEBCwUAA4ICAQC3EeHXUPhpe31K2DL43Hfh6qkvBHyR1RlD9lVIklcRCR50 # ZHzoWs6EBlTFyohvkpclVCuRdQW33tS6vtKPOucpDDv4wsA+6zkJYI8fHouW6Tqa # 1W47YSrc5AOShIcJ9+NpNbKNGih3doSlcio2mUKCX5I/ZrzJBkQpJ0kYha/pUST2 # CbE3JroJf2vQWGUiI+J3LdiPNHmhO1l+zaQkSxv0cVDETMfQGZKKRVESZ6Fg61b0 # djvQSx510MdbxtKMjvS3ZtAytqnQHk1ipP+Rg+M5lFHrSkUlnpGa+f3nuQhxDb7N # 9E8hUVevxALTrFifg8zhslVRH5/Df/CxlMKXC7op30/AyQsOQxHW1uNx3tG1DMgi # zpwBasrxh6wa7iaA+Lp07q1I92eLhrYbtw3xC2vNIGdMdN7nd76yMIjdYnAn7r38 # wwtaJ3KYD0QTl77EB8u/5cCs3ShZdDdyg4K7NoJl8iEHrbqtooAHOMLiJpiL2i9Y # n8kQMB6/Q6RMO3IUPLuycB9o6DNiwQHf6Jt5oW7P09k5NxxBEmksxwNbmZvNQ65Z # n3exUAKqG+x31Egz5IZ4U/jPzRalElEIpS0rgrVg8R8pEOhd95mEzp5WERKFyXhe # 6nB6bSYHv8clLAV0iMku308rpfjMiQkqS3LLzfUJ5OHqtKKQNMLxz9z185UCszGC # BlMwggZPAgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ # bmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBS # U0E0MDk2IFNIQTM4NCAyMDIxIENBMQIQB8JSdCgUotar/iTqF+XdLjANBglghkgB # ZQMEAgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJ # AzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8G # CSqGSIb3DQEJBDEiBCBKBu8nRbd470XQLGixtJRrGB/8SczvRJvbNanNXDJgazAN # BgkqhkiG9w0BAQEFAASCAgAFSJS4e5AuWC/B0JGp5ea9VOVtpTWx6XIUUUpB8BVs # w/JwkEBmJssbkEnQBiGFYoPObCWjuYS8VUHO/+ZI0u/yfk6tEdFre1iTlt19BEl2 # x0KhRjUeD3KJBPTbjIa05cyrpYKFLacs2KbZ42akrHDshRjU+PUzH0lkpRKBJ/+q # nhXDOjZSfxj2xK8AD3Eest08TA2aMgLJJVrFRrRfYAt+N25CacDx4qRmcjsqvHvk # m4vVHgysNvCCxyLy/ofrOJuV4W4Se/fMq/wVNQ6EK63lQDaaslt57nhzefhgSjpi # Kzb6uV9ZtocxbpVOMV0hOnZQwITmU9w0OrtV77XS5MdhfBtz06LZ47g9OVCONHlF # J9yITvrdeCaKSvylPPkqm2c15zEvPj6/aWalqspqqEKv94V7sNaIGjSCmEg4ef4e # 2xGygKdPthYEAZooVXVAaWJaPL+FWxPIfR1jPjD+V4cx0doa2RgJSSbQJTGDLgjG # c7SE8onp+ATkoM657/zFcHz71v3984/uejC9FKK6NwhyLPrcd7l6NytE9HW9UkwL # aJ3Zbj36g9CM+ZXK6UFP71YN/zMKZ7EU1W+m2TkjFz24c0vaMcJwz31dHRSme2by # mUOUgYh0YtYJLj7IXJiV4aJdFpPT+feE5aLXQGm5xJvLnb/O+3M8B/ZYyEpKo/14 # A6GCAyAwggMcBgkqhkiG9w0BCQYxggMNMIIDCQIBATB3MGMxCzAJBgNVBAYTAlVT # MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1 # c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAVEr/OUnQg5 # pr/bP1/lYRYwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcN # AQcBMBwGCSqGSIb3DQEJBTEPFw0yNDA4MTExNDQ4MzVaMC8GCSqGSIb3DQEJBDEi # BCDsokXtiy4YefbPtCY1UPHvqqbZ8yXQzuIVcsFUDN270zANBgkqhkiG9w0BAQEF # AASCAgCcsoXbBqk4RBSQwAX4eNE0cBFDP+nyPO96tQBK5CG92Bf5iGiaiPW6Mfx5 # QCLV8lYTi1P2SuFqbYASP0i5ciSRyp5vIoRnm0nGlUBnHp7x0HKwfg8j6tCHAm7P # Ar2GTdNNgfiZgpOVg+ao3TFWciXhWkR4sMcJgJItp5/Zi0Oiq4iyOVImBu9v559I # IwY4namghhp/G7DHQ71C22+GRWRR1ughGHi5GlNqBVzD4f+gDz6yXE/o3nAflXiI # 5fPzYCtzDJf997EBc6OsUpbdAAPwU0Jmqf7ymrxoD2tiPx4sZQy7QUES0W7gA38t # i+TGgHk6qkqcgNfkceAiPIqPSen70s67m2eSf/Q4NelRt/YBt00j3/z+T1tVCpBp # R+Pr5vGPBWgXczC8xjPvzMAn9nMim2nM+z1UQDi+4yuV7te3q/6uAB7SEYRZCfT0 # qC/zjNS2M6cp2KpPyP9jZgWuSu0JN3j786qJknv2aFfue5zF5WqLDhATuZsxPWyh # QwPFGM31ftUhZteqSpP8uoVLClFGARRfOCwxRClDIauQutfyXWoDlEdAFUCp95jX # SYFTX9PkUn+59+/rv3WqZjtSrX5IrGIg9hCiYAwQfxjjt01+ZgmuIijmi1tljWKr # rYHBbHmBJLAbgK2ArRPj1j1PRm2nl/8Z2RFD/C2gGsFkmyR4hw== # SIG # End signature block |