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