UMN-Common.psm1
####### UMN-Common Module #### ### # Copyright 2017 University of Minnesota, Office of Information Technology # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with Foobar. If not, see <http://www.gnu.org/licenses/>. ### function Convert-ColumnIndexToA1Notation { <# .SYNOPSIS Short description .DESCRIPTION Long description .EXAMPLE Example .NOTES General notes #> [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [int]$ColumnIndex ) process { while ($ColumnIndex -gt 0) { $Temp = ($ColumnIndex -1) % 26 #$Letter = } } } #END Convert-ColumnIndexToA1Notation <# .Synopsis convert text or byte array to URL friendly BAse64 .DESCRIPTION convert text or byte array to URL friendly BAse64 .EXAMPLE ConvertTo-Base64URL -text $headerJSON .EXAMPLE ConvertTo-Base64URL -Bytes $rsa.SignData($toSign,"SHA256") #> function ConvertTo-Base64URL { param ( [Parameter(ParameterSetName='String')] [string]$text, [Parameter(ParameterSetName='Bytes')] [System.Byte[]]$Bytes ) if($Bytes){$base = $Bytes} else{$base = [System.Text.Encoding]::UTF8.GetBytes($text)} $base64Url = [System.Convert]::ToBase64String($base) $base64Url = $base64Url.Split('=')[0] $base64Url = $base64Url.Replace('+', '-') $base64Url = $base64Url.Replace('/', '_') $base64Url } function ConvertTo-OrderedDictionary { <# .SYNOPSIS Converts a hashtable or array to an ordered dictionary. .DESCRIPTION Takes in a hashtable or array and then returns an ordered dictionary. .PARAMETER Object Object to convert to an ordered dictionary .NOTES Name: ConvertTo-OrderedDictionary Author: Jeff Bolduan LASTEDIT: 3/11/2016 .EXAMPLE @{"Item1" = "Value1"; "Item2" = "Value2"; "Item3" = "Value3"; "Item4" = "Value4"} | ConvertTo-OrderedDictionary Will return the following: Name Value ---- ----- Item1 Value1 Item2 Value2 Item3 Value3 Item4 Value4 .EXAMPLE ConvertTo-OrderedDictionary -Object @{"Item1" = "Value1"; "Item2" = "Value2"; "Item3" = "Value3"; "Item4" = "Value4"} Will return the following: Name Value ---- ----- Item1 Value1 Item2 Value2 Item3 Value3 Item4 Value4 #> [CmdletBinding()] param( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [ValidateNotNullOrEmpty()] $Object ) Begin { $Dictionary = [ordered]@{} } Process { if($Object -is [System.Collections.Hashtable]) { foreach($Key in ($Object.Keys | sort)) { $Dictionary.Add($Key, $Object[$Key]) } } elseif($Object -is [System.Array]) { for($i = 0; $ -lt $Object.Count; $i++) { $Dictionary.Add($i, $Object[$i]) } } else { throw [System.IO.InvalidDataException] } } End { return $Dictionary } } #END ConvertTo-OrderedDictionary <# .Synopsis zip up module for DSC pull Server - can not use 7zip .DESCRIPTION zip up module for DSC pull Server - can not use 7zip .EXAMPLE CreateZipFromPSModulePath -ListModuleNames cChoco -Destination $dest #> function CreateZipFromPSModulePath { param( [Parameter(Mandatory)] [string[]]$ListModuleNames, [Parameter(Mandatory)] [string]$Destination ) foreach ($module in $ListModuleNames) { $allVersions = Get-Module -Name $module -ListAvailable -Verbose # Package all versions of the module foreach ($moduleVersion in $allVersions) { $name = $moduleVersion.Name $source = "$Destination\$name" # Create package zip $path = $moduleVersion.ModuleBase $version = $moduleVersion.Version.ToString() Compress-Archive -Path "$path\*" -DestinationPath "$source.zip" -Verbose -Force $newName = "$Destination\$name" + "_" + "$version" + ".zip" # Rename the module folder to contain the version info. if (Test-Path $newName) { Remove-Item $newName -Recurse -Force } Rename-Item -Path "$source.zip" -NewName $newName -Force } } } function Get-ARP { <# .SYNOPSIS This function is designed to return all ARP entries .DESCRIPTION This function returns an object containing all arp entries and details for each sub item property. On 64-bit powershell sessions there's dynamic paramters to specify the the 32-bit registry or 64-bit registry only .NOTES Name: Get-ARP Author: Aaron Miller LASTEDIT: 05/08/2013 .EXAMPLE $ARP = Get-ARP This returns all arp entries into a variable for processing later. #> [CmdletBinding(DefaultParameterSetName='none')] Param () DynamicParam { if ([IntPtr]::size -eq 8) { $att1 = new-object -Type System.Management.Automation.ParameterAttribute -Property @{ParameterSetName="x64ARP"} $attC1 = new-object -Type System.Collections.ObjectModel.Collection[System.Attribute] $attC1.Add($att1) $dynParam1 = new-object -Type System.Management.Automation.RuntimeDefinedParameter("x64ARP", [switch], $attC1) $att2 = new-object -Type System.Management.Automation.ParameterAttribute -Property @{ParameterSetName="x86ARP"} $attC2 = new-object -Type System.Collections.ObjectModel.Collection[System.Attribute] $attC2.Add($att2) $dynParam2 = new-object -Type System.Management.Automation.RuntimeDefinedParameter("x86ARP", [switch], $attC2) $paramDictionary = new-object -Type System.Management.Automation.RuntimeDefinedParameterDictionary $paramDictionary.Add("x64ARP", $dynParam1) $paramDictionary.Add("x86ARP", $dynParam2) return $paramDictionary } } Begin { $Primary = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" $Wow = "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" $toProcess = @() switch ($PsCmdlet.ParameterSetName) { "x64ARP" {$toProcess+=$Primary} "x86ARP" {$toProcess+=$Wow} default {$toProcess+=$Primary;if ([IntPtr]::size -eq 8) {$toProcess+=$Wow}} } } End {Return [array]($toProcess | ForEach-Object {Get-ChildItem $_} | ForEach-Object {Get-ItemProperty $_.pspath})} } #END Get-ARP function Get-ExceptionsList { <# .SYNOPSIS Get's all exceptions available on current machine. .DESCRIPTION Goes through all the assemblies on the current computer and gets every exception then outputs them to the console. .NOTES Name: Get-ExceptionsList Author: Jeff Bolduan LASTEDIT: 3/11/2016 #> [CmdletBinding()] param() # Get all current assemblies $CurrentDomainAssemblies = [appdomain]::CurrentDomain.GetAssemblies() # Loop through assemblies and output any members which contain exception in the name foreach($Assembly in $CurrentDomainAssemblies) { try { $Assembly.GetExportedTypes() | Where-Object { $_.Fullname -match 'Exception' } } catch { } } } #END Get-ExceptionsList function Get-RandomString { <# .SYNOPSIS Returns a random string of a given length. .DESCRIPTION Takes in a minimum and maximum lenth and then builds a string of that size. .PARAMETER LengthMin Integer for the minimum length of the string .PARAMETER LengthMax Integer for the maximum length of the string .NOTES Name: Get-RandomString Author: Jeff Bolduan LASTEDIT: 3/11/2016 .EXAMPLE Get-RandomString -LengthMin 5 -LengthMax 10 Will return a random string composed of [a-z][A-Z][0-9] and dash, underscore and period. It's length will be between 5 and 10. #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$LengthMin, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$LengthMax, [Parameter(Mandatory=$false)] [ValidateNotNullOrEmpty()] [string]$ValidCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_." ) $PossibleCharacters = $ValidCharacters.ToCharArray() $Result = "" if($LengthMin -eq $LengthMax) { $Length = $LengthMin } else { $Length = Get-Random -Minimum $LengthMin -Maximum $LengthMax } #Write-Verbose -Message "Length: $Length" for($i = 0; $i -lt $Length; $i++) { $Result += $PossibleCharacters | Get-Random } return $Result } #END Get-RandomString #region Get-UsersIDM function Get-UsersIDM { <# .Synopsis Fetch list of users from IDM .DESCRIPTION Fetch list of users from IDM .EXAMPLE $users = Get-UsersIDM -ldapCredential $ldapCredential -ldapServer $ldapServer -ldapSearchString "(Role=*.cur*)" .EXAMPLE $users = Get-UsersIDM -ldapCredential $ldapCredential -ldapServer $ldapServer -ldapSearchString "(&(Role=*.staff.*)(cn=mrEd))" #> [CmdletBinding()] Param ( [System.Net.NetworkCredential]$ldapCredential, [Parameter(Mandatory)] [string]$ldapServer, [Parameter(Mandatory)] [string]$ldapSearchString, [string]$searchDN ) #Load the assemblies needed for ldap lookups $null = [System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.Protocols") $null = [System.Reflection.Assembly]::LoadWithPartialName("System.Net") #setup the ldap connection $ldapConnection = New-Object System.DirectoryServices.Protocols.LdapConnection((New-Object System.DirectoryServices.Protocols.LdapDirectoryIdentifier($ldapServer,636)), $ldapCredential) $ldapConnection.AuthType = [System.DirectoryServices.Protocols.AuthType]::Basic $ldapConnection.SessionOptions.ProtocolVersion = 3 #cert validation fails, so this will never validate the cert and just connect things $ldapConnection.SessionOptions.VerifyServerCertificate = { return $true; } $ldapConnection.SessionOptions.SecureSocketLayer = $true $ldapConnection.Bind() #build the ldap query $ldapSearch = New-Object System.DirectoryServices.Protocols.SearchRequest $ldapSearch.Filter = $ldapSearchString $ldapSearch.Scope = "Subtree" $ldapSearch.DistinguishedName = $searchDN #execute query for Students...30 minute timeout...generally takes about 12 minutes $ldapResponse = $ldapConnection.SendRequest($ldapSearch, (New-Object System.TimeSpan(0,30,0))) -as [System.DirectoryServices.Protocols.SearchResponse] $null = $ldapConnection.Dispose() return ($ldapResponse) } #endregion #region Get-WebReqErrorDetails function Get-WebReqErrorDetails { <# .SYNOPSIS Returns JSON Responsbody data from an Error thrown by Invoke-Webrequest or Invoke-RestMethod .DESCRIPTION Returns JSON Responsbody data from an Error thrown by Invoke-Webrequest or Invoke-RestMethod .PARAMETER err Error thrown by Invoke-Webrequest or Invoke-RestMethod .NOTES Author: Travis Sobeck #> [CmdletBinding()] param ( [Parameter(Mandatory)] [System.Management.Automation.ErrorRecord]$err ) $reader = New-Object System.IO.StreamReader($err.Exception.Response.GetResponseStream()) $reader.BaseStream.Position = 0 $reader.DiscardBufferedData() return ($reader.ReadToEnd() | ConvertFrom-Json) } #endregion function Out-RecursiveHash { <# .SYNOPSIS Outputs a hashtable recursively .DESCRIPTION Takes in a hashtable and then writes the values stored within to output. .PARAMETER hash Hashtable to be outputted recursively .NOTES Name: Out-RecursiveHash Author: Jeff Bolduan LASTEDIT: 3/11/2016 .EXAMPLE $hashtable = @{ "Item1" = @{ "SubItem1" = "Value" }; "Item2" = "Value2" } Out-RecursiveHash -Hash $hashtable This will output: SubItem1 : Value Item2 : Value2 #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [hashtable]$Hash ) $Return = "" # Loop through each of the hashtable keys and output the key pair unless it's a hashtable then recursive call foreach($key in $hash.keys) { if($hash[$key] -is [HashTable]) { $Return += (Out-RecursiveHash $hash[$key]) } else { $Return += "$key : $($hash[$key])`n" } } return $Return } #END Out-RecursiveHash #region Send-SplunkHEC function Send-SplunkHEC { <# .Synopsis Send event to Splunk HTTP Event Collector .DESCRIPTION Send event to Splunk HTTP Event Collector .PARAMETER uri URI for HEC endpoint .PARAMETER header contains auth token .PARAMETER host Part of Splunk Metadata for event. Device data being sent from .PARAMETER source Part of Splunk Metadata for event. Source .PARAMETER sourceType Part of Splunk Metadata for event. SourceType .PARAMETER Retries Set how many retries will be attempted if invoking fails .PARAMETER SecondsDelay Set how many seconds to wait between retries .PARAMETER metadata Part of Splunk Metadata for event. Combination of host,source,sourcetype in performatted hashtable, will be comverted to JSON .PARAMETER eventData Event Data in hastable or pscustomeobject, will be comverted to JSON .PARAMETER JsonDepth Optional, specifies the Depth parameter to pass to ConvertTo-JSON, defaults to 100 #> [CmdletBinding()] Param ( # Param1 help description [Parameter(Mandatory)] [string]$uri, [Parameter(Mandatory)] [Collections.Hashtable]$header, [Parameter(Mandatory,ParameterSetName='Components')] [String]$source, [Parameter(Mandatory,ParameterSetName='Components')] [String]$sourceType, [Parameter(Mandatory,ParameterSetName='Components')] [String]$host, [Parameter(Mandatory,ParameterSetName='hashtable')] [Collections.Hashtable]$metadata, # This can be [Management.Automation.PSCustomObject] or [Collections.Hashtable] [Parameter(Mandatory)] $eventData, [int]$Retries = 5, [int]$SecondsDelay = 10, [int]$JsonDepth = 100 ) Begin{ $retryCount = 0 $completed = $false $response = $null } Process { if ($metadata){$bodySplunk = $metadata} else {$bodySplunk = @{'host' = $host;'source' = $source;'sourcetype' = $sourcetype}} #Splunk takes time in Unix Epoch format, so first get the current date, #convert it to UTC (what Epoch is based on) then format it to seconds since January 1 1970. #Without converting it to UTC the date would be offset by a number of hours equal to your timezone's offset from UTC $bodySplunk['time'] = (Get-Date).toUniversalTime() | Get-Date -UFormat %s $eventData.Add('retry',$retryCount) $bodySplunk['event'] = $eventData while (-not $completed) { try { $response = Invoke-RestMethod -Uri $uri -Headers $header -UseBasicParsing -Body ($bodySplunk | ConvertTo-Json -Depth $JsonDepth) -Method Post if ($response.text -ne 'Success' -or $response.code -ne 0){throw "Failed to submit to Splunk HEC $($response)"} $completed = $true } catch { if ($retrycount -ge $Retries) { throw } else { Start-Sleep $SecondsDelay $retrycount++ $bodySplunk.event.retry = $retryCount } } } } End{return $true} } #endregion #region Get-CurrentEpochTime function Get-CurrentEpochTime { <# .Synopsis Get current Epoch Time (seconds from 00:00:00 1 January 1970 UTC) as string with 1/100,000 of a second precision .DESCRIPTION Get current Epoch Time (seconds from 00:00:00 1 January 1970 UTC) as string with 1/100,000 of a second precision #> [OutputType([string])] [CmdletBinding()] Param ( ) Begin{} Process { (Get-Date).toUniversalTime() | Get-Date -UFormat %s } End{} } #endregion function Set-ModuleLatestVersion { <# .Synopsis installs latest version of a module and deletes the old one .DESCRIPTION The problem with Update-module is it leave the old one behind, this cleans that up .EXAMPLE Set-ModuleLatestVersion -module xPSDesiredStateConfiguration #> [CmdletBinding()] Param ( # Param1 help description [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=0)] [string]$module ) Begin { } Process { $currentMod = get-module -ListAvailable $module if ($currentMod.count -gt 1){throw "Multiple version of module installed, clear out old $($currentMod.Version)"} $currentVersion = $currentMod.Version.ToString() Update-Module $module -Force if((get-module -ListAvailable $module).count -gt 1){Uninstall-Module -Name $module -RequiredVersion $currentVersion;get-module -ListAvailable $module} else {Write-Warning "Current version was latest version"} } End { } } function Test-RegistryValue { <# .SYNOPSIS This function takes in a registry path, a name and then determines whether the registry value exists. .NOTES Name: Test-RegistryValue Author: Jeff Bolduan LASTEDIT: 09/01/2016 .EXAMPLE Test-RegistryValue -Path HKLM:\Foo\Bar -Value FooBar #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string]$Path, [Parameter(Mandatory=$true)] [string]$Name, [Parameter(Mandatory=$false)] [switch]$PassThru ) if(Test-Path -LiteralPath $Path) { $Key = Get-Item -LiteralPath $Path if($Key.GetValue($Value, $null) -ne $null) { if($PassThru) { Get-ItemProperty -LiteralPath $Path -Name $Name } else { $true } } else { $false } } else { return $false } } #END Test-RegistryValue function Write-Log { <# .SYNOPSIS This function is used to pass messages to a ScriptLog. It can also be leveraged for other purposes if more complex logging is required. .DESCRIPTION Write-Log function is setup to write to a log file in a format that can easily be read using CMTrace.exe. Variables are setup to adjust the output. .PARAMETER Message The message you want to pass to the log. .PARAMETER Path The full path to the script log that you want to write to. .PARAMETER Severity Manual indicator (highlighting) that the message being written to the log is of concern. 1 - No Concern (Default), 2 - Warning (yellow), 3 - Error (red). .PARAMETER Component Provide a non null string to explain what is being worked on. .PARAMETER Context Provide a non null string to explain why. .PARAMETER Thread Provide a optional thread number. .PARAMETER Source What was the root cause or action. .PARAMETER Console Adjusts whether output is also directed to the console window. .NOTES Name: Write-Log Author: Aaron Miller LASTEDIT: 01/23/2013 10:09:00 .EXAMPLE Write-Log -Message $exceptionMsg -Path $ScriptLog -Severity 3 Writes the content of $exceptionMsg to the file at $ScriptLog and marks it as an error highlighted in red #> PARAM( [Parameter(Mandatory=$true)][string]$Message, [Parameter(Mandatory=$false)][string]$Path = "$env:TEMP\CMTrace.Log", [Parameter(Mandatory=$false)][int]$Severity = 1, [Parameter(Mandatory=$false)][string]$Component = " ", [Parameter(Mandatory=$false)][string]$Context = " ", [Parameter(Mandatory=$false)][string]$Thread = "1", [Parameter(Mandatory=$false)][string]$Source = "", [Parameter(Mandatory=$false)][switch]$Console ) # Setup the log message $time = Get-Date -Format "HH:mm:ss.fff" $date = Get-Date -Format "MM-dd-yyyy" $LogMsg = '<![LOG['+$Message+']LOG]!><time="'+$time+'+000" date="'+$date+'" component="'+$Component+'" context="'+$Context+'" type="'+$Severity+'" thread="'+$Thread+'" file="'+$Source+'">' # Write out the log file using the ComObject Scripting.FilesystemObject $ForAppending = 8 $oFSO = New-Object -ComObject scripting.filesystemobject $oFile = $oFSO.OpenTextFile($Path, $ForAppending, $true) $oFile.WriteLine($LogMsg) $oFile.Close() Remove-Variable oFSO Remove-Variable oFile # Write to the console if $Console is set to True if ($Console -eq $true) {Write-Host $Message} } #END Write-Log # SIG # Begin signature block # MIIfBwYJKoZIhvcNAQcCoIIe+DCCHvQCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUowCAFAeTFVOMpJo0Q8RNhli3 # 3MGgghoTMIIEhDCCA2ygAwIBAgIQQhrylAmEGR9SCkvGJCanSzANBgkqhkiG9w0B # AQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNV # BAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRU # cnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTA1MDYwNzA4MDkxMFoXDTIwMDUzMDEw # NDgzOFowgZUxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2Fs # dCBMYWtlIENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8G # A1UECxMYaHR0cDovL3d3dy51c2VydHJ1c3QuY29tMR0wGwYDVQQDExRVVE4tVVNF # UkZpcnN0LU9iamVjdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6q # gT+jo2F4qjEAVZURnicPHxzfOpuCaDDASmEd8S8O+r5596Uj71VRloTN2+O5bj4x # 2AogZ8f02b+U60cEPgLOKqJdhwQJ9jCdGIqXsqoc/EHSoTbL+z2RuufZcDX65OeQ # w5ujm9M89RKZd7G3CeBo5hy485RjiGpq/gt2yb70IuRnuasaXnfBhQfdDWy/7gbH # d2pBnqcP1/vulBe3/IW+pKvEHDHd17bR5PDv3xaPslKT16HUiaEHLr/hARJCHhrh # 2JU022R5KP+6LhHC5ehbkkj7RwvCbNqtMoNB86XlQXD9ZZBt+vpRxPm9lisZBCzT # bafc8H9vg2XiaquHhnUCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rE # JlTvA73gJMtUGjAdBgNVHQ4EFgQU2u1kdBScFDyr3ZmpvVsoTYs8ydgwDgYDVR0P # AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQG # A1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVz # dEV4dGVybmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGG # GWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEFBQADggEBAE1C # L6bBiusHgJBYRoz4GTlmKjxaLG3P1NmHVY15CxKIe0CP1cf4S41VFmOtt1fcOyu9 # 08FPHgOHS0Sb4+JARSbzJkkraoTxVHrUQtr802q7Zn7Knurpu9wHx8OSToM8gUmf # ktUyCepJLqERcZo20sVOaLbLDhslFq9s3l122B9ysZMmhhfbGN6vRenf+5ivFBjt # pF72iZRF8FUESt3/J90GSkD2tLzx5A+ZArv9XQ4uKMG+O18aP5cQhLwWPtijnGMd # ZstcX9o+8w8KCTUi29vAPwD55g1dZ9H9oB4DK9lA977Mh2ZUgKajuPUZYtXSJrGY # Ju6ay0SnRVqBlRUa9VEwggTmMIIDzqADAgECAhBiXE2QjNVC+6supXM/8VQZMA0G # CSqGSIb3DQEBBQUAMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNV # BAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdv # cmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEdMBsGA1UEAxMU # VVROLVVTRVJGaXJzdC1PYmplY3QwHhcNMTEwNDI3MDAwMDAwWhcNMjAwNTMwMTA0 # ODM4WjB6MQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVy # MRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEg # MB4GA1UEAxMXQ09NT0RPIFRpbWUgU3RhbXBpbmcgQ0EwggEiMA0GCSqGSIb3DQEB # AQUAA4IBDwAwggEKAoIBAQCqgvGEqVvYcbXSXSvt9BMgDPmb6dGPdF5u7uspSNjI # vizrCmFgzL2SjXzddLsKnmhOqnUkcyeuN/MagqVtuMgJRkx+oYPp4gNgpCEQJ0Ca # WeFtrz6CryFpWW1jzM6x9haaeYOXOh0Mr8l90U7Yw0ahpZiqYM5V1BIR8zsLbMaI # upUu76BGRTl8rOnjrehXl1/++8IJjf6OmqU/WUb8xy1dhIfwb1gmw/BC/FXeZb5n # OGOzEbGhJe2pm75I30x3wKoZC7b9So8seVWx/llaWm1VixxD9rFVcimJTUA/vn9J # AV08m1wI+8ridRUFk50IYv+6Dduq+LW/EDLKcuoIJs0ZAgMBAAGjggFKMIIBRjAf # BgNVHSMEGDAWgBTa7WR0FJwUPKvdmam9WyhNizzJ2DAdBgNVHQ4EFgQUZCKGtkqJ # yQQP0ARYkiuzbj0eJ2wwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8C # AQAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEQYDVR0gBAowCDAGBgRVHSAAMEIGA1Ud # HwQ7MDkwN6A1oDOGMWh0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZp # cnN0LU9iamVjdC5jcmwwdAYIKwYBBQUHAQEEaDBmMD0GCCsGAQUFBzAChjFodHRw # Oi8vY3J0LnVzZXJ0cnVzdC5jb20vVVROQWRkVHJ1c3RPYmplY3RfQ0EuY3J0MCUG # CCsGAQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEB # BQUAA4IBAQARyT3hBeg7ZazJdDEDt9qDOMaSuv3N+Ntjm30ekKSYyNlYaDS18Ash # U55ZRv1jhd/+R6pw5D9eCJUoXxTx/SKucOS38bC2Vp+xZ7hog16oYNuYOfbcSV4T # p5BnS+Nu5+vwQ8fQL33/llqnA9abVKAj06XCoI75T9GyBiH+IV0njKCv2bBS7vzI # 7bec8ckmONalMu1Il5RePeA9NbSwyVivx1j/YnQWkmRB2sqo64sDvcFOrh+RMrjh # JDt77RRoCYaWKMk7yWwowiVp9UphreAn+FOndRWwUTGw8UH/PlomHmB+4uNqOZrE # 6u4/5rITP1UDBE0LkHLU6/u8h5BRsjgZMIIE/jCCA+agAwIBAgIQK3PbdGMRTFpb # MkryMFdySTANBgkqhkiG9w0BAQUFADB6MQswCQYDVQQGEwJHQjEbMBkGA1UECBMS # R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD # T01PRE8gQ0EgTGltaXRlZDEgMB4GA1UEAxMXQ09NT0RPIFRpbWUgU3RhbXBpbmcg # Q0EwHhcNMTkwNTAyMDAwMDAwWhcNMjAwNTMwMTA0ODM4WjCBgzELMAkGA1UEBhMC # R0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9y # ZDEYMBYGA1UECgwPU2VjdGlnbyBMaW1pdGVkMSswKQYDVQQDDCJTZWN0aWdvIFNI # QS0xIFRpbWUgU3RhbXBpbmcgU2lnbmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A # MIIBCgKCAQEAv1I2gjrcdDcNeNV/FlAZZu26GpnRYziaDGayQNungFC/aS42Lwpn # P0ChSopjNZvQGcx0qhcZkSu1VSAZ+8AaOm3KOZuC8rqVoRrYNMe4iXtwiHBRZmns # d/7GlHJ6zyWB7TSCmt8IFTcxtG2uHL8Y1Q3P/rXhxPuxR3Hp+u5jkezx7M5ZBBF8 # rgtgU+oq874vAg/QTF0xEy8eaQ+Fm0WWwo0Si2euH69pqwaWgQDfkXyVHOaeGWTf # dshgRC9J449/YGpFORNEIaW6+5H6QUDtTQK0S3/f4uA9uKrzGthBg49/M+1BBuJ9 # nj9ThI0o2t12xr33jh44zcDLYCQD3npMqwIDAQABo4IBdDCCAXAwHwYDVR0jBBgw # FoAUZCKGtkqJyQQP0ARYkiuzbj0eJ2wwHQYDVR0OBBYEFK7u2WC6XvUsARL9jo2y # VXI1Rm/xMA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQM # MAoGCCsGAQUFBwMIMEAGA1UdIAQ5MDcwNQYMKwYBBAGyMQECAQMIMCUwIwYIKwYB # BQUHAgEWF2h0dHBzOi8vc2VjdGlnby5jb20vQ1BTMEIGA1UdHwQ7MDkwN6A1oDOG # MWh0dHA6Ly9jcmwuc2VjdGlnby5jb20vQ09NT0RPVGltZVN0YW1waW5nQ0FfMi5j # cmwwcgYIKwYBBQUHAQEEZjBkMD0GCCsGAQUFBzAChjFodHRwOi8vY3J0LnNlY3Rp # Z28uY29tL0NPTU9ET1RpbWVTdGFtcGluZ0NBXzIuY3J0MCMGCCsGAQUFBzABhhdo # dHRwOi8vb2NzcC5zZWN0aWdvLmNvbTANBgkqhkiG9w0BAQUFAAOCAQEAen+pStKw # pBwdDZ0tXMauWt2PRR3wnlyQ9l6scP7T2c3kGaQKQ3VgaoOkw5mEIDG61v5MzxP4 # EPdUCX7q3NIuedcHTFS3tcmdsvDyHiQU0JzHyGeqC2K3tPEG5OfkIUsZMpk0uRlh # dwozkGdswIhKkvWhQwHzrqJvyZW9ljj3g/etfCgf8zjfjiHIcWhTLcuuquIwF4Mi # KRi14YyJ6274fji7kE+5Xwc0EmuX1eY7kb4AFyFu4m38UnnvgSW6zxPQ+90rzYG2 # V4lO8N3zC0o0yoX/CLmWX+sRE+DhxQOtVxzhXZIGvhvIPD+lIJ9p0GnBxcLJPufF # cvfqG5bilK+GLjCCBawwggSUoAMCAQICEHJNXiAT1cKRQFXzfFSJVHEwDQYJKoZI # hvcNAQELBQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk1JMRIwEAYDVQQHEwlB # bm4gQXJib3IxEjAQBgNVBAoTCUludGVybmV0MjERMA8GA1UECxMISW5Db21tb24x # JTAjBgNVBAMTHEluQ29tbW9uIFJTQSBDb2RlIFNpZ25pbmcgQ0EwHhcNMTcxMjE0 # MDAwMDAwWhcNMjAxMjEzMjM1OTU5WjCByzELMAkGA1UEBhMCVVMxDjAMBgNVBBEM # BTU1NDU1MRIwEAYDVQQIDAlNaW5uZXNvdGExFDASBgNVBAcMC01pbm5lYXBvbGlz # MRgwFgYDVQQJDA8xMDAgVW5pb24gU3QgU0UxIDAeBgNVBAoMF1VuaXZlcnNpdHkg # b2YgTWlubmVzb3RhMSQwIgYDVQQLDBtDb21wdXRlciBhbmQgRGV2aWNlIFN1cHBv # cnQxIDAeBgNVBAMMF1VuaXZlcnNpdHkgb2YgTWlubmVzb3RhMIIBIjANBgkqhkiG # 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwk6kLE9u+tWv0JUkIJSn5pWfa09g6cqFLucC # XomNj9NYj8t+JfPna3gC6LHv3OQAUDHOoC5H+8N3ea7qVGYIiwPRHzXOGqG/tVai # U5s5hG3vBhfRX8W1/2g4/hpgeXUzrxYn/2c5SOGGy0MU1ZJyUSFEdsjXHEV7HXK4 # qmFGV9RJxtiLZH1qUldCglxcj7zw0QnUdG6oAxpwTCeVp057/WXbnIR8a0gPse+y # /new5+CBUGTAvrw6K2BrJQVsdIIVn/q+BbcZxh9PpeZfTtsi6lgkvy0bUWtl5sSp # d75+hvw4Sl3HAaWZtoWN7LPmbDbbVRO2Arv4doh4Chod4wJ5xQIDAQABo4IB2DCC # AdQwHwYDVR0jBBgwFoAUrjUjF///Bj2cUOCMJGUzHnAQiKIwHQYDVR0OBBYEFF4L # EhElVUvT8n5txOJSNAczooSAMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAA # MBMGA1UdJQQMMAoGCCsGAQUFBwMDMBEGCWCGSAGG+EIBAQQEAwIEEDBmBgNVHSAE # XzBdMFsGDCsGAQQBriMBBAMCATBLMEkGCCsGAQUFBwIBFj1odHRwczovL3d3dy5p # bmNvbW1vbi5vcmcvY2VydC9yZXBvc2l0b3J5L2Nwc19jb2RlX3NpZ25pbmcucGRm # MEkGA1UdHwRCMEAwPqA8oDqGOGh0dHA6Ly9jcmwuaW5jb21tb24tcnNhLm9yZy9J # bkNvbW1vblJTQUNvZGVTaWduaW5nQ0EuY3JsMH4GCCsGAQUFBwEBBHIwcDBEBggr # BgEFBQcwAoY4aHR0cDovL2NydC5pbmNvbW1vbi1yc2Eub3JnL0luQ29tbW9uUlNB # Q29kZVNpZ25pbmdDQS5jcnQwKAYIKwYBBQUHMAGGHGh0dHA6Ly9vY3NwLmluY29t # bW9uLXJzYS5vcmcwGQYDVR0RBBIwEIEOb2l0bXB0QHVtbi5lZHUwDQYJKoZIhvcN # AQELBQADggEBAENRlesMKmBaZ0g68lttYEMtaPiz+DaNpOlXBs1gH66aghB1aP6i # iRJcFVasGLUVFncdG1xbw503LTrBUc5PECMVDVF7KKCfHA1OeFV9vOWyvdVgbe3p # aDy1sj4CADO2D0gnxcGiZoFhEZiBkTvSsj4S3GXZEvoFHJxJLw2kvdLnzy0gH/b/ # b/yblwA1fKXw4locUpDM6qTwM7SiKgkQ5W7/280EYu8BI6c8rpiJmqM1tZLcpswu # avB00T52Y+ZZmz3tMMVgFHn9pFFltYr3s3bEek7I6pU8unISbiyQzxqhIUKaBi8h # y8LgoY5UnGjX5jHsIvINzms+JX5Ity02sL0wggXrMIID06ADAgECAhBl4eLj1d5Q # RYXzJiSABeLUMA0GCSqGSIb3DQEBDQUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE # CBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRo # ZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0 # aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNDA5MTkwMDAwMDBaFw0yNDA5MTgyMzU5 # NTlaMHwxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJNSTESMBAGA1UEBxMJQW5uIEFy # Ym9yMRIwEAYDVQQKEwlJbnRlcm5ldDIxETAPBgNVBAsTCEluQ29tbW9uMSUwIwYD # VQQDExxJbkNvbW1vbiBSU0EgQ29kZSBTaWduaW5nIENBMIIBIjANBgkqhkiG9w0B # AQEFAAOCAQ8AMIIBCgKCAQEAwKAvix56u2p1rPg+3KO6OSLK86N25L99MCfmutOY # MlYjXAaGlw2A6O2igTXrC/Zefqk+aHP9ndRnec6q6mi3GdscdjpZh11emcehsrip # hHMMzKuHRhxqx+85Jb6n3dosNXA2HSIuIDvd4xwOPzSf5X3+VYBbBnyCV4RV8zj7 # 8gw2qblessWBRyN9EoGgwAEoPgP5OJejrQLyAmj91QGr9dVRTVDTFyJG5XMY4Drk # N3dRyJ59UopPgNwmucBMyvxR+hAJEXpXKnPE4CEqbMJUvRw+g/hbqSzx+tt4z9mJ # mm2j/w2nP35MViPWCb7hpR2LB8W/499Yqu+kr4LLBfgKCQIDAQABo4IBWjCCAVYw # HwYDVR0jBBgwFoAUU3m/WqorSs9UgOHYm8Cd8rIDZsswHQYDVR0OBBYEFK41Ixf/ # /wY9nFDgjCRlMx5wEIiiMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/ # AgEAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMBEGA1UdIAQKMAgwBgYEVR0gADBQBgNV # HR8ESTBHMEWgQ6BBhj9odHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVNFUlRydXN0 # UlNBQ2VydGlmaWNhdGlvbkF1dGhvcml0eS5jcmwwdgYIKwYBBQUHAQEEajBoMD8G # CCsGAQUFBzAChjNodHRwOi8vY3J0LnVzZXJ0cnVzdC5jb20vVVNFUlRydXN0UlNB # QWRkVHJ1c3RDQS5jcnQwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVzZXJ0cnVz # dC5jb20wDQYJKoZIhvcNAQENBQADggIBAEYstn9qTiVmvZxqpqrQnr0Prk41/PA4 # J8HHnQTJgjTbhuET98GWjTBEE9I17Xn3V1yTphJXbat5l8EmZN/JXMvDNqJtkyOh # 26owAmvquMCF1pKiQWyuDDllxR9MECp6xF4wnH1Mcs4WeLOrQPy+C5kWE5gg/7K6 # c9G1VNwLkl/po9ORPljxKKeFhPg9+Ti3JzHIxW7LdyljffccWiuNFR51/BJHAZIq # UDw3LsrdYWzgg4x06tgMvOEf0nITelpFTxqVvMtJhnOfZbpdXZQ5o1TspxfTEVOQ # Asp05HUNCXyhznlVLr0JaNkM7edgk59zmdTbSGdMq8Ztuu6VyrivOlMSPWmay5Mj # vwTzuNorbwBv0DL+7cyZBp7NYZou+DoGd1lFZN0jU5IsQKgm3+00pnnJ67crdFwf # z/8bq3MhTiKOWEb04FT3OZVp+jzvaChHWLQ8gbCORgClaZq1H3aqI7JeRkWEEEp6 # Tv4WAVsr/i7LoXU72gOb8CAzPFqwI4Excdrxp0I4OXbECHlDqU4sTInqwlMwofmx # eO4u94196qIqJQl+8Sykl06VktqMux84Iw3ZQLH08J8LaJ+WDUycc4OjY61I7FGx # CDkbSQf3npXeRFm0IBn8GiW+TRDk6J2XJFLWEtVZmhboFlBLoUlqHUCKu0QOhU/+ # AEOqnY98j2zRMYIEXjCCBFoCAQEwgZAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgT # Ak1JMRIwEAYDVQQHEwlBbm4gQXJib3IxEjAQBgNVBAoTCUludGVybmV0MjERMA8G # A1UECxMISW5Db21tb24xJTAjBgNVBAMTHEluQ29tbW9uIFJTQSBDb2RlIFNpZ25p # bmcgQ0ECEHJNXiAT1cKRQFXzfFSJVHEwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcC # AQwxCjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYB # BAGCNwIBCzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFED0YKUhucHP # 8WvdFnuBZdgqyChWMA0GCSqGSIb3DQEBAQUABIIBAB1a+lH2aR0t33136qoDlyuF # vCzAC+r2dVQDtD5tx4H5Y2fri6EFEgf/Czi25awfiOyFmRE5Tr1xT3kKxxiST+wP # chP8AJYZZnKfyv5zxrcPfoSyhVvL8icrxUjmmt78irzV0qNT55CsNPXHU58md/+g # CcOZee70eJ69r6uFm9VOcnROvU4AE/OZ5WlV92DvMy66P46gEGvTpi0wtq3psW59 # FpyAwTtMdod4guei3iMxGeo7Z7R5U95x175Lkuam2L7qiRBGjY2gq+ePjaZlClwX # t5WbXz99sp6qfGavdOxDypTlfol7/RMD+VZsirfVtwYsxdLTLAGTCGiriIubxuyh # ggIoMIICJAYJKoZIhvcNAQkGMYICFTCCAhECAQEwgY4wejELMAkGA1UEBhMCR0Ix # GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEa # MBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxIDAeBgNVBAMTF0NPTU9ETyBUaW1l # IFN0YW1waW5nIENBAhArc9t0YxFMWlsySvIwV3JJMAkGBSsOAwIaBQCgXTAYBgkq # hkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xOTEyMDkyMDU5 # MzVaMCMGCSqGSIb3DQEJBDEWBBQ6I8F6/qfOZLA30rDiGrVmT75EGzANBgkqhkiG # 9w0BAQEFAASCAQBf29Cz/MjMeJqP7J2za/lYdj6Igz4bXM/ZpGYWucv7g3GnmDDI # 4KsdOmhzSz/URk7JaLqtYCNrc9iJOAIfhJlyq3S+aid+O13ltoNZen7qioCDJjsH # EkeeCEUXg39wFVPgnccriKtR0vOXBV4alMQUyTbZ/weRgSjLe9FSxI2llPdzwATk # RJlxo1wiJA23Pl1tDkoZpN6Hz9WYBqZWIrEUbQrHVmSW5WDk6yL0crS8zit4fnfu # 3x62vdm5AYuosepnhKz2M3Vq+s7/CJj12ZDZSFUjhXF9I8qy2tx+QkDeMyEKBNSc # vd6P9KvRzbrftyBEHanV9XJGFwTHhGA8i4Uv # SIG # End signature block |