AiLogging.psm1
|
#region Public commands function Add-AiMetadata { <# .SYNOPSIS Adds a metadata entry to a metadata dictionary. .DESCRIPTION Adds a named metadata value to a dictionary created by New-AiMetadata. If the metadata name already exists, the value is only replaced when -Force is used. Use -PassThrough to keep the dictionary on the pipeline for chaining. .PARAMETER Metadata Metadata dictionary created by New-AiMetadata. .PARAMETER Name Metadata key to add. .PARAMETER Value Metadata value to add. .PARAMETER Force Replaces an existing metadata value when the same key is already present. .PARAMETER PassThrough Returns the updated metadata dictionary so you can continue piping it. .EXAMPLE New-AiMetadata | Add-AiMetadata -Name 'TenantId' -Value 'contoso' -PassThrough Creates a metadata dictionary, adds a key, and emits the updated dictionary. .EXAMPLE $metadata | Add-AiMetadata -Name 'Environment' -Value 'Prod' -Force Replaces the value of an existing metadata key named Environment. #> [CmdletBinding()] param ( [Parameter(Mandatory,ValueFromPipeline)] [System.Collections.Generic.Dictionary[String,String]] #Metadata object created by New-AiMetadata $Metadata, [Parameter(Mandatory)] [string] #Name of metadata to be registered. Will become custom dimension of telemetry data #Allows for easy searching/filtering in ApplicationInsights logs $Name, [Parameter(Mandatory)] [string] #Value of metadata $Value, [switch] #allows rewrite of already present metadata $Force, [switch] #causes input object to be passed to output, enabling chaining $PassThrough ) process { if($metadata.ContainsKey($Name) -and -not $Force) { Write-Warning "Metadata with name $Name already exists. Use -Force to overwrite" } else { $metadata[$Name] = $Value } if($PassThrough) { $metadata } } } function Add-AiMetric { <# .SYNOPSIS Adds a metric entry to a metrics dictionary. .DESCRIPTION Adds a named metric value to a dictionary created by New-AiMetric. If the metric name already exists, the value is only replaced when -Force is used. Use -PassThrough to keep the dictionary on the pipeline for chaining. .PARAMETER Metrics Metrics dictionary created by New-AiMetric. .PARAMETER Name Metric name to add. .PARAMETER Value Metric value to add. .PARAMETER Force Replaces an existing metric value when the same name is already present. .PARAMETER PassThrough Returns the updated metrics dictionary so you can continue piping it. .EXAMPLE New-AiMetric | Add-AiMetric -Name 'ProcessedItems' -Value 42 -PassThrough Creates a metrics dictionary, adds a metric, and emits the updated dictionary. .EXAMPLE $metrics | Add-AiMetric -Name 'DurationMs' -Value 1375 -Force Replaces the value of an existing metric named DurationMs. #> [CmdletBinding()] param ( [Parameter(Mandatory,ValueFromPipeline)] [System.Collections.Generic.Dictionary[String,Double]] #Metrics collection created by New-AiMetric $Metrics, [Parameter(Mandatory)] [string] #Name of metadata to be registered. Will become custom dimension of telemetry data #Allows for easy searching/filtering in ApplicationInsights logs $Name, [Parameter(Mandatory)] [string] #Value of metadata $Value, [switch] #allows rewrite of already present metrics $Force, [switch] #causes input object to be passed to output, enabling chaining $PassThrough ) process { if($Metrics.ContainsKey($Name) -and -not $Force) { Write-Warning "Metric with name $Name already exists. Use -Force to overwrite" } else { $Metrics[$Name] = $Value } if($PassThrough) { $Metrics } } } function Connect-AiLogger { <# .SYNOPSIS Creates an Application Insights telemetry connection. .DESCRIPTION Creates a connection to Application Insights, configures default metadata, and stores the connection as the most recently created connection used by other commands. Telemetry is buffered and sent to Application Insights ingestion REST API. If -ConnectionString is omitted, the command reads APPLICATIONINSIGHTS_CONNECTION_STRING from the current environment. By default, telemetry is authenticated by instrumentation key from connection string. When -Factory is specified, each request additionally carries an Entra ID access token obtained from the AadAuthenticationFactory module (required when local authentication is disabled on Application Insights resource). The identity needs the 'Monitoring Metrics Publisher' role on the Application Insights resource. .PARAMETER ConnectionString Application Insights connection string. When omitted, the command tries to use the APPLICATIONINSIGHTS_CONNECTION_STRING environment variable. .PARAMETER Application Application name added to default telemetry metadata and used when building the metric namespace. .PARAMETER Component Component name added to default telemetry metadata and used when building the metric namespace. .PARAMETER Role Optional cloud role name associated with emitted telemetry. .PARAMETER Instance Optional cloud role instance associated with emitted telemetry. .PARAMETER DefaultMetadata Additional metadata to attach to every telemetry item sent through the connection. .PARAMETER Factory Optional AadAuthenticationFactory instance (created by New-AadAuthenticationFactory) used to obtain access tokens. When omitted, instrumentation key authentication is used. .PARAMETER Scopes Scopes requested from Factory. Defaults to https://monitor.azure.com//.default .PARAMETER MaxBatchSize Number of buffered telemetry items that triggers sending. Defaults to 500. .PARAMETER FlushIntervalSeconds Max age of buffered telemetry, checked whenever telemetry is written. Defaults to 30 seconds. Use 0 to send every telemetry item immediately. .OUTPUTS AiLogging.Connection .EXAMPLE Connect-AiLogger -Application 'myAutomationAccount' -Component 'MyRunbook' Creates a connection using the APPLICATIONINSIGHTS_CONNECTION_STRING environment variable. .EXAMPLE $metadata = New-AiMetadata -Name 'Environment' -Value 'Prod' Connect-AiLogger -ConnectionString $env:APPLICATIONINSIGHTS_CONNECTION_STRING -Application 'Orders' -Component 'Worker' -Role 'HybridWorker' -Instance $env:COMPUTERNAME -DefaultMetadata $metadata Creates a connection with explicit role and default metadata. .EXAMPLE $factory = New-AadAuthenticationFactory -UseManagedIdentity Connect-AiLogger -Application 'Orders' -Component 'Worker' -Factory $factory Creates a connection that authenticates telemetry with access token of managed identity. #> param ( [Parameter()] [string] #AppInsights connection string $ConnectionString, [Parameter(Mandatory)] [String] #Name of the application that is producing the logs #For automation accounts, it can be name of automation account #Is automatically registered to metadata sent with every piece data logged $Application, [Parameter(Mandatory)] [String] #Name of the component producing the logs #For automation accounts, it can be name of the runbook #Is automatically registered to metadata sent with every piece data logged $Component, [Parameter()] [string] #Identifier of role sending the data #For automation accounts, it can be useful when runbook is running on more hosts, e.g. in case of hybrid workers $Role, [Parameter()] [string] #Identifier of instance sending the data #For automation accounts, it may be usefult for runbooks that run in multiple instances on the same hosts, e.g. runbooks with multiple configurations $Instance, [System.Collections.Generic.Dictionary[String,String]] #Additional metadata to be added to each emited telemetry $DefaultMetadata, [Parameter()] #AadAuthenticationFactory instance (or name) used to obtain access tokens for ingestion #When not specified, instrumentation key authentication is used $Factory, [Parameter()] [string[]] #Scopes requested when obtaining access token from Factory $Scopes = @('https://monitor.azure.com//.default'), [Parameter()] [ValidateRange(1, 1000)] [int] #Max number of telemetry items buffered before they are sent $MaxBatchSize = 500, [Parameter()] [ValidateRange(0, 3600)] [int] #Max age in seconds of buffered telemetry; checked when new telemetry is written #0 means that every telemetry item is sent immediately $FlushIntervalSeconds = 30 ) process { #deliver anything still buffered on previous connection if($null -ne $script:LastCreatedAiLogger) { Send-AiTelemetry -Connection $script:LastCreatedAiLogger } if([string]::IsNullOrEmpty($ConnectionString)) { #try to load connection string from environment variable $ConnectionString = $env:APPLICATIONINSIGHTS_CONNECTION_STRING } if([string]::IsNullOrEmpty($ConnectionString)) { Write-Warning 'Connection string was not provided and no environment variable APPLICATIONINSIGHTS_CONNECTION_STRING was found' $ConnectionString = '' } $parsed = ConvertFrom-AiConnectionString -ConnectionString $ConnectionString $script:AiSdkVersion = "ailogging-ps:$($MyInvocation.MyCommand.Module.Version)" if($null -ne $Factory -and $null -eq (Get-Command Get-AadToken -ErrorAction SilentlyContinue)) { throw 'AadAuthenticationFactory module is required for token based authentication. Install it via Install-Module AadAuthenticationFactory' } #setup base metadata $metadata = New-Object 'System.Collections.Generic.Dictionary[String,String]' $metadata['Application'] = $Application $metadata['Component'] = $Component if($null -ne $DefaultMetadata) { foreach($key in $DefaultMetadata.Keys) { if(-not $metadata.ContainsKey($key)) { $metadata[$key] = $DefaultMetadata[$key] } } } $connection = [PSCustomObject]@{ PSTypeName = 'AiLogging.Connection' InstrumentationKey = $parsed.InstrumentationKey IngestionEndpoint = $parsed.IngestionEndpoint TrackUri = "$($parsed.IngestionEndpoint)/v2.1/track" AuthenticationFactory = $Factory Scopes = $Scopes TelemetryMetadata = $metadata MetricNamespace = "$Application`.$Component" RoleName = $(if([string]::IsNullOrEmpty($Role)) {"$Application`.$Component"} else {$Role}) RoleInstance = $(if([string]::IsNullOrEmpty($Instance)) {[Environment]::MachineName} else {$Instance}) MaxBatchSize = $MaxBatchSize FlushInterval = [TimeSpan]::FromSeconds($FlushIntervalSeconds) LastFlush = [DateTime]::UtcNow Buffer = New-Object 'System.Collections.Generic.List[object]' } $script:LastCreatedAiLogger = $connection Write-Verbose ( ($ConnectionString.Split(';') | Where-Object{$_ -notlike 'InstrumentationKey=*'}) -join ';') Write-Verbose "AiLogger: Authentication: $(if($null -ne $Factory) {'access token'} else {'instrumentation key'})" $connection } } function Get-AiConnection { <# .SYNOPSIS Returns the most recently created telemetry connection. .DESCRIPTION Returns the last connection created by Connect-AiLogger from module scope. Use this when you want to inspect or reuse the active telemetry connection. .OUTPUTS AiLogging.Connection .EXAMPLE Get-AiConnection Returns the active telemetry connection for the current session. #> [CmdletBinding()] param () process { $script:LastCreatedAiLogger } } Function New-AiMetadata { <# .SYNOPSIS Creates a metadata dictionary. .DESCRIPTION Creates a System.Collections.Generic.Dictionary[String,String] for telemetry metadata. You can optionally seed it with an initial key-value pair and then add more entries with Add-AiMetadata. .PARAMETER Name Optional initial metadata key. .PARAMETER Value Optional initial metadata value. .OUTPUTS System.Collections.Generic.Dictionary[String,String] .EXAMPLE New-AiMetadata Creates an empty metadata dictionary. .EXAMPLE New-AiMetadata -Name 'TenantId' -Value 'contoso' Creates a metadata dictionary with a single initial entry. #> param ( [Parameter()] [string] #Name of the metric $Name, [Parameter()] [string] #Value of the metric $Value ) Process { $metadata = new-object 'System.Collections.Generic.Dictionary[String,String]' if(-not [string]::IsNullOrEmpty($Name)) {$metadata[$Name] = $Value} return $metadata } } Function New-AiMetric { <# .SYNOPSIS Creates a metrics dictionary. .DESCRIPTION Creates a System.Collections.Generic.Dictionary[String,Double] for metric values. You can optionally seed it with an initial metric and then add more entries with Add-AiMetric. .PARAMETER Name Optional initial metric name. .PARAMETER Value Optional initial metric value. .OUTPUTS System.Collections.Generic.Dictionary[String,Double] .EXAMPLE New-AiMetric Creates an empty metrics dictionary. .EXAMPLE New-AiMetric -Name 'ProcessedItems' -Value 42 Creates a metrics dictionary with a single initial metric. #> param ( [Parameter()] [string] #Name of the metric $Name, [Parameter()] [double] #Value of the metric $Value ) Process { $metric = new-object 'System.Collections.Generic.Dictionary[String,Double]' if(-not [string]::IsNullOrEmpty($Name)) {$metric[$Name] = $Value} return $metric } } Function Set-AiDefaultMetadata { <# .SYNOPSIS Updates default metadata on a telemetry connection. .DESCRIPTION Updates default metadata for the connection. This metadata is sent with all telemetry items unless overridden by specific metadata. Values of any existing default metadata are replaced by the new values. .PARAMETER Metadata Metadata dictionary whose entries should be merged into the connection defaults. .PARAMETER Connection Telemetry connection created by Connect-AiLogger. When omitted, the most recently created connection is used. .EXAMPLE New-AiMetadata -Name 'Environment' -Value 'Prod' | Set-AiDefaultMetadata Adds or replaces the Environment default metadata entry on the active connection. .EXAMPLE $metadata | Set-AiDefaultMetadata -Connection $connection Applies metadata defaults to a specific connection. #> param ( [Parameter(Mandatory, ValueFromPipeline)] [System.Collections.Generic.Dictionary[String,String]] #Optional metadata to be sent with metric $Metadata, [Parameter()] #Connection created by Connect-AiLogger #Defaults to last created connection $Connection = $script:LastCreatedAiLogger ) begin { } Process { foreach($key in $Metadata.Keys) { Write-Verbose "AiLogger: Setting default metadata $key = $($Metadata[$key])" if(-not $connection.TelemetryMetadata.ContainsKey($key)) { Write-Verbose "AiLogger: Adding default metadata $key = $($Metadata[$key])" } else { Write-Verbose "AiLogger: Replacing default metadata $key = $($Metadata[$key])" } $connection.TelemetryMetadata[$key] = $Metadata[$key] } } } Function Submit-AiData { <# .SYNOPSIS Flushes buffered telemetry to Application Insights. .DESCRIPTION Sends any buffered telemetry for the specified connection to Application Insights and waits for the send to complete. Call this before short-lived scripts and runbooks exit. .PARAMETER Connection Telemetry connection created by Connect-AiLogger. When omitted, the most recently created connection is used. .EXAMPLE Submit-AiData Flushes buffered telemetry for the active connection before the script exits. .EXAMPLE Submit-AiData -Connection $connection Flushes buffered telemetry for a specific connection. #> param ( [Parameter()] #Connection created by Connect-AiLogger #Defaults to last created connection $Connection = $script:LastCreatedAiLogger ) Process { Write-Verbose "AiLogger: Flushing buffered data to Application Insights" Send-AiTelemetry -Connection $Connection } } function Write-AiDependency { <# .SYNOPSIS Logs a dependency call to an external service. .DESCRIPTION Writes dependency telemetry to Application Insights for calls to external systems such as HTTP endpoints, SQL databases, or other services. Dependency telemetry can be used by Application Insights to populate the Application Map. .PARAMETER Target Host name or other target identifier for the dependency. .PARAMETER DependencyType Dependency type such as HTTP, SQL, or FTP. .PARAMETER Name Operation name for the dependency call. .PARAMETER Data Optional dependency details such as a URL, stored procedure name, or command text. .PARAMETER Start Start time of the dependency call. .PARAMETER Duration Duration of the dependency call. When omitted, the duration is calculated from Start to the current UTC time. .PARAMETER ResultCode Optional status or result code returned by the dependency. .PARAMETER Metrics Optional metrics dictionary to include with the dependency telemetry. .PARAMETER Success Indicates whether the dependency call succeeded. .PARAMETER Connection Telemetry connection created by Connect-AiLogger. When omitted, the most recently created connection is used. .EXAMPLE $start = Get-Date Invoke-WebRequest -Uri $uri | Out-Null Write-AiDependency -Target $uri.Host -DependencyType 'HTTP' -Name 'Invoke-WebRequest' -Data $uri.AbsoluteUri -Start $start Logs an HTTP dependency and calculates the duration automatically. .EXAMPLE Write-AiDependency -Target 'sql-prod-01' -DependencyType 'SQL' -Name 'usp_LoadOrders' -Data 'usp_LoadOrders' -Start $start -Duration $duration -ResultCode '0' -Success $true -Connection $connection Logs a SQL dependency using an explicit connection and duration. #> param ( [Parameter(Mandatory)] [string] #Name of endpoint (hostname or host identifier for server being called) $Target, [Parameter(Mandatory)] [string] #Name of the dependency type, such as FTP, SQL or HTTP $DependencyType, [Parameter(Mandatory)] [string] #Name of the dependency, such as FileDownload, StoredProcedureCall $Name, [Parameter()] [string] #Dependency details, such as name of SQL stored procedure, or complete URL requested via HTTP $Data, [Parameter(Mandatory)] [DateTime] #When the call started $Start, [Parameter()] [TimeSpan] #Duration of the call #Default: (current time in UTC) - Start $Duration, [Parameter()] [string] #Result code returned by the service $ResultCode=$null, [Parameter()] [System.Collections.Generic.Dictionary[String,Double]] #Optional metrics to be sent with the dependency data $Metrics=$null, [Parameter()] [bool] #Whether or not call was successful $Success = $true, [Parameter()] #Connection created by Connect-AiLogger #Defaults to last created connection $Connection = $script:LastCreatedAiLogger ) begin { } Process { if(-not $PSBoundParameters.ContainsKey('Duration')) { $Duration = ([DateTime]::UtcNow) - $Start.ToUniversalTime() } $baseData = [ordered]@{ id = [Guid]::NewGuid().ToString('N').Substring(0, 16) name = $Name type = $DependencyType target = $Target data = $Data duration = $Duration.ToString('c', [System.Globalization.CultureInfo]::InvariantCulture) success = $Success } if(-not [string]::IsNullOrEmpty($ResultCode)) { $baseData['resultCode'] = $ResultCode } if($null -ne $Metrics) { $baseData['measurements'] = $Metrics } $envelope = New-AiEnvelope -Connection $Connection -TelemetryType RemoteDependency -BaseData $baseData -Timestamp $Start Write-Verbose "Writing dependency: Name = $Name, Type = $DependencyType, Start = $Start, Duration = $Duration, Success = $Success, Target = $Target, Data = $Data" Add-AiTelemetry -Connection $Connection -Envelope $envelope } } Function Write-AiEvent { <# .SYNOPSIS Logs an event with optional metadata and metrics. .DESCRIPTION Writes event telemetry to Application Insights. Use events for important business or workflow milestones that may also carry related metadata and numeric measurements. .PARAMETER EventName Name of the event to record. .PARAMETER Metrics Optional metrics dictionary to include with the event. .PARAMETER Metadata Optional metadata dictionary to include with the event. .PARAMETER Connection Telemetry connection created by Connect-AiLogger. When omitted, the most recently created connection is used. .EXAMPLE Write-AiEvent -EventName 'IngestionStarted' Writes a simple event using the active connection. .EXAMPLE Write-AiEvent -EventName 'IngestionCompleted' -Metadata $metadata -Metrics $metrics -Connection $connection Writes an event with additional metadata and metrics. #> param ( [Parameter(Mandatory, ValueFromPipeline)] [string] #Event to be traced $EventName, [Parameter()] [System.Collections.Generic.Dictionary[String,Double]] #Optional metrics to be sent with the event $Metrics=$null, [Parameter()] [System.Collections.Generic.Dictionary[String,String]] #Optional metadata to be sent with the event $Metadata=$null, [Parameter()] #Connection created by Connect-AiLogger #Defaults to last created connection $Connection = $script:LastCreatedAiLogger ) begin { } Process { $baseData = [ordered]@{ name = $EventName } if($null -ne $Metrics) { $baseData['measurements'] = $Metrics } $envelope = New-AiEnvelope -Connection $Connection -TelemetryType Event -BaseData $baseData -Metadata $Metadata Write-Verbose "AiLogger: Writing event $EventName" Add-AiTelemetry -Connection $Connection -Envelope $envelope } } Function Write-AiException { <# .SYNOPSIS Logs an exception with optional metadata and metrics. .DESCRIPTION Writes exception telemetry to Application Insights. Use this command inside catch blocks to record exceptions together with relevant metadata and measurements. .PARAMETER Exception Exception instance to record. .PARAMETER Metrics Optional metrics dictionary to include with the exception telemetry. .PARAMETER Metadata Optional metadata dictionary to include with the exception telemetry. .PARAMETER Connection Telemetry connection created by Connect-AiLogger. When omitted, the most recently created connection is used. .EXAMPLE try { Invoke-RiskyOperation } catch { $_.Exception | Write-AiException } Writes the caught exception by using the pipeline. .EXAMPLE $_.Exception | Write-AiException -Metadata $metadata -Metrics $metrics -Connection $connection Writes the exception together with additional context. #> param ( [Parameter(Mandatory, ValueFromPipeline)] [System.Exception] #Exception to be traced $Exception, [Parameter()] [System.Collections.Generic.Dictionary[String,Double]] #Optional metrics to be sent with exception $Metrics=$null, [Parameter()] [System.Collections.Generic.Dictionary[String,String]] #Optional metadata to be sent with exception $Metadata=$null, [Parameter()] #Connection created by Connect-AiLogger #Defaults to last created connection $Connection = $script:LastCreatedAiLogger ) begin { } Process { $exceptions = New-Object System.Collections.Generic.List[object] $current = $Exception $outerId = 0 while($null -ne $current) { $id = $current.GetHashCode() $stack = "$($current.StackTrace)" $exceptions.Add([ordered]@{ id = $id outerId = $outerId typeName = $current.GetType().FullName message = "$($current.Message)" hasFullStack = $true stack = $stack.Substring(0, [Math]::Min($stack.Length, 32768)) }) $outerId = $id $current = $current.InnerException } $baseData = [ordered]@{ exceptions = $exceptions.ToArray() severityLevel = 'Error' } if($null -ne $Metrics) { $baseData['measurements'] = $Metrics } $envelope = New-AiEnvelope -Connection $Connection -TelemetryType Exception -BaseData $baseData -Metadata $Metadata Write-Verbose "AiLogger: Writing exception $($Exception.Message)" Add-AiTelemetry -Connection $Connection -Envelope $envelope } } Function Write-AiMetric { <# .SYNOPSIS Logs metric values with optional metadata. .DESCRIPTION Writes each metric in a metrics dictionary to Application Insights without local aggregation. The metric namespace is taken from the connection created by Connect-AiLogger. .PARAMETER Metrics Metrics dictionary to write. .PARAMETER Metadata Optional metadata dictionary to include with every metric written by this call. .PARAMETER Connection Telemetry connection created by Connect-AiLogger. When omitted, the most recently created connection is used. .EXAMPLE New-AiMetric -Name 'ProcessedItems' -Value 42 | Write-AiMetric Writes a single metric by using the active connection. .EXAMPLE $metrics | Write-AiMetric -Metadata $metadata -Connection $connection Writes multiple metrics together with shared metadata. #> param ( [Parameter(Mandatory, ValueFromPipeline)] [System.Collections.Generic.Dictionary[String,Double]] #Values of metrics to be sent $Metrics, [Parameter()] [System.Collections.Generic.Dictionary[String,String]] #Optional metadata to be sent with metric $Metadata=$null, [Parameter()] #Connection created by Connect-AiLogger #Defaults to last created connection $Connection = $script:LastCreatedAiLogger ) begin { } Process { foreach($metricKey in $metrics.Keys) { $baseData = [ordered]@{ metrics = @( [ordered]@{ ns = $Connection.MetricNamespace name = $metricKey kind = 0 value = $Metrics[$metricKey] count = 1 } ) } $envelope = New-AiEnvelope -Connection $Connection -TelemetryType Metric -BaseData $baseData -Metadata $Metadata Write-Verbose "AiLogger: Writing metric $metricKey = $($metrics[$metricKey])" Add-AiTelemetry -Connection $Connection -Envelope $envelope } } } Function Write-AiTrace { <# .SYNOPSIS Writes a trace message with optional severity and metadata. .DESCRIPTION Writes trace telemetry to Application Insights. Trace messages are useful for operational logging and diagnostics. The default severity is Information. .PARAMETER Message Trace message to record. .PARAMETER Severity Severity level to associate with the trace. Defaults to Information. .PARAMETER Metadata Optional metadata dictionary to include with the trace. .PARAMETER Connection Telemetry connection created by Connect-AiLogger. When omitted, the most recently created connection is used. .EXAMPLE Write-AiTrace 'Beginning processing' Writes an informational trace by using the active connection. .EXAMPLE $meta = New-AiMetadata | Add-AiMetadata -Name 'Context' -Value 'MyContext' -PassThrough Write-AiTrace -Message "Performed context-specific action" -Metadata $meta Writes a trace with custom metadata. .EXAMPLE Write-AiTrace -Message 'Validation failed' -Severity Warning -Connection $connection Writes a warning trace on a specific connection. #> param ( [Parameter(Mandatory, ValueFromPipeline)] [string] #Message to be traced $Message, [Parameter()] [ValidateSet('Verbose','Information','Warning','Error','Critical')] [string] #Severity of message sent $Severity='Information', [Parameter()] [System.Collections.Generic.Dictionary[String,String]] #Optional metadata to be sent with trace $Metadata=$null, [Parameter()] #Connection created by Connect-AiLogger #Defaults to last created connection $Connection = $script:LastCreatedAiLogger ) begin { } Process { $baseData = [ordered]@{ message = $Message severityLevel = $Severity } $envelope = New-AiEnvelope -Connection $Connection -TelemetryType Message -BaseData $baseData -Metadata $Metadata Write-Verbose "AiLogger: Writing trace $Severity`: $Message" Add-AiTelemetry -Connection $Connection -Envelope $envelope } } #endregion Public commands #region Internal commands function Add-AiTelemetry { param ( [Parameter(Mandatory)] $Connection, [Parameter(Mandatory)] $Envelope ) process { if($null -eq $Connection) { Write-Warning 'AiLogger: No connection available. Call Connect-AiLogger first.' return } $Connection.Buffer.Add($Envelope) if($Connection.Buffer.Count -ge $Connection.MaxBatchSize -or ([DateTime]::UtcNow - $Connection.LastFlush) -ge $Connection.FlushInterval) { Send-AiTelemetry -Connection $Connection } } } function ConvertFrom-AiConnectionString { param ( [Parameter(Mandatory)] [string]$ConnectionString ) process { $parts = @{} foreach($segment in $ConnectionString.Split(';', [StringSplitOptions]::RemoveEmptyEntries)) { $idx = $segment.IndexOf('=') if($idx -lt 1) { continue } $parts[$segment.Substring(0, $idx).Trim()] = $segment.Substring($idx + 1).Trim() } $endpoint = $parts['IngestionEndpoint'] if([string]::IsNullOrEmpty($endpoint)) { if(-not [string]::IsNullOrEmpty($parts['EndpointSuffix'])) { $prefix = if([string]::IsNullOrEmpty($parts['Location'])) {''} else {"$($parts['Location'])."} $endpoint = "https://$($prefix)dc.$($parts['EndpointSuffix'])" } else { $endpoint = 'https://dc.services.visualstudio.com' } } [PSCustomObject]@{ InstrumentationKey = $parts['InstrumentationKey'] IngestionEndpoint = $endpoint.TrimEnd('/') } } } function Get-AiAuthorizationHeader { param ( [Parameter(Mandatory)] $Connection ) process { if($null -eq $Connection.AuthenticationFactory) { return } #factory caches and refreshes tokens as needed $header = Get-AadToken -Factory $Connection.AuthenticationFactory -Scopes $Connection.Scopes -AsHashTable -ErrorAction Stop $header['Authorization'] } } function Init { Add-Type -AssemblyName System.Net.Http if($PSVersionTable.PSEdition -ne 'Core') { #Windows PowerShell on older .NET Framework may not offer TLS 1.2 by default, which ingestion endpoints require $protocol = [Net.ServicePointManager]::SecurityProtocol if([int]$protocol -ne 0 -and -not ($protocol -band [Net.SecurityProtocolType]::Tls12)) { [Net.ServicePointManager]::SecurityProtocol = $protocol -bor [Net.SecurityProtocolType]::Tls12 } } $script:AiHttpClient = [System.Net.Http.HttpClient]::new() $script:AiHttpClient.Timeout = [TimeSpan]::FromSeconds(30) #try to deliver buffered telemetry when module is removed $ExecutionContext.SessionState.Module.OnRemove = { if($null -ne $script:LastCreatedAiLogger) { Send-AiTelemetry -Connection $script:LastCreatedAiLogger } $script:AiHttpClient.Dispose() } } function New-AiEnvelope { param ( [Parameter(Mandatory)] $Connection, [Parameter(Mandatory)] [ValidateSet('Event','Message','Metric','RemoteDependency','Exception')] [string]$TelemetryType, [Parameter(Mandatory)] [System.Collections.IDictionary]$BaseData, [Parameter()] [System.Collections.Generic.Dictionary[String,String]]$Metadata, [Parameter()] [DateTime]$Timestamp = [DateTime]::UtcNow ) process { #default metadata first, then call-specific metadata overrides it $properties = @{} foreach($key in $Connection.TelemetryMetadata.Keys) { $properties[$key] = $Connection.TelemetryMetadata[$key] } if($null -ne $Metadata) { foreach($key in $Metadata.Keys) { $properties[$key] = $Metadata[$key] } } $BaseData['ver'] = 2 $BaseData['properties'] = $properties $tags = [ordered]@{ 'ai.internal.sdkVersion' = $script:AiSdkVersion } if(-not [string]::IsNullOrEmpty($Connection.RoleName)) { $tags['ai.cloud.role'] = $Connection.RoleName } if(-not [string]::IsNullOrEmpty($Connection.RoleInstance)) { $tags['ai.cloud.roleInstance'] = $Connection.RoleInstance } $iKeyNoDashes = "$($Connection.InstrumentationKey)".Replace('-', '') [ordered]@{ name = "Microsoft.ApplicationInsights.$iKeyNoDashes.$TelemetryType" time = $Timestamp.ToUniversalTime().ToString('o', [System.Globalization.CultureInfo]::InvariantCulture) iKey = $Connection.InstrumentationKey tags = $tags data = [ordered]@{ baseType = "$($TelemetryType)Data" baseData = $BaseData } } } } function Send-AiTelemetry { param ( [Parameter(Mandatory)] $Connection ) process { $Connection.LastFlush = [DateTime]::UtcNow if($Connection.Buffer.Count -eq 0) { return } $pending = $Connection.Buffer.ToArray() $Connection.Buffer.Clear() if([string]::IsNullOrEmpty($Connection.InstrumentationKey)) { Write-Verbose "AiLogger: No instrumentation key configured, discarding $($pending.Count) telemetry item(s)" return } $retriableCodes = @(408, 429, 439, 500, 502, 503, 504) $maxAttempts = 3 for($attempt = 1; $attempt -le $maxAttempts; $attempt++) { $statusCode = 0 $responseBody = $null try { $json = ConvertTo-Json -InputObject @($pending) -Depth 10 -Compress $request = [System.Net.Http.HttpRequestMessage]::new([System.Net.Http.HttpMethod]::Post, $Connection.TrackUri) $request.Content = [System.Net.Http.StringContent]::new($json, [System.Text.Encoding]::UTF8, 'application/json') $authHeader = Get-AiAuthorizationHeader -Connection $Connection if(-not [string]::IsNullOrEmpty($authHeader)) { [void]$request.Headers.TryAddWithoutValidation('Authorization', $authHeader) } Write-Verbose "AiLogger: Sending $($pending.Count) telemetry item(s) to $($Connection.TrackUri) (attempt $attempt)" $response = $script:AiHttpClient.SendAsync($request).GetAwaiter().GetResult() $statusCode = [int]$response.StatusCode $responseBody = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult() $response.Dispose() $request.Dispose() } catch { Write-Verbose "AiLogger: Sending telemetry failed: $($_.Exception.Message)" } switch($statusCode) { 200 { return } 206 { $result = $responseBody | ConvertFrom-Json $retry = new-object System.Collections.Generic.List[object] foreach($err in $result.errors) { if($err.statusCode -in $retriableCodes) { $retry.Add($pending[$err.index]) } else { Write-Warning "AiLogger: Telemetry item rejected: $($err.statusCode) $($err.message)" } } if($retry.Count -eq 0) { return } $pending = $retry.ToArray() break } {$_ -in 401, 403} { Write-Warning "AiLogger: Telemetry rejected with status $statusCode (authentication/authorization failure): $responseBody" return } {$_ -eq 0 -or $_ -in $retriableCodes} { Write-Verbose "AiLogger: Transient failure (status $statusCode)" break } default { Write-Warning "AiLogger: Telemetry rejected with status $statusCode`: $responseBody" return } } if($attempt -lt $maxAttempts) { Start-Sleep -Seconds ([Math]::Pow(2, $attempt - 1)) } } Write-Warning "AiLogger: Failed to send $($pending.Count) telemetry item(s) after $maxAttempts attempts" } } #endregion Internal commands #region Module initialization Init #endregion Module initialization # SIG # Begin signature block # MII9CQYJKoZIhvcNAQcCoII8+jCCPPYCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD3hEJhC/4SHI+6 # 6Jv7Z4Mm2iFtnXlKdjn5T/PI8uXhj6CCIc4wggXMMIIDtKADAgECAhBUmNLR1FsZ # lUgTecgRwIeZMA0GCSqGSIb3DQEBDAUAMHcxCzAJBgNVBAYTAlVTMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xSDBGBgNVBAMTP01pY3Jvc29mdCBJZGVu # dGl0eSBWZXJpZmljYXRpb24gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAy # MDAeFw0yMDA0MTYxODM2MTZaFw00NTA0MTYxODQ0NDBaMHcxCzAJBgNVBAYTAlVT # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xSDBGBgNVBAMTP01pY3Jv # c29mdCBJZGVudGl0eSBWZXJpZmljYXRpb24gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRo # b3JpdHkgMjAyMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALORKgeD # Bmf9np3gx8C3pOZCBH8Ppttf+9Va10Wg+3cL8IDzpm1aTXlT2KCGhFdFIMeiVPvH # or+Kx24186IVxC9O40qFlkkN/76Z2BT2vCcH7kKbK/ULkgbk/WkTZaiRcvKYhOuD # PQ7k13ESSCHLDe32R0m3m/nJxxe2hE//uKya13NnSYXjhr03QNAlhtTetcJtYmrV # qXi8LW9J+eVsFBT9FMfTZRY33stuvF4pjf1imxUs1gXmuYkyM6Nix9fWUmcIxC70 # ViueC4fM7Ke0pqrrBc0ZV6U6CwQnHJFnni1iLS8evtrAIMsEGcoz+4m+mOJyoHI1 # vnnhnINv5G0Xb5DzPQCGdTiO0OBJmrvb0/gwytVXiGhNctO/bX9x2P29Da6SZEi3 # W295JrXNm5UhhNHvDzI9e1eM80UHTHzgXhgONXaLbZ7LNnSrBfjgc10yVpRnlyUK # xjU9lJfnwUSLgP3B+PR0GeUw9gb7IVc+BhyLaxWGJ0l7gpPKWeh1R+g/OPTHU3mg # trTiXFHvvV84wRPmeAyVWi7FQFkozA8kwOy6CXcjmTimthzax7ogttc32H83rwjj # O3HbbnMbfZlysOSGM1l0tRYAe1BtxoYT2v3EOYI9JACaYNq6lMAFUSw0rFCZE4e7 # swWAsk0wAly4JoNdtGNz764jlU9gKL431VulAgMBAAGjVDBSMA4GA1UdDwEB/wQE # AwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTIftJqhSobyhmYBAcnz1AQ # T2ioojAQBgkrBgEEAYI3FQEEAwIBADANBgkqhkiG9w0BAQwFAAOCAgEAr2rd5hnn # LZRDGU7L6VCVZKUDkQKL4jaAOxWiUsIWGbZqWl10QzD0m/9gdAmxIR6QFm3FJI9c # Zohj9E/MffISTEAQiwGf2qnIrvKVG8+dBetJPnSgaFvlVixlHIJ+U9pW2UYXeZJF # xBA2CFIpF8svpvJ+1Gkkih6PsHMNzBxKq7Kq7aeRYwFkIqgyuH4yKLNncy2RtNwx # AQv3Rwqm8ddK7VZgxCwIo3tAsLx0J1KH1r6I3TeKiW5niB31yV2g/rarOoDXGpc8 # FzYiQR6sTdWD5jw4vU8w6VSp07YEwzJ2YbuwGMUrGLPAgNW3lbBeUU0i/OxYqujY # lLSlLu2S3ucYfCFX3VVj979tzR/SpncocMfiWzpbCNJbTsgAlrPhgzavhgplXHT2 # 6ux6anSg8Evu75SjrFDyh+3XOjCDyft9V77l4/hByuVkrrOj7FjshZrM77nq81YY # uVxzmq/FdxeDWds3GhhyVKVB0rYjdaNDmuV3fJZ5t0GNv+zcgKCf0Xd1WF81E+Al # GmcLfc4l+gcK5GEh2NQc5QfGNpn0ltDGFf5Ozdeui53bFv0ExpK91IjmqaOqu/dk # ODtfzAzQNb50GQOmxapMomE2gj4d8yu8l13bS3g7LfU772Aj6PXsCyM2la+YZr9T # 03u4aUoqlmZpxJTG9F9urJh4iIAGXKKy7aIwggaUMIIEfKADAgECAhMzAAl2bRD3 # WOtuF1mYAAAACXZtMA0GCSqGSIb3DQEBDAUAMFoxCzAJBgNVBAYTAlVTMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKzApBgNVBAMTIk1pY3Jvc29mdCBJ # RCBWZXJpZmllZCBDUyBBT0MgQ0EgMDQwHhcNMjYxMDI2MTAwNDU0WhcNMjYxMDI5 # MTAwNDU0WjBWMQswCQYDVQQGEwJDWjEPMA0GA1UEBxMGUHJhZ3VlMRowGAYDVQQK # ExFHcmV5Q2VyYmVsIHMuci5vLjEaMBgGA1UEAxMRR3JleUNlcmJlbCBzLnIuby4w # ggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCs1RzwOEVbn97HeguUphHh # 72DmOpA+rfCrvVhkKadcnGBaHeqxy7pRN6dYBBWHvpSTiAj9bht5mEFa5tJVJ3Ax # aDmduARepE8r0ESRb9BtQyk48aboLCG7RI9/p2Btewe1OwtuWQaTD9zPweddJcca # f/wqTfgMmMkNX3IeotL/Qe4R8bh3Tp6hJvnBMuQfzt/iYEAIYiBlWR7O5qw2SKNg # 48TBQQfHKTp/iBh9O+RYFK4WQ1BOyWVbNg0D1glqz22azf6dfvanG0+WGR26vcUu # rsBAFSOw6JGz2jDI/5A/r/J/eCeA3zBbVrIxRhpZEYfJuwmNoUESYXxg69rcdqNa # V2R795mFsd5xxXTTM05Q6RYfzx6EmycEV6g3kp1C5Yyef4CP/pM8MxJVwCK5jO+S # AvcKvaNj6uVapW88FZFE9e5rsI548SH7WpboM2+EFAN6KsQ72trwxQBtGs7tYci9 # dUiDcyIMHxiOf63khaOnK8Fbtg1gGLQ2UOeMwLgXJMsCAwEAAaOCAdUwggHRMAwG # A1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgeAMDwGA1UdJQQ1MDMGCisGAQQBgjdh # AQAGCCsGAQUFBwMDBhsrBgEEAYI3YdCyjwSCv87eAoKO+MFRgeXAqlwwHQYDVR0O # BBYEFEig1YPNb27kCKL+XcmvZkr6K+FyMB8GA1UdIwQYMBaAFGslQd77a3z9GIAK # LX+Pdl2qcz24MGcGA1UdHwRgMF4wXKBaoFiGVmh0dHA6Ly93d3cubWljcm9zb2Z0 # LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyMElEJTIwVmVyaWZpZWQlMjBDUyUy # MEFPQyUyMENBJTIwMDQuY3JsMHQGCCsGAQUFBwEBBGgwZjBkBggrBgEFBQcwAoZY # aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQl # MjBJRCUyMFZlcmlmaWVkJTIwQ1MlMjBBT0MlMjBDQSUyMDA0LmNydDBUBgNVHSAE # TTBLMEkGBFUdIAAwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMA0GCSqGSIb3DQEBDAUAA4IC # AQCetbl5cdzfNoLLMwpv5hpxPe8h4VJPwLWQvzBmLSM0ulz0nDwZTPHElbEVdukE # xzzy4hDOEoO3Sg5rv5ribDA6egU/ojPYJkXjr6s6cbYPpjnMM2v8qmKGRSI6tgQD # hxY9DUwbjL7aPYHe3asWXC5YwntP+dvQ6thqcU1p4Br+NM1/3RX6noSwBbrTikdG # O/pP32n2R63m9n2NUWBxO876FKSwa94az2N0geiZY9qbE7zg1HNFRR5t8b245Jpt # wo1k27Zmrbl2i3AjNhi4ZZI+jmm31y7VoZcHBqOuwQ7UMZLZgXncqtArfR0yfz1k # fc8PitOtIbV+o8kdz6/kMYzFkvWtM5WmHEPwX2Qs2CeHq2AMvD9F9p6WD0BQZIw9 # LV1mZKPGJUwnjtXglTMgaONagmF/9ZDA0vHtcBC42mOoOQtV/mWMgLO7PxH80Bv/ # +cn4Oi3RJ4Ymn1M+RKvb7j6dWj62Ju5L34BkpyHbWRl6ouR+dhV8LyJOhhpPivsw # O6VTV+WSWPJKigWMtSLu8+lJD17HXK0aQmcG6O3DNxMa07ZBWY9ur5EENd43KQTw # PLvTVDhvAEbXZ0Wr4hgM3+/s9enV4K9c+wiSVbhM7zducas4b1IN80zh7K5MUEow # yQfK+/q9q6W/q1SehmKKzwAMRZT6zNLrhx8CYr9obHaIbTCCBpQwggR8oAMCAQIC # EzMACXZtEPdY624XWZgAAAAJdm0wDQYJKoZIhvcNAQEMBQAwWjELMAkGA1UEBhMC # VVMxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjErMCkGA1UEAxMiTWlj # cm9zb2Z0IElEIFZlcmlmaWVkIENTIEFPQyBDQSAwNDAeFw0yNjEwMjYxMDA0NTRa # Fw0yNjEwMjkxMDA0NTRaMFYxCzAJBgNVBAYTAkNaMQ8wDQYDVQQHEwZQcmFndWUx # GjAYBgNVBAoTEUdyZXlDZXJiZWwgcy5yLm8uMRowGAYDVQQDExFHcmV5Q2VyYmVs # IHMuci5vLjCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAKzVHPA4RVuf # 3sd6C5SmEeHvYOY6kD6t8Ku9WGQpp1ycYFod6rHLulE3p1gEFYe+lJOICP1uG3mY # QVrm0lUncDFoOZ24BF6kTyvQRJFv0G1DKTjxpugsIbtEj3+nYG17B7U7C25ZBpMP # 3M/B510lxxp//CpN+AyYyQ1fch6i0v9B7hHxuHdOnqEm+cEy5B/O3+JgQAhiIGVZ # Hs7mrDZIo2DjxMFBB8cpOn+IGH075FgUrhZDUE7JZVs2DQPWCWrPbZrN/p1+9qcb # T5YZHbq9xS6uwEAVI7DokbPaMMj/kD+v8n94J4DfMFtWsjFGGlkRh8m7CY2hQRJh # fGDr2tx2o1pXZHv3mYWx3nHFdNMzTlDpFh/PHoSbJwRXqDeSnULljJ5/gI/+kzwz # ElXAIrmM75IC9wq9o2Pq5VqlbzwVkUT17muwjnjxIftalugzb4QUA3oqxDva2vDF # AG0azu1hyL11SINzIgwfGI5/reSFo6crwVu2DWAYtDZQ54zAuBckywIDAQABo4IB # 1TCCAdEwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCB4AwPAYDVR0lBDUwMwYK # KwYBBAGCN2EBAAYIKwYBBQUHAwMGGysGAQQBgjdh0LKPBIK/zt4Cgo74wVGB5cCq # XDAdBgNVHQ4EFgQUSKDVg81vbuQIov5dya9mSvor4XIwHwYDVR0jBBgwFoAUayVB # 3vtrfP0YgAotf492XapzPbgwZwYDVR0fBGAwXjBcoFqgWIZWaHR0cDovL3d3dy5t # aWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwSUQlMjBWZXJpZmll # ZCUyMENTJTIwQU9DJTIwQ0ElMjAwNC5jcmwwdAYIKwYBBQUHAQEEaDBmMGQGCCsG # AQUFBzAChlhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01p # Y3Jvc29mdCUyMElEJTIwVmVyaWZpZWQlMjBDUyUyMEFPQyUyMENBJTIwMDQuY3J0 # MFQGA1UdIARNMEswSQYEVR0gADBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1p # Y3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0wDQYJKoZIhvcN # AQEMBQADggIBAJ61uXlx3N82gsszCm/mGnE97yHhUk/AtZC/MGYtIzS6XPScPBlM # 8cSVsRV26QTHPPLiEM4Sg7dKDmu/muJsMDp6BT+iM9gmReOvqzpxtg+mOcwza/yq # YoZFIjq2BAOHFj0NTBuMvto9gd7dqxZcLljCe0/529Dq2GpxTWngGv40zX/dFfqe # hLAFutOKR0Y7+k/fafZHreb2fY1RYHE7zvoUpLBr3hrPY3SB6Jlj2psTvODUc0VF # Hm3xvbjkmm3CjWTbtmatuXaLcCM2GLhlkj6OabfXLtWhlwcGo67BDtQxktmBedyq # 0Ct9HTJ/PWR9zw+K060htX6jyR3Pr+QxjMWS9a0zlaYcQ/BfZCzYJ4erYAy8P0X2 # npYPQFBkjD0tXWZko8YlTCeO1eCVMyBo41qCYX/1kMDS8e1wELjaY6g5C1X+ZYyA # s7s/EfzQG//5yfg6LdEnhiafUz5Eq9vuPp1aPrYm7kvfgGSnIdtZGXqi5H52FXwv # Ik6GGk+K+zA7pVNX5ZJY8kqKBYy1Iu7z6UkPXsdcrRpCZwbo7cM3ExrTtkFZj26v # kQQ13jcpBPA8u9NUOG8ARtdnRaviGAzf7+z16dXgr1z7CJJVuEzvN25xqzhvUg3z # TOHsrkxQSjDJB8r7+r2rpb+rVJ6GYorPAAxFlPrM0uuHHwJiv2hsdohtMIIHKDCC # BRCgAwIBAgITMwAAABYxko2SAmV7mgAAAAAAFjANBgkqhkiG9w0BAQwFADBjMQsw # CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTQwMgYD # VQQDEytNaWNyb3NvZnQgSUQgVmVyaWZpZWQgQ29kZSBTaWduaW5nIFBDQSAyMDIx # MB4XDTI2MDMyNjE4MTEyOVoXDTMxMDMyNjE4MTEyOVowWjELMAkGA1UEBhMCVVMx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjErMCkGA1UEAxMiTWljcm9z # b2Z0IElEIFZlcmlmaWVkIENTIEFPQyBDQSAwNDCCAiIwDQYJKoZIhvcNAQEBBQAD # ggIPADCCAgoCggIBAMpV+sjb6Akwz/RtDk5Uo1284BLMttRQy9e5/5WXtga6h89p # kdWjeAwcXmKdP4YxKkhx8hn2q1dTVQbryvLy81tC04vg2bfSJ9emojX4HKIBYs7V # hPRafMbtc866hN55aw1m/kWaPEKxF4Fm/LPLMLJdlu7URB8nFZMfh5tTC0CJb2uo # x/14OP/BiGIR8214lXdkV6JsPbO0Iev0mEV133tducIeBChMipzTZfnGVEq1QYFr # 7460cEGOIn+7AGfNOSq7gWOlmNB4m2uZ1r66vUJPaN+VYgH/Kmfu7tX229b3Alsl # i38fYS0nQY41bElntMS7yNY+Kd047eXM8/tS3NL+ZUNX3ge5xZqW2aytrrNIbwGg # QnzsgzxBJvu9+b87jUWFiRS/z1YiPkRLY7iTHKIxJ973kIyK8K5itE/aEq/Ht6A8 # ytaAMMGTEwuCspk72FE1Qyby+TfDlv1KiAc7IlWHHIWbxoVd8jGCoMXhLSDhuFuG # fOWOZKUIuW6YxlxcUYOOkXa02gC7dUYUZi0e1NI1Uq9mmAHdvjKdqgORs9/5aDjn # beO3hWd5qwGBmELWytkjY0KcIEF+CMurTimoQoBYSiHJbYrb0pKSQe+3lVoTVpzd # i36jKI7MxLnu1fBAMksa8uD85cU7RU29zMOd18h9dgTEH9pvY+4xLB3lUgE7AgMB # AAGjggHcMIIB2DAOBgNVHQ8BAf8EBAMCAYYwEAYJKwYBBAGCNxUBBAMCAQAwHQYD # VR0OBBYEFGslQd77a3z9GIAKLX+Pdl2qcz24MFQGA1UdIARNMEswSQYEVR0gADBB # MD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0Rv # Y3MvUmVwb3NpdG9yeS5odG0wGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwEgYD # VR0TAQH/BAgwBgEB/wIBADAfBgNVHSMEGDAWgBTZQSmwDw9jbO9p1/XNKZ6kSGow # 5jBwBgNVHR8EaTBnMGWgY6Bhhl9odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp # b3BzL2NybC9NaWNyb3NvZnQlMjBJRCUyMFZlcmlmaWVkJTIwQ29kZSUyMFNpZ25p # bmclMjBQQ0ElMjAyMDIxLmNybDB9BggrBgEFBQcBAQRxMG8wbQYIKwYBBQUHMAKG # YWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0 # JTIwSUQlMjBWZXJpZmllZCUyMENvZGUlMjBTaWduaW5nJTIwUENBJTIwMjAyMS5j # cnQwDQYJKoZIhvcNAQEMBQADggIBAAbVUF5UdNVEGWOVxkPciIzHA/IyNDIs1oSd # W7mY4ObaFEEl8fwawEsBOascBzwXjuOaTkKelQ+IiC4JvDpn8pWPgOyiKrbbw1Is # wt7AV8YyuLu7A0YshILlxyny9R0AMMQixLZ0T5Aj07CQOm/de5QPt36ECBwtVww6 # XUCx8Rm2xl6eEa9JhSub8W4urQGoQYv0Zczlz4ej2ryj5Wf9L4ZZA3bL6CRE7Xmz # SQjTmdSmr904PiuL2uBWzq2KkR3RhmoaAP4Jk2JplasNM5Bs0+dx3YX2o6xRrbSa # Jiu6hPk/AkBoj5BCJMTZkl4wk6Q6nOFNSCpUxnBmJ0RRkoKq51p5ADTxbCeRAx8r # IfNpTyPjxQtxTiFTC8yy/t9K6s570YB1FAI+8XxZxIrmgMd7xVUkzi2/oooKb3Ue # ovH0lqYGBEjpHZJ9jee7boQbOe7SsKD/vr3PzFabg9VwLZlovpWUpWoWnU2w3xio # zJ/m35tsZVvT2egUpwkb9TDIaXFGZAGV94FRDHn/K2XNnOwSeGskX19MB+N7yPc5 # 1fmUSd49MVAtGW/NkUGpRech5Aq/d8dCML2bZ+j9nTUMgj+qnd3wuEZVRbQEIbTh # 9HAck0fyKqoIb+qh8y/6TKbYDEEOcfM2BGMvrQk4WIGCK6u8uFhA2EH3kpaiMUDq # yFCeCCQxMIIHnjCCBYagAwIBAgITMwAAAAeHozSje6WOHAAAAAAABzANBgkqhkiG # 9w0BAQwFADB3MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMUgwRgYDVQQDEz9NaWNyb3NvZnQgSWRlbnRpdHkgVmVyaWZpY2F0aW9u # IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMjAwHhcNMjEwNDAxMjAwNTIw # WhcNMzYwNDAxMjAxNTIwWjBjMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9z # b2Z0IENvcnBvcmF0aW9uMTQwMgYDVQQDEytNaWNyb3NvZnQgSUQgVmVyaWZpZWQg # Q29kZSBTaWduaW5nIFBDQSAyMDIxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAsvDArxmIKOLdVHpMSWxpCFUJtFL/ekr4weslKPdnF3cpTeuV8veqtmKV # gok2rO0D05BpyvUDCg1wdsoEtuxACEGcgHfjPF/nZsOkg7c0mV8hpMT/GvB4uhDv # WXMIeQPsDgCzUGzTvoi76YDpxDOxhgf8JuXWJzBDoLrmtThX01CE1TCCvH2sZD/+ # Hz3RDwl2MsvDSdX5rJDYVuR3bjaj2QfzZFmwfccTKqMAHlrz4B7ac8g9zyxlTpkT # uJGtFnLBGasoOnn5NyYlf0xF9/bjVRo4Gzg2Yc7KR7yhTVNiuTGH5h4eB9ajm1OC # ShIyhrKqgOkc4smz6obxO+HxKeJ9bYmPf6KLXVNLz8UaeARo0BatvJ82sLr2gqlF # Bdj1sYfqOf00Qm/3B4XGFPDK/H04kteZEZsBRc3VT2d/iVd7OTLpSH9yCORV3oIZ # QB/Qr4nD4YT/lWkhVtw2v2s0TnRJubL/hFMIQa86rcaGMhNsJrhysLNNMeBhiMez # U1s5zpusf54qlYu2v5sZ5zL0KvBDLHtL8F9gn6jOy3v7Jm0bbBHjrW5yQW7S36AL # At03QDpwW1JG1Hxu/FUXJbBO2AwwVG4Fre+ZQ5Od8ouwt59FpBxVOBGfN4vN2m3f # Zx1gqn52GvaiBz6ozorgIEjn+PhUXILhAV5Q/ZgCJ0u2+ldFGjcCAwEAAaOCAjUw # ggIxMA4GA1UdDwEB/wQEAwIBhjAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQU # 2UEpsA8PY2zvadf1zSmepEhqMOYwVAYDVR0gBE0wSzBJBgRVHSAAMEEwPwYIKwYB # BQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBv # c2l0b3J5Lmh0bTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAPBgNVHRMBAf8E # BTADAQH/MB8GA1UdIwQYMBaAFMh+0mqFKhvKGZgEByfPUBBPaKiiMIGEBgNVHR8E # fTB7MHmgd6B1hnNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9N # aWNyb3NvZnQlMjBJZGVudGl0eSUyMFZlcmlmaWNhdGlvbiUyMFJvb3QlMjBDZXJ0 # aWZpY2F0ZSUyMEF1dGhvcml0eSUyMDIwMjAuY3JsMIHDBggrBgEFBQcBAQSBtjCB # szCBgQYIKwYBBQUHMAKGdWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv # Y2VydHMvTWljcm9zb2Z0JTIwSWRlbnRpdHklMjBWZXJpZmljYXRpb24lMjBSb290 # JTIwQ2VydGlmaWNhdGUlMjBBdXRob3JpdHklMjAyMDIwLmNydDAtBggrBgEFBQcw # AYYhaHR0cDovL29uZW9jc3AubWljcm9zb2Z0LmNvbS9vY3NwMA0GCSqGSIb3DQEB # DAUAA4ICAQB/JSqe/tSr6t1mCttXI0y6XmyQ41uGWzl9xw+WYhvOL47BV09Dgfnm # /tU4ieeZ7NAR5bguorTCNr58HOcA1tcsHQqt0wJsdClsu8bpQD9e/al+lUgTUJEV # 80Xhco7xdgRrehbyhUf4pkeAhBEjABvIUpD2LKPho5Z4DPCT5/0TlK02nlPwUbv9 # URREhVYCtsDM+31OFU3fDV8BmQXv5hT2RurVsJHZgP4y26dJDVF+3pcbtvh7R6NE # DuYHYihfmE2HdQRq5jRvLE1Eb59PYwISFCX2DaLZ+zpU4bX0I16ntKq4poGOFaaK # tjIA1vRElItaOKcwtc04CBrXSfyL2Op6mvNIxTk4OaswIkTXbFL81ZKGD+24uMCw # o/pLNhn7VHLfnxlMVzHQVL+bHa9KhTyzwdG/L6uderJQn0cGpLQMStUuNDArxW2w # F16QGZ1NtBWgKA8Kqv48M8HfFqNifN6+zt6J0GwzvU8g0rYGgTZR8zDEIJfeZxwW # DHpSxB5FJ1VVU1LIAtB7o9PXbjXzGifaIMYTzU4YKt4vMNwwBmetQDHhdAtTPplO # XrnI9SI6HeTtjDD3iUN/7ygbahmYOHk7VB7fwT4ze+ErCbMh6gHV1UuXPiLciloN # xH6K4aMfZN1oLVk6YFeIJEokuPgNPa6EnTiOL60cPqfny+Fq8UiuZzGCGpEwghqN # AgEBMHEwWjELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh # dGlvbjErMCkGA1UEAxMiTWljcm9zb2Z0IElEIFZlcmlmaWVkIENTIEFPQyBDQSAw # NAITMwAJdm0Q91jrbhdZmAAAAAl2bTANBglghkgBZQMEAgEFAKBeMBAGCisGAQQB # gjcCAQwxAjAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMC8GCSqGSIb3DQEJ # BDEiBCBEnt852EPHPdCCZlwh96E8CwAskAaq/E5oXN0GLvOXbzANBgkqhkiG9w0B # AQEFAASCAYA0zMOYB/t8ePgkixCIYvCy4I3y9URk9GJOvj1FkUlfRwfkFBUktrcU # aRdZRtTVbLS0xNlHUiJHsoOkBEOSq4fBwaluCyL5A/6mzea5YQtpYNU2WiCElhJQ # ss7nxVKLJU8MjoqchKJfSek9DKJbCc3HX+m+l8js183WZ8bjPBl6RVTsmXt0BKVa # ZwFJ1RgoDMtIEuT4G3yVETIGs6hmoCwsNchmmAZlLtcAlKVNO9zCiEjL3i7nLid0 # FXBxScJ3q6984dnW7urQA7X/DwVnE4DHC5Eyq9nUWHQSK/haFAJ87FqKubpzZTNM # hWJBuKD5xRtdg9iTRpjKM2gBMoIyCBFrc6BlpLVsDx5a72Ri+KPYAlpAUhOcbf4V # qyWgKiXlDkOjZ17WWhx+iG2GBVB6R8Cikps0wUzL0et2x+mKFuLlJ9OY947RKKla # CpcbKsForfACI1tkeQBAHMrPQhe93O7AJp0R8cr2GSSNsClHGBWZjv4oRnT/RpgM # LZvlZ2CxtX2hghgRMIIYDQYKKwYBBAGCNwMDATGCF/0wghf5BgkqhkiG9w0BBwKg # ghfqMIIX5gIBAzEPMA0GCWCGSAFlAwQCAQUAMIIBYgYLKoZIhvcNAQkQAQSgggFR # BIIBTTCCAUkCAQEGCisGAQQBhFkKAwEwMTANBglghkgBZQMEAgEFAAQgnB3Sdxgc # ly/amzfLr0EZ7gQ9tdsSl8j5WegMQeJ20N0CBmrgqJlPxhgTMjAyNjEwMjgwOTQ0 # NTEuNDYxWjAEgAIB9KCB4aSB3jCB2zELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh # c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD # b3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9u # czEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkE1MDAtMDVFMC1EOTQ3MTUwMwYD # VQQDEyxNaWNyb3NvZnQgUHVibGljIFJTQSBUaW1lIFN0YW1waW5nIEF1dGhvcml0 # eaCCDyEwggeCMIIFaqADAgECAhMzAAAABeXPD/9mLsmHAAAAAAAFMA0GCSqGSIb3 # DQEBDAUAMHcxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y # YXRpb24xSDBGBgNVBAMTP01pY3Jvc29mdCBJZGVudGl0eSBWZXJpZmljYXRpb24g # Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAyMDAeFw0yMDExMTkyMDMyMzFa # Fw0zNTExMTkyMDQyMzFaMGExCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3Nv # ZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBQdWJsaWMgUlNBIFRp # bWVzdGFtcGluZyBDQSAyMDIwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC # AgEAnnznUmP94MWfBX1jtQYioxwe1+eXM9ETBb1lRkd3kcFdcG9/sqtDlwxKoVIc # aqDb+omFio5DHC4RBcbyQHjXCwMk/l3TOYtgoBjxnG/eViS4sOx8y4gSq8Zg49RE # Af5huXhIkQRKe3Qxs8Sgp02KHAznEa/Ssah8nWo5hJM1xznkRsFPu6rfDHeZeG1W # a1wISvlkpOQooTULFm809Z0ZYlQ8Lp7i5F9YciFlyAKwn6yjN/kR4fkquUWfGmMo # pNq/B8U/pdoZkZZQbxNlqJOiBGgCWpx69uKqKhTPVi3gVErnc/qi+dR8A2MiAz0k # N0nh7SqINGbmw5OIRC0EsZ31WF3Uxp3GgZwetEKxLms73KG/Z+MkeuaVDQQheang # OEMGJ4pQZH55ngI0Tdy1bi69INBV5Kn2HVJo9XxRYR/JPGAaM6xGl57Ei95HUw9N # V/uC3yFjrhc087qLJQawSC3xzY/EXzsT4I7sDbxOmM2rl4uKK6eEpurRduOQ2hTk # mG1hSuWYBunFGNv21Kt4N20AKmbeuSnGnsBCd2cjRKG79+TX+sTehawOoxfeOO/j # R7wo3liwkGdzPJYHgnJ54UxbckF914AqHOiEV7xTnD1a69w/UTxwjEugpIPMIIE6 # 7SFZ2PMo27xjlLAHWW3l1CEAFjLNHd3EQ79PUr8FUXetXr0CAwEAAaOCAhswggIX # MA4GA1UdDwEB/wQEAwIBhjAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQUa2ko # OjUvSGNAz3vYr0npPtk92yEwVAYDVR0gBE0wSzBJBgRVHSAAMEEwPwYIKwYBBQUH # AgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0 # b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMA # dQBiAEMAQTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFMh+0mqFKhvKGZgE # ByfPUBBPaKiiMIGEBgNVHR8EfTB7MHmgd6B1hnNodHRwOi8vd3d3Lm1pY3Jvc29m # dC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBJZGVudGl0eSUyMFZlcmlmaWNh # dGlvbiUyMFJvb3QlMjBDZXJ0aWZpY2F0ZSUyMEF1dGhvcml0eSUyMDIwMjAuY3Js # MIGUBggrBgEFBQcBAQSBhzCBhDCBgQYIKwYBBQUHMAKGdWh0dHA6Ly93d3cubWlj # cm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwSWRlbnRpdHklMjBW # ZXJpZmljYXRpb24lMjBSb290JTIwQ2VydGlmaWNhdGUlMjBBdXRob3JpdHklMjAy # MDIwLmNydDANBgkqhkiG9w0BAQwFAAOCAgEAX4h2x35ttVoVdedMeGj6TuHYRJkl # FaW4sTQ5r+k77iB79cSLNe+GzRjv4pVjJviceW6AF6ycWoEYR0LYhaa0ozJLU5Yi # +LCmcrdovkl53DNt4EXs87KDogYb9eGEndSpZ5ZM74LNvVzY0/nPISHz0Xva71Qj # D4h+8z2XMOZzY7YQ0Psw+etyNZ1CesufU211rLslLKsO8F2aBs2cIo1k+aHOhrw9 # xw6JCWONNboZ497mwYW5EfN0W3zL5s3ad4Xtm7yFM7Ujrhc0aqy3xL7D5FR2J7x9 # cLWMq7eb0oYioXhqV2tgFqbKHeDick+P8tHYIFovIP7YG4ZkJWag1H91KlELGWi3 # SLv10o4KGag42pswjybTi4toQcC/irAodDW8HNtX+cbz0sMptFJK+KObAnDFHEsu # kxD+7jFfEV9Hh/+CSxKRsmnuiovCWIOb+H7DRon9TlxydiFhvu88o0w35JkNbJxT # k4MhF/KgaXn0GxdH8elEa2Imq45gaa8D+mTm8LWVydt4ytxYP/bqjN49D9NZ81co # E6aQWm88TwIf4R4YZbOpMKN0CyejaPNN41LGXHeCUMYmBx3PkP8ADHD1J2Cr/6tj # uOOCztfp+o9Nc+ZoIAkpUcA/X2gSMkgHAPUvIdtoSAHEUKiBhI6JQivRepyvWcl+ # JYbYbBh7pmgAXVswggeXMIIFf6ADAgECAhMzAAAAYHIGJIpKBbjZAAAAAABgMA0G # CSqGSIb3DQEBDAUAMGExCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQg # Q29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBQdWJsaWMgUlNBIFRpbWVz # dGFtcGluZyBDQSAyMDIwMB4XDTI2MDkxNzE4NTcyN1oXDTI3MDkxNDE4NTcyN1ow # gdsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsT # HE1pY3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJzAlBgNVBAsTHm5TaGllbGQg # VFNTIEVTTjpBNTAwLTA1RTAtRDk0NzE1MDMGA1UEAxMsTWljcm9zb2Z0IFB1Ymxp # YyBSU0EgVGltZSBTdGFtcGluZyBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA # A4ICDwAwggIKAoICAQCaE2NLwFPHivpDfMeASpyJR/YkExXCIhEJXza+XHOY1hGk # A6ao2ad71PwUkWBnp707PqB7OFEH4XwXj1KLqcH76xP65J0WzFdOUXyufOYEF5+L # 9niiFkra3EJXSc1wzDuEgJO8QSo2ArhmWlyFdWvxyFj7FQsrJFRqs1cYjvJpuC+I # 9amZgH5b8ageWd9Xot3lqdg3IfhRI5NJVVAD/Uilf70bg7vS52a97Sc1dMwdpmq1 # y25Pv6ItqWnoAPZRaSDRT+jO8vVlU8Zjdciqmg/hEg5TZIn5D2/wgDd5l4g20Aic # ErzwL7sAW9/J2qu7o++A7JnCbvvwkCat8EID4WM5jh/J9qKDWUlzXX/2xH4vrsFs # teuPGZubow2DGMn3ov0uPJVUpFbjkxsTChZ9inGRJns3lXP3Tz9ri2iDJdiqGmNy # bNR3BhB9e8pJdkwQXnFQYp5/N+FZx845ERvqd9QoxNc0ggoRA9RWtKnTjAp/2pA7 # WyToq2gQ/XhkTWEjYHyQsCmkb/8JYoPMoxQJNQ3LPxMZ5L8zBnyxPlq3ifwZhOPf # UtO9rj1g1OKEkAsFJmJzJGfuLwTc1AbkuGVTpFOco0YJWcx0gr9QVfFOVZhBAXew # 3cXNGlJebmTUM9qOI5Cor38YMQUWc4KijuiQsKGqfpUekuS4i1BA8G49scji3wID # AQABo4IByzCCAccwHQYDVR0OBBYEFIhUKHRhUAQ7Ewm4gt6So7de2mpiMB8GA1Ud # IwQYMBaAFGtpKDo1L0hjQM972K9J6T7ZPdshMGwGA1UdHwRlMGMwYaBfoF2GW2h0 # dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyMFB1 # YmxpYyUyMFJTQSUyMFRpbWVzdGFtcGluZyUyMENBJTIwMjAyMC5jcmwweQYIKwYB # BQUHAQEEbTBrMGkGCCsGAQUFBzAChl1odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20v # cGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFB1YmxpYyUyMFJTQSUyMFRpbWVzdGFt # cGluZyUyMENBJTIwMjAyMC5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAK # BggrBgEFBQcDCDAOBgNVHQ8BAf8EBAMCB4AwZgYDVR0gBF8wXTBRBgwrBgEEAYI3 # TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3Br # aW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMAgGBmeBDAEEAjANBgkqhkiG9w0BAQwF # AAOCAgEAV4vmfykFNJc9GbTl2Jxub4K5oulwAz+QKHvLEpujstjXFiHDnIhr96U5 # YuOmFVa58aCecRcGqrja4YM84DMVEUe4GXDyHbsLGW6ixMxzGzwSuz2uBBvs2Ccc # c259zOavuXOft2QhOcvBijiXXbFuSSPZXALUvR5i1uEkSOypuBedo4fUCs49v+vG # pe2dqsS4O9mIEzBs2XsuRBeVOYNKOHwCcXlXw4Ka8B5LPsMwbfMO3GYVt/NXQGx4 # lZPPVmInNNe84JUaHSwILSb7FUHGmf58TySy2QohU7wPgxmsC32uhAVgf8Aqjd2d # OOurzxS4DvqqlFpmGvT7N/aIXSAoAcGAZg82c9nvcgdBEkxCJ9IKr8yhZBrfw7ex # 9pwNHa+DG8Nn6N+jstAVeD4IKAfaHPodl+F8PxKdN72YBgdj9ExoeFH36MnSqVu8 # WMdoQEXeKwCpaimxMlZUMw8TLjuHA4h+QDIv1zj9xwiXNLScUdErGE3ng5fU39s3 # BnHBtIcuhftj5VJ1YR+n/CudtSh8jkrKFqB7iBk4xle3fdzHFUUYGtOSqk27E2sB # ha/22Ex91IRC8ZV6UCAvJ50yNCRX97xQP+Vrs3nx1Z3Nw2VcVt81F78WHOx/hKtb # UiWXIW5cjYaxnJdjUoeEKcAaTyni13gki4MhW2rpGgFeECXSABYxggdDMIIHPwIB # ATB4MGExCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xMjAwBgNVBAMTKU1pY3Jvc29mdCBQdWJsaWMgUlNBIFRpbWVzdGFtcGluZyBD # QSAyMDIwAhMzAAAAYHIGJIpKBbjZAAAAAABgMA0GCWCGSAFlAwQCAQUAoIIEnDAR # BgsqhkiG9w0BCRACDzECBQAwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwG # CSqGSIb3DQEJBTEPFw0yNjEwMjgwOTQ0NTFaMC8GCSqGSIb3DQEJBDEiBCCjNGlU # 4yYNbMiuujPrjLm7qcf7kEbODGSQPbwFnRTV7zCBuQYLKoZIhvcNAQkQAi8xgakw # gaYwgaMwgaAEIDfLjLnwvS54utKqPKxmcYlH7Orna0xc019MpXpStgKzMHwwZaRj # MGExCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # MjAwBgNVBAMTKU1pY3Jvc29mdCBQdWJsaWMgUlNBIFRpbWVzdGFtcGluZyBDQSAy # MDIwAhMzAAAAYHIGJIpKBbjZAAAAAABgMIIDXgYLKoZIhvcNAQkQAhIxggNNMIID # SaGCA0UwggNBMIICKQIBATCCAQmhgeGkgd4wgdsxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVyaWNhIE9w # ZXJhdGlvbnMxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjpBNTAwLTA1RTAtRDk0 # NzE1MDMGA1UEAxMsTWljcm9zb2Z0IFB1YmxpYyBSU0EgVGltZSBTdGFtcGluZyBB # dXRob3JpdHmiIwoBATAHBgUrDgMCGgMVAJe/M8jCvUlk8+ItY60JUWKjdUI3oGcw # ZaRjMGExCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xMjAwBgNVBAMTKU1pY3Jvc29mdCBQdWJsaWMgUlNBIFRpbWVzdGFtcGluZyBD # QSAyMDIwMA0GCSqGSIb3DQEBCwUAAgUA7ovP2zAiGA8yMDI2MTAyODAxMzUyM1oY # DzIwMjYxMDI5MDEzNTIzWjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDui8/bAgEA # MAcCAQACAgqFMAcCAQACAhJxMAoCBQDujSFbAgEAMDYGCisGAQQBhFkKBAIxKDAm # MAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcN # AQELBQADggEBACLSP1pfw5/v6ulvqWpONujfxVIRhAGkC42hTJjb9cUDy/vicHL6 # LxMaPmN2cNwPL4d2VsFkHJ1UeL3/TQ1hhpq8Vs8+WKMte8UZZ+tG8cOdvU6gJm+C # HaU7TUdBeO6zzDQyKESQeRueVR/2AeTeoJhi8NI2SIn2vtqYFjhP/MLWfszv//2P # RrSPeM11Y6kQwX/WQ2GvRlWWKgBR4lGzMdC/XVvTdICKeMNBvpjr9MjMvSB/YG3O # PYa2AWHWVQJxr+PjjPK9kVSNsRX1d+dQHmfmo5wLNAOHy1vZzxpUJ8Xwc8g2W6vz # tMaDc0ynQFnP7FFxFHEm4htTMYutFPRCVzYwDQYJKoZIhvcNAQEBBQAEggIATjKu # r31nhPhh69cYfdVrw3T88YxjsbEPQrd4+Go39NH5D4eQE7AmuWA8fHipbb+jDqeC # ZjO3ThECFgahgKvq/VS9gFzFK/ZSQ18uKqaugaNbmwirCZZsw8+eAvo3JQ7sFXRO # ATQ3sOJHN8/JudUcvVh2LAy9PWN8iYXoGm28Ihh/8BHx5AcaES0F0orNQzARxcl9 # /OTbcRrjZqv8AYN5RhDd9LfeTvL4Ylr77M5K72vyk2AmKgLEwPr8VpdcFEYmE8RL # Zh3Kg8qsEEyNAeXIwqpORaMYzSYz7Vw8/ytGZZ4bEfx50LtZ89jG3lT9162hUrVb # pTV+pKF0zU2FxG6vqVWKFJiDfs9L7k1uB5UF6TogKoqKz9/IzpWnZudaY+i/CyW5 # Y0/QLryqnBHKyhKSzr6OlS3V1huKDMXvWfFBXQZxhE6PCL5x4aQT8Nstq8mtPHzo # 8rONvl+MMAwPBCm5L4I3l8LrElO6i36CaOFhWg+3AwhkMEwBAzVpPYHeqzE5FsPp # TLOW5pBo/s5Tb+Xlf0wdUqJbkdDDWM/wotMsOyqm0FnNuqo6Gogea6RMtGa3sqdr # 64iJg+bxZbjvvNWhMaqlR3o7Iv+/WSxilOJv7vaHAri5TRq0KIOsZpeIvn8vfJ4/ # hRieZY1dGNZ+oPQOIQFG0XHrxkhbpN6ityq2DzI= # SIG # End signature block |