AzurePSDrive.psm1

using namespace Microsoft.PowerShell.SHiPS
using module .\AzurePSDriveResource.psm1
using module .\AzurePSDriveStorageAccount.psm1
using module .\AzurePSDriveVM.psm1
using module .\AzurePSDriveWebApp.psm1

$script:AzureRM_Profile = if($IsCoreCLR){'AzureRM.Profile.NetCore'}else{'AzureRM.Profile'}
$script:AzureRM_Resources = if($IsCoreCLR){'AzureRM.Resources.Netcore'}else{'AzureRM.Resources'}
$script:pathPattern = [System.IO.Path]::Combine('Azure:', '*', 'ResourceGroups', '*')
$script:pathSeparator = if([System.IO.Path]::DirectorySeparatorChar -eq '\'){'\\'}else{'/'}

# Ensure Session is logged-on to access Azure resources
$context = (& "$script:AzureRM_Profile\Get-AzureRmContext")
if ([string]::IsNullOrEmpty($($context.Account)))
{
    throw "Ensure that session has access to Azure resources - use $script:AzureRM_Profile\Add-AzureRMAccount or $script:AzureRM_Profile\Login-AzureRMAccount"
}

# Automatically pick resource group when inside resourcegroups of Azure drive
$Global:PSDefaultParameterValues['*-AzureRM*:ResourceGroupName'] = {if($pwd -like $script:pathPattern){($pwd -split $script:pathSeparator)[3]}}

[SHiPSProvider(UseCache=$true)]
class Azure : SHiPSDirectory
{
    Azure([string]$name): base($name)
    {        
    } 

    [object[]] GetChildItem()
    {
        $obj =  @()  
        
        $defaultTenantId = $null      
        
        # Cloud Shell provides us with a default directory for Azure -> that maps to a tenant
        # This is provided in the form of tenantId in env variable ACC_TID
        if (-not $env:ACC_TID)
        {
            # Default tenantId not provided (perhaps provider is being run standalone => not in Cloud Shell)
            $tenant = (& "$script:AzureRM_Profile\Get-AzureRmTenant")

            if (($tenant -eq $null) -or ($tenant.Count -eq 0))
            {
                throw ('Unable to obtain tenant for the account. Check your subscription to ensure there is at least one tenant')
            }

            # Use the first tenant, since this maps to the default directory chosen by the user via Portal
            $defaultTenantId = $tenant[0].Id
            Write-Verbose "Using TenantId '$($tenant[0].TenantId)'"                
            Write-Verbose "To change default tenant: Use AzureRM.profile\Get-AzureRmTenant to retrieve your tenants corresponding to directories and set environment variable 'ACC_TID' to desired tenant"
            Write-Verbose "Reload AzurePSDrive provider OR use 'dir -Force' when navigating the subscription"
        }
        else
        {
            Write-Verbose "Using TenantId '$($env:ACC_TID)' from 'ACC_TID' environment variable..."
            $defaultTenantId = $env:ACC_TID
        }
        
        $subscriptions = $((& "$script:AzureRM_Profile\Get-AzureRmSubscription" -TenantId $defaultTenantId) | Sort-Object -Property Name)
        $subGroup = $subscriptions | Group-Object -Property Name

        foreach ($subscription in $subscriptions)
        {         
            
            $obj += [Subscription]::new($subscription.Name, $subscription.Name, $subscription.Id, $subscription.TenantId, $subscription.State)
            
        }

        return $obj;
    }
}

[SHiPSProvider(UseCache=$true)]
class Subscription : SHiPSDirectory
{
    [string]$SubscriptionName = $null
    [string]$SubscriptionId = $null
    [string]$TenantId = $null
    [string]$State = $null
            
    Subscription ([string]$subNameWithTenantId, [string]$subName, [string]$subId, [string]$tenantId, [string]$state) : base ($subNameWithTenantId)
    {
        $this.SubscriptionName = $subName
        $this.SubscriptionId = $subId
        $this.TenantId = $tenantId
        $this.State = $state
    }    

    [object[]] GetChildItem()
    {        
        & "$script:AzureRM_Profile\Select-AzureRmSubscription" -SubscriptionName $this.SubscriptionName -TenantId $this.TenantId
        $obj =  @()

        $obj+=[AllResources]::new();
        $obj+=[ResourceGroups]::new("ResourceGroups", $this.SubscriptionName, $this.SubscriptionId, $this.TenantId, $this.State)
        $obj+=[StorageAccounts]::new();
        $obj+=[VirtualMachines]::new();
        $obj+=[WebApps]::new();

        return $obj;
    }
 }
[SHiPSProvider(UseCache=$true)]
class ResourceGroups : SHiPSDirectory
{
    [string]$SubscriptionName = $null
    [string]$SubscriptionId = $null
    [string]$TenantId = $null
    [string]$State = $null
    [object[]]$rgs

    ResourceGroups ([string]$name, [string]$subName, [string]$subId, [string]$tenantId, [string]$state) : base ($name)
    {
        $this.SubscriptionName = $subName
        $this.SubscriptionId = $subId
        $this.TenantId = $tenantId
        $this.State = $state
    }


    [object[]] GetChildItem()
    {
        #AzureRM.profile\Select-AzureRmSubscription -SubscriptionName $this.SubscriptionName -TenantId $this.TenantId
        $obj =  @()
        $subId = $this.SubscriptionId

        @(& "$script:AzureRM_Resources\Get-AzureRmResourceGroup").Foreach{
             
            $obj +=  [ResourceGroup]::new($subId, $_.ResourceGroupName, $_.Location, $_.ProvisioningState);                  
        }

        return $obj;
    }
 }

[SHiPSProvider(UseCache=$true)]
class ResourceGroup : SHiPSDirectory
{
    [string]$SubscriptionId = $null
    [string]$ResourceGroupName = $null
    [string]$Location = $null
    [string]$ProvisioningState = $null
        
    ResourceGroup ([string]$subscriptionId, [string]$name, [string]$location, [string]$provisioningState) : base ($name) 
    {
        $this.SubscriptionId = $subscriptionId
        $this.ResourceGroupName = $name
        $this.Location = $location
        $this.ProvisioningState = $provisioningState
    }

    [object[]] GetChildItem()
    {        
        $obj =  @()

        $resourceTypes = @(& "$script:AzureRM_Resources\Get-AzureRmResource" | Where-Object {$_.ResourceGroupName -eq $this.ResourceGroupName}  | select-Object -Property ResourceType -Unique).ForEach{$_.ResourceType.Split('/')[0]} | Select-Object -Unique
        foreach ($resourceType in $resourceTypes)
        {            
            $tempObj = [ResourceProvider]::new($resourceType, $this.ResourceGroupName);            
            
            $obj +=  $tempObj
        }
        
        return $obj; 
    }
 }

[SHiPSProvider(UseCache=$true)]
class ResourceProvider : SHiPSDirectory
{
    [string]$providerNamespace = $null
    [string]$resourceGroupName = $null

    ResourceProvider([string]$name): base($name)
    {
    }

    ResourceProvider ([string]$name, [string]$resourceGroupName) : base ($name) 
    {
        $this.providerNamespace = $name
        $this.resourceGroupName = $resourceGroupName
    }

    [object[]] GetChildItem()
    {        
        $obj =  @()

        $resourceTypeTokens = @()
        @(& "$script:AzureRM_Resources\Get-AzureRmResource" | Where-Object {$_.ResourceGroupName -eq $this.resourceGroupName} | Select-Object -Property ResourceType -Unique).ForEach{

            $providerNS = $_.ResourceType.Split('/')[0]
            $resourceType = $_.ResourceType.Substring($_.ResourceType.IndexOf('/')+1)
            $resourceType = $resourceType.Replace('/','-')
            if ($this.providerNamespace -eq $providerNS)
            {
                    $resourceTypeTokens += $resourceType
            }
        }

        foreach ($resourceTypeToken in ($resourceTypeTokens | Select-Object -Unique))
        {   
            $tempObj =  [ResourceType]::new($resourceTypeToken, $this.providerNamespace, $this.resourceGroupName);             

            $obj += $tempObj
        }        

        return $obj; 
    }
 }

[SHiPSProvider(UseCache=$true)]
class ResourceType : SHiPSDirectory
{
    [string]$resourceTypeName = $null
    [string]$resourceType = $null
    [string]$resourceGroupName = $null
    [string]$providerNamespace = $null
    [object]$Properties = $null

    ResourceType([string]$name): base($name)
    {
    }

    ResourceType ([string]$name, [string]$providerNamespace, [string]$resourceGroupName) : base ($name) 
    {   
        $this.resourceTypeName = $name
        $this.resourceGroupName = $resourceGroupName 
        $this.providerNamespace = $providerNamespace 
        $this.resourceType = $providerNamespace + '/' + $this.resourceTypeName.Replace('-', '/')
    }

    [object[]] GetChildItem()
    {        
        $obj =  @()

        $azureRMResourceParams = @{'ResourceGroupName'="$($this.resourceGroupName)"}
        $azureRMResourceParams += @{'ResourceType'="$($this.resourceType)"}
        $azureRMResourceParams += @{'ExpandProperties'=$true}

        if ($this.ProviderContext.Filter)
        {
            $azureRMResourceParams += @{'ODataQuery'=(Get-ODataQueryFilter -filter $this.ProviderContext.Filter)}
        }
        
        @(& "$script:AzureRM_Resources\Get-AzureRmResource" @azureRMResourceParams).Foreach{
                    
            if ($_.PSTypeNames.Contains('Microsoft.Network.networkSecurityGroups'))
            {   
                $typeName = $_.PSTypeNames[$_.PSTypeNames.IndexOf('Microsoft.Network.networkSecurityGroups')]

                foreach ($securityRule in $_.Properties.securityRules)
                {
                    $tempObj = $securityRule

                    1..2 | ForEach-Object {$tempObj.PSTypeNames.RemoveAt(0)}
                    $tempObj.PSTypeNames.Insert(0, $typeName + '.Rules')
                    
                    $obj += $tempObj
                }

                foreach ($defaultSecurityRule in $_.Properties.defaultSecurityRules)
                {
                    $tempObj = $defaultSecurityRule
                    
                    1..2 | ForEach-Object {$tempObj.PSTypeNames.RemoveAt(0)}            
                    $tempObj.PSTypeNames.Insert(0, $typeName + '.Rules')
                                        
                    $obj += $tempObj
                }
            }
            elseif ($_.PSTypeNames.Contains('Microsoft.Network.routeTables'))
            {                
                $typeName = $_.PSTypeNames[$_.PSTypeNames.IndexOf('Microsoft.Network.routeTables')]

                foreach ($route in $_.Properties.routes)
                {
                    $tempObj = $route

                    1..2 | ForEach-Object {$tempObj.PSTypeNames.RemoveAt(0)}
                    $tempObj.PSTypeNames.Insert(0, $typeName + '.routes')      

                    $obj += $tempObj
                }                

            }
            else

            {     
                $tempObj = $_
                1..2 | ForEach-Object {$tempObj.PSTypeNames.RemoveAt(0)}                                           
                $obj += $tempObj            
            }
        }
        
        return $obj; 

    }
 }

 #region Utilities

 # Given the filter string, return corresponding OData query filter in the format '$filter=<Name> <operator> <Value>'
 # Else, return null for invalid cases
 function Get-ODataQueryFilter
 {
    [OutputType([string])]
    param (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string] $filter
    )
    

    if ('*' -eq $filter)
    {
        return $null
    }

    if ($filter.Contains('*'))
    {
        if ($filter.StartsWith('*') -and $filter.EndsWith('*'))
        {
            return "`$filter=substringof(Name, $filter.Replace('*', '')) eq true"
        }
        elseif ($dynamicParameters.Filter.StartsWith('*') -and (-not $dynamicParameters.Filter.EndsWith('*')))
        {            
            return "`$filter=EndsWith(Name, $filter.Replace('*', ''))"
        }
        elseif ($dynamicParameters.Filter.EndsWith('*') -and (-not $dynamicParameters.Filter.StartsWith('*')))
        {            
            return "`$filter=StartsWith(Name, $filter.Replace('*', ''))"
        }
        else
        {
            $filterTokens = $filter.Split('*')
            return "`$filter=StartsWith(Name, $filterTokens[0])"
        }
    }

    return $null
 }

 #endregion

# SIG # Begin signature block
# MIIdhQYJKoZIhvcNAQcCoIIddjCCHXICAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUAl5s3+nsr1x2dfIZJ8TeBWdS
# 2vCgghhTMIIEwTCCA6mgAwIBAgITMwAAANjkdflFb0j3rgAAAAAA2DANBgkqhkiG
# 9w0BAQUFADB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G
# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSEw
# HwYDVQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EwHhcNMTcxMDAyMjI1NzU3
# WhcNMTkwMTAyMjI1NzU3WjCBsTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEMMAoGA1UECxMDQU9DMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo3
# MERELTRCNUItNDU2ODElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# dmljZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMR2sf6C9y+kIdyj
# 16QouyVvlvPjzdmE1FFIAb7gUl7UCknd2nlquXeGJHQdX22zmY18QH/8EjH8voTu
# 57DqKJRFkqkD+PQ87+M4j2aW27QCHiHVATEHdHelT0ANUSoxETlAe4d6gd6sL/aA
# wkqFSqTncLfVeAenMJ7Te3tLmLYBk59CI/Tmf2YCsU+Z0nQ0S0AH6IHAKbDLLUPZ
# 1KW4d5Mmig1YMInsaoDHJvmuXyUZ6GxluZ7GX+WxF2XoxFRuMo6OWrCER3gnx/W3
# omzHOc1/C/oBI8hELBQH8uTJYqI8iGx8yqDyYETHoZNdH0oFeIOfAvVFlUYTE3JW
# pbMtF0cCAwEAAaOCAQkwggEFMB0GA1UdDgQWBBSgHm6PHyXdykNng5Up+ne9UdYe
# VjAfBgNVHSMEGDAWgBQjNPjZUkZwCu1A+3b7syuwwzWzDzBUBgNVHR8ETTBLMEmg
# R6BFhkNodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9N
# aWNyb3NvZnRUaW1lU3RhbXBQQ0EuY3JsMFgGCCsGAQUFBwEBBEwwSjBIBggrBgEF
# BQcwAoY8aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNyb3Nv
# ZnRUaW1lU3RhbXBQQ0EuY3J0MBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0GCSqGSIb3
# DQEBBQUAA4IBAQCFDewTPdV6/bMFMK0kqgvI8Y7t1YvHrGmvpdA/Y2zx+ERd0g9d
# ENtHTlfAPC1X15YlDXZNPdo7LY6wMJco/rXcjzFZ/tIvGHcIaQE52tJKW+pmXfrv
# QWW4X3pQdbPTsCwcDSGPcDImnec0dathWPicWxBg1NIeSDDsdsqpESp0kSs9g9fL
# QWUi9wHlFehburgOJCWpQ1jkNspUvJ7xMmtTTEIu6WPEDGHU8LxHraClsL0/BzPN
# KE85uB3+5/yOurKU/V8kH/obxzB03XxI4QpbpU1D2yasOd7JVmCGEbHBRamtHVz6
# SnVVVviJUsoGV5/jdzHuXyUIy9LKSUwuTdykMIIGATCCA+mgAwIBAgITMwAAAMTp
# ifh6gVDp/wAAAAAAxDANBgkqhkiG9w0BAQsFADB+MQswCQYDVQQGEwJVUzETMBEG
# A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj
# cm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQgQ29kZSBTaWdu
# aW5nIFBDQSAyMDExMB4XDTE3MDgxMTIwMjAyNFoXDTE4MDgxMTIwMjAyNFowdDEL
# MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v
# bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEeMBwGA1UEAxMVTWlj
# cm9zb2Z0IENvcnBvcmF0aW9uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
# AQEAiIq4JMMHj5qAeRX8JmD8cogs+vSjl4iWRrejy1+JLzozLh6RePp8qR+CAbV6
# yxq8A8pG68WZ9/sEHfKFCv8ibqHyZz3FJxjlKB/1BJRBY+zjuhWM7ROaNd44cFRv
# O+ytRQkwScG+jzCZDMt2yfdzlRZ30Yu7lMcIhSDtHqg18XHC4HQAS4rS3JHr1nj+
# jfqtYIg9vbkfrmKXv8WEsZCu1q8r01T7NdrNcZLmHv/scWvLfwh2dOAQUUjU8QDI
# SEyjBzXlWQ39fJzI5lrjhfXWmg8fjqbkhBfB1sqfHQHH/UinE5IzlyFIMvjCJKIA
# sr5TyoNuKVuB7zhugPO77BML6wIDAQABo4IBgDCCAXwwHwYDVR0lBBgwFgYKKwYB
# BAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0OBBYEFMvWYoTPYDnq/2fCXNLIu6u3wxOY
# MFIGA1UdEQRLMEmkRzBFMQ0wCwYDVQQLEwRNT1BSMTQwMgYDVQQFEysyMzAwMTIr
# YzgwNGI1ZWEtNDliNC00MjM4LTgzNjItZDg1MWZhMjI1NGZjMB8GA1UdIwQYMBaA
# FEhuZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93
# d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFf
# MjAxMS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEA
# BhYf21fCUMgjT6JReNft+P3NvdXA8fkbVu1TyGlHBdXEy+zi/JlblV8ROCjABUUT
# 4Jp5iLxmq9u76wJVI7c9I3hBba748QBalJmKHMwJldCaHEQwqaUWx7pHW/UrNIuf
# j1g3w04cryLKEM3YghCpNfCuIsiPJKaBi98nHORmHYk+Lv9XA03BboOgMuu0sy9Q
# Vl0GsRWMyB1jt3MM49Z6Jg8qlkWnMoM+lj5XSXcjif6xEMeK5QgVUcUrWjFbOWqW
# qKSIa5Yob/HEruq9RRfMYk6BtVQaR46YpW3AbifG+CcfyO0gqQux8c4LmpTiap1p
# g6E2120g/oXV/8O4lzYJ/j0UwZgUqcCGzO+CwatVJEMYtUiFeIbQ+dKdPxnZFInn
# jZ9oJIhoO6nHgE4m5wghTGP9nJMVTTO1VmBP10q5OI7/Lt2xX6RDa8l4z7G7a4+D
# bIdyquql+5/dGtY5/GTJbT4I5XyDsa28o7p7z5ZWpHpYyxJHYtIh7/w8xDEL9y8+
# ZKU3b2BQP7dEkE+gC4u+flj2x2eHYduemMTIjMtvR+HALpTtsfawMG3sakmo6ZZ2
# yL0IxP479a5zNwayVs8Z1Lv1lMqHHPKAagFPthuBc7PTWyI/OlgY34juZ8RJpy/c
# JYs9XtDsNESRHbyRDHaCPu/E2C2hBAKOSPnv3QLPA6IwggYHMIID76ADAgECAgph
# Fmg0AAAAAAAcMA0GCSqGSIb3DQEBBQUAMF8xEzARBgoJkiaJk/IsZAEZFgNjb20x
# GTAXBgoJkiaJk/IsZAEZFgltaWNyb3NvZnQxLTArBgNVBAMTJE1pY3Jvc29mdCBS
# b290IENlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0wNzA0MDMxMjUzMDlaFw0yMTA0
# MDMxMzAzMDlaMHcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# ITAfBgNVBAMTGE1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQTCCASIwDQYJKoZIhvcN
# AQEBBQADggEPADCCAQoCggEBAJ+hbLHf20iSKnxrLhnhveLjxZlRI1Ctzt0YTiQP
# 7tGn0UytdDAgEesH1VSVFUmUG0KSrphcMCbaAGvoe73siQcP9w4EmPCJzB/LMySH
# nfL0Zxws/HvniB3q506jocEjU8qN+kXPCdBer9CwQgSi+aZsk2fXKNxGU7CG0OUo
# Ri4nrIZPVVIM5AMs+2qQkDBuh/NZMJ36ftaXs+ghl3740hPzCLdTbVK0RZCfSABK
# R2YRJylmqJfk0waBSqL5hKcRRxQJgp+E7VV4/gGaHVAIhQAQMEbtt94jRrvELVSf
# rx54QTF3zJvfO4OToWECtR0Nsfz3m7IBziJLVP/5BcPCIAsCAwEAAaOCAaswggGn
# MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFCM0+NlSRnAK7UD7dvuzK7DDNbMP
# MAsGA1UdDwQEAwIBhjAQBgkrBgEEAYI3FQEEAwIBADCBmAYDVR0jBIGQMIGNgBQO
# rIJgQFYnl+UlE/wq4QpTlVnkpKFjpGEwXzETMBEGCgmSJomT8ixkARkWA2NvbTEZ
# MBcGCgmSJomT8ixkARkWCW1pY3Jvc29mdDEtMCsGA1UEAxMkTWljcm9zb2Z0IFJv
# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5ghB5rRahSqClrUxzWPQHEy5lMFAGA1Ud
# HwRJMEcwRaBDoEGGP2h0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3By
# b2R1Y3RzL21pY3Jvc29mdHJvb3RjZXJ0LmNybDBUBggrBgEFBQcBAQRIMEYwRAYI
# KwYBBQUHMAKGOGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWlj
# cm9zb2Z0Um9vdENlcnQuY3J0MBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0GCSqGSIb3
# DQEBBQUAA4ICAQAQl4rDXANENt3ptK132855UU0BsS50cVttDBOrzr57j7gu1BKi
# jG1iuFcCy04gE1CZ3XpA4le7r1iaHOEdAYasu3jyi9DsOwHu4r6PCgXIjUji8FMV
# 3U+rkuTnjWrVgMHmlPIGL4UD6ZEqJCJw+/b85HiZLg33B+JwvBhOnY5rCnKVuKE5
# nGctxVEO6mJcPxaYiyA/4gcaMvnMMUp2MT0rcgvI6nA9/4UKE9/CCmGO8Ne4F+tO
# i3/FNSteo7/rvH0LQnvUU3Ih7jDKu3hlXFsBFwoUDtLaFJj1PLlmWLMtL+f5hYbM
# UVbonXCUbKw5TNT2eb+qGHpiKe+imyk0BncaYsk9Hm0fgvALxyy7z0Oz5fnsfbXj
# pKh0NbhOxXEjEiZ2CzxSjHFaRkMUvLOzsE1nyJ9C/4B5IYCeFTBm6EISXhrIniIh
# 0EPpK+m79EjMLNTYMoBMJipIJF9a6lbvpt6Znco6b72BJ3QGEe52Ib+bgsEnVLax
# aj2JoXZhtG6hE6a/qkfwEm/9ijJssv7fUciMI8lmvZ0dhxJkAj0tr1mPuOQh5bWw
# ymO0eFQF1EEuUKyUsKV4q7OglnUa2ZKHE3UiLzKoCG6gW4wlv6DvhMoh1useT8ma
# 7kng9wFlb4kLfchpyOZu6qeXzjEp/w7FW1zYTRuh2Povnj8uVRZryROj/TCCB3ow
# ggVioAMCAQICCmEOkNIAAAAAAAMwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYT
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBS
# b290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDExMB4XDTExMDcwODIwNTkwOVoX
# DTI2MDcwODIxMDkwOVowfjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0
# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
# dGlvbjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMTCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKvw+nIQHC6t2G6qghBNNLry
# tlghn0IbKmvpWlCquAY4GgRJun/DDB7dN2vGEtgL8DjCmQawyDnVARQxQtOJDXlk
# h36UYCRsr55JnOloXtLfm1OyCizDr9mpK656Ca/XllnKYBoF6WZ26DJSJhIv56sI
# UM+zRLdd2MQuA3WraPPLbfM6XKEW9Ea64DhkrG5kNXimoGMPLdNAk/jj3gcN1Vx5
# pUkp5w2+oBN3vpQ97/vjK1oQH01WKKJ6cuASOrdJXtjt7UORg9l7snuGG9k+sYxd
# 6IlPhBryoS9Z5JA7La4zWMW3Pv4y07MDPbGyr5I4ftKdgCz1TlaRITUlwzluZH9T
# upwPrRkjhMv0ugOGjfdf8NBSv4yUh7zAIXQlXxgotswnKDglmDlKNs98sZKuHCOn
# qWbsYR9q4ShJnV+I4iVd0yFLPlLEtVc/JAPw0XpbL9Uj43BdD1FGd7P4AOG8rAKC
# X9vAFbO9G9RVS+c5oQ/pI0m8GLhEfEXkwcNyeuBy5yTfv0aZxe/CHFfbg43sTUkw
# p6uO3+xbn6/83bBm4sGXgXvt1u1L50kppxMopqd9Z4DmimJ4X7IvhNdXnFy/dygo
# 8e1twyiPLI9AN0/B4YVEicQJTMXUpUMvdJX3bvh4IFgsE11glZo+TzOE2rCIF96e
# TvSWsLxGoGyY0uDWiIwLAgMBAAGjggHtMIIB6TAQBgkrBgEEAYI3FQEEAwIBADAd
# BgNVHQ4EFgQUSG5k5VAF04KqFzc3IrVtqMp1ApUwGQYJKwYBBAGCNxQCBAweCgBT
# AHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgw
# FoAUci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDov
# L2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0
# MjAxMV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAwTgYIKwYBBQUHMAKG
# Qmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0
# MjAxMV8yMDExXzAzXzIyLmNydDCBnwYDVR0gBIGXMIGUMIGRBgkrBgEEAYI3LgMw
# gYMwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv
# ZG9jcy9wcmltYXJ5Y3BzLmh0bTBABggrBgEFBQcCAjA0HjIgHQBMAGUAZwBhAGwA
# XwBwAG8AbABpAGMAeQBfAHMAdABhAHQAZQBtAGUAbgB0AC4gHTANBgkqhkiG9w0B
# AQsFAAOCAgEAZ/KGpZjgVHkaLtPYdGcimwuWEeFjkplCln3SeQyQwWVfLiw++MNy
# 0W2D/r4/6ArKO79HqaPzadtjvyI1pZddZYSQfYtGUFXYDJJ80hpLHPM8QotS0LD9
# a+M+By4pm+Y9G6XUtR13lDni6WTJRD14eiPzE32mkHSDjfTLJgJGKsKKELukqQUM
# m+1o+mgulaAqPyprWEljHwlpblqYluSD9MCP80Yr3vw70L01724lruWvJ+3Q3fMO
# r5kol5hNDj0L8giJ1h/DMhji8MUtzluetEk5CsYKwsatruWy2dsViFFFWDgycSca
# f7H0J/jeLDogaZiyWYlobm+nt3TDQAUGpgEqKD6CPxNNZgvAs0314Y9/HG8VfUWn
# duVAKmWjw11SYobDHWM2l4bf2vP48hahmifhzaWX0O5dY0HjWwechz4GdwbRBrF1
# HxS+YWG18NzGGwS+30HHDiju3mUv7Jf2oVyW2ADWoUa9WfOXpQlLSBCZgB/QACnF
# sZulP0V3HjXG0qKin3p6IvpIlR+r+0cjgPWe+L9rt0uX4ut1eBrs6jeZeRhL/9az
# I2h15q/6/IvrC4DqaTuv/DDtBEyO3991bWORPdGdVk5Pv4BXIqF4ETIheu9BCrE/
# +6jMpF3BoYibV3FWTkhFwELJm3ZbCoBIa/15n8G9bW1qyVJzEw16UM0xggScMIIE
# mAIBATCBlTB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G
# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgw
# JgYDVQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExAhMzAAAAxOmJ
# +HqBUOn/AAAAAADEMAkGBSsOAwIaBQCggbAwGQYJKoZIhvcNAQkDMQwGCisGAQQB
# gjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkE
# MRYEFOqsmbeFLnG8gSHUv3StJaSr+w18MFAGCisGAQQBgjcCAQwxQjBAoBaAFABQ
# AG8AdwBlAHIAUwBoAGUAbABsoSaAJGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9Q
# b3dlclNoZWxsIDANBgkqhkiG9w0BAQEFAASCAQBMFb/66IYRSKc6v6OzH1CyV6LJ
# 9e66BwBJrMKRH5ZRNyI6U1DqGRVZdoFCtYJ4okaVAYDX8n7eQwsQaiv7COk/Xcuy
# nlN9N46gT4WDJtwl197LcPO2M4rhd4kI9OCR3Z7M4FLdrNDkE5vX4p+OkymO7xUk
# O4qk9lXiCqMcGTilAhgBbmXUTkm3D5BPez9uQf/9PvpvO891BPfdOHYiU5XJl6Jq
# bVtecdny4jdT/l5ij/9nTTS34bnFPDZ2oJeR8Qvb/px5LkGVgZgQRQZY5w2N+m1R
# qKOkJJf3qne9tWk3YNmJuXuqllwB9HZImTRHbS2IztqvgGApa45NIY4bskOFoYIC
# KDCCAiQGCSqGSIb3DQEJBjGCAhUwggIRAgEBMIGOMHcxCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xITAfBgNVBAMTGE1pY3Jvc29mdCBUaW1lLVN0
# YW1wIFBDQQITMwAAANjkdflFb0j3rgAAAAAA2DAJBgUrDgMCGgUAoF0wGAYJKoZI
# hvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTgwNDMwMjE1OTU4
# WjAjBgkqhkiG9w0BCQQxFgQUI2R70wvfZAUrggTqkYT37Ycm0P0wDQYJKoZIhvcN
# AQEFBQAEggEAnt29VA3l60h+3uaNvod2yt5yxU8D/jnloebisOSXvMuLwoz3vxcE
# WtUvI4sc/OXCv8oqVGRsJgIIQcj5J0RFDSIt4fEFhMgMKuZXpmjgXHF9sThgSBG8
# 4SxgXpxtR76V0EKVXj1EAmcvp+yMMZOsKsn7wDjxmFPZ65Vi4v3DQ1kGf9LJ4NDZ
# rfsWk2FcdWitD8RFXLIBXfcTLB37Rs3tWHEDANlK3pph+hKQwdF3xo6ZmYejy7At
# 51182jAE/YuMxA3WxxzA4rVYuFhrjSQn97nOc6DKeryrwCUVdtzuEkQF+v7ThDGY
# EDhcrT2ZWSwavCxNRP6A7kz+5mc37HI1yw==
# SIG # End signature block