Functions/Public/Utilities.ps1
|
# Exported utility functions (charts, lists, conversions) Function Convert-NectarNumToTelURI { <# .SYNOPSIS Converts a Nectar formatted number "+12223334444 x200" into a valid TEL uri "+12223334444;ext=200" .DESCRIPTION Converts a Nectar formatted number "+12223334444 x200" into a valid TEL uri "+12223334444;ext=200" .PARAMETER PhoneNumber The phone number to convert to a TEL URI .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Convert-NectarNumToTelsURI "+12224243344 x3344" Converts the above number to a TEL URI .EXAMPLE Get-NectarUnallocatedNumber -LocationName Jericho | Convert-NectarNumToTelURI Returns the next available phone number in the Jericho location in Tel URI format .NOTES Version 1.1 #> Param ( [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName, Mandatory=$true)] [Alias("number")] [string]$PhoneNumber ) $PhoneNumber = "tel:" + $PhoneNumber.Replace(" x", ";ext=") Return $PhoneNumber } Function Get-LatLong { <# .SYNOPSIS Returns the geographical coordinates for an address. .DESCRIPTION Returns the geographical coordinates for an address. .PARAMETER Address The address of the location to return information on. Include as much detail as possible. .EXAMPLE Get-LatLong -Address "33 Main Street, Jonestown, NY, USA" Retrieves the latitude/longitude for the selected location .NOTES Version 1.0 #> Param( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [String]$Address ) Begin { $GoogleGeoAPIKey = [System.Environment]::GetEnvironmentVariable('GoogleGeocode_API_Key','user') $GeoapifyAPIKey = [System.Environment]::GetEnvironmentVariable('Geoapify_API_Key','user') If ($GeoapifyAPIKey) { $GeoProvider = 'Geoapify' $GeoAPIKey = $GeoapifyAPIKey } If ($GoogleGeoAPIKey) { $GeoProvider = 'Google' $GeoAPIKey = $GoogleGeoAPIKey } If(!$GeoProvider) { Write-Host -ForegroundColor Red 'No valid GeoProvider found. You need to register for an API key from either Google or Geoapify and save it as persistent environment variable called GoogleGeocode_API_Key or Geoapify_API_Key on this machine.' Write-Host Write-Host -ForegroundColor Red 'For a Google API Key (requires payment) - https://developers.google.com/maps/documentation/geocoding/get-api-key' Write-Host -ForegroundColor Red 'For a Geoapify API Key (free account allows 3000 lookups/day) - https://www.geoapify.com/api/geocoding-api/' Write-Host Write-Host -ForegroundColor Red 'Once obtained, save the API key as a persistent environment variable using one of the following commands:' Write-Host -ForegroundColor Red " [System.Environment]::SetEnvironmentVariable('GoogleGeocode_API_Key', 'YourAPIKey',[System.EnvironmentVariableTarget]::User)" Write-Host -ForegroundColor Red " [System.Environment]::SetEnvironmentVariable('Geoapify_API_Key', 'YourAPIKey',[System.EnvironmentVariableTarget]::User)" Break } } Process { Switch ($GeoProvider) { 'Geoapify' { $URI = "https://api.geoapify.com/v1/geocode/search?text=$Address&apiKey=$GeoAPIKey" Break } 'Google' { $URI = "https://maps.googleapis.com/maps/api/geocode/json?address=$Address&key=$GeoAPIKey" Break } } Try { Write-Verbose $URI $JSON = Invoke-RestMethod -Method GET -Uri $URI -ErrorVariable EV Switch ($GeoProvider) { 'Geoapify' { [double]$Lat = $JSON.features.geometry.coordinates[1] [double]$Lng = $JSON.features.geometry.coordinates[0] Break } 'Google' { [double]$Lat = $JSON.results.geometry.location.lat [double]$Lng = $JSON.results.geometry.location.lng Break } } If ($Lat) { $LatLong = New-Object PSObject $LatLong | Add-Member -type NoteProperty -Name 'Latitude' -Value $Lat $LatLong | Add-Member -type NoteProperty -Name 'Longitude' -Value $Lng } Else { Write-Host -ForegroundColor Yellow "WARNING: Address geolocation failed for $Address. Defaulting to 0:0" $LatLong = New-Object PSObject $LatLong | Add-Member -type NoteProperty -Name 'Latitude' -Value 0 $LatLong | Add-Member -type NoteProperty -Name 'Longitude' -Value 0 } } Catch { "Something went wrong. Please try again." $ev.message } Return $LatLong } } Function Show-GroupAndStats { <# .SYNOPSIS Groups a set of data by one parameter, and shows sum, average, min, max for another numeric parameter (such as duration) .DESCRIPTION Groups a set of data by one parameter, and shows sum, average, min, max for another numeric parameter (such as duration) .PARAMETER InputObject The data to group and sum. Can be pipelined .PARAMETER GroupBy The parameter to group on .PARAMETER SumBy The parameter to calculate numerical statistics. Must be a numeric field .PARAMETER ShowGroupMembers The field to show the members of the field used in the current grouping .PARAMETER ShowSumByAsTimeFormat If the SumBy parameter is in seconds (Duration is an example), format the output as dd.hh:mm:ss instead of seconds .EXAMPLE Get-NectarSession | Show-GroupAndStats -GroupBy CallerLocation -SumBy Duration Will group all calls by caller location and show sum, avg, min, max for the Duration column .NOTES Version 1.0 #> Param ( [Parameter(ValueFromPipeline, Mandatory=$True)] [Alias('Input')] [pscustomobject[]]$InputObject, [Parameter(Mandatory=$True)] [string[]]$GroupBy, [Parameter(Mandatory=$True)] [string]$SumBy, [Parameter(Mandatory=$False)] [string]$ShowGroupMembers, [switch]$ShowSumByAsTimeFormat ) # Validate the parameters exist and are the proper format ForEach ($Item in $GroupBy) { If ($NULL -eq ($InputObject | Get-Member $Item)) { Write-Error "$Item is not a valid parameter for the source data" Break } } If ($NULL -eq ($InputObject | Get-Member $SumBy)) { Write-Error "$SumBy is not a valid parameter for the source data" Break } # ElseIf (($InputObject | Get-Member $SumBy).Definition -NotMatch 'int|float|double|decimal') { # Write-Error "$SumBy is not a numeric field" # Break # } # First, use the standard Group-Object to do the grouping. [System.Collections.ArrayList]$Output = @() $InputObject | Group-Object $GroupBy | Sort-Object Count -Descending | ForEach-Object { $RowData = [pscustomobject][ordered] @{} Write-Verbose "Name: $($_.Name)" # If grouping by multiple fields, the 'name' field consists of each of the grouped items separated by commas. # This function splits them apart into their own column ForEach ($Item in $GroupBy) { $ItemName = ($_.Name.Split(',')[$GroupBy.IndexOf($Item)]) If (!$ItemName) { $ItemName = 'Unknown'} # Don't allow for blanks. $RowData | Add-Member -MemberType NoteProperty -Name $Item -Value $ItemName.Trim() } $RowData | Add-Member -MemberType NoteProperty -Name 'Count' -Value $_.Count If ($ShowSumByAsTimeFormat) { $RowData | Add-Member -MemberType NoteProperty -Name "SUM_$SumBy" -Value ([timespan]::FromSeconds(($_.Group | Measure-Object $SumBy -Sum).Sum)) $RowData | Add-Member -MemberType NoteProperty -Name "AVG_$SumBy" -Value ([timespan]::FromSeconds([math]::Round(($_.Group | Measure-Object $SumBy -Average).Average))) $RowData | Add-Member -MemberType NoteProperty -Name "MIN_$SumBy" -Value ([timespan]::FromSeconds(($_.Group | Measure-Object $SumBy -Minimum).Minimum)) $RowData | Add-Member -MemberType NoteProperty -Name "MAX_$SumBy" -Value ([timespan]::FromSeconds(($_.Group | Measure-Object $SumBy -Maximum).Maximum)) } Else { $RowData | Add-Member -MemberType NoteProperty -Name "SUM_$SumBy" -Value ($_.Group | Measure-Object $SumBy -Sum).Sum $RowData | Add-Member -MemberType NoteProperty -Name "AVG_$SumBy" -Value ([math]::Round(($_.Group | Measure-Object $SumBy -Average).Average,2)) $RowData | Add-Member -MemberType NoteProperty -Name "MIN_$SumBy" -Value ($_.Group | Measure-Object $SumBy -Minimum).Minimum $RowData | Add-Member -MemberType NoteProperty -Name "MAX_$SumBy" -Value ($_.Group | Measure-Object $SumBy -Maximum).Maximum } If ($ShowGroupMembers) { $GroupMemberList = $NULL $_.Group.$ShowGroupMembers | ForEach-Object { $GroupMemberList += ($(If($GroupMemberList){','}) + $_) } $RowData | Add-Member -MemberType NoteProperty -Name "$($ShowGroupMembers)_List" -Value $GroupMemberList } $Output += $RowData } Return $Output } Function New-Chart { <# .SYNOPSIS Creates a chart PNG file based on the input data .DESCRIPTION Creates a chart PNG file based on the input data. ONLY WORKS IN MS WINDOWS .PARAMETER InputData The data source to use for the chart. Can be either a variable or a command enclosed in brackets .PARAMETER TimeObjectName The name of the data column to use for the time (x-axis) .PARAMETER BarNames The names of the bars to display separated by commas. Must match up with the names of the desired column in the input data .PARAMETER BarColours The colours of the bars to display separated by commas. Colours will be matched up with the BarNames by position. .PARAMETER Interval The interval between numbers to show on the axis. Defaults to auto. .PARAMETER ChartName The name to use for the chart header and the filename. Defaults to the type of chart being generated. .PARAMETER ChartType The chart type to display. Defaults to StackedColumn. .EXAMPLE $Data = Get-NectarSessionCount -TimePeriod LAST WEEK New-Chart -InputData $Data -BarNames Good,Average,Poor -BarColors Green,Yellow,Red Creates a bar chart using a variable from a previous command .EXAMPLE New-Chart -InputData (Get-NectarSessionCount -TimePeriod LAST WEEK) -BarNames Good,Average,Poor -BarColors Green,Yellow,Red Same results as previous example, but shown as full command written within the New-Chart command .NOTES Version 1.0 #> param ( [Parameter(Mandatory=$True)] [PSCustomObject]$InputData, [Parameter(Mandatory=$True)] [string]$TimeObjectName, [Parameter(Mandatory=$True)] [string[]]$BarNames, [Parameter(Mandatory=$False)] [string[]]$BarColours, [Parameter(Mandatory=$False)] [int32]$Interval, [Parameter(Mandatory=$False)] [string]$ChartName, [Parameter(Mandatory=$False)] [ValidateSet('Area', 'Bar', 'BoxPlot', 'Bubble', 'Column', 'Doughnut', 'Line', 'Pie', 'Point', 'Polar', 'Radar', 'Range', 'RangeBar', 'RangeColumn', 'Spline', 'SplineArea', 'SplineRange', 'StackedArea', 'StackedArea100', 'StackedBar', 'StackedBar100', 'StackedColumn', 'StackedColumn100', 'StepLine', IgnoreCase=$True)] [string]$ChartType = 'StackedColumn' ) [void][Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms.DataVisualization") # Creating chart object # The System.Windows.Forms.DataVisualization.Charting namespace contains methods and properties for the Chart Windows forms control. $ChartObject = New-Object System.Windows.Forms.DataVisualization.Charting.Chart $ChartObject.Width = 2000 $ChartObject.Height = 1000 $ChartObject.BackColor = [System.Drawing.Color]::white # Set Chart title If (!$ChartName) { $ChartName = $ChartType } [void]$ChartObject.Titles.Add("$ChartName for $($InputData[0].TenantName)") $ChartObject.Titles[0].Font = "Arial,13pt" $ChartObject.Titles[0].Alignment = "TopCenter" # Create a chartarea to draw on and add to chart $ChartAreaObject = New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea $ChartAreaObject.Name = "ChartArea1" $ChartAreaObject.AxisY.Title = "Count" $ChartAreaObject.AxisX.Title = "Date" $ChartAreaObject.AxisY.Interval = $Interval $ChartAreaObject.AxisX.Interval = 1 $ChartAreaObject.BackColor = [System.Drawing.Color]::white $ChartObject.ChartAreas.Add($ChartAreaObject) # Creating legend for the chart $ChartLegend = New-Object system.Windows.Forms.DataVisualization.Charting.Legend $ChartLegend.name = "Legend1" $ChartObject.Legends.Add($ChartLegend) ForEach ($Bar in $BarNames) { [void]$ChartObject.Series.Add($Bar) $ChartObject.Series[$Bar].ChartType = $ChartType $ChartObject.Series[$Bar].BorderWidth = 3 $ChartObject.Series[$Bar].IsVisibleInLegend = $true $ChartObject.Series[$Bar].chartarea = "ChartArea1" $ChartObject.Series[$Bar].Legend = "Legend1" If ($BarColours[$BarNames.IndexOf($Bar)]) { $ChartObject.Series[$Bar].color = $BarColours[$BarNames.IndexOf($Bar)] } $InputData | ForEach-Object {$NULL = $ChartObject.Series[$Bar].Points.addxy([datetime]$_.$TimeObjectName, $_.$Bar) } } # Save chart with the Time frame for identifying the usage at the specific time $ChartObject.SaveImage("$($ChartName.Replace(' ','_'))_$($InputData[0].TenantName).png","png") Write-Host "Chart saved as $($ChartName.Replace(' ','_'))_$($InputData[0].TenantName).png" } # From https://github.com/allynl93/getSAMLResponse-Interactive # Unfortunately, it relies on System.Windows.Forms, which uses IE and doesn't work with most modern IDPs Function Select-FromList { <# .SYNOPSIS Displays a list of items and allows the user to select one. .DESCRIPTION Displays a list of items and allows the user to select one, with optional highlighting and lowlighting of specific items. .PARAMETER Items The list of items to display. .PARAMETER Prompt The prompt to display to the user. .PARAMETER Highlight An array of strings representing the *exact* items to highlight (brighter color). Only items that match *exactly* (case-insensitive) will be highlighted. This can be a variable containing an array. .PARAMETER Lowlight An array of strings representing the *exact* items to lowlight (dimmer color). Only items that match *exactly* (case-insensitive) will be lowlighted. This can be a variable containing an array. Lowlight takes precedence over Highlight. .PARAMETER Default The item to pre-select (highlight) in the list. This takes precedence over both `Highlight` and `Lowlight`. .EXAMPLE Select-FromList -Items @("Item1", "Item2", "Item3") -Prompt "Choose an item:" .EXAMPLE Select-FromList -Items $PSList -Prompt "Choose an item:" -Default "Item2" .EXAMPLE Select-FromList -Items @("apple", "banana", "orange", "grapefruit") -Prompt "Select a fruit:" -Highlight @("orange", "grapefruit") # This will highlight "orange" and "grapefruit". .EXAMPLE $items = 1..10 | ForEach-Object {"Item $_"} Select-FromList -Items $items -Prompt "Select a number:" -Highlight @("Item 1", "Item 5", "Item 10") -Default "Item 3" # Highlights "Item 1", "Item 10", "Item 5". "Item 3" will be selected by default. .EXAMPLE Select-FromList -Items @("File1.txt", "File2.log", "File3.txt", "Backup.zip") -Prompt "Select a file:" -Highlight @("File1.txt", "File3.txt") # Highlights "File1.txt" and "File3.txt". .EXAMPLE $myHighlights = @("Item2", "Item4") Select-FromList -Items @("Item1", "Item2", "Item3", "Item4", "Item5") -Prompt "Select an item:" -Highlight $myHighlights # Highlights "Item2" and "Item4" .EXAMPLE $myHighlights = @("item2", "ITEM4") # mixed case $myLowlights = @("Item3") Select-FromList -Items @("Item1", "Item2", "Item3", "Item4", "Item5") -Prompt "Select:" -Highlight $myHighlights -Lowlight $myLowlights -Default "item3" # Highlights "Item2" and "Item4", Lowlights "Item3", and selects "Item3" by default. .EXAMPLE $myLowlights = @("Item3", "Item1") Select-FromList -Items (1..5 | ForEach-Object {"Item$_"}) -Lowlight $myLowlights #> Param( [Parameter(Mandatory = $true)] [string[]]$Items, [string]$Prompt = "Select an item:", [string[]]$Highlight, [string[]]$Lowlight, [string]$Default ) # Validate if $items is empty If ($Items.Length -eq 0) { Write-Host "Error: List is empty" -ForegroundColor Red Return $null; } $selectedIndex = 0 $itemsCount = $Items.Count # Set the initial index based on Default If ($Default) { For ($i = 0; $i -lt $itemsCount; $i++) { If ($Items[$i] -like "*$($Default)*") { # Use -ieq for case-insensitive comparison $selectedIndex = $i Break } } } While ($true) { Clear-Host Write-Host $Prompt For ($i = 0; $i -lt $itemsCount; $i++) { $line = " " + ($i + 1) + ". " + $Items[$i] $isHighlighted = $false $isLowlighted = $false # Check for lowlight first (takes precedence, *except* when selected) if ($Lowlight) { if ($Items[$i] -in $Lowlight) { $isLowlighted = $true } } # Check for highlight (only if not lowlighted) if ($Highlight -and !$isLowlighted) { if ($Items[$i] -in $Highlight) { $isHighlighted = $true } } # Selected item is *always* green if ($i -eq $selectedIndex) { Write-Host ">>" -NoNewline -ForegroundColor Green Write-Host $line -ForegroundColor Green } elseif ($isLowlighted) { Write-Host " " -NoNewline Write-Host $line -ForegroundColor DarkGray # Just Lowlighted } elseif ($isHighlighted) { Write-Host " " -NoNewline Write-Host $line -ForegroundColor Yellow # Just Highlighted } else { Write-Host " " -NoNewline Write-Host $line # Normal item } } $key = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") If ($key.VirtualKeyCode -eq 38) { # Up Arrow $selectedIndex = ($selectedIndex - 1 + $itemsCount) % $itemsCount } ElseIf ($key.VirtualKeyCode -eq 40) { # Down Arrow $selectedIndex = ($selectedIndex + 1) % $itemsCount } ElseIf ($key.VirtualKeyCode -eq 13) { # Enter Return $Items[$selectedIndex] Break } ElseIf ($key.VirtualKeyCode -eq 27) { # ESCAPE Return $null Break } } } Function Get-NectarLocalTimeZone { <# .SYNOPSIS Returns the IANA (Java) timezone ID of the local machine, for use in Nectar DXP report and alert schedules .DESCRIPTION Nectar DXP expects timezones as IANA IDs such as 'America/Toronto' or 'Europe/London'. Windows reports its timezone as a Windows ID such as 'Eastern Standard Time', so this function converts between the two. PowerShell 7 uses the built-in .NET conversion. Windows PowerShell 5.1 falls back to an embedded mapping table. If the zone cannot be mapped, a fixed-offset 'Etc/GMT' zone is returned as a last resort. .PARAMETER WindowsId The Windows timezone ID to convert. Defaults to the local machine's timezone. .EXAMPLE Get-NectarLocalTimeZone Returns e.g. 'America/New_York' on a machine set to Eastern Time .EXAMPLE Get-NectarLocalTimeZone -WindowsId 'GMT Standard Time' Returns 'Europe/London' .NOTES Version 1.0 #> [CmdletBinding()] Param ( [Parameter(Mandatory=$False)] [string]$WindowsId = [System.TimeZoneInfo]::Local.Id ) # Already an IANA-style ID (contains a slash), so return as-is If ($WindowsId -like '*/*') { Return $WindowsId } # .NET 6+ (PowerShell 7) can convert natively $Converter = [System.TimeZoneInfo].GetMethod('TryConvertWindowsIdToIanaId', [type[]]@([string], [string].MakeByRefType())) If ($Converter) { $IanaId = $Null If ([System.TimeZoneInfo]::TryConvertWindowsIdToIanaId($WindowsId, [ref]$IanaId) -And $IanaId) { Return $IanaId } } # Windows PowerShell 5.1: embedded mapping (CLDR windowsZones, territory 001) $Map = @{ 'Dateline Standard Time'='Etc/GMT+12'; 'UTC-11'='Etc/GMT+11'; 'Aleutian Standard Time'='America/Adak'; 'Hawaiian Standard Time'='Pacific/Honolulu' 'Marquesas Standard Time'='Pacific/Marquesas'; 'Alaskan Standard Time'='America/Anchorage'; 'UTC-09'='Etc/GMT+9'; 'Pacific Standard Time (Mexico)'='America/Tijuana' 'UTC-08'='Etc/GMT+8'; 'Pacific Standard Time'='America/Los_Angeles'; 'US Mountain Standard Time'='America/Phoenix'; 'Mountain Standard Time (Mexico)'='America/Mazatlan' 'Mountain Standard Time'='America/Denver'; 'Yukon Standard Time'='America/Whitehorse'; 'Central America Standard Time'='America/Guatemala'; 'Central Standard Time'='America/Chicago' 'Easter Island Standard Time'='Pacific/Easter'; 'Central Standard Time (Mexico)'='America/Mexico_City'; 'Canada Central Standard Time'='America/Regina'; 'SA Pacific Standard Time'='America/Bogota' 'Eastern Standard Time (Mexico)'='America/Cancun'; 'Eastern Standard Time'='America/New_York'; 'Haiti Standard Time'='America/Port-au-Prince'; 'Cuba Standard Time'='America/Havana' 'US Eastern Standard Time'='America/Indianapolis'; 'Turks And Caicos Standard Time'='America/Grand_Turk'; 'Paraguay Standard Time'='America/Asuncion'; 'Atlantic Standard Time'='America/Halifax' 'Venezuela Standard Time'='America/Caracas'; 'Central Brazilian Standard Time'='America/Cuiaba'; 'SA Western Standard Time'='America/La_Paz'; 'Pacific SA Standard Time'='America/Santiago' 'Newfoundland Standard Time'='America/St_Johns'; 'Tocantins Standard Time'='America/Araguaina'; 'E. South America Standard Time'='America/Sao_Paulo'; 'SA Eastern Standard Time'='America/Cayenne' 'Argentina Standard Time'='America/Buenos_Aires'; 'Greenland Standard Time'='America/Godthab'; 'Montevideo Standard Time'='America/Montevideo'; 'Magallanes Standard Time'='America/Punta_Arenas' 'Saint Pierre Standard Time'='America/Miquelon'; 'Bahia Standard Time'='America/Bahia'; 'UTC-02'='Etc/GMT+2'; 'Azores Standard Time'='Atlantic/Azores' 'Cape Verde Standard Time'='Atlantic/Cape_Verde'; 'UTC'='Etc/UTC'; 'GMT Standard Time'='Europe/London'; 'Greenwich Standard Time'='Atlantic/Reykjavik' 'Sao Tome Standard Time'='Africa/Sao_Tome'; 'Morocco Standard Time'='Africa/Casablanca'; 'W. Europe Standard Time'='Europe/Berlin'; 'Central Europe Standard Time'='Europe/Budapest' 'Romance Standard Time'='Europe/Paris'; 'Central European Standard Time'='Europe/Warsaw'; 'W. Central Africa Standard Time'='Africa/Lagos'; 'Jordan Standard Time'='Asia/Amman' 'GTB Standard Time'='Europe/Bucharest'; 'Middle East Standard Time'='Asia/Beirut'; 'Egypt Standard Time'='Africa/Cairo'; 'E. Europe Standard Time'='Europe/Chisinau' 'Syria Standard Time'='Asia/Damascus'; 'West Bank Standard Time'='Asia/Hebron'; 'South Africa Standard Time'='Africa/Johannesburg'; 'FLE Standard Time'='Europe/Kiev' 'Israel Standard Time'='Asia/Jerusalem'; 'South Sudan Standard Time'='Africa/Juba'; 'Kaliningrad Standard Time'='Europe/Kaliningrad'; 'Sudan Standard Time'='Africa/Khartoum' 'Libya Standard Time'='Africa/Tripoli'; 'Namibia Standard Time'='Africa/Windhoek'; 'Arabic Standard Time'='Asia/Baghdad'; 'Turkey Standard Time'='Europe/Istanbul' 'Arab Standard Time'='Asia/Riyadh'; 'Belarus Standard Time'='Europe/Minsk'; 'Russian Standard Time'='Europe/Moscow'; 'E. Africa Standard Time'='Africa/Nairobi' 'Volgograd Standard Time'='Europe/Volgograd'; 'Iran Standard Time'='Asia/Tehran'; 'Arabian Standard Time'='Asia/Dubai'; 'Astrakhan Standard Time'='Europe/Astrakhan' 'Azerbaijan Standard Time'='Asia/Baku'; 'Russia Time Zone 3'='Europe/Samara'; 'Mauritius Standard Time'='Indian/Mauritius'; 'Saratov Standard Time'='Europe/Saratov' 'Georgian Standard Time'='Asia/Tbilisi'; 'Caucasus Standard Time'='Asia/Yerevan'; 'Afghanistan Standard Time'='Asia/Kabul'; 'West Asia Standard Time'='Asia/Tashkent' 'Ekaterinburg Standard Time'='Asia/Yekaterinburg'; 'Pakistan Standard Time'='Asia/Karachi'; 'Qyzylorda Standard Time'='Asia/Qyzylorda'; 'India Standard Time'='Asia/Calcutta' 'Sri Lanka Standard Time'='Asia/Colombo'; 'Nepal Standard Time'='Asia/Katmandu'; 'Central Asia Standard Time'='Asia/Almaty'; 'Bangladesh Standard Time'='Asia/Dhaka' 'Omsk Standard Time'='Asia/Omsk'; 'Myanmar Standard Time'='Asia/Rangoon'; 'SE Asia Standard Time'='Asia/Bangkok'; 'Altai Standard Time'='Asia/Barnaul' 'W. Mongolia Standard Time'='Asia/Hovd'; 'North Asia Standard Time'='Asia/Krasnoyarsk'; 'N. Central Asia Standard Time'='Asia/Novosibirsk'; 'Tomsk Standard Time'='Asia/Tomsk' 'China Standard Time'='Asia/Shanghai'; 'North Asia East Standard Time'='Asia/Irkutsk'; 'Singapore Standard Time'='Asia/Singapore'; 'W. Australia Standard Time'='Australia/Perth' 'Taipei Standard Time'='Asia/Taipei'; 'Ulaanbaatar Standard Time'='Asia/Ulaanbaatar'; 'Aus Central W. Standard Time'='Australia/Eucla'; 'Transbaikal Standard Time'='Asia/Chita' 'Tokyo Standard Time'='Asia/Tokyo'; 'North Korea Standard Time'='Asia/Pyongyang'; 'Korea Standard Time'='Asia/Seoul'; 'Yakutsk Standard Time'='Asia/Yakutsk' 'Cen. Australia Standard Time'='Australia/Adelaide'; 'AUS Central Standard Time'='Australia/Darwin'; 'E. Australia Standard Time'='Australia/Brisbane'; 'AUS Eastern Standard Time'='Australia/Sydney' 'West Pacific Standard Time'='Pacific/Port_Moresby'; 'Tasmania Standard Time'='Australia/Hobart'; 'Vladivostok Standard Time'='Asia/Vladivostok'; 'Lord Howe Standard Time'='Australia/Lord_Howe' 'Bougainville Standard Time'='Pacific/Bougainville'; 'Russia Time Zone 10'='Asia/Srednekolymsk'; 'Magadan Standard Time'='Asia/Magadan'; 'Norfolk Standard Time'='Pacific/Norfolk' 'Sakhalin Standard Time'='Asia/Sakhalin'; 'Central Pacific Standard Time'='Pacific/Guadalcanal'; 'Russia Time Zone 11'='Asia/Kamchatka'; 'New Zealand Standard Time'='Pacific/Auckland' 'UTC+12'='Etc/GMT-12'; 'Fiji Standard Time'='Pacific/Fiji'; 'Chatham Islands Standard Time'='Pacific/Chatham'; 'UTC+13'='Etc/GMT-13' 'Tonga Standard Time'='Pacific/Tongatapu'; 'Samoa Standard Time'='Pacific/Apia'; 'Line Islands Standard Time'='Pacific/Kiritimati' } If ($Map.ContainsKey($WindowsId)) { Return $Map[$WindowsId] } # Last resort: fixed offset zone. Note the Etc/GMT sign is inverted (Etc/GMT+5 is UTC-5) and does not follow daylight saving. $Offset = [System.TimeZoneInfo]::Local.BaseUtcOffset.Hours If ($Offset -eq 0) { Return 'Etc/UTC' } Write-Warning "Could not map Windows timezone '$WindowsId' to an IANA ID. Falling back to a fixed offset zone." If ($Offset -lt 0) { Return "Etc/GMT+$(-$Offset)" } Else { Return "Etc/GMT-$Offset" } } # SIG # Begin signature block # MIIlcwYJKoZIhvcNAQcCoIIlZDCCJWACAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDoECro/c9TOHiM # 0cnkUZRgKrFlV99d7NpSsZ4GCTLDIaCCEvMwggXdMIIDxaADAgECAgh7LJvTFoAy # mTANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx # EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8G # A1UEAwwoU1NMLmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTAe # Fw0xNjAyMTIxNzM5MzlaFw00MTAyMTIxNzM5MzlaMHwxCzAJBgNVBAYTAlVTMQ4w # DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENv # cnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBB # dXRob3JpdHkgUlNBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA+Q/d # oyt9y9Aq/uxnhabnLhu6d+Hj9a+k7PpKXZHEV0drGHdrdvL9k+Q9D8IWngtmw1aU # nheDhc5W7/IW/QBi9SIJVOhlF05BueBPRpeqG8i4bmJeabFf2yoCfvxsyvNB2O3Q # 6Pw/YUjtsAMUHRAOSxngu07shmX/NvNeZwILnYZVYf16OO3+4hkAt2+hUGJ1dDyg # +sglkrRueiLH+B6h47LdkTGrKx0E/6VKBDfphaQzK/3i1lU0fBmkSmjHsqjTt8qh # k4jrwZe8jPkd2SKEJHTHBD1qqSmTzOu4W+H+XyWqNFjIwSNUnRuYEcM4nH49hmyl # D0CGfAL0XAJPKMuucZ8POsgz/hElNer8usVgPdl8GNWyqdN1eANyIso6wx/vLOUu # qfqeLLZRRv2vA9bqYGjqhRY2a4XpHsCz3cQk3IAqgUFtlD7I4MmBQQCeXr9/xQiY # ohgsQkCz+W84J0tOgPQ9gUfgiHzqHM61dVxRLhwrfxpyKOcAtdF0xtfkn60Hk7ZT # NTX8N+TD9l0WviFz3pIK+KBjaryWkmo++LxlVZve9Q2JJgT8JRqmJWnLwm3KfOJZ # X5es6+8uyLzXG1k8K8zyGciTaydjGc/86Sb4ynGbf5P+NGeETpnr/LN4CTNwumam # du0bc+sapQ3EIhMglFYKTixsTrH9z5wJuqIz7YcCAwEAAaNjMGEwHQYDVR0OBBYE # FN0ECQei9Xp9UlMSkpXuOIAlDaZZMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgw # FoAU3QQJB6L1en1SUxKSle44gCUNplkwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 # DQEBCwUAA4ICAQAgGBGUKfsmnRweHnBh8ZVyk3EkrWiTWI4yrxuzcAP8JSt0hZA9 # eGr0uYullzu1GJG7Hqf5QFuR+VWZrx4R0Fwdp2bjsZQHDDI5puobsHnYHZxwROOK # 3cT5lR+KOEM/AYWlR6c9RrK85SJo93uc2Cw+CiHILTOsv8WBmTF0wXVxxb6x8CNF # 9J1r/BljnaO8BMYYCyW7U4kPs4BQ3kXuRH+rlHhkmNP2KN2H2HBldPsOuRPrpw9h # qTKWzN677WNMGLupQPegVG4giHF1GOp6tDRy4CMnd1y2kOqGJUCr7zMPy5+CvqIg # +/a1LRrmwoWxdA/7yGUCpFIBR91JIsG/2OtrrH7e7GMzFbcjCI/GD41BWt2OxbmP # 5UU/eNu60htAsf5xTT/ggaK6XrTsFeCT3QgffuFVmQsh3pOeCvvmo0m9NjD+53ey # oHWXtS2BiBdlIPfakACfyVLMMso1fPU9D9gr1/UmbMkGNJYW6nBZGjJ5eQu2iH8P # Ukg9v2zYokQu0U63cljTiROV/kSr+NeLG26cvCygW9VqAK9fN+HV+hALmJyG5yaP # zvDsbopXC4DjTrLAoGNhkLpVaDd0araS25+hhiK2ZScO7LafQmDkZ8K12kELxNOL # YRu8+h+RK9dEB166KazZxenvU0ha64DxKFghzbAGVfsnP1OQcKkEHlcnuTCCBnIw # ggRaoAMCAQICCGQzUdPHOJ8IMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVT # MQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NM # IENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlv # biBBdXRob3JpdHkgUlNBMB4XDTE2MDYyNDIwNDQzMFoXDTMxMDYyNDIwNDQzMFow # eDELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9u # MREwDwYDVQQKDAhTU0wgQ29ycDE0MDIGA1UEAwwrU1NMLmNvbSBDb2RlIFNpZ25p # bmcgSW50ZXJtZWRpYXRlIENBIFJTQSBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIP # ADCCAgoCggIBAJ+DE3OqsMZtIcvbi3qHdNBx3I6Xcprku4g0tN2AA8YvRaR0mr8e # D1Dqnm1485/6USapPZ3RspRXPvs5iRuRK1bvZ8vmC+MOOYzGNfSMPd0l6QGsF0J9 # WBZA3PnVKEQdlWQwYTpk8pfXc0x9eyMCbfN161U9b6otxK++dKxd/mq2/OpceekP # Q5y1UgUP7z6xsY/QSa2m40IZVD/zLw6hy3z+E/kjOdolHLg+AEo6bzIwN2Qex651 # B9hV0hjJDoq8o1zwfAqnhYHCDq+PmVzTYCW8g1ppHCUTzXL165yAm9wsZ8TdyQmY # 1XPrxCGj5TKOPi9SmMZgN2SMsm9KVHIYzCeH+s11omMhTLU9ZP0rpptVryZMYLS5 # XP6rQ72t0BNmUB8L0omm/9eABvHDEQIzM2EX91Yfji87aOcV8XdWSimeA9rCKyZh # MlugVuVJKY02p/XHUqJWAyAvOHiAvfYGrkE0y5RFvZvHiRgfC7r/qa5qQJkT3e9Q # 3wG68gTW0DHfNDheV1vIOB5W1KxIpu3/+bjBO+3CJL5EYKd3zdU9mFm0Q+qqYH3N # wuUv8ev11CDVlzRuXQRrBRHS05KMCSdE7U81MUZ+dBkFYuyJ4+ojcJjk0S/UihMY # RpNl5Vhz00w9J3oiP8P4o1W3+eaHguxFHsVuOnyxTrmraPebY9WRQbypAgMBAAGj # gfswgfgwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTdBAkHovV6fVJTEpKV # 7jiAJQ2mWTAwBggrBgEFBQcBAQQkMCIwIAYIKwYBBQUHMAGGFGh0dHA6Ly9vY3Nw # cy5zc2wuY29tMBEGA1UdIAQKMAgwBgYEVR0gADATBgNVHSUEDDAKBggrBgEFBQcD # AzA7BgNVHR8ENDAyMDCgLqAshipodHRwOi8vY3Jscy5zc2wuY29tL3NzbC5jb20t # cnNhLVJvb3RDQS5jcmwwHQYDVR0OBBYEFFTC/hCVAJPNavXnwNfZsku4jwzjMA4G # A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEA9Q8mh3CvmaLK9dbJ8I1m # PTmC04gj2IK/j1SEJ7bTgwfXnieJTYSOVNEg7mBD21dCPMewlfa+zOqjPY5PBsYr # WYZ/63MbyuVAJuA9b8z2vXHGzX0OIEA51gXSr5QIv3/CUbcrtXuDIfBj2uWc4Wku # dR1Oy2Ee9aUz3wKdFdntaZNXukZFLoC8Zb7nEj7eR/+QnBCt9laypNT61vwuvJch # s3aD0pH6BlDRsYAogP7brQ9n7fh93NlwW3q6aLWzSmYXj+fw51fdaf68XuHVjJ8T # u5WaFft5K4XVbT5nR24bB1z7VEUPFhEuEcOwvLVuHDNXlB7+QjRGjjFQTtszV5X6 # OOTmEturWC5Ft9kiyvRaR0ksKOhPjEI8ZGjp5kOsGZGpxxOCX/xxCje3nVB7PF33 # olKCNeS159MKb2v+jfmk19UdS+d9Ygj42desmUnbtYRBFC72LmCXU0ua/vGIenS6 # nnXp4NqnycwsO3tMCnjPlPc2YLaDPIpUy04NaCqUEXUmFOogN8zreRd2VXhxbeJJ # ODM32+RsWccjYua8zi5US/1eAyrI3R5LcUTQdT4xYmWLKabtJOF6HYQ0f6QXfLSs # fT81WMvDvxrdn1RWbUXlU/OIiisxo8o+UNEANOwnCMNnxlzoaL/PLhZluDxm/zuy # lauajZ3MlPDteFB/7GRHo50wggaYMIIEgKADAgECAhBHaHS/7B9x7N3XWwMIISW7 # MA0GCSqGSIb3DQEBCwUAMHgxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQ # MA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxNDAyBgNVBAMMK1NT # TC5jb20gQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBDQSBSU0EgUjEwHhcNMjYw # NzA5MTQzMTE4WhcNMjcxMDEwMTQzMTE4WjB/MQswCQYDVQQGEwJVUzERMA8GA1UE # CAwITmV3IFlvcmsxEDAOBgNVBAcMB0plcmljaG8xHjAcBgNVBAoMFU5lY3RhciBT # ZXJ2aWNlcyBDb3JwLjELMAkGA1UECwwCSVQxHjAcBgNVBAMMFU5lY3RhciBTZXJ2 # aWNlcyBDb3JwLjCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAJmOMPRo # al1bEwe/HPHB533mkxLFf2LGg8R3yZQfDMkVWS0ME52aHJT33z61eMLqFamuvqY7 # qeb6jcOjqenYUhNmFcnl1Oq2hFWWE7McdgYAc0boWHemqxHWrUXN53sCp1DFVTh3 # NFwTo2PaY/KLdIjsXXvmA9qQBytzxEmeaF5HyCmmTlQ/N4eDeqpMOAbQn1n7lLi7 # xiHq1UAll3uvB+y7Vq/3V/dCO33/aiQif1t6h2R+N2JhoW0vtvd+yMyY58klGfN6 # O9WHto+MAiGoyP8rs9XWAzhIRZ2JjwFjfTqQOxVv0lUH+ZolhJ9uQHvybL3wHwLN # Qu8szoII3tqksiosdPc4Xk1nSyzrBfETafVVpey19HD6RhSIHrnPPHwWOzrdsT9j # O9szScE3i+cfJcKiL98lJkhFq/ZGOIwAPgxPvZZQZqbzEcsR0iddA+KNLxKqzkv9 # 1GtETHdFoU07xY7+2NsY1xhs9jFFNhoeCVh+dk3lIBvpWkuMwM8mZKziUQIDAQAB # o4IBlTCCAZEwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBRUwv4QlQCTzWr158DX # 2bJLuI8M4zB6BggrBgEFBQcBAQRuMGwwSAYIKwYBBQUHMAKGPGh0dHA6Ly9jZXJ0 # LnNzbC5jb20vU1NMY29tLVN1YkNBLUNvZGVTaWduaW5nLVJTQS00MDk2LVIxLmNl # cjAgBggrBgEFBQcwAYYUaHR0cDovL29jc3BzLnNzbC5jb20wUQYDVR0gBEowSDAI # BgZngQwBBAEwPAYMKwYBBAGCqTABAwMBMCwwKgYIKwYBBQUHAgEWHmh0dHBzOi8v # d3d3LnNzbC5jb20vcmVwb3NpdG9yeTATBgNVHSUEDDAKBggrBgEFBQcDAzBNBgNV # HR8ERjBEMEKgQKA+hjxodHRwOi8vY3Jscy5zc2wuY29tL1NTTGNvbS1TdWJDQS1D # b2RlU2lnbmluZy1SU0EtNDA5Ni1SMS5jcmwwHQYDVR0OBBYEFB7iZgW8t2SQ3I8n # dKd+fgqBvDYxMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEAhCFC # ydfNbwEfqJhrp612HM4Qh7JUphMm5ZLNomSEsonhjz1ltmMpB/0nGVH9Ond23hac # 12+xzfnDAB2HsNhA9vVWYbgbxcH1duQbdRatjrtWKd1+YOfsvAS+O652yRQf26OL # +2efdAnzaqsTYFoEJwFcgl1M5Uo+uSht961Jm7McHouRm/zYOnC+NjvGOgIL40KN # P1pLQTxK5zc54+yFQANjiCPNI07AnkYCSgHDJCoKZbJTcUk26kQNqNE9AvjsIlpY # uY8sTXHSIkN3WNuSPYr66GHaYj9m0AGUKGt/ZZePKmOsLu+nON5hwqXx+QX75yc2 # AIlKw/ItfZvBLeRuejoKnbOh6QiyDxn0lfAH8f/upDPCUfvBVADF56itElnCo+fC # 36rzFnfBaVPbLVzqlAGSs7e6Yn669snsTHkKQm1fALEiZyt3dqOvppr8AO1f+oCc # r/0jVezz0VyNUemChoAMBosREctn/fOYfL5NClq/mnI5m+tAHOajVk8LI6h7mlNv # tIFKwfHCIH2EJM4ze+ZrSXWt8bfY+6pQwd4xCB9UmCw2DBBMQ11rvu1rLD+u3kIL # NBAXHQGEnCoP+sY6h17wc/2Jb8BPg+yAvPXevqXmAdAKJrhvMmgsbttRxOQuVjrq # 42h1me0i3Jy0obhODmpXsrVPm02ZBIYgiRcK/q8xghHWMIIR0gIBATCBjDB4MQsw # CQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xETAP # BgNVBAoMCFNTTCBDb3JwMTQwMgYDVQQDDCtTU0wuY29tIENvZGUgU2lnbmluZyBJ # bnRlcm1lZGlhdGUgQ0EgUlNBIFIxAhBHaHS/7B9x7N3XWwMIISW7MA0GCWCGSAFl # AwQCAQUAoHwwEAYKKwYBBAGCNwIBDDECMAAwGQYJKoZIhvcNAQkDMQwGCisGAQQB # gjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkE # MSIEIL1ALmpxvunlbgXfKg0f7pvVGR/n0zee8uv5RtBx0JuYMA0GCSqGSIb3DQEB # CwUABIIBgHqO97Zm/x2d3Q+BAJzRKjtxQzDhg5/YXtWrzQ31QEHNGlpuk3qSXpjt # hfAu1vH4nJRJTPJLB+3LF5w7Lo7tqs2psBOUCrBqOBDjZxPdUB6W4oGZu6aF3bIP # xz9WjDi9KWdIhpH8jtJ9WcYRaCvVRE5Rqu8pb43ppTyAbC/Y+boz0SW3N1MbQU1d # kTYoy9zQcB0CFcTG0IAWFn8bI0XuXJ3z0e0Y2OzNsINI5Swzq2WyGuKoLwIO/CJM # mAa5JsWGFdjkSX504I0asAqud50Yeom5XC4+hJkEKRCVJS1VXyXrJjD/KFQFngNE # yAchuWkS1Ff7Pvia1aSlQI/uHvkLQcPv4o568ScPUJAdDrsKkqBRZQFgoUtGhX8p # u9ITaqgef2XUK+FTJjro2BPaKjXa3izi0K4I5gYChzxXZ/ew2SJxKWzbU6Ciw8PL # gvsSCQVCix7C/21j5+moogQeEK22/oKlQIS1EqzmPgERxO6WhlbdiClBLW7hOXeg # uI3/XrKLbaGCDxwwgg8YBgorBgEEAYI3AwMBMYIPCDCCDwQGCSqGSIb3DQEHAqCC # DvUwgg7xAgEDMQ0wCwYJYIZIAWUDBAIBMH0GCyqGSIb3DQEJEAEEoG4EbDBqAgEB # BgwrBgEEAYKpMAEDBgEwLzALBglghkgBZQMEAgEEIMrvscYasc6tYZIb2B1p+RVe # OELKE6Lfo2EkN77AdQpuAggGW0UDeIXUmBgPMjAyNjA5MTUxNzMxNTNaMAMCAQEC # BgGgpiAKvqCCDAAwggT8MIIC5KADAgECAhAfaxZi0i4bbF3xwMGgYA44MA0GCSqG # SIb3DQEBCwUAMHMxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UE # BwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxLzAtBgNVBAMMJlNTTC5jb20g # VGltZXN0YW1waW5nIElzc3VpbmcgUlNBIENBIFIxMB4XDTI1MDIxODE2MzIwMloX # DTM0MTExMjE4NTAwNVowbjELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAw # DgYDVQQHDAdIb3VzdG9uMREwDwYDVQQKDAhTU0wgQ29ycDEqMCgGA1UEAwwhU1NM # LmNvbSBUaW1lc3RhbXBpbmcgVW5pdCAyMDI1IEUxMFkwEwYHKoZIzj0CAQYIKoZI # zj0DAQcDQgAEG/tRUcdv5lWW7E9eV8Tczq2DReerx2Jz47e884JGlqVQzW870D4Z # HNJVWPLKAeFisHDrZcsWHWS/t77JF39pNqOCAVowggFWMB8GA1UdIwQYMBaAFAyd # ECWOmqcbmYdDzwh+4b2BkPTPMFEGCCsGAQUFBwEBBEUwQzBBBggrBgEFBQcwAoY1 # aHR0cDovL2NlcnQuc3NsLmNvbS9TU0wuY29tLXRpbWVTdGFtcGluZy1JLVJTQS1S # MS5jZXIwUQYDVR0gBEowSDA8BgwrBgEEAYKpMAEDBgEwLDAqBggrBgEFBQcCARYe # aHR0cHM6Ly93d3cuc3NsLmNvbS9yZXBvc2l0b3J5MAgGBmeBDAEEAjAWBgNVHSUB # Af8EDDAKBggrBgEFBQcDCDBGBgNVHR8EPzA9MDugOaA3hjVodHRwOi8vY3Jscy5z # c2wuY29tL1NTTC5jb20tdGltZVN0YW1waW5nLUktUlNBLVIxLmNybDAdBgNVHQ4E # FgQUznzZwASAxSQQagnqHKslPRH9qNIwDgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3 # DQEBCwUAA4ICAQCAc3Ukhb1mU2KnTsV9j2vUsnAspOXWH/L2vUGMOAcwTPtTsnuY # DLfYnEDUovKMIImo2S5F+EMcYUR9m2NM6u7sBAwNIOJQO8IJzeNrPmnL2Ma/Ah7m # emQttepeED5KoLMbvX1RKKDCEeRivu/w2JehpjRe7TenQGJlmt5mWmeCYYH37zo3 # 3gWogXHYjlnmK67t3iPtoA5kE3F9T2MUMggYO1Z9Z4KkXRDyssT/cMcOXMkqzkiX # eL9Wg6XutNT3fyhKvEzDDDoYMGUpfysYfG+SOAhv0xeRWCUlIMew0BkN4JL+KdrE # ocD4KG4Hwrg7EjFrqTV754cHKlqQBjfC43vDs+U+aE3qTkh2pmfcdkezZWOhHzjV # n3CZU8V0YN2QFntc6Zvk5lRoq5+y+0RHRVtjYOTNqoBoi23WRz4j4VTPs+JXPY9T # Ol6CR+1FHG+s/IgvTxuUlOdsxDReuoM3SsR+5Mu/heGGcrIlpeHEJR2M79xG6YzN # nflBNQwi0FbLXEanSLKgVWcDJrak+xUy4Aj6zLXPGU5L2XmJLG8onyCmek6COphN # ru7V7Jmj7gmVwaiKJHXsu2ExOsXWrra07nE6kjy3FRnqC0oa2QlXrB2P69ktzApn # Yz3capWk6jpQGUaPHWwqxVnsAhTMlmWLg0nQzYphyt82eV5uRgqkOdKpbDCCBvww # ggTkoAMCAQICEG1SGHCH6CNNhWAA0ICPk1YwDQYJKoZIhvcNAQELBQAwfDELMAkG # A1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYD # VQQKDA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0 # aWZpY2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTkxMTEzMTg1MDA1WhcNMzQxMTEy # MTg1MDA1WjBzMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcM # B0hvdXN0b24xETAPBgNVBAoMCFNTTCBDb3JwMS8wLQYDVQQDDCZTU0wuY29tIFRp # bWVzdGFtcGluZyBJc3N1aW5nIFJTQSBDQSBSMTCCAiIwDQYJKoZIhvcNAQEBBQAD # ggIPADCCAgoCggIBAK5REBPS+TwgoCCF3slQHGTJ4f3F6TT/Cn8xSOhyWsVeqGH9 # 8Yf3UVz7t+bQwcITsD7CY6KoGP04OskBgareubfeMKcdKwIE1YBBjKhq4urwiOqx # LUmVcvb2oM0wx3BnxQ3NBLu9ZkwMnjQlIY2mEwZMgDaqfZuiEa2BFzinXf3kRLKl # Q5oa8ne3QU0vcG4qZvphy0xxBQXayqigzN3z2HQTq6N28EOjpnA2dajGPtiZ9aNJ # eDfcDka5j3KbhBkzk4RWCjx5vP8H6DKHIIs02GHgxv/jG8JMIxWY1isG+IaB09li # vKbxlvzhNAKZK5fQmUstrpYrVo7qqXAhJtv1tUaHzrp6QpuUL9dE/bSAC7UKO9xh # yJSA1OsYWDx/wAmBA84JzX8IJ1olJjCEmlJ2F4o6dCARKA2Zhk+EU4LogpowBReT # lTW2NNwUKAW+8Cte0rhrMBZQ47Vjd92V0gEvouOTMtQJgk2QVeqGwFVw8y4HSdQN # a8sl8+Kay2MnyUXhLoQLFaeVaLs4SVXBOe3Ua1Gp5j3J2+8Yue1T4V5wrsNuocNR # 3frpSt4yRIG3N68Bz1qqhk+eNUyO8WpXWlg6POZOJUdm0BzzRsB8V7kst8nM8joO # e03KqhunBN69Ckeo8M32qo07zeveRrDwD2P4dmJLDYBflwZ1A/SQbS+HN+AHAgMB # AAGjggGBMIIBfTASBgNVHRMBAf8ECDAGAQH/AgEAMB8GA1UdIwQYMBaAFN0ECQei # 9Xp9UlMSkpXuOIAlDaZZMIGDBggrBgEFBQcBAQR3MHUwUQYIKwYBBQUHMAKGRWh0 # dHA6Ly93d3cuc3NsLmNvbS9yZXBvc2l0b3J5L1NTTGNvbVJvb3RDZXJ0aWZpY2F0 # aW9uQXV0aG9yaXR5UlNBLmNydDAgBggrBgEFBQcwAYYUaHR0cDovL29jc3BzLnNz # bC5jb20wPwYDVR0gBDgwNjA0BgRVHSAAMCwwKgYIKwYBBQUHAgEWHmh0dHBzOi8v # d3d3LnNzbC5jb20vcmVwb3NpdG9yeTATBgNVHSUEDDAKBggrBgEFBQcDCDA7BgNV # HR8ENDAyMDCgLqAshipodHRwOi8vY3Jscy5zc2wuY29tL3NzbC5jb20tcnNhLVJv # b3RDQS5jcmwwHQYDVR0OBBYEFAydECWOmqcbmYdDzwh+4b2BkPTPMA4GA1UdDwEB # /wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAkhl1DaZaQs8ZB9ny/JT6wJvwFelE # llovcTPdUOUTe5mTdw/E+3JtV8u6ppyLRbpIHbYlMy20KJAychU6xdaci4BsP9oV # NxSRMsEjfHKz7ARqPNdpclhYAINLjsFGMO1iUNbXiAsnF/xboNCgfeMcMYbLyQYk # U6UMobv9isrtQZ8e0EAQNV7qXJn4W0KyuTt0P8iIv/5DdDpIUBIktDZcjz2KEW6B # 1gvvsKIM1esjYwWylAazBcQAake5pANMdSn8t1HdPKsiwuWfOguyRQazAX8oXz6S # lZSIok0Lis9a02vGVtdhEaB0R3HxIyNRMMKWV1yuSeUXFuoexWav3GRPZC0WYb50 # SrW/l+wgrS8doetaMwyZon2L7ioYlIPSy1h9Dq/Q911PsSkbEZ3zrsB1roVnIfBu # 5BJp0xvQrQ/Q4LavuvCoFR7QFoypNrotbNYi2AGMZw5td4zGZtCqUTPZi0BwSuRm # +HRYAEMMThTwbJX/fYV1oC8mBN970yIvadIGKhh7+DmYdRJYBrL8inVFCZAK+YX2 # w1+qWEnCSPL/VTWJtSRMhQFfceDKbJC+pBNksvKzqkva0J1ZyMj1i4vDfSuBmbz4 # rfzsvvJxS+quZDdkmW6MeXevWGBXvqzdbAw+AqTVsAQUyP6tFeKZIL4S/fSFdl2r # Ix2X+KXkqx3S+EYxggJYMIICVAIBATCBhzBzMQswCQYDVQQGEwJVUzEOMAwGA1UE # CAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xETAPBgNVBAoMCFNTTCBDb3JwMS8w # LQYDVQQDDCZTU0wuY29tIFRpbWVzdGFtcGluZyBJc3N1aW5nIFJTQSBDQSBSMQIQ # H2sWYtIuG2xd8cDBoGAOODALBglghkgBZQMEAgGgggFhMBoGCSqGSIb3DQEJAzEN # BgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMjYwOTE1MTczMTUzWjAoBgkq # hkiG9w0BCTQxGzAZMAsGCWCGSAFlAwQCAaEKBggqhkjOPQQDAjAvBgkqhkiG9w0B # CQQxIgQgnwpNsGp4vQ/Tt/meRKxo9/3YriIL23NxQRTZTPBLlHMwgckGCyqGSIb3 # DQEJEAIvMYG5MIG2MIGzMIGwBCBUKvmhao1yLmYRSXiK6ZTBipqu5aZcs0SiVJr5 # bHnHizCBizB3pHUwczELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYD # VQQHDAdIb3VzdG9uMREwDwYDVQQKDAhTU0wgQ29ycDEvMC0GA1UEAwwmU1NMLmNv # bSBUaW1lc3RhbXBpbmcgSXNzdWluZyBSU0EgQ0EgUjECEB9rFmLSLhtsXfHAwaBg # DjgwCgYIKoZIzj0EAwIERzBFAiAjzouzQaGyRDI7+RHaaZhHM2UI0UzZnyzBE6Pf # KwZYXQIhAJJHOKvyujQ8O5+Ckh1ZkcSZYwk5uJXShiP2vmP5QCle # SIG # End signature block |