CimInventory.psm1

param (
    $PathDatabase = "$PSScriptRoot\database.sdf",
    $PathWQLQueries = "$PSScriptRoot\Queries.txt",
    $PathMap = "$PSScriptRoot\Map.txt",
    $PathComputerNames = "$PSScriptRoot\ComputerNames.txt"
)

$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {
    # Disconnect SQL
    $command.Dispose()
    $connection.Close()
    $connection.Dispose()
}

#region Connecting to SQL

if (-not (Test-Path $PathDatabase)) {
    try {
        $engine = New-Object Data.SqlServerCe.SqlCeEngine
        $engine.LocalConnectionString = "Data Source = $PathDatabase"
        $engine.CreateDatabase()
    } catch {
        throw "Could not created database! $_"
    }
}

$connection = New-Object Data.SqlServerCe.SqlCeConnection
$connection.ConnectionString = "Data Source = $PathDatabase"
    
$connection.Open()

$command = New-Object Data.SqlServerCe.SqlCeCommand
$command.Connection = $connection

#endregion

#region Support functions

function Get-SqlData {

<#
    .Synopsis
        Helper function.
 
    .Description
        This function reads data from Sql using
        -- pre-defined object of type SqlCeCommand
        -- provided query
        -- creates object of selected pseudo-type
#>


param (
    # Query that will be used to retrieve objects.
    [string]$Query,

    # Command object that will be used.
    [Data.SqlServerCe.SqlCeCommand]$Command = $Script:command,

    # Pseudo-type configured on retrieved objects.
    [string]$Type
)

    $Command.CommandText = $Query
    try {
        $adapter = New-Object System.Data.SqlServerCe.SqlCeDataAdapter $Command
        $table = New-Object System.Data.DataTable
        $adapter.Fill($table) | Out-Null

        $columnNames = $table.Columns | Select-Object -ExpandProperty ColumnName
        foreach ($row in $table.Rows) {
            $properties = @{}
            foreach ($property in $columnNames) {
                $properties.Add($property, $row.$property)
            }
            $Out = New-Object -TypeName PSObject -Property $properties
            if ($Type) {
                $Out.PSTypeNames.Insert(0,$Type)
            }
            $Out
        }
    } catch {
        Write-Error "Issue with connecting to database: $_"
    } finally {
        if ($adapter) {
            $adapter.Dispose()
        }
    }
}


function Invoke-SqlCommand {

<#
    .Synopsis
        Helper function.
 
    .Description
        This function runs Sql command using
        -- pre-defined object of type SqlCeCommand
        -- provided query
        -- does not return any objects
 
#>


param (
    # Query to run.
    [string]$TSQL,

    # Object of type SqlCeCommand that will be used to execute query.
    [Data.SqlServerCe.SqlCeCommand]$Command = $Script:command
)

    $Command.CommandText = $TSQL    
    try {
        $Command.ExecuteNonQuery() | Out-Null
    } catch {
        Write-Error "Issue with running query: $TSQL`n$_"
    }
}

#endregion

#region Creating 'Computers' table

if (-not (Get-SqlData -Query "SELECT Table_Name FROM information_schema.tables WHERE Table_Name = 'Computers'")) {
    try {
        Invoke-SqlCommand -TSQL @"
            CREATE TABLE Computers (
                Computer_Id int IDENTITY(1,1) PRIMARY KEY,
                Name nvarchar(30) UNIQUE
            );
"@
             
    } catch  {
        throw "Could not create table 'Computers'!`n $_"
    }
}

#endregion

#region Exported functions

function Get-CimData {
<#
    .Synopsis
        Get data from remote computer using WMI.
 
    .Description
        Uses Get-CimInstance to read WMI data from remote box.
        Supports both WSMan and DCOM protocol.
        Optionally you can try to connect only to computers that respond to Test-Connection.
        Queries are read from a file.
        Mapping lets you configure what WMI properties will map to in custom object that will be created.
 
    .Example
        Get-CimData -ComputerName localhost
        Reads data from WMI on local computer.
 
    .Example
        'Alpha', 'Beta', 'Gamma' | Get-CimData
        Reads WMI data from computers: Alpha, Beta i Gamma.
 
#>


[OutputType('ITPro.Wmi.Obiekt')]
[CmdletBinding()]
param (

    # Computer that commad will read WMI information from.
    [Parameter(
        ValueFromPipeline = $true,
        Mandatory = $true,
        HelpMessage = 'Please give name of computer to process'
    )]
    [Alias('CN','Node','Computer','Name','Server')]
    [string]$ComputerName,

    # Option credentials to use to connect to remote computers.
    [Management.Automation.PSCredential]
    [Management.Automation.Credential()]
    $Credential = [Management.Automation.PSCredential]::Empty,

    # Path to file with WQL querries.
    [ValidateScript({
        Test-Path -Path $_
    })]
    [string]$WQLPath = $Script:PathWQLQueries, 

    # Map converting name of WMI properties to properties of generated custom object.
    [hashtable]$PropertyMap,

    # Enables checking for network connection before running WMI queries.
    [switch]$TestNetwork,

    # Protocol that will be used to read WMI data.
    [Microsoft.Management.Infrastructure.CimCmdlets.ProtocolType]$Protocol = 'Default'
)

begin {
    try {
        $queriesData = Import-Csv -Delimiter / -Path $WQLPath -ErrorAction Stop
    } catch {
        throw "Problem with reading WQL querries configuration: $_"
    }

    if ($PropertyMap) {
        $map = $PropertyMap
    } else {
        $map = ConvertFrom-StringData (Get-Content $Script:PathMap | Out-String)
    }
    $cimSessionOption = New-CimSessionOption -Protocol $Protocol
}

process {
    if ($TestNetwork) {
        if (-not (Test-Connection -Quiet -Count 1 -ComputerName $ComputerName)) {
            return
        }
    }
    try {
        $properties = @{}
        $computerData = @{
            ComputerName = $ComputerName
            SessionOption = $cimSessionOption
        }

        if ($Credential -ne [Management.Automation.PSCredential]::Empty) {
            $computerData.Credential = $Credential
        }

        $cimCommonParam = @{
            CimSession = New-CimSession @computerData
            ErrorAction = 'Stop'
        }

        foreach ($item in $queriesData) {
            $propertyList = $item.Property.Split('\') 
            $cimItemParam = @{ 
                Property = $propertyList
                ClassName = $item.Class
            }

            if ($item.Filter) {
                $cimItemParam.Filter = $item.Filter
            }

            $nodeData = @(Get-CimInstance @cimCommonParam @cimItemParam)[0]
            foreach ($property in $propertyList) {
                if (-not ($name = $map[$property])) {
                    $name = $property
                }
                $properties[$name] = $nodeData.$property
            }
        }
        $properties.Modified = Get-Date
        if (-not $properties.Name) {
            $properties.Name = $ComputerName
        }
        New-Object PSObject -Property $properties | 
            Add-Member -TypeName ITPro.WMI.Obiekt -PassThru
    } catch {
        Write-Error "Issue with reading information from $ComputerName`: $_"
    } 
}
} 


function Import-CimData {

<#
    .Synopsis
        Reads data from database and creates custom object from it.
     
    .Description
        Function reads data from database.
        It gives you ability to:
        -- Limit properties
        -- Filter objects (using TSQL syntax)
     
    .Example
        Import-CimData
        Reads data about all computers stored in database.
 
    .Example
        Import-CimData -Properties Name, UserName, HDSize
        Reads data about all computers stored in database but retrieves only information about Name, UserName and HDSize.
 
    .Example
        Import-CimData -Filter "UserName LIKE '%ba%'
        Retrieves information about all computers where UserName matches 'ba'
#>


[OutputType('ITPro.WMI.Obiekt')]
[CmdletBinding()]
param (
    # Filter used to limit the results.
    [string]$Filter,

    # Properties retrieved from database.
    [string[]]$Properties
)
    if ($Properties) {
        $propertiesString = $Properties -join ', '
    } else {
        $propertiesString = '*'
    }

    if ($Filter) {
        $filterString = "WHERE $Filter"
    } else {
        $filterString = ''
    }
    
    $query = "SELECT $propertiesString FROM Computers $filterString"
    try {
        Get-SqlData -Query $query -Type ITPro.WMI.Obiekt
    } catch {
        Write-Error "Issue with reading data from database: $_"
    }
}


function Export-CimData {

<#
    .Synopsis
        Function that saves data from WMI to SQL table.
     
    .Description
        Function saves data from WMI to database. If database does not contain necessary columns, they will be added.
 
        Type column will use depends on input data:
        -- datetime (SQL) for datetime (NET)
        -- bigint (SQL) for *int*
        -- nvarchar(100) (SQL) for any other data
 
    .Example
        Get-CimData -ComputerName Test | Export-CimData
        Reads data from computer Test and saves them to database.
 
#>


[CmdletBinding(
    SupportsShouldProcess = $true,
    ConfirmImpact = 'Medium'
)]
param (
    [Parameter(ValueFromPipeline = $true)]
    [Alias('IO')]
    [PSObject]$InputObject
)

begin {

    $query = @{
        Query = @"
    SELECT Column_Name
    FROM information_schema.columns
    WHERE table_name='Computers'
"@

    }

    $addColumn = 'ALTER Table Computers Add Column {0} {1}'        
}

process {
    try {
        $columns = Get-SqlData @query | 
            Select-Object -ExpandProperty COLUMN_NAME
    } catch {
        Write-Error "Issue with retrieving columns list: $_"
    }

    if ( Get-SqlData -Query "SELECT Name FROM Computers WHERE Name='$($InputObject.Name)'" ) {
        if ($psCmdlet.ShouldProcess("$($InputObject.Name)", 'Replace data in table: Computers')) {
            try {
                Invoke-SqlCommand -TSQL "DELETE FROM Computers WHERE Name='$($InputObject.Name)'" 
            } catch {
                Write-Error "Issue with removing record: $($InputObject.Name)"
            }
        } else {
            return
        }
    } 

    $InputObject.Psobject.Properties | 
        Where-Object { @('Property','NoteProperty') -contains $_.MemberType } | 
        Foreach-Object -Begin {
            $names = $values = @()
        } -Process {
            $names += $_.Name

            if ($columns -notcontains $_.Name) {
                Write-Verbose "Column $($_.Name) is not in database, we need to add it first..."
                $type = switch -Regex ($_.TypeNameOfValue) {
                    int { 'bigint' }
                    datetime { 'datetime' }
                    default { 'nvarchar(100)' }
                }
                try {
                    Invoke-SqlCommand -TSQL ($addColumn -f $_.Name, $type)
                } catch {
                    Write-Error "Issue with adding column: $($_.Name)`n$_"    
                } 
            }
            $values += "'$($_.Value)'"
        } -End {
            try {
                Invoke-SqlCommand -TSQL @"
                    INSERT INTO Computers
                    ( $($names -join ', ') )
                    VALUES
                    ( $($values -join ', ') )
"@

            } catch {
                Write-Error "Issue with adding record $($InputObject.Name) to Computers table: $_"
            }
        }
}
}

#endregion

#region Aliases

New-Alias -Name gcd -Value Get-CimData
New-Alias -Name ipcd -Value Import-CimData
New-Alias -Name epcd -Value Export-CimData

#endregion

Export-ModuleMember -Function * -Alias *
# SIG # Begin signature block
# MIIfYQYJKoZIhvcNAQcCoIIfUjCCH04CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUE/HHHek1sME/q908Ct4I6Myg
# tTigghqTMIIGajCCBVKgAwIBAgIQA5/t7ct5W43tMgyJGfA2iTANBgkqhkiG9w0B
# AQUFADBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD
# VQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBBc3N1cmVk
# IElEIENBLTEwHhcNMTMwNTIxMDAwMDAwWhcNMTQwNjA0MDAwMDAwWjBHMQswCQYD
# VQQGEwJVUzERMA8GA1UEChMIRGlnaUNlcnQxJTAjBgNVBAMTHERpZ2lDZXJ0IFRp
# bWVzdGFtcCBSZXNwb25kZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQC6aUqBTW+lFBaqis1nvku/xmmPWBzgeegenVgmmNpc1Hyj+dsrjBI2w/z5ZAax
# u8KomAoXDeGV60C065ZtmL+mj3nPvIqSe22cGAZR2KUYUzIBJxlh6IRB38bw6Mr+
# d61f2J57jGBvhVxGvWvnD4DO5wPDfDHPt2VVxvvgmQjkc1r7l9rQTL60tsYPfyaS
# qbj8OO605DqkSNBM6qlGJ1vPkhGTnBan/tKtHyLFHqzBce+8StsBCUTfmBwtZ7qo
# igMzyVG19wJNCaRN/oBexddFw30IqgEzzDPYTzAW5P8iMi7rfjvw+R4y65Ul0vL+
# bVSEutXl1NHdG6+9WXuUhTABAgMBAAGjggM1MIIDMTAOBgNVHQ8BAf8EBAMCB4Aw
# DAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDCCAb8GA1UdIASC
# AbYwggGyMIIBoQYJYIZIAYb9bAcBMIIBkjAoBggrBgEFBQcCARYcaHR0cHM6Ly93
# d3cuZGlnaWNlcnQuY29tL0NQUzCCAWQGCCsGAQUFBwICMIIBVh6CAVIAQQBuAHkA
# IAB1AHMAZQAgAG8AZgAgAHQAaABpAHMAIABDAGUAcgB0AGkAZgBpAGMAYQB0AGUA
# IABjAG8AbgBzAHQAaQB0AHUAdABlAHMAIABhAGMAYwBlAHAAdABhAG4AYwBlACAA
# bwBmACAAdABoAGUAIABEAGkAZwBpAEMAZQByAHQAIABDAFAALwBDAFAAUwAgAGEA
# bgBkACAAdABoAGUAIABSAGUAbAB5AGkAbgBnACAAUABhAHIAdAB5ACAAQQBnAHIA
# ZQBlAG0AZQBuAHQAIAB3AGgAaQBjAGgAIABsAGkAbQBpAHQAIABsAGkAYQBiAGkA
# bABpAHQAeQAgAGEAbgBkACAAYQByAGUAIABpAG4AYwBvAHIAcABvAHIAYQB0AGUA
# ZAAgAGgAZQByAGUAaQBuACAAYgB5ACAAcgBlAGYAZQByAGUAbgBjAGUALjALBglg
# hkgBhv1sAxUwHwYDVR0jBBgwFoAUFQASKxOYspkH7R7for5XDStnAs0wHQYDVR0O
# BBYEFGMvyd95knu1I8q74aTuM37j4p36MH0GA1UdHwR2MHQwOKA2oDSGMmh0dHA6
# Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRENBLTEuY3JsMDig
# NqA0hjJodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURD
# QS0xLmNybDB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3Nw
# LmRpZ2ljZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNl
# cnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEQ0EtMS5jcnQwDQYJKoZIhvcNAQEFBQAD
# ggEBAKt0vUAATHYVJVc90xwD/31FyEUSZucoZWDY3zuz+g3BrDOP9IG5YfGd+5hV
# 195HQ7qAPfFIzD9nMFYfzvTQTIS9h6SexeEPqAZd0C9uXtwZ6PCH6uBOrz1sII5z
# b37WhxjghtOa/J7qjHLpQQ+4cbU4LPgpstUcop0b7F8quNw3IOHLu/DQbGyls8uf
# SvZU4yY0PS64wSsct/bDPf7RLR5Q9JTI+P3uc9tJtRv09f+lkME5FBvY7XEbapj7
# +kCaRKkpDlVeeLi3pIPDcAHwZkDlrnk04StNA6Et5ttUYhjt1QmLoqrWDMhPGr6Z
# JXhpmYnUWYne34jw02dedKWdpkQwggajMIIFi6ADAgECAhAPqEkGFdcAoL4hdv3F
# 7G29MA0GCSqGSIb3DQEBBQUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdp
# Q2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0Rp
# Z2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xMTAyMTExMjAwMDBaFw0yNjAy
# MTAxMjAwMDBaMG8xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
# GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xLjAsBgNVBAMTJURpZ2lDZXJ0IEFz
# c3VyZWQgSUQgQ29kZSBTaWduaW5nIENBLTEwggEiMA0GCSqGSIb3DQEBAQUAA4IB
# DwAwggEKAoIBAQCcfPmgjwrKiUtTmjzsGSJ/DMv3SETQPyJumk/6zt/G0ySR/6hS
# k+dy+PFGhpTFqxf0eH/Ler6QJhx8Uy/lg+e7agUozKAXEUsYIPO3vfLcy7iGQEUf
# T/k5mNM7629ppFwBLrFm6aa43Abero1i/kQngqkDw/7mJguTSXHlOG1O/oBcZ3e1
# 1W9mZJRru4hJaNjR9H4hwebFHsnglrgJlflLnq7MMb1qWkKnxAVHfWAr2aFdvftW
# k+8b/HL53z4y/d0qLDJG2l5jvNC4y0wQNfxQX6xDRHz+hERQtIwqPXQM9HqLckvg
# VrUTtmPpP05JI+cGFvAlqwH4KEHmx9RkO12rAgMBAAGjggNDMIIDPzAOBgNVHQ8B
# Af8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwggHDBgNVHSAEggG6MIIBtjCC
# AbIGCGCGSAGG/WwDMIIBpDA6BggrBgEFBQcCARYuaHR0cDovL3d3dy5kaWdpY2Vy
# dC5jb20vc3NsLWNwcy1yZXBvc2l0b3J5Lmh0bTCCAWQGCCsGAQUFBwICMIIBVh6C
# AVIAQQBuAHkAIAB1AHMAZQAgAG8AZgAgAHQAaABpAHMAIABDAGUAcgB0AGkAZgBp
# AGMAYQB0AGUAIABjAG8AbgBzAHQAaQB0AHUAdABlAHMAIABhAGMAYwBlAHAAdABh
# AG4AYwBlACAAbwBmACAAdABoAGUAIABEAGkAZwBpAEMAZQByAHQAIABDAFAALwBD
# AFAAUwAgAGEAbgBkACAAdABoAGUAIABSAGUAbAB5AGkAbgBnACAAUABhAHIAdAB5
# ACAAQQBnAHIAZQBlAG0AZQBuAHQAIAB3AGgAaQBjAGgAIABsAGkAbQBpAHQAIABs
# AGkAYQBiAGkAbABpAHQAeQAgAGEAbgBkACAAYQByAGUAIABpAG4AYwBvAHIAcABv
# AHIAYQB0AGUAZAAgAGgAZQByAGUAaQBuACAAYgB5ACAAcgBlAGYAZQByAGUAbgBj
# AGUALjASBgNVHRMBAf8ECDAGAQH/AgEAMHkGCCsGAQUFBwEBBG0wazAkBggrBgEF
# BQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUFBzAChjdodHRw
# Oi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0Eu
# Y3J0MIGBBgNVHR8EejB4MDqgOKA2hjRodHRwOi8vY3JsMy5kaWdpY2VydC5jb20v
# RGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMDqgOKA2hjRodHRwOi8vY3JsNC5k
# aWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMB0GA1UdDgQW
# BBR7aM4pqsAXvkl64eU/1qf3RY81MjAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun
# pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAe3IdZP+IyDrBt+nnqcSHu9uUkteQ
# WTP6K4feqFuAJT8Tj5uDG3xDxOaM3zk+wxXssNo7ISV7JMFyXbhHkYETRvqcP2pR
# ON60Jcvwq9/FKAFUeRBGJNE4DyahYZBNur0o5j/xxKqb9to1U0/J8j3TbNwj7aqg
# TWcJ8zqAPTz7NkyQ53ak3fI6v1Y1L6JMZejg1NrRx8iRai0jTzc7GZQY1NWcEDzV
# sRwZ/4/Ia5ue+K6cmZZ40c2cURVbQiZyWo0KSiOSQOiG3iLCkzrUm2im3yl/Brk8
# Dr2fxIacgkdCcTKGCZlyCXlLnXFp9UH/fzl3ZPGEjb6LHrJ9aKOlkLEM/zCCBqkw
# ggWRoAMCAQICEAd+v5PimrXIy2OpNVvw4rEwDQYJKoZIhvcNAQEFBQAwbzELMAkG
# A1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRp
# Z2ljZXJ0LmNvbTEuMCwGA1UEAxMlRGlnaUNlcnQgQXNzdXJlZCBJRCBDb2RlIFNp
# Z25pbmcgQ0EtMTAeFw0xNDAzMDYwMDAwMDBaFw0xNTAyMjUxMjAwMDBaMHUxCzAJ
# BgNVBAYTAlBMMRswGQYDVQQIExJaYWNob2RuaW9wb21vcnNraWUxETAPBgNVBAcT
# CEtvc3phbGluMRowGAYDVQQKExFCYXJ0b3N6IEJpZWxhd3NraTEaMBgGA1UEAxMR
# QmFydG9zeiBCaWVsYXdza2kwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQCXWsOTV3sfxKH6LQ35wtn15F/bL4MffaYyYReMlbRxQr4dHcCISP80k75lOq6X
# TY4GzCBOUrYrLzBYdK0MqdH5Gvt0Wg/RO5qHG615E+M1pFHcyJrJ7be2yDXuUnek
# DUak5SBAu6YAxmXkstYs0efS5fOm7v2WPAlneRwe5vk/RaXOHXHmcFIygpCbk5/D
# 9xr2dDXSRhvEjR7Gu8fqP/4U5HpVoP+SJHkCt9l+dXXDo3AWpExENZBTEzeMaIfp
# Hb1iVJocet+Swerx54hiJ0McJeRu9gysvl1HQ6HSBvwSLWPb57qd76KHMYAHu2Sr
# epgCZj+BimRPFI063EsjcJCFAgMBAAGjggM5MIIDNTAfBgNVHSMEGDAWgBR7aM4p
# qsAXvkl64eU/1qf3RY81MjAdBgNVHQ4EFgQUiN2MhL1ZJLwqbtaD/rI5klOxyDww
# DgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHMGA1UdHwRsMGow
# M6AxoC+GLWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9hc3N1cmVkLWNzLTIwMTFh
# LmNybDAzoDGgL4YtaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL2Fzc3VyZWQtY3Mt
# MjAxMWEuY3JsMIIBxAYDVR0gBIIBuzCCAbcwggGzBglghkgBhv1sAwEwggGkMDoG
# CCsGAQUFBwIBFi5odHRwOi8vd3d3LmRpZ2ljZXJ0LmNvbS9zc2wtY3BzLXJlcG9z
# aXRvcnkuaHRtMIIBZAYIKwYBBQUHAgIwggFWHoIBUgBBAG4AeQAgAHUAcwBlACAA
# bwBmACAAdABoAGkAcwAgAEMAZQByAHQAaQBmAGkAYwBhAHQAZQAgAGMAbwBuAHMA
# dABpAHQAdQB0AGUAcwAgAGEAYwBjAGUAcAB0AGEAbgBjAGUAIABvAGYAIAB0AGgA
# ZQAgAEQAaQBnAGkAQwBlAHIAdAAgAEMAUAAvAEMAUABTACAAYQBuAGQAIAB0AGgA
# ZQAgAFIAZQBsAHkAaQBuAGcAIABQAGEAcgB0AHkAIABBAGcAcgBlAGUAbQBlAG4A
# dAAgAHcAaABpAGMAaAAgAGwAaQBtAGkAdAAgAGwAaQBhAGIAaQBsAGkAdAB5ACAA
# YQBuAGQAIABhAHIAZQAgAGkAbgBjAG8AcgBwAG8AcgBhAHQAZQBkACAAaABlAHIA
# ZQBpAG4AIABiAHkAIAByAGUAZgBlAHIAZQBuAGMAZQAuMIGCBggrBgEFBQcBAQR2
# MHQwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBMBggrBgEF
# BQcwAoZAaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJl
# ZElEQ29kZVNpZ25pbmdDQS0xLmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEB
# BQUAA4IBAQAAJXpB/DUxvJUxZjJ2obIU43JIBO2V5RbfMJiXAGPwQqjRXi0PpYxT
# cnFAB2ccaZ7tNFdx1Hk2armq6pjzScARlqrFDJSbVDfej/9lhyxux9QgKJvXviHg
# FFQ3y6VgJObuGSNOynDYkni26TZPPx8ZfWzITls0Ax1b2029BpRgyHRTEhIvvOwT
# Ic+qVWMbSeaOEYR2U7bB0QuBgNWtZK/utQ25VA91IcELj2NTbf/sNMmZBqXvnX6E
# 8ie37Ipl5fEtTq90a/9fz8WdbCmE/IKjEoGAckq5gbjelBG0rOzEQFewlBEXiLZ4
# sK8vJTeKhZNH/IfrdunRqAEzUlFIfrplMIIGzTCCBbWgAwIBAgIQBv35A5YDreoA
# Cus/J7u6GzANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMM
# RGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQD
# ExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcN
# MjExMTEwMDAwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQg
# SW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2Vy
# dCBBc3N1cmVkIElEIENBLTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDogi2Z+crCQpWlgHNAcNKeVlRcqcTSQQaPyTP8TUWRXIGf7Syc+BZZ3561JBXC
# mLm0d0ncicQK2q/LXmvtrbBxMevPOkAMRk2T7It6NggDqww0/hhJgv7HxzFIgHwe
# og+SDlDJxofrNj/YMMP/pvf7os1vcyP+rFYFkPAyIRaJxnCI+QWXfaPHQ90C6Ds9
# 7bFBo+0/vtuVSMTuHrPyvAwrmdDGXRJCgeGDboJzPyZLFJCuWWYKxI2+0s4Grq2E
# b0iEm09AufFM8q+Y+/bOQF1c9qjxL6/siSLyaxhlscFzrdfx2M8eCnRcQrhofrfV
# dwonVnwPYqQ/MhRglf0HBKIJAgMBAAGjggN6MIIDdjAOBgNVHQ8BAf8EBAMCAYYw
# OwYDVR0lBDQwMgYIKwYBBQUHAwEGCCsGAQUFBwMCBggrBgEFBQcDAwYIKwYBBQUH
# AwQGCCsGAQUFBwMIMIIB0gYDVR0gBIIByTCCAcUwggG0BgpghkgBhv1sAAEEMIIB
# pDA6BggrBgEFBQcCARYuaHR0cDovL3d3dy5kaWdpY2VydC5jb20vc3NsLWNwcy1y
# ZXBvc2l0b3J5Lmh0bTCCAWQGCCsGAQUFBwICMIIBVh6CAVIAQQBuAHkAIAB1AHMA
# ZQAgAG8AZgAgAHQAaABpAHMAIABDAGUAcgB0AGkAZgBpAGMAYQB0AGUAIABjAG8A
# bgBzAHQAaQB0AHUAdABlAHMAIABhAGMAYwBlAHAAdABhAG4AYwBlACAAbwBmACAA
# dABoAGUAIABEAGkAZwBpAEMAZQByAHQAIABDAFAALwBDAFAAUwAgAGEAbgBkACAA
# dABoAGUAIABSAGUAbAB5AGkAbgBnACAAUABhAHIAdAB5ACAAQQBnAHIAZQBlAG0A
# ZQBuAHQAIAB3AGgAaQBjAGgAIABsAGkAbQBpAHQAIABsAGkAYQBiAGkAbABpAHQA
# eQAgAGEAbgBkACAAYQByAGUAIABpAG4AYwBvAHIAcABvAHIAYQB0AGUAZAAgAGgA
# ZQByAGUAaQBuACAAYgB5ACAAcgBlAGYAZQByAGUAbgBjAGUALjALBglghkgBhv1s
# AxUwEgYDVR0TAQH/BAgwBgEB/wIBADB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUH
# MAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDov
# L2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNy
# dDCBgQYDVR0fBHoweDA6oDigNoY0aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0Rp
# Z2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDA6oDigNoY0aHR0cDovL2NybDQuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDAdBgNVHQ4EFgQU
# FQASKxOYspkH7R7for5XDStnAs0wHwYDVR0jBBgwFoAUReuir/SSy4IxLVGLp6ch
# nfNtyA8wDQYJKoZIhvcNAQEFBQADggEBAEZQPsm3KCSnOB22WymvUs9S6TFHq1Zc
# e9UNC0Gz7+x1H3Q48rJcYaKclcNQ5IK5I9G6OoZyrTh4rHVdFxc0ckeFlFbR67s2
# hHfMJKXzBBlVqefj56tizfuLLZDCwNK1lL1eT7EF0g49GqkUW6aGMWKoqDPkmzmn
# xPXOHXh2lCVz5Cqrz5x2S+1fwksW5EtwTACJHvzFebxMElf+X+EevAJdqP77BzhP
# DcZdkbkPZ0XN1oPt55INjbFpjE/7WeAjD9KqrgB87pxCDs+R1ye3Fu4Pw718CqDu
# LAhVhSK46xgaTfwqIa1JMYNHlXdx3LEbS0scEJx3FMGdTy9alQgpECYxggQ4MIIE
# NAIBATCBgzBvMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkw
# FwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMS4wLAYDVQQDEyVEaWdpQ2VydCBBc3N1
# cmVkIElEIENvZGUgU2lnbmluZyBDQS0xAhAHfr+T4pq1yMtjqTVb8OKxMAkGBSsO
# AwIaBQCgeDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEM
# BgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMCMGCSqG
# SIb3DQEJBDEWBBSWbfurHaNtQ1RmlRYSTNTJ0i4XczANBgkqhkiG9w0BAQEFAASC
# AQAnj4oULnPSKgMpqVpLRKwUS8B0PN9wwfSMYgKsIlAUbM6WIw6qukGonHI9i9Av
# 43CLLBtmsTZMRXRxrHpcXmfk7SHbcV24MRHWKLzQDPlkrjsLcUYFx2dnME6mIfJ9
# dkrUvKp+ixAtdLyfm+vYDPY9nTOl4jEGLQ2tqZC44Sj6g57pYZe2MKImMstDljcW
# jedwD0pwDLpg6qeVpn2wnLyH9h4l8Xi8OOAroRCyv4ghaxet9qkRRO1HtmEYt50Y
# 5W4iSkedN/STP7ySg5R+HbSHbQMlRde9SyUeShnlH21B5YyUJYafGuJDXjcnTMqj
# UQnG8bfxcANbCpb4MGDJAYl5oYICDzCCAgsGCSqGSIb3DQEJBjGCAfwwggH4AgEB
# MHYwYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UE
# CxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgQXNzdXJlZCBJ
# RCBDQS0xAhADn+3ty3lbje0yDIkZ8DaJMAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0B
# CQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xNDA1MTQyMjQxMzlaMCMG
# CSqGSIb3DQEJBDEWBBTSbUkyBqnDmUcTedDM19Gt/2WYtjANBgkqhkiG9w0BAQEF
# AASCAQADKat30gRIMi9D0HKkkTqgdZ3GvAydJQ6DNUZpckEkIGWVEnzSQP+2TkDa
# EO0katB/7Tqa9H88Vvu6CqGzWdjjVUFldbvo8gtcBnzsueJzrYnbyXYbv4HIQIGQ
# NKvO5D645KNLipFIYBxv6V03gSgTtIqsMY16EWJrEeJQnHdJsqYreI8xBArs0bJd
# YDFPZaywr9DZ+jWt65xlE//K1zscX8/m02oor94glSodl0NSpro3B5hcNcJpsft9
# 6cqwTrzmOGCqaYTlGfCOgP8IANlqkt5H4EQSg/5icG35jzdGNU0lcaLsPTA3pr2Z
# 8NgQV6OYvsjFiiULvpJhmB95wq6J
# SIG # End signature block