PSRule.Rules.Azure.psm1

# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

#
# PSRule.Rules.Azure module
#

$m = Import-Module 'Az.Resources' -MinimumVersion 5.6.0 -Global -ErrorAction SilentlyContinue -PassThru;
if ($Null -eq $m) {
    Write-Warning -Message "To use PSRule for Azure export cmdlets please install Az.Resources.";
}

Set-StrictMode -Version latest;

[PSRule.Rules.Azure.Configuration.PSRuleOption]::UseExecutionContext($ExecutionContext);

#
# Localization
#

#
# Public functions
#

#region Public functions

# .ExternalHelp PSRule.Rules.Azure-help.xml
function Export-AzRuleData {
    [CmdletBinding(SupportsShouldProcess = $True, DefaultParameterSetName = 'Default')]
    [OutputType([System.IO.FileInfo])]
    [OutputType([PSObject])]
    param (
        [Parameter(Position = 0, Mandatory = $False)]
        [String]$OutputPath = $PWD,

        # Filter by Subscription name or id
        [Parameter(Mandatory = $False, ParameterSetName = 'Default')]
        [String[]]$Subscription = $Null,

        # Filter by Tenant id
        [Parameter(Mandatory = $False, ParameterSetName = 'Default')]
        [String[]]$Tenant = $Null,

        # Filter by Resource Group name
        [Parameter(Mandatory = $False)]
        [String[]]$ResourceGroupName = $Null,

        # Filter by Tag
        [Parameter(Mandatory = $False)]
        [Hashtable]$Tag,

        [Parameter(Mandatory = $False)]
        [Switch]$PassThru = $False,

        [Parameter(Mandatory = $False, ParameterSetName = 'All')]
        [Switch]$All = $False
    )
    begin {
        Write-Verbose -Message "[Export-AzRuleData] BEGIN::";
    }
    process {
        # Get subscriptions
        $context = FindAzureContext -Subscription $Subscription -Tenant $Tenant -All:$All -Verbose:$VerbosePreference;

        if ($Null -eq $context) {
            return;
        }
        if (!(Test-Path -Path $OutputPath)) {
            if ($PSCmdlet.ShouldProcess('Create output directory', $OutputPath)) {
                $Null = New-Item -Path $OutputPath -ItemType Directory -Force;
            }
        }

        $getParams = @{ };
        $filterParams = @{ };

        if ($PSBoundParameters.ContainsKey('Tag')) {
            $getParams['Tag'] = $Tag;
        }
        if ($PSBoundParameters.ContainsKey('ResourceGroupName')) {
            $getParams['ResourceGroupName'] = $ResourceGroupName;
            $filterParams['ResourceGroupName'] = $ResourceGroupName;
        }

        foreach ($c in $context) {
            Write-Verbose -Message "[Export] -- Using subscription: $($c.Subscription.Name)";
            $filePath = Join-Path -Path $OutputPath -ChildPath "$($c.Subscription.Id).json";
            GetAzureResource @getParams -Context $c -Verbose:$VerbosePreference `
            | FilterAzureResource @filterParams -Verbose:$VerbosePreference `
            | ExportAzureResource -Path $filePath -PassThru $PassThru -Verbose:$VerbosePreference;
        }
    }
    end {
        Write-Verbose -Message "[Export-AzRuleData] END::";
    }
}

# .ExternalHelp PSRule.Rules.Azure-help.xml
function Export-AzRuleTemplateData {
    [CmdletBinding()]
    [OutputType([System.IO.FileInfo])]
    [OutputType([PSObject])]
    param (
        [Parameter(Position = 0, Mandatory = $False)]
        [String]$Name,

        [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)]
        [String]$TemplateFile,

        [Parameter(Mandatory = $False, ValueFromPipelineByPropertyName = $True)]
        [Alias('TemplateParameterFile')]
        [String[]]$ParameterFile,

        [Parameter(Mandatory = $False)]
        [Alias('ResourceGroupName')]
        [PSRule.Rules.Azure.Configuration.ResourceGroupReference]$ResourceGroup,

        [Parameter(Mandatory = $False)]
        [PSRule.Rules.Azure.Configuration.SubscriptionReference]$Subscription,

        [Parameter(Mandatory = $False)]
        [String]$OutputPath = $PWD,

        [Parameter(Mandatory = $False)]
        [Switch]$PassThru = $False
    )
    begin {
        Write-Verbose -Message '[Export-AzRuleTemplateData] BEGIN::';
        if ($MyInvocation.InvocationName -eq 'Export-AzTemplateRuleData') {
            Write-Warning -Message "The cmdlet 'Export-AzTemplateRuleData' is has been renamed to 'Export-AzRuleTemplateData'. Use of 'Export-AzTemplateRuleData' is deprecated and will be removed in the next major version."
        }

        $Option = [PSRule.Rules.Azure.Configuration.PSRuleOption]::FromFileOrDefault($PWD);
        $Option.Output.Path = $OutputPath;

        # Build the pipeline
        $builder = [PSRule.Rules.Azure.Pipeline.PipelineBuilder]::Template($Option);
        $builder.Deployment($Name);
        $builder.PassThru($PassThru);

        # Bind to subscription context
        if ($PSBoundParameters.ContainsKey('Subscription')) {
            $subscriptionOption = GetSubscription -InputObject $Subscription -ErrorAction SilentlyContinue;
            if ($Null -ne $subscriptionOption) {
                $builder.Subscription($subscriptionOption);
            }
        }
        # Bind to resource group
        if ($PSBoundParameters.ContainsKey('ResourceGroup')) {
            $resourceGroupOption = GetResourceGroup -InputObject $ResourceGroup -ErrorAction SilentlyContinue;
            if ($Null -ne $resourceGroupOption) {
                $builder.ResourceGroup($resourceGroupOption);
            }
        }

        $builder.UseCommandRuntime($PSCmdlet);
        $builder.UseExecutionContext($ExecutionContext);
        try {
            $pipeline = $builder.Build();
            $pipeline.Begin();
        }
        catch {
            $pipeline.Dispose();
        }
    }
    process {
        if ($Null -ne (Get-Variable -Name pipeline -ErrorAction SilentlyContinue)) {
            try {
                $source = [PSRule.Rules.Azure.Pipeline.TemplateSource]::new($TemplateFile, $ParameterFile);
                $pipeline.Process($source);
            }
            catch {
                $pipeline.Dispose();
                throw;
            }
        }
    }
    end {
        if ($Null -ne (Get-Variable -Name pipeline -ErrorAction SilentlyContinue)) {
            try {
                $pipeline.End();
            }
            finally {
                $pipeline.Dispose();
            }
        }
        Write-Verbose -Message '[Export-AzRuleTemplateData] END::';
    }
}

# .ExternalHelp PSRule.Rules.Azure-help.xml
function Get-AzRuleTemplateLink {
    [CmdletBinding()]
    [OutputType([PSRule.Rules.Azure.Data.Metadata.ITemplateLink])]
    param (
        [Parameter(Position = 1, Mandatory = $False, ValueFromPipelineByPropertyName = $True)]
        [Alias('f', 'TemplateParameterFile', 'FullName')]
        [SupportsWildcards()]
        [String[]]$InputPath = '*.parameters.json',

        [Parameter(Mandatory = $False)]
        [Switch]$SkipUnlinked,

        [Parameter(Position = 0, Mandatory = $False)]
        [Alias('p')]
        [String]$Path = $PWD
    )
    begin {
        Write-Verbose -Message '[Get-AzRuleTemplateLink] BEGIN::';

        # Build the pipeline
        $builder = [PSRule.Rules.Azure.Pipeline.PipelineBuilder]::TemplateLink($Path);
        $builder.SkipUnlinked($SkipUnlinked);
        $builder.UseCommandRuntime($PSCmdlet);
        $builder.UseExecutionContext($ExecutionContext);
        $pipeline = $builder.Build();
        if ($Null -ne (Get-Variable -Name pipeline -ErrorAction SilentlyContinue)) {
            try {
                $pipeline.Begin();
            }
            catch {
                $pipeline.Dispose();
                throw;
            }
        }
    }
    process {
        if ($Null -ne (Get-Variable -Name pipeline -ErrorAction SilentlyContinue)) {
            try {
                foreach ($p in $InputPath) {
                    $pipeline.Process($p);
                }
            }
            catch {
                $pipeline.Dispose();
                throw;
            }
        }
    }
    end {
        if ($Null -ne (Get-Variable -Name pipeline -ErrorAction SilentlyContinue)) {
            try {
                $pipeline.End();
            }
            finally {
                $pipeline.Dispose();
            }
        }
        Write-Verbose -Message '[Get-AzRuleTemplateLink] END::';
    }
}

function Export-AzPolicyAssignmentData {
    [CmdletBinding(SupportsShouldProcess = $True, DefaultParameterSetName = 'Default')]
    [OutputType([System.IO.FileInfo])]
    [OutputType([PSObject])]
    param (
        # Name of policy assignment
        [Parameter(ParameterSetName = 'Name', Mandatory = $False)]
        [String]$Name,

        # Fully qualified resource ID of policy assignment
        [Parameter(ParameterSetName = 'Id', Mandatory = $True)]
        [Alias('AssignmentId')]
        [String]$Id,

        # Specifies assignment policy scope
        [Parameter(ParameterSetName = 'Name', Mandatory = $False)]
        [Parameter(ParameterSetName = 'IncludeDescendent', Mandatory = $False)]
        [String]$Scope,

        # Specifies the policy definition ID of the policy assignment
        [Parameter(ParameterSetName = 'Name', Mandatory = $False)]
        [Parameter(ParameterSetName = 'Id', Mandatory = $False)]
        [String]$PolicyDefinitionId,

        # Include all assignments related to given scope
        [Parameter(ParameterSetName = 'IncludeDescendent', Mandatory = $True)]
        [Switch]$IncludeDescendent = $False,

        [Parameter(Mandatory = $False)]
        [String]$OutputPath = $PWD,

        [Parameter(Mandatory = $False)]
        [Switch]$PassThru = $False
    )
    begin {
        Write-Verbose -Message '[Export-AzPolicyAssignmentData] BEGIN::';
    }
    process {
        $context = GetAzureContext -ErrorAction SilentlyContinue

        if ($Null -eq $context) {
            Write-Error -Message 'Could not find an existing context. Use Connect-AzAccount to establish a PowerShell context with Azure.';
            return;
        }

        if (!(Test-Path -Path $OutputPath)) {
            if ($PSCmdlet.ShouldProcess('Create output directory', $OutputPath)) {
                $Null = New-Item -Path $OutputPath -ItemType Directory -Force;
            }
        }

        $getParams = @{ };

        Write-Verbose -Message "Parameter Set: $($PSCmdlet.ParameterSetName)";

        if ($PSCmdlet.ParameterSetName -eq 'Name') {
            if ($PSBoundParameters.ContainsKey('Name')) {
                $getParams['Name'] = $Name;
            }

            if ($PSBoundParameters.ContainsKey('PolicyDefinitionId')) {
                $getParams['PolicyDefinitionId'] = $PolicyDefinitionId;
            }
    
            if ($PSBoundParameters.ContainsKey('Scope')) {
                $getParams['Scope'] = $Scope;
            }
            else {
                $getParams['Scope'] = GetDefaultSubscriptionScope -Context $context
            }

            Write-Verbose -Message "Scope: $($getParams['Scope'])";
        }
        elseif ($PSCmdlet.ParameterSetName -eq 'Id') {
            $getParams['Id'] = $Id;

            if ($PSBoundParameters.ContainsKey('PolicyDefinitionId')) {
                $getParams['PolicyDefinitionId'] = $PolicyDefinitionId;
            }
        }
        elseif ($PSCmdlet.ParameterSetName -eq 'IncludeDescendent') {
            $getParams['IncludeDescendent'] = $IncludeDescendent;

            if ($PSBoundParameters.ContainsKey('Scope')) {
                $getParams['Scope'] = $Scope;
            }
            else {
                $getParams['Scope'] = GetDefaultSubscriptionScope -Context $context
            }
        }

        Write-Verbose -Message "[Export] -- Using subscription: $($context.Subscription.Name)";
        $filePath = Join-Path -Path $OutputPath -ChildPath "$($context.Subscription.Id).assignment.json";
        Get-AzPolicyAssignment @getParams -Verbose:$VerbosePreference `
        | ExpandPolicyAssignment -Context $context -Verbose:$VerbosePreference `
        | ExportAzureResource -Path $filePath -PassThru $PassThru -Verbose:$VerbosePreference;
    }
    end {
        Write-Verbose -Message "[Export-AzPolicyAssignmentData] END::";
    }
}

function Export-AzPolicyAssignmentRuleData {
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    [OutputType([System.IO.FileInfo])]
    [OutputType([PSObject])]
    param (
        # Name of Policy assignment
        [Parameter(Mandatory = $False)]
        [String]$Name,

        # Assignment file path
        [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)]
        [String]$AssignmentFile,

        [Parameter(Mandatory = $False)]
        [Alias('ResourceGroupName')]
        [PSRule.Rules.Azure.Configuration.ResourceGroupReference]$ResourceGroup,

        [Parameter(Mandatory = $False)]
        [PSRule.Rules.Azure.Configuration.SubscriptionReference]$Subscription,

        [Parameter(Mandatory = $False)]
        [String]$OutputPath = $PWD,

        [Parameter(Mandatory = $False)]
        [Switch]$PassThru = $False
    )
    begin {
        Write-Verbose -Message '[Export-AzPolicyAssignmentRuleData] BEGIN::';

        $option = [PSRule.Rules.Azure.Configuration.PSRuleOption]::FromFileOrDefault($PWD);
        $option.Output.Path = $OutputPath;

        # Build the pipeline
        $builder = [PSRule.Rules.Azure.Pipeline.PipelineBuilder]::Assignment($option);
        $builder.Assignment($Name);
        $builder.PassThru($PassThru);

        # Bind to subscription context
        if ($PSBoundParameters.ContainsKey('Subscription')) {
            $subscriptionOption = GetSubscription -InputObject $Subscription -ErrorAction SilentlyContinue;
            if ($Null -ne $subscriptionOption) {
                $builder.Subscription($subscriptionOption);
            }
        }
        # Bind to resource group
        if ($PSBoundParameters.ContainsKey('ResourceGroup')) {
            $resourceGroupOption = GetResourceGroup -InputObject $ResourceGroup -ErrorAction SilentlyContinue;
            if ($Null -ne $resourceGroupOption) {
                $builder.ResourceGroup($resourceGroupOption);
            }
        }

        $builder.UseCommandRuntime($PSCmdlet);
        $builder.UseExecutionContext($ExecutionContext);
        try {
            $pipeline = $builder.Build();
            $pipeline.Begin();
        }
        catch {
            $pipeline.Dispose();
        }
    }
    process {
        if ($Null -ne (Get-Variable -Name pipeline -ErrorAction SilentlyContinue)) {
            try {
                $source = [PSRule.Rules.Azure.Pipeline.PolicyAssignmentSource]::new($AssignmentFile);
                $pipeline.Process($source);
            }
            catch {
                $pipeline.Dispose();
                throw;
            }
        }
    }
    end {
        if ($Null -ne (Get-Variable -Name pipeline -ErrorAction SilentlyContinue)) {
            try {
                $pipeline.End();
            }
            finally {
                $pipeline.Dispose();
            }
        }
        Write-Verbose -Message '[Export-AzPolicyAssignmentRuleData] END::';
    }
}

function Get-AzPolicyAssignmentDataSource {
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    [OutputType([PSRule.Rules.Azure.Pipeline.PolicyAssignmentSource])]
    param (
        [Parameter(Mandatory = $False, ValueFromPipelineByPropertyName = $True)]
        [Alias('f', 'AssignmentFile', 'FullName')]
        [SupportsWildcards()]
        [String[]]$InputPath = '*.assignment.json',

        [Parameter(Mandatory = $False)]
        [Alias('p')]
        [String]$Path = $PWD
    )
    begin {
        Write-Verbose -Message '[Get-AzPolicyAssignmentDataSource] BEGIN::';

        # Build the pipeline
        $builder = [PSRule.Rules.Azure.Pipeline.PipelineBuilder]::AssignmentSearch($Path);
        $builder.UseCommandRuntime($PSCmdlet);
        $builder.UseExecutionContext($ExecutionContext);
        $pipeline = $builder.Build();
        if ($Null -ne (Get-Variable -Name pipeline -ErrorAction SilentlyContinue)) {
            try {
                $pipeline.Begin();
            }
            catch {
                $pipeline.Dispose();
                throw;
            }
        }
    }
    process {
        if ($Null -ne (Get-Variable -Name pipeline -ErrorAction SilentlyContinue)) {
            try {
                foreach ($p in $InputPath) {
                    $pipeline.Process($p);
                }
            }
            catch {
                $pipeline.Dispose();
                throw;
            }
        }
    }
    end {
        if ($Null -ne (Get-Variable -Name pipeline -ErrorAction SilentlyContinue)) {
            try {
                $pipeline.End();
            }
            finally {
                $pipeline.Dispose();
            }
        }
        Write-Verbose -Message '[Get-AzPolicyAssignmentDataSource] END::';
    }
}

#endregion Public functions

#
# Helper functions
#

function GetDefaultSubscriptionScope {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        return [string]::Concat('/subscriptions/', $context.Subscription.Id);
    }
}

function GetResourceGroup {
    [CmdletBinding()]
    [OutputType([PSRule.Rules.Azure.Configuration.ResourceGroupOption])]
    param (
        [Parameter(Mandatory = $True)]
        [PSRule.Rules.Azure.Configuration.ResourceGroupReference]$InputObject
    )
    process {
        $result = $InputObject.ToResourceGroupOption();
        if ($InputObject.FromName) {
            $o = Get-AzResourceGroup -Name $InputObject.Name -ErrorAction SilentlyContinue;
            if ($Null -ne $o) {
                $result.Name = $o.ResourceGroupName
                $result.Location = $o.Location
                $result.ManagedBy = $o.ManagedBy
                $result.Properties.ProvisioningState = $o.ProvisioningState
                $result.Tags = $o.Tags
            }
        }
        return $result;
    }
}

function GetSubscription {
    [CmdletBinding()]
    [OutputType([PSRule.Rules.Azure.Configuration.SubscriptionOption])]
    param (
        [Parameter(Mandatory = $True)]
        [PSRule.Rules.Azure.Configuration.SubscriptionReference]$InputObject
    )
    process {
        $result = $InputObject.ToSubscriptionOption();
        if ($InputObject.FromName) {
            $o = (Set-AzContext -Subscription $InputObject.DisplayName -ErrorAction SilentlyContinue).Subscription;
            if ($Null -ne $o) {
                $result.DisplayName = $o.Name
                $result.SubscriptionId = $o.SubscriptionId
                $result.State = $o.State
                $result.TenantId = $o.TenantId
            }
        }
        return $result;
    }
}

function FindAzureContext {
    [CmdletBinding()]
    [OutputType([Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer[]])]
    param (
        [Parameter(Mandatory = $False)]
        [String[]]$Subscription = $Null,

        [Parameter(Mandatory = $False)]
        [String[]]$Tenant = $Null,

        [Parameter(Mandatory = $False)]
        [System.Boolean]$All = $False
    )
    process {
        $listAvailable = $False;
        if ($Null -ne $Subscription -or $Null -ne $Tenant -or $All) {
            $listAvailable = $True;
        }

        # Get subscription contexts
        $context = @(GetAzureContext -ListAvailable:$listAvailable);
        if ($Null -eq $context -and $context.Length -gt 0) {
            Write-Error -Message 'Could not find an existing context. Use Connect-AzAccount to establish a PowerShell context with Azure.';
            return;
        }

        Write-Verbose "[Context] -- Found ($($context.Length)) subscription contexts";
        $filteredContext = @($context | ForEach-Object -Process {
                if (
                ($Null -eq $Tenant -or $Tenant.Length -eq 0 -or ($_.Tenant.Id -in $Tenant)) -and
                ($Null -eq $Subscription -or $Subscription.Length -eq 0 -or ($_.Subscription.Id -in $Subscription) -or ($_.Subscription.Name -in $Subscription))
                ) {
                    $_;
                    Write-Verbose "[Context] -- Using subscription: $($_.Subscription.Name)";
                }
            })
        Write-Verbose "[Context] -- Using [$($filteredContext.Length)/$($context.Length)] subscription contexts";
        return $filteredContext;
    }
}

function GetAzureContext {
    [CmdletBinding()]
    [OutputType([Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer[]])]
    param (
        [Parameter(Mandatory = $False)]
        [System.Boolean]$ListAvailable = $False
    )
    process {
        $getParams = @{ };
        if ($ListAvailable) {
            $getParams['ListAvailable'] = $True;
        }

        # Get contexts
        return Get-AzContext @getParams;
    }
}

function GetAzureResource {
    [CmdletBinding()]
    [OutputType([PSObject])]
    param (
        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context,

        [Parameter(Mandatory = $False)]
        [Hashtable]$Tag,

        [Parameter(Mandatory = $False)]
        [String[]]$ResourceGroupName = $Null
    )
    begin {
        $watch = New-Object -TypeName System.Diagnostics.Stopwatch;
    }
    process {
        $resourceParams = @{ };
        $rgParams = @{ };

        if ($PSBoundParameters.ContainsKey('Tag')) {
            $resourceParams['Tag'] = $Tag;
            $rgParams['Tag'] = $Tag;
        }

        try {
            Write-Verbose -Message "[Export] -- Getting Azure resources";
            $watch.Restart();

            if ($PSBoundParameters.ContainsKey('ResourceGroupName')) {
                foreach ($rg in $ResourceGroupName) {
                    Write-Verbose -Message "[Export] -- Getting Azure resources for Resource Group: $rg";
                    Get-AzResource @resourceParams -ResourceGroupName $rg -ExpandProperties -ODataQuery "SubscriptionId EQ '$($Context.DefaultContext.Subscription.Id)'" -DefaultProfile $Context `
                    | ExpandResource -Context $Context -Verbose:$VerbosePreference;
                    Get-AzResourceGroup @rgParams -Name $rg -DefaultProfile $Context |
                    SetResourceType 'Microsoft.Resources/resourceGroups' |
                    ExpandResource -Context $Context -Verbose:$VerbosePreference;
                }
            }
            else {
                Get-AzResource @resourceParams -ExpandProperties -DefaultProfile $Context |
                ExpandResource -Context $Context -Verbose:$VerbosePreference;
                Get-AzResourceGroup @rgParams -DefaultProfile $Context |
                SetResourceType 'Microsoft.Resources/resourceGroups' |
                ExpandResource -Context $Context -Verbose:$VerbosePreference;
            }

            Write-Verbose -Message "[Export] -- Azure resources exported in [$($watch.ElapsedMilliseconds) ms]";
            $watch.Restart();

            Write-Verbose -Message "[Export] -- Getting Azure subscription: $($Context.DefaultContext.Subscription.Id)";
            Get-AzSubscription -SubscriptionId $Context.DefaultContext.Subscription.Id |
            SetResourceType 'Microsoft.Subscription' |
            ExpandResource -Context $Context -Verbose:$VerbosePreference;

            Write-Verbose -Message "[Export] -- Azure subscription exported in [$($watch.ElapsedMilliseconds) ms]";
        }
        finally {
            $watch.Stop();
        }
    }
}

function FilterAzureResource {
    [CmdletBinding()]
    [OutputType([PSObject])]
    param (
        [Parameter(Mandatory = $False)]
        [String[]]$ResourceGroupName = $Null,

        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$InputObject
    )
    process {
        if (($Null -eq $ResourceGroupName) -or ($InputObject.ResourceType -eq 'Microsoft.Subscription') -or (@($InputObject.PSObject.Properties | Where-Object { $_.Name -eq 'ResourceGroupName' }).Length -eq 0)) {
            return $InputObject;
        }
        elseif ($InputObject.ResourceGroupName -in $ResourceGroupName) {
            return $InputObject;
        }
    }
}

function ExportAzureResource {
    [CmdletBinding(SupportsShouldProcess = $True)]
    [OutputType([System.IO.FileInfo])]
    [OutputType([PSObject])]
    param (
        [Parameter(Mandatory = $True)]
        [String]$Path,

        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$InputObject,

        [Parameter(Mandatory = $False)]
        [System.Boolean]$PassThru = $False
    )
    begin {
        $resources = @();
    }
    process {
        if ($PassThru) {
            $InputObject;
        }
        else {
            # Collect passed through resources
            $resources += $InputObject;
        }
    }
    end {
        $watch = New-Object -TypeName System.Diagnostics.Stopwatch;
        Write-Verbose -Message "[Export] -- Exporting to JSON";
        $watch.Restart();

        if (!$PassThru) {
            # Save to JSON
            ConvertTo-Json -InputObject $resources -Depth 100 | Set-Content -Path $Path;
            Get-Item -Path $Path;
        }
        $watch.Stop();
        Write-Verbose -Message "[Export] -- Exported to JSON in [$($watch.ElapsedMilliseconds) ms]";
    }
}

function GetSubResource {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context,

        [Parameter(Mandatory = $True)]
        [String]$ResourceType,

        [Parameter(Mandatory = $True)]
        [String]$ApiVersion
    )
    process {
        $getParams = @{
            Name              = $Resource.Name
            ResourceType      = $ResourceType
            ResourceGroupName = $Resource.ResourceGroupName
            DefaultProfile    = $Context
            ApiVersion        = $ApiVersion
        }
        try {
            Get-AzResource @getParams -ExpandProperties;
        }
        catch {
            Write-Warning -Message "Failed to read $($Resource.Name): $ResourceType";
        }
    }
}

function GetResourceById {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True)]
        [PSObject]$ResourceId,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context,

        [Parameter(Mandatory = $True)]
        [String]$ApiVersion
    )
    process {
        $getParams = @{
            ResourceId     = $ResourceId
            DefaultProfile = $Context
            ApiVersion     = $ApiVersion
        }
        try {
            Get-AzResource @getParams -ExpandProperties;
        }
        catch {
            Write-Warning -Message "Failed to read $ResourceId";
        }
    }
}

function GetSubResourceId {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context,

        [Parameter(Mandatory = $True)]
        [String]$Property,

        [Parameter(Mandatory = $True)]
        [String]$ApiVersion
    )
    process {
        $getParams = @{
            ResourceId     = [String]::Concat($Resource.Id, '/', $Property)
            DefaultProfile = $Context
            ApiVersion     = $ApiVersion
        }
        try {
            Get-AzResource @getParams -ExpandProperties;
        }
        catch {
            Write-Warning -Message "Failed to read $($Resource.Name): $Property";
        }
    }
}

function GetRestProperty {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context,

        [Parameter(Mandatory = $True)]
        [String]$Property,

        [Parameter(Mandatory = $True)]
        [String]$ApiVersion
    )
    process {
        try {
            $token = GetRestToken -Context $Context;
            $getParams = @{
                Uri     = [String]::Concat('https://management.azure.com', $Resource.Id, '/', $Property, '?api-version=', $ApiVersion)
                Headers = @{
                    Authorization = "Bearer $($token)"
                }
            }
            Invoke-RestMethod -Method Get @getParams -UseBasicParsing -Verbose:$VerbosePreference;
        }
        catch {
            Write-Warning -Message "Failed to read $($Resource.Name): $Property";
        }
    }
}

function GetRestToken {
    [CmdletBinding()]
    [OutputType([String])]
    param (
        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        return ($Context.DefaultContext.TokenCache.ReadItems() | Where-Object {
                $_.TenantId -eq $Context.DefaultContext.Tenant.Id -and
                $_.Resource -eq 'https://management.core.windows.net/' -and
                $_.Authority -eq "https://login.windows.net/$($Context.DefaultContext.Tenant.Id)/"
            }).AccessToken;
    }
}

function GetSubProvider {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context,

        [Parameter(Mandatory = $True)]
        [String]$ResourceType,

        [Parameter(Mandatory = $True)]
        [String]$ApiVersion,

        [Parameter(Mandatory = $False)]
        [Switch]$ExpandProperties
    )
    process {
        $getParams = @{
            ResourceId     = [String]::Concat($Resource.Id, '/providers/', $ResourceType)
            DefaultProfile = $Context
            ApiVersion     = $ApiVersion
        }
        try {
            Get-AzResource @getParams -ExpandProperties:$ExpandProperties;
        }
        catch {
            Write-Warning -Message "Failed to read $($Resource.Name): $ResourceType";
        }
    }
}

function VisitAPIManagement {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        $resources = @();
        $apis += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.ApiManagement/service/apis' -ApiVersion '2019-12-01';
        foreach ($api in $apis) {
            $resources += $api;
            $apiParams = @{
                Name              = "$($Resource.Name)/$($api.Name)"
                ResourceType      = 'Microsoft.ApiManagement/service/apis/policies'
                ResourceGroupName = $Resource.ResourceGroupName
                DefaultProfile    = $Context
                ApiVersion        = '2019-12-01'
            };
            $resources += Get-AzResource @apiParams;
        }

        # Add zones in from REST API because they are not included from Get-AzResource
        $apiManagementServicePrimaryZones = ((Invoke-AzRestMethod -Path "$($Resource.ResourceId)?api-version=2020-12-01" -Method GET).Content | ConvertFrom-Json).zones;
        $Resource = $Resource | Add-Member -MemberType NoteProperty -Name zones -Value $apiManagementServicePrimaryZones -PassThru;

        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.ApiManagement/service/backends' -ApiVersion '2019-12-01';
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.ApiManagement/service/products' -ApiVersion '2019-12-01';
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.ApiManagement/service/policies' -ApiVersion '2019-12-01';
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.ApiManagement/service/identityProviders' -ApiVersion '2019-12-01';
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.ApiManagement/service/diagnostics' -ApiVersion '2019-12-01';
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.ApiManagement/service/loggers' -ApiVersion '2019-12-01';
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.ApiManagement/service/certificates' -ApiVersion '2019-12-01';
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.ApiManagement/service/namedValues' -ApiVersion '2019-12-01';
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.ApiManagement/service/portalsettings' -ApiVersion '2019-12-01';
        $Resource | Add-Member -MemberType NoteProperty -Name resources -Value $resources -PassThru;
    }
}

function VisitSqlServer {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        $sqlServer = $resource;
        $resources = @();

        # Get SQL Server firewall rules
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.Sql/servers/firewallRules' -ApiVersion '2015-05-01-preview';
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.Sql/servers/administrators' -ApiVersion '2014-04-01';
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.Sql/servers/securityAlertPolicies' -ApiVersion '2017-03-01-preview';
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.Sql/servers/vulnerabilityAssessments' -ApiVersion '2018-06-01-preview';
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.Sql/servers/auditingSettings' -ApiVersion '2017-03-01-preview';
        $sqlServer | Add-Member -MemberType NoteProperty -Name resources -Value $resources -PassThru;
    }
}

function VisitSqlDatabase {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        $resources = @();
        $getParams = @{
            ResourceGroupName = $Resource.ResourceGroupName
            DefaultProfile    = $Context
            ErrorAction       = 'SilentlyContinue'
        }
        $idParts = $Resource.ResourceId.Split('/');
        $serverName = $idParts[-3];
        $resourceName = "$serverName/$($Resource.Name)";
        $resources += Get-AzResource @getParams -Name $resourceName -ResourceType 'Microsoft.Sql/servers/databases/dataMaskingPolicies' -ApiVersion '2014-04-01' -ExpandProperties
        $resources += Get-AzResource @getParams -Name $resourceName -ResourceType 'Microsoft.Sql/servers/databases/transparentDataEncryption' -ApiVersion '2014-04-01' -ExpandProperties;
        $resources += Get-AzResource @getParams -Name $resourceName -ResourceType 'Microsoft.Sql/servers/databases/connectionPolicies' -ApiVersion '2014-04-01' -ExpandProperties;
        $resources += Get-AzResource @getParams -Name $resourceName -ResourceType 'Microsoft.Sql/servers/databases/geoBackupPolicies' -ApiVersion '2014-04-01' -ExpandProperties;
        $Resource | Add-Member -MemberType NoteProperty -Name resources -Value $resources -PassThru;
    }
}

function VisitPostgreSqlServer {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        $sqlServer = $resource;
        $resources = @();

        # Get Postgre SQL Server firewall rules
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.DBforPostgreSQL/servers/firewallRules' -ApiVersion '2017-12-01';
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.DBforPostgreSQL/servers/securityAlertPolicies' -ApiVersion '2017-12-01';
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.DBforPostgreSQL/servers/configurations' -ApiVersion '2017-12-01';
        $sqlServer | Add-Member -MemberType NoteProperty -Name resources -Value $resources -PassThru;
    }
}

function VisitMySqlServer {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        $sqlServer = $resource;
        $resources = @();

        # Get MySQL Server firewall rules
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.DBforMySQL/servers/firewallRules' -ApiVersion '2017-12-01';
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.DBforMySQL/servers/securityAlertPolicies' -ApiVersion '2017-12-01';
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.DBforMySQL/servers/configurations' -ApiVersion '2017-12-01';
        $sqlServer | Add-Member -MemberType NoteProperty -Name resources -Value $resources -PassThru;
    }
}

function VisitSqlManagedInstance {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        $sqlMI = $resource;
        $resources = @();

        $resources += Get-AzResource -Name $resource.Name -ResourceType 'Microsoft.Sql/managedInstances/securityAlertPolicies' -ResourceGroupName $resource.ResourceGroupName -DefaultProfile $Context -ApiVersion '2017-03-01-preview' -ExpandProperties;
        $resources += Get-AzResource -Name $resource.Name -ResourceType 'Microsoft.Sql/managedInstances/vulnerabilityAssessments' -ResourceGroupName $resource.ResourceGroupName -DefaultProfile $Context -ApiVersion '2018-06-01-preview' -ExpandProperties;
        $resources += Get-AzResource -Name $resource.Name -ResourceType 'Microsoft.Sql/managedInstances/administrators' -ResourceGroupName $resource.ResourceGroupName -DefaultProfile $Context -ApiVersion '2017-03-01-preview' -ExpandProperties;
        $sqlMI | Add-Member -MemberType NoteProperty -Name resources -Value $resources -PassThru;
    }
}

function VisitAutomationAccount {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        $aa = $Resource
        $resources = @();
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.Automation/AutomationAccounts/variables' -ApiVersion '2015-10-31';
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.Automation/AutomationAccounts/webhooks' -ApiVersion '2015-10-31';

        $diagnosticSettingsResourceParams = @{
            Name              = $Resource.Name
            ResourceType      = 'Microsoft.Automation/automationAccounts/providers/microsoft.insights/diagnosticSettings'
            ResourceGroupName = $Resource.ResourceGroupName
            DefaultProfile    = $Context
            ExpandProperties  = $True
            ApiVersion        = '2021-05-01-preview'
        }

        $resources += Get-AzResource @diagnosticSettingsResourceParams

        $aa | Add-Member -MemberType NoteProperty -Name resources -Value $resources -PassThru;
    }
}

# function VisitDataFactoryV2 {
# param (
# [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
# [PSObject]$Resource,

# [Parameter(Mandatory = $True)]
# [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
# )
# process {
# $df = $resource;
# $resources = @();

# # Get linked services
# $resources += Get-AzDataFactoryV2LinkedService -DataFactoryName $resource.Name -ResourceGroupName $resource.ResourceGroupName -DefaultProfile $Context | ForEach-Object -Process {
# $linkedService = $_;
# $type = $linkedService.Properties.GetType().Name;
# $linkedService.Properties.AdditionalProperties = $Null;
# if ($Null -ne $linkedService.Properties.EncryptedCredential) {
# $linkedService.Properties.EncryptedCredential = $Null;
# }

# $linkedService | Add-Member -MemberType NoteProperty -Name 'ResourceType' -Value 'linkedServices';
# $linkedService | Add-Member -MemberType NoteProperty -Name 'Type' -Value $type;
# $linkedService;
# };
# $df | Add-Member -MemberType NoteProperty -Name resources -Value $resources -PassThru;
# }
# }

function VisitCDNEndpoint {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        $resources = @();
        $resources += GetSubResourceId @PSBoundParameters -Property 'customdomains' -ApiVersion '2019-04-15';
        $Resource | Add-Member -MemberType NoteProperty -Name resources -Value $resources -PassThru;
    }
}

function VisitContainerRegistry {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        $resources = @();
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.ContainerRegistry/registries/replications' -ApiVersion '2019-12-01-preview';
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.ContainerRegistry/registries/webhooks' -ApiVersion '2019-12-01-preview';
        $resources += GetSubResource @PSBoundParameters -ResourceType 'Microsoft.ContainerRegistry/registries/tasks' -ApiVersion '2019-06-01-preview';
        $resources += GetRestProperty @PSBoundParameters -Property 'listUsages' -ApiVersion '2019-05-01' | SetResourceType 'Microsoft.ContainerRegistry/registries/listUsages';
        $resources += GetSubProvider @PSBoundParameters -ResourceType 'Microsoft.Security/assessments' -ApiVersion '2019-01-01-preview';
        $Resource | Add-Member -MemberType NoteProperty -Name resources -Value $resources -PassThru;
    }
}

function VisitAKSCluster {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        $resources = @();

        # Only add VNET resource if AKS cluster is using Azure CNI network plugin
        # Supported network plugins: azure or kubenet
        # https://docs.microsoft.com/en-us/azure/templates/microsoft.containerservice/managedclusters?tabs=json#containerservicenetworkprofile-object
        if ($Resource.Properties.networkProfile.networkPlugin -eq 'azure') {
            $nodePools = @($Resource.Properties.agentPoolProfiles);
            foreach ($nodePool in $nodePools) {
                $vnetId = $nodePool.vnetSubnetID;
                $resources += GetResourceById -ResourceId $vnetId -ApiVersion '2020-05-01' -Context $Context;
            }
        }

        $resources += Get-AzResource -Name $Resource.Name -ResourceType 'Microsoft.ContainerService/managedClusters/providers/microsoft.insights/diagnosticSettings' -ResourceGroupName $Resource.ResourceGroupName -DefaultProfile $Context -ApiVersion '2017-05-01-preview' -ExpandProperties;

        $Resource | Add-Member -MemberType NoteProperty -Name resources -Value $resources -PassThru;
    }
}

function VisitPublicIP {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        # Get-AzResource does not return zones, even with latest API version
        # Had to fetch the zones using ARM REST API and insert them into the resource
        # Logged an issue with Az PowerShell: https://github.com/Azure/azure-powershell/issues/15905
        $publicIp = ((Invoke-AzRestMethod -Path "$($Resource.ResourceId)?api-version=2021-02-01" -Method GET).Content | ConvertFrom-Json).PSObject.Properties['zones'];
        if ($Null -ne $publicIp) {
            $Resource | Add-Member -MemberType NoteProperty -Name zones -Value $publicIp.value -PassThru;
        }
        else {
            $Resource;
        }
    }
}

function VisitRedisCache {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        # Get-AzResource does not return zones, even with latest API version
        # Had to fetch the zones using ARM REST API and insert them into the resource
        # Logged an issue with Az PowerShell: https://github.com/Azure/azure-powershell/issues/15905
        $redisCacheZones = ((Invoke-AzRestMethod -Path "$($Resource.ResourceId)?api-version=2021-06-01" -Method GET).Content | ConvertFrom-Json).PSObject.Properties['zones'];
        if ($Null -ne $redisCacheZones) {
            $Resource | Add-Member -MemberType NoteProperty -Name zones -Value $redisCacheZones.value -PassThru;
        }
        else {
            $Resource;
        }
    }
}

function VisitRedisEnterpriseCache {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        # Get-AzResource does not return zones, even with latest API version
        # Had to fetch the zones using ARM REST API and insert them into the resource
        # Logged an issue with Az PowerShell: https://github.com/Azure/azure-powershell/issues/15905
        $redisEnterpriseCacheZones = ((Invoke-AzRestMethod -Path "$($Resource.ResourceId)?api-version=2021-08-01" -Method GET).Content | ConvertFrom-Json).PSObject.Properties['zones'];
        if ($Null -ne $redisEnterpriseCacheZones) {
            $Resource | Add-Member -MemberType NoteProperty -Name zones -Value $redisEnterpriseCacheZones.value -PassThru;
        }
        else {
            $Resource;
        }
    }
}

function VisitStorageAccount {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        $resources = @();
        if ($Resource.Kind -ne 'FileStorage') {
            $blobServices = @(GetSubResource @PSBoundParameters -ResourceType 'Microsoft.Storage/storageAccounts/blobServices' -ApiVersion '2019-04-01');
            foreach ($blobService in $blobServices) {
                $resources += $blobService;
                $resources += Get-AzResource -Name "$($Resource.Name)/$($blobService.Name)" -ResourceType 'Microsoft.Storage/storageAccounts/blobServices/containers' -ResourceGroupName $Resource.ResourceGroupName -DefaultProfile $Context -ApiVersion '2019-04-01' -ExpandProperties;
            }
        }
        $Resource | Add-Member -MemberType NoteProperty -Name resources -Value $resources -PassThru;
    }
}

function VisitStorageSyncService {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        $resources = @();
        $resources += Get-AzStorageSyncServer -ParentResourceId $Resource.ResourceId -DefaultProfile $Context;
        $Resource | Add-Member -MemberType NoteProperty -Name resources -Value $resources -PassThru;
    }
}

function VisitWebApp {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        $resources = @();
        $configResourceType = 'Microsoft.Web/sites/config';

        # Handle slots
        if ($Resource.ResourceType -eq 'Microsoft.Web/sites/slots') {
            $configResourceType = 'Microsoft.Web/sites/slots/config';
        }

        $resources += Get-AzResource -Name $Resource.Name -ResourceType $configResourceType -ResourceGroupName $Resource.ResourceGroupName -DefaultProfile $Context -ApiVersion '2018-11-01' -ExpandProperties;
        $Resource | Add-Member -MemberType NoteProperty -Name resources -Value $resources -PassThru;
    }
}

function VisitRecoveryServices {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        $resources = @();
        $resources += Get-AzResource -Name $resource.Name -ResourceType 'Microsoft.RecoveryServices/vaults/replicationRecoveryPlans' -ResourceGroupName $resource.ResourceGroupName -DefaultProfile $Context -ApiVersion '2018-07-10' -ExpandProperties;
        $resources += Get-AzResource -Name $resource.Name -ResourceType 'Microsoft.RecoveryServices/vaults/replicationAlertSettings' -ResourceGroupName $resource.ResourceGroupName -DefaultProfile $Context -ApiVersion '2018-07-10' -ExpandProperties;
        $resources += Get-AzResource -Name $resource.Name -ResourceType 'Microsoft.RecoveryServices/vaults/backupstorageconfig/vaultstorageconfig' -ResourceGroupName $resource.ResourceGroupName -DefaultProfile $Context -ApiVersion '2018-07-10' -ExpandProperties;
        $Resource | Add-Member -MemberType NoteProperty -Name resources -Value $resources -PassThru;
    }
}

function VisitVirtualMachine {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        $resources = @();
        $networkInterfaceId = $Resource.Properties.networkProfile.networkInterfaces.id;
        foreach ($id in $networkInterfaceId) {
            $resources += Get-AzResource -ResourceId $id -ExpandProperties -DefaultProfile $Context;
        }
        $Resource | Add-Member -MemberType NoteProperty -Name resources -Value $resources -PassThru;
    }
}

function VisitKeyVault {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        $resources = @();
        $resources += Get-AzResource -Name $resource.Name -ResourceType 'Microsoft.KeyVault/vaults/providers/microsoft.insights/diagnosticSettings' -ResourceGroupName $resource.ResourceGroupName -DefaultProfile $Context -ApiVersion '2017-05-01-preview' -ExpandProperties;

        $resources += GetResourceById -ResourceId "$($Resource.Id)/keys" -Context $Context -ApiVersion '2021-11-01-preview';

        $Resource | Add-Member -MemberType NoteProperty -Name resources -Value $resources -PassThru;
    }
}

function VisitFrontDoor {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        # Patch Front Door properties not fully returned from the default API version
        $Resource = Get-AzResource -Name $resource.Name -ResourceGroupName $resource.ResourceGroupName -DefaultProfile $Context -ResourceType 'Microsoft.Network/frontdoors' -ExpandProperties -ApiVersion '2018-08-01';

        $resources = @();
        $resources += Get-AzResource -Name $resource.Name -ResourceType 'Microsoft.Network/frontdoors/providers/microsoft.insights/diagnosticSettings' -ResourceGroupName $resource.ResourceGroupName -DefaultProfile $Context -ApiVersion '2017-05-01-preview' -ExpandProperties;
        $Resource | Add-Member -MemberType NoteProperty -Name resources -Value $resources -PassThru;
    }
}

function VisitFrontDoorWAFPolicy {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        # Patch Front Door WAF policy properties not fully returned from the default API version
        $Resource = Get-AzResource -Name $resource.Name -ResourceGroupName $resource.ResourceGroupName -DefaultProfile $Context -ResourceType 'Microsoft.Network/FrontDoorWebApplicationFirewallPolicies' -ExpandProperties -ApiVersion '2019-10-01';
        $Resource;
    }
}

function VisitNetworkConnection {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        # Patch connections
        if (@($Resource.Properties.PSObject.Properties.Match('sharedKey')).Length -gt 0) {
            $Resource.Properties.sharedKey = "*** MASKED ***";
        }
        $Resource;
    }
}

function VisitSubscription {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        $resources = @();
        $resources += Get-AzRoleAssignment -DefaultProfile $Context -IncludeClassicAdministrators | SetResourceType 'Microsoft.Authorization/roleAssignments';
        $resources += Get-AzResource -DefaultProfile $Context -ApiVersion '2017-08-01-preview' -ResourceId "/subscriptions/$($Resource.Id)/providers/Microsoft.Security/autoProvisioningSettings";
        $resources += Get-AzResource -DefaultProfile $Context -ApiVersion '2017-08-01-preview' -ResourceId "/subscriptions/$($Resource.Id)/providers/Microsoft.Security/securityContacts";
        $resources += Get-AzResource -DefaultProfile $Context -ApiVersion '2018-06-01' -ResourceId "/subscriptions/$($Resource.Id)/providers/Microsoft.Security/pricings";
        $resources += Get-AzResource -DefaultProfile $Context -ApiVersion '2019-06-01' -ResourceId "/subscriptions/$($Resource.Id)/providers/Microsoft.Authorization/policyAssignments";
        $resources += Get-AzResource -DefaultProfile $Context -ResourceType 'microsoft.insights/activityLogAlerts' -ExpandProperties;
        $Resource | Add-Member -MemberType NoteProperty -Name resources -Value $resources -PassThru;

        Get-AzPolicyDefinition -Custom -DefaultProfile $Context;
        Get-AzPolicySetDefinition -Custom -DefaultProfile $Context;
    }
}

function VisitResourceGroup {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        $resources = @();
        $resources += Get-AzRoleAssignment -DefaultProfile $Context -Scope $Resource.ResourceId `
        | Where-Object { $_.Scope.StartsWith($Resource.ResourceId) } `
        | SetResourceType 'Microsoft.Authorization/roleAssignments';
        $resources += Get-AzResourceLock -DefaultProfile $Context -ResourceGroupName $Resource.ResourceGroupName | SetResourceType 'Microsoft.Authorization/locks';
        $Resource `
        | Add-Member -MemberType NoteProperty -Name Name -Value $Resource.ResourceGroupName -PassThru `
        | Add-Member -MemberType NoteProperty -Name resources -Value $resources -PassThru;
    }
}

function VisitDataExplorerCluster {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        $resources = @();
        $getParams = @{
            ResourceGroupName = $Resource.ResourceGroupName
            DefaultProfile    = $Context
            ErrorAction       = 'SilentlyContinue'
        }
        $resources += Get-AzResource @getParams -Name $Resource.Name -ResourceType 'Microsoft.Kusto/clusters/databases' -ApiVersion '2021-08-27' -ExpandProperties;
        $Resource | Add-Member -MemberType NoteProperty -Name resources -Value $resources -PassThru;
    }
}

function VisitEventHubNamespaces {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        $resources = @();
        $getParams = @{
            ResourceGroupName = $Resource.ResourceGroupName
            DefaultProfile    = $Context
            ErrorAction       = 'SilentlyContinue'
        }
        $resources += Get-AzResource @getParams -Name $Resource.Name -ResourceType 'Microsoft.EventHub/namespaces/eventhubs' -ApiVersion '2021-11-01' -ExpandProperties;
        $Resource | Add-Member -MemberType NoteProperty -Name resources -Value $resources -PassThru;
    }
}

function VisitServiceBusNamespaces {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        $resources = @();
        $getParams = @{
            ResourceGroupName = $Resource.ResourceGroupName
            DefaultProfile    = $Context
            ErrorAction       = 'SilentlyContinue'
        }
        $resources += Get-AzResource @getParams -Name $Resource.Name -ResourceType 'Microsoft.ServiceBus/namespaces/queues' -ApiVersion '2021-06-01-preview' -ExpandProperties;
        $resources += Get-AzResource @getParams -Name $Resource.Name -ResourceType 'Microsoft.ServiceBus/namespaces/topics' -ApiVersion '2021-06-01-preview' -ExpandProperties;
        $Resource | Add-Member -MemberType NoteProperty -Name resources -Value $resources -PassThru;
    }
}

function ExpandPolicyAssignment {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Assignment,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        $policyDefinitionId = $Assignment.Properties.PolicyDefinitionId;

        Write-Verbose -Message "[Export] -- Expanding: $policyDefinitionId";

        $policyDefinitions = [System.Collections.Generic.List[PSObject]]@();

        if ($policyDefinitionId -like '*/providers/Microsoft.Authorization/policyDefinitions/*') {
            $definition = Get-AzPolicyDefinition -Id $policyDefinitionId -DefaultProfile $Context;
            $policyDefinitions.Add($definition);
        }
        elseif ($policyDefinitionId -like '*/providers/Microsoft.Authorization/policySetDefinitions/*') {
            $policySetDefinition = Get-AzPolicySetDefinition -Id $policyDefinitionId -DefaultProfile $Context;

            foreach ($definition in $policySetDefinition.Properties.PolicyDefinitions) {
                $definitionId = $definition.policyDefinitionId;
                Write-Verbose -Message "[Export] -- Expanding: $definitionId";
                $definition = Get-AzPolicyDefinition -Id $definitionId -DefaultProfile $Context;
                $policyDefinitions.Add($definition);
            }
        }

        $Assignment | Add-Member -MemberType NoteProperty -Name PolicyDefinitions -Value $policyDefinitions -PassThru;
    }
}

# Add additional information to resources with child resources
function ExpandResource {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True)]
        [Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer]$Context
    )
    process {
        $resourceId = '';
        if ($Resource.ResourceType -eq 'Microsoft.Subscription') {
            $resourceId = $Resource.Id;
        }
        else {
            $resourceId = $Resource.ResourceId;
        }
        Write-Verbose -Message "[Export] -- Expanding: $($resourceId)";
        switch ($Resource.ResourceType) {
            'Microsoft.ApiManagement/service' { VisitAPIManagement @PSBoundParameters; }
            'Microsoft.Automation/automationAccounts' { VisitAutomationAccount @PSBoundParameters; }
            'Microsoft.Cdn/profiles/endpoints' { VisitCDNEndpoint @PSBoundParameters; }
            'Microsoft.ContainerRegistry/registries' { VisitContainerRegistry @PSBoundParameters; }
            'Microsoft.ContainerService/managedClusters' { VisitAKSCluster @PSBoundParameters; }
            'Microsoft.Sql/servers' { VisitSqlServer @PSBoundParameters; }
            'Microsoft.Sql/servers/databases' { VisitSqlDatabase @PSBoundParameters; }
            'Microsoft.DBforPostgreSQL/servers' { VisitPostgreSqlServer @PSBoundParameters; }
            'Microsoft.DBforMySQL/servers' { VisitMySqlServer @PSBoundParameters; }
            # 'Microsoft.Sql/managedInstances' { VisitSqlManagedInstance @PSBoundParameters; }
            # 'Microsoft.DataFactory/factories' { VisitDataFactoryV2 @PSBoundParameters; }
            'Microsoft.Storage/storageAccounts' { VisitStorageAccount @PSBoundParameters; }
            # "Microsoft.StorageSync/storageSyncServices" { VisitStorageSyncService @PSBoundParameters; }
            'Microsoft.Web/sites' { VisitWebApp @PSBoundParameters; }
            'Microsoft.Web/sites/slots' { VisitWebApp @PSBoundParameters; }
            'Microsoft.RecoveryServices/vaults' { VisitRecoveryServices @PSBoundParameters; }
            'Microsoft.Compute/virtualMachines' { VisitVirtualMachine @PSBoundParameters; }
            'Microsoft.KeyVault/vaults' { VisitKeyVault @PSBoundParameters; }
            'Microsoft.Network/frontDoors' { VisitFrontDoor @PSBoundParameters; }
            'Microsoft.Network/FrontDoorWebApplicationFirewallPolicies' { VisitFrontDoorWAFPolicy @PSBoundParameters; }
            'Microsoft.Network/connections' { VisitNetworkConnection @PSBoundParameters; }
            'Microsoft.Subscription' { VisitSubscription @PSBoundParameters; }
            'Microsoft.Resources/resourceGroups' { VisitResourceGroup @PSBoundParameters; }
            'Microsoft.Network/publicIPAddresses' { VisitPublicIP @PSBoundParameters; }
            'Microsoft.Cache/Redis' { VisitRedisCache @PSBoundParameters; }
            'Microsoft.Cache/redisEnterprise' { VisitRedisEnterpriseCache @PSBoundParameters; }
            'Microsoft.Kusto/Clusters' { VisitDataExplorerCluster @PSBoundParameters; }
            'Microsoft.EventHub/namespaces' { VisitEventHubNamespaces @PSBoundParameters; }
            'Microsoft.ServiceBus/namespaces' { VisitServiceBusNamespaces @PSBoundParameters; }
            default { $Resource; }
        }
    }
}

function SetResourceType {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        [PSObject]$Resource,

        [Parameter(Mandatory = $True, Position = 0)]
        [String]$ResourceType
    )
    process {
        if ($ResourceType -eq 'Microsoft.Resources/resourceGroups') {
            $Resource = $Resource | Add-Member -MemberType NoteProperty -Name Id -Value $Resource.ResourceId -PassThru -Force;
        }
        $Resource | Add-Member -MemberType NoteProperty -Name ResourceType -Value $ResourceType -PassThru -Force;
    }
}

#
# Export module
#

New-Alias -Name 'Export-AzTemplateRuleData' -Value 'Export-AzRuleTemplateData' -Force;

Export-ModuleMember -Function @(
    'Export-AzRuleData'
    'Export-AzRuleTemplateData'
    'Get-AzRuleTemplateLink'
    'Export-AzPolicyAssignmentData'
    'Export-AzPolicyAssignmentRuleData'
    'Get-AzPolicyAssignmentDataSource'
);

Export-ModuleMember -Alias @(
    'Export-AzTemplateRuleData'
);

# SIG # Begin signature block
# MIInqwYJKoZIhvcNAQcCoIInnDCCJ5gCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDcwydHHwpHsXYU
# +feOx0uEWn0WZhFXlIuiA16ZtT/YMaCCDXYwggX0MIID3KADAgECAhMzAAACy7d1
# OfsCcUI2AAAAAALLMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NTU5WhcNMjMwNTExMjA0NTU5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQC3sN0WcdGpGXPZIb5iNfFB0xZ8rnJvYnxD6Uf2BHXglpbTEfoe+mO//oLWkRxA
# wppditsSVOD0oglKbtnh9Wp2DARLcxbGaW4YanOWSB1LyLRpHnnQ5POlh2U5trg4
# 3gQjvlNZlQB3lL+zrPtbNvMA7E0Wkmo+Z6YFnsf7aek+KGzaGboAeFO4uKZjQXY5
# RmMzE70Bwaz7hvA05jDURdRKH0i/1yK96TDuP7JyRFLOvA3UXNWz00R9w7ppMDcN
# lXtrmbPigv3xE9FfpfmJRtiOZQKd73K72Wujmj6/Su3+DBTpOq7NgdntW2lJfX3X
# a6oe4F9Pk9xRhkwHsk7Ju9E/AgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUrg/nt/gj+BBLd1jZWYhok7v5/w4w
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzQ3MDUyODAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAJL5t6pVjIRlQ8j4dAFJ
# ZnMke3rRHeQDOPFxswM47HRvgQa2E1jea2aYiMk1WmdqWnYw1bal4IzRlSVf4czf
# zx2vjOIOiaGllW2ByHkfKApngOzJmAQ8F15xSHPRvNMmvpC3PFLvKMf3y5SyPJxh
# 922TTq0q5epJv1SgZDWlUlHL/Ex1nX8kzBRhHvc6D6F5la+oAO4A3o/ZC05OOgm4
# EJxZP9MqUi5iid2dw4Jg/HvtDpCcLj1GLIhCDaebKegajCJlMhhxnDXrGFLJfX8j
# 7k7LUvrZDsQniJZ3D66K+3SZTLhvwK7dMGVFuUUJUfDifrlCTjKG9mxsPDllfyck
# 4zGnRZv8Jw9RgE1zAghnU14L0vVUNOzi/4bE7wIsiRyIcCcVoXRneBA3n/frLXvd
# jDsbb2lpGu78+s1zbO5N0bhHWq4j5WMutrspBxEhqG2PSBjC5Ypi+jhtfu3+x76N
# mBvsyKuxx9+Hm/ALnlzKxr4KyMR3/z4IRMzA1QyppNk65Ui+jB14g+w4vole33M1
# pVqVckrmSebUkmjnCshCiH12IFgHZF7gRwE4YZrJ7QjxZeoZqHaKsQLRMp653beB
# fHfeva9zJPhBSdVcCW7x9q0c2HVPLJHX9YCUU714I+qtLpDGrdbZxD9mikPqL/To
# /1lDZ0ch8FtePhME7houuoPcMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGYswghmHAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAALLt3U5+wJxQjYAAAAAAsswDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIJzpQZLvywfsdzIjrdnlubAO
# HPdHg41AQzV9DJamlLvqMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAZe0MMq7fOJ6AzuHT8/RPVFzh+GxGReinUvVGWh5tVLYLoxCKa8x0EEEO
# TPfDZu8CIhZH7x7+y7p40nchF81Y8xNljAiHCZbTIQBepe8oj2+CQ0DljeVxUnEX
# 3tUbTFWH9DMTQFeK2dnj2TcJj0xfc0cE4b5F9NJL8HuqU3PRhi9TZQy0INbxHsgl
# vDYZn18R774wpu5clCC4bvsx91Y/DDmDT7Zo1MQNgRCWxqpdc0/xS3mBwN/8Q1iR
# f5RhtHCFgM1bPPVYpqUXB3N6/cUqSb8mFzanmkliJ5Q13jj4f6GduLJrJOV+Xulh
# YNqOwBnpW2JsAiXCuZa5T3hqi6TRFKGCFxUwghcRBgorBgEEAYI3AwMBMYIXATCC
# Fv0GCSqGSIb3DQEHAqCCFu4wghbqAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFYBgsq
# hkiG9w0BCRABBKCCAUcEggFDMIIBPwIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCCO8DB2t5SZFpt8Wf4svTj9b25GZfo/GmGg/Ue4O0NNdQIGYt6MnGXJ
# GBIyMDIyMDgxMDE3MDIzMS41N1owBIACAfSggdikgdUwgdIxCzAJBgNVBAYTAlVT
# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVs
# YW5kIE9wZXJhdGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046
# MTc5RS00QkIwLTgyNDYxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNl
# cnZpY2WgghFlMIIHFDCCBPygAwIBAgITMwAAAYo+OI3SDgL66AABAAABijANBgkq
# hkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ
# MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
# MSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMTEw
# MjgxOTI3NDJaFw0yMzAxMjYxOTI3NDJaMIHSMQswCQYDVQQGEwJVUzETMBEGA1UE
# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z
# b2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVy
# YXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjE3OUUtNEJC
# MC04MjQ2MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIC
# IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAt/+ut6GDAyAZvegBhagWd0Go
# qT8lFHMepoWNOLPPEEoLuya4X3n+K14FvlZwFmKwqap6B+6EkITSjkecTSB6QRA4
# kivdJydlLvKrg8udtBu67LKyjQqwRzDQTRhECxpU30tdBE/AeyP95k7qndhIu/Op
# T4QGyGJUiMDlmZAiDPY5FJkitUgGvwMBHwogJz8FVEBFnViAURTJ4kBDiU6ppbv4
# PI97+vQhpspDK+83gayaiRC3gNTGy3iOie6Psl03cvYIiFcAJRP4O0RkeFlv/SQo
# omz3JtsMd9ooS/XO0vSN9h2DVKONMjaFOgnN5Rk5iCqwmn6qsme+haoR/TrCBS0z
# XjXsWTgkljUBtt17UBbW8RL+9LNw3cjPJ8EYRglMNXCYLM6GzCDXEvE9T//sAv+k
# 1c84tmoiZDZBqBgr/SvL+gVsOz3EoDZQ26qTa1bEn/npxMmXctoZSe8SRDqgK0JU
# WhjKXgnyaOADEB+FtfIi+jdcUJbpPtAL4kWvVSRKipVv8MEuYRLexXEDEBi+V4tf
# KApZhE4ga0p+QCiawHLBZNoj3UQNzM5QVmGai3MnQFbZkhqbUDypo9vaWEeVeO35
# JfdLWjwRgvMX3VKZL57d7jmRjiVlluXjZFLx+rhJL7JYVptOPtF1MAtMYlp6Ougn
# OpG+4W4MGHqj7YYfP0UCAwEAAaOCATYwggEyMB0GA1UdDgQWBBQj2kPY/WwZ1Jeu
# p0lHhD4xkGkkAzAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNV
# HR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2Ny
# bC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYI
# KwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAy
# MDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0G
# CSqGSIb3DQEBCwUAA4ICAQDF9MESsPXDeRtfFo1f575iPfF9ARWbeuuNfM583IfT
# xfzZf2dv/me3DNi/KcNNEnR1TKbZtG7Lsg0cy/pKIEQOJG2fYaWwIIKYwuyDJI2Q
# 4kVi5mzbV/0C5+vQQsQcCvfsM8K5X2ffifJi7tqeG0r58Cjgwe7xBYvguPmjUNxw
# TWvEjZIPfpjVUoaPCl6qqs0eFUb7bcLhzTEEYBnAj8MENhiP5IJd4Pp5lFqHTtpe
# c67YFmGuO/uIA/TjPBfctM5kUI+uzfyh/yIdtDNtkIz+e/xmXSFhiQER0uBjRobQ
# ZV6c+0TNtvRNLayU4u7Eekd7OaDXzQR0RuWGaSiwtN6Xc/PoNP0rezG6Ovcyow1q
# MoUkUEQ7qqD0Qq8QFwK0DKCdZSJtyBKMBpjUYCnNUZbYvTTWm4DXK5RYgf23bVBJ
# W4Xo5w490HHo4TjWNqz17PqPyMCTnM8HcAqTnPeME0dPYvbdwzDMgbumydbJaq/0
# 6FImkJ7KXs9jxqDiE2PTeYnaj82n6Q//PqbHuxxJmwQO4fzdOgVqAEkG1XDmppVK
# W/rJxBN3IxyVr6QP9chY2MYVa0bbACI2dvU+R2QJlE5AjoMKy68WI1pmFT3JKBrr
# acpy6HUjGrtV+/1U52brrElClVy5Fb8+UZWZLp82cuCztJMMSqW+kP5zyVBSvLM+
# 4DCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQEL
# BQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNV
# BAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4X
# DTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMxEzAR
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3Rh
# bXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM
# 57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9cT8dm
# 95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzB
# RMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6GnszrYBb
# fowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCO
# Mcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLVwIYw
# XE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW
# /aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0gg/w
# EPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFphAXPK
# Z6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJYfM2
# BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXbGjfH
# CBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYB
# BAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnPEP8v
# BO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYM
# KwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0
# LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEF
# BQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBW
# BgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUH
# AQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp
# L2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0BAQsF
# AAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518Jx
# Nj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgADsAW+
# iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2
# pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZiefw
# C2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7
# T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RILLFO
# Ry3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgkujhL
# mm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9af3L
# wUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzbaukz5
# m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/OHBE
# 0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggLUMIICPQIB
# ATCCAQChgdikgdUwgdIxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRl
# ZDEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046MTc5RS00QkIwLTgyNDYxJTAjBgNV
# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUrDgMCGgMV
# AIDw82OvG1MFBB2n/4weVqpzV8ShoIGDMIGApH4wfDELMAkGA1UEBhMCVVMxEzAR
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3Rh
# bXAgUENBIDIwMTAwDQYJKoZIhvcNAQEFBQACBQDmniJXMCIYDzIwMjIwODEwMjAy
# NTU5WhgPMjAyMjA4MTEyMDI1NTlaMHQwOgYKKwYBBAGEWQoEATEsMCowCgIFAOae
# IlcCAQAwBwIBAAICD4IwBwIBAAICEcgwCgIFAOafc9cCAQAwNgYKKwYBBAGEWQoE
# AjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkq
# hkiG9w0BAQUFAAOBgQCvg4tUNDNW9Je5f92AwlKy6Sg7oL4RfeOcKBjXkSvEFGkw
# T+qxqEVKDmltq9HEyutIMr0b4idLdZMAB/yy1y3JP7ihAtV/goOAXBO6taXH0Acz
# obotWTsRqfi6HTT7pg5NrsiQKk6mesJWvxuHpESVSzmADaIo4csMFxINiO6tGDGC
# BA0wggQJAgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB
# ij44jdIOAvroAAEAAAGKMA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMx
# DQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIKeihlvf59qKPfp/T8JnfIWu
# j7yByeV8xuEWvqfYel6DMIH6BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCBvQQg9L3g
# q3XfSr5+879/MPgxtZCFBoTtEeQ4foCSOU1UKb0wgZgwgYCkfjB8MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQg
# VGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAYo+OI3SDgL66AABAAABijAiBCAjHYmm
# ZQe2AnbtMuFKTjAjTLGbV8A68IoQsAEE4lEaAzANBgkqhkiG9w0BAQsFAASCAgB8
# Ar4byI0kOLkIp9OV6Kwqp5pQuX9jV2YBKxN/HDT835LtqWie08PTtsNbriIQ0oyw
# e3aXrMlNEU0uPPkCIWhVc4ecZ403hHbnbsVDS9WI9UikVudW7zCdRZz2DBSR4vrm
# YzNXJYzp3AJvc/ZNmP5Jmqoi2TP2NCFYmpYOOlx1HQqTSSgGFBz9UwF304byjvCw
# 0H7NolWFPuk6yILVpFtW/uiwp8rV0MNMTE9bWib4Lx2rfnDi4mv9oYFn8bYDb/ws
# sqy+6lOzksPom5IP7No78TxWDXQLds+3UsGWFlI6nsKi130S5uskITQdghH5Zzkc
# 5UblBja6EZ7v3hdPqCJ+E1ADgWhz8jBHFM+nNNW5IORH3E6xTRSC5q6PI6WWm+z1
# MeJ8LU+hHwj32Tq8oEgXRZlUgNASvRQx7gF1O7KZB6R7E5HJ0WZPKc+lwXs28EMw
# /zM3TsQpkSQNp9j9Yy/Dw33tdNId+EurUcNujagTrDA7zwLFifGsdbGURapFwBz3
# METPX2Va2PF4EyHt33PUay5ffM1lfKEixcUz3sYeDjeVVgaIPYR4qpWeGR5yic3R
# yY1EMmvGMSMTeb5EZ7ZYKTQmqRQ7bsBx8BX2gVheRgnwD//Aykz1prroqfdOLxhV
# 1YSBLF+rL+hI5vFLTElL9yHoxvhGN079TwwqSHe6rg==
# SIG # End signature block