InstallMarvellQLogicFCPowerKit.psm1

#/************************************************************************
#* *
#* NOTICE *
#* *
#* COPYRIGHT 2019-2022 Marvell Semiconductor, Inc *
#* ALL RIGHTS RESERVED *
#* *
#* This computer program is CONFIDENTIAL and contains TRADE SECRETS of *
#* Marvell Semiconductor, Inc. The receipt or possession of this *
#* program does not convey any rights to reproduce or disclose its *
#* contents, or to manufacture, use, or sell anything that it may *
#* describe, in whole or in part, without the specific written consent *
#* of Marvell Semiconductor, Inc. *
#* Any reproduction of this program without the express written consent *
#* of Marvell Semiconductor, Inc is a violation of the copyright laws *
#* and may subject you to civil liability and criminal prosecution. *
#* *
#************************************************************************/

# Script level parameters
Param (
    [Switch] $ConfigureWinRM = $false,  # For silent WinRM configuration
    [Switch] $AddIISAccount = $false,   # Avoids a prompt - still need to enter username and password though.
    [Switch] $SkipEnablingWindowsFeaturesDEBUG = $false
)


# Helper Functions

Function Out-Log {
    # This function controls console output, displaying it only if the Verbose variable is set.
    # This function now has another parameter, Level. Commands that may output messages now have a level parameter set. Key is below.
    # Level = 0 - Completely silent except for success / failure messages.
    # Level = 1 - Default if $Script:verbose is not set, about 5 output messages per added appx.
    # Level = 2 - Level 1 + Appcmd output messages, Add-AppxPackage messages
    # Level = 3 - Level 2 + Registry, XML, enabling of features, WinRM, setx, IO, netsh, etc. Very verbose.
    Param (
        [Parameter(Mandatory=$true)] [AllowNull()] $Data,
        [Parameter(Mandatory=$true)] [AllowNull()] $Level,
        [Parameter(Mandatory=$false)] [AllowNull()] $ForegroundColor,
        [Parameter(Mandatory=$false)] [AllowNull()] [Switch] $NoNewline
    )
    if ($Script:Verbose -eq $null) { $Script:Verbose = 1 }
    if ($Data -eq $null) { return }
    $Data = ($Data | Out-String)
    $Data = $Data.Substring(0, $Data.Length - 2)  # Remove extra `n from Out-String (only one)
    $Data = $Data.Replace('"', '`"')
    $expression = "Write-Host -Object `"$Data`""
    if ($ForegroundColor -ne $null) { $expression += " -ForegroundColor $ForegroundColor" }
    if ($NoNewline) { $expression += " -NoNewline" }
    if ($Level -le $Script:verbose) {
        Invoke-Expression $expression
    }
}

Function Test-ExecutionPolicy {
    # This function checks to make sure the ExecutionPolicy is not Unrestricted and if it is, prompts the user to change it to either RemoteSigned or Bypass.
    # If the ExecutionPolicy is left at Unrestricted, the user will receive one prompt for every cmdlet each time they open a new PowerShell window.
    $currentExecutionPolicy = Get-ExecutionPolicy
    if ($currentExecutionPolicy -eq 'Unrestricted') {

        $title = 'Execution Policy Configuration'
        $message = "Your current execution policy is $currentExecutionPolicy. This policy causes PowerShell to prompt you every session for every cmdlet. Set the execution policy to RemoteSigned or Bypass to avoid this."
        $remoteSigned = New-Object System.Management.Automation.Host.ChoiceDescription '&RemoteSigned', `
            'Sets the execution policy to RemoteSigned.'
        $bypass = New-Object System.Management.Automation.Host.ChoiceDescription '&Bypass', `
            'Sets the execution policy to Bypass.'
        $ignoreAndContinue = New-Object System.Management.Automation.Host.ChoiceDescription '&Ignore and Continue', `
            'Does not change the execution policy.'
        $options = [System.Management.Automation.Host.ChoiceDescription[]]($remoteSigned, $bypass, $ignoreAndContinue)
        $tempResult = $host.ui.PromptForChoice($title, $message, $options, 0)


        if ($tempResult -eq 0) { $newExecutionPolicy = 'RemoteSigned' }
        if ($tempResult -eq 1) { $newExecutionPolicy = 'Bypass' }
        if ($tempResult -eq 2) { return }

        # If the user has set a CurrentUser sceope level ExecutionPolicy, then change this to RemoteSigned/Bypass as well.
        if ((Get-ExecutionPolicy -Scope CurrentUser) -ne 'Undefined') {
            Set-ExecutionPolicy -ExecutionPolicy $newExecutionPolicy -Scope CurrentUser -Force
        }
        Set-ExecutionPolicy -ExecutionPolicy $newExecutionPolicy -Scope LocalMachine -Force

        Out-Log -Data "ExecutionPolicy changed from $currentExecutionPolicy to $(Get-ExecutionPolicy)." -Level 0
    }
}


Function Get-CmdletsAppxFileName {
    return '.\MRVLFCProvider-WindowsServer.appx'
}

Function Add-ManyToRegistryWriteOver {
    # This function changes multiple registry values at once.
    # Path is the path to the registry key to change, e.g. "HKLM:\Software\Classes\CLSID\{A31B8A5E-8B6A-421C-8BB1-F85C9C34E659}\InprocServer32"
    # NameTypeValueArray is an array of arrays of size 3 containing Name, Type, and Value for the registry entry, e.g.
        # @(
            # @('(Default)', 'String', "C:\Program Files\WindowsApps\MRVLFCProvider_1.0.1.0_x64_NorthAmerica_yym61f5wjvd3m\MRVLFCProvider.dll"),
            # @('ThreadingModel', 'String', 'Free')
        # )

    Param (
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Path,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $NameTypeValueArray
    )
    # if path doesn't exist, create it
    if (-not (Test-Path -LiteralPath $Path)) {
        Out-Log -Data (New-Item -Path $Path -Force 2>&1) -Level 3
    }

    $arr = $NameTypeValueArray
    if ($arr[0] -isnot [System.Array]) {
        Out-Log -Data (New-ItemProperty -LiteralPath $Path -Name $arr[0] -Type $arr[1] -Value $arr[2] -Force 2>&1) -Level 3
        return
    }
    foreach ($ntv in $arr) {
        Out-Log -Data (New-ItemProperty -LiteralPath $Path -Name $ntv[0] -Type $ntv[1] -Value $ntv[2] -Force 2>&1) -Level 3
    }
}

Function Test-RegistryValue {
    Param (
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Path,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Value
    )

    try {
        Out-Log -Data (Get-ItemProperty -Path $Path | Select-Object -ExpandProperty $Value -ErrorAction Stop 2>&1) -Level 3
        return $true
    } catch {
        return $false
    }
}


Function Convert-CredentialToHeaderAuthorizationString {
    Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Credential )
    return [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("$($Credential.UserName)`:$(ConvertFrom-SecureToPlain -SecurePassword $Credential.Password)"))
}

Function Get-AppxPackageWrapper {
    # This function tries the Get-AppxPackage command several times before really failing.
    # This is necessary to prevent some intermittent issues with Get-AppxPackage.
    # This may no longer be necessary as it seems to only happen after an Add-AppxProvisionedPackage which is no longer used.
    Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $AppxName )

    $numAttempts = 3
    $numSecondsBetweenAttempts = 5
    for ($i=0; $i -lt $numAttempts; $i++) {
        $appxPkg = Get-AppxPackage | Where-Object { $_.name -eq $AppxName }
        if ($appxPkg -ne $null) { return $appxPkg }
        Out-Log -Data ("Couldn't find Appx package. Trying again in $numSecondsBetweenAttempts seconds (attempt $($i+1) of $numAttempts) ...") -ForegroundColor DarkRed -Level 1
        Start-Sleep -Seconds $numSecondsBetweenAttempts
    }
    Out-Log -Data 'Failed to find Appx package. Please try running this script again.' -ForegroundColor Red -Level 0
    Exit
}

Function Invoke-IOWrapper {
    # This function tries the Expression several times before really failing.
    # This is necessary to prevent some intermittent issues with certain IO commands, especially Copy-Item.
    Param (
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Expression,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $SuccessMessage,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $FailMessage
    )

    $numAttempts = 5
    $numSecondsBetweenAttempts = 1
    $Expression += ' -ErrorAction Stop'
    for ($i=0; $i -lt $numAttempts; $i++) {
        try {
            Invoke-Expression $Expression
            if ($i -ne 0) { Out-Log -Data 'Success!' -ForegroundColor DarkGreen -Level 3 }
            Out-Log -Data $SuccessMessage -Level 1
            return
        } catch {
            if ($i -ne 0) { Out-Log -Data 'Failed.' -ForegroundColor DarkRed -Level 3 }
            Out-Log -Data "$FailMessage Trying again in $numSecondsBetweenAttempts seconds (attempt $($i+1) of $numAttempts) ... " -ForegroundColor DarkRed -NoNewline -Level 3
            Start-Sleep -Seconds $numSecondsBetweenAttempts
            $Global:err = $_
        }
    }
    Out-Log -Data 'Failed.' -ForegroundColor DarkRed -Level 3
    Out-Log -Data "$FailMessage Failed after $numAttempts attempts with $numSecondsBetweenAttempts second(s) between each attempt." -ForegroundColor Red -Level 0
    Out-Log -Data "Failed to install $Script:CurrentInstallation." -ForegroundColor Red -Level 0
    Exit
}


# Prompts
Function Set-WinRMConfigurationPrompt {
    if ($ConfigureWinRM) { return $true }

    $title = 'WinRM Configuration'
    $message = 'Do you want to configure WinRM in order to connect to Linux machines remotely?'
    $yes = New-Object System.Management.Automation.Host.ChoiceDescription '&Yes', `
        'Configures WinRM to allow remote connections to Linux machines.'
    $no = New-Object System.Management.Automation.Host.ChoiceDescription '&No', `
        'Does not configure WinRM. Remote connection to Linux machines will not be possible.'
    $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
    $tempResult = $host.ui.PromptForChoice($title, $message, $options, 0)
    if ($tempResult -eq 0) { return $true } else { return $false }
}


Function Get-CredentialWrapper {
    Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $AddAccountToIISResponse )
    if (-not $AddAccountToIISResponse) { return $null }

    try {
        return Get-Credential -Message 'Enter the credentials for the account you would like to be added to IIS' -UserName $env:USERNAME
    } catch {
        Out-Log -Data 'No credentials supplied; no account will be added to IIS.' -Level 1
    }
}


# Main Functions

Function Add-AppxTrustedAppsRegistryKey {
    # This function remembers user's current AllowAllTrustedApps registry setting and then changes it to
    # allow installation of all trusted apps.

    # Remember user's registry setting before modify it to be able to set it back later.
    $appxPolicyRegKey = 'HKLM:\Software\Policies\Microsoft\Windows\Appx'
    $appxPolicyRegValName = 'AllowAllTrustedApps'
    if (Test-Path -Path $appxPolicyRegKey) {
        $appxPolicyRegValData = (Get-ItemProperty -Path $appxPolicyRegKey).$appxPolicyRegValName
        if ($appxPolicyRegValData -ne $null) {
            $Script:savedAppxPolicyRegValData = $appxPolicyRegValData
        }
        Start-Sleep -Milliseconds 1000  # Otherwise access denied error when do Add-ManyToRegistryWriteOver below
    }

    Add-ManyToRegistryWriteOver -Path $appxPolicyRegKey -NameTypeValueArray @(
        @($appxPolicyRegValName, 'DWord', 1) )
}

Function Remove-CLSIDRegistryPaths {
    # if (-not $Script:isNano) { return } # Skip if NOT Nano
    if (Test-Path -Path "Registry::HKCR\CLSID\$Script:CLSID") {
        Remove-Item -Path "Registry::HKCR\CLSID\$Script:CLSID" -Recurse
    }
    if (Test-Path -Path "HKLM:\Software\Classes\CLSID\$Script:CLSID") {
        Remove-Item -Path "HKLM:\Software\Classes\CLSID\$Script:CLSID" -Recurse
    }
}

Function Add-AppxPackageWrapper {
    Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $AppxFileName )

    $savedProgressPreference = $Global:ProgressPreference
    $Global:ProgressPreference = 'SilentlyContinue'
    Out-Log -Data (Add-AppxPackage $AppxFileName 2>&1) -Level 2
    $Global:ProgressPreference = $savedProgressPreference
    Out-Log -Data "Added '$($AppxFileName.Replace('.\', ''))' AppxPackage." -Level 1
}

Function Add-CLSIDRegistryPathsAndMofCompInstall {
    # if ($Script:isNano) { return } # Skip if Nano
    # Perform steps of Register-CimProvider.exe -Namespace Root/qlgcfc -ProviderName MRVLFCProvider -Path MRVLFCProvider.dll -verbose -ForceUpdate -HostingModel LocalSystemHost
    $appxPath = (Get-AppxPackageWrapper -AppxName 'MRVLFCProvider').InstallLocation
    $mofcompOutput = & mofcomp.exe -N:root\qlgcfc $appxPath\MRVLFCProvider.mof 2>&1
    if ($mofcompOutput -match 'Error') {
        Out-Log -Data "Failed to register '$appxPath\MRVLFCProvider.mof': $($mofcompOutput -match 'Error')" -ForegroundColor Red -Level 1
    }

    Add-ManyToRegistryWriteOver -Path "HKLM:\Software\Classes\CLSID\$Script:CLSID" -NameTypeValueArray @(
        @('(Default)', 'String', 'MRVLFCProvider') )
    Add-ManyToRegistryWriteOver -Path "HKLM:\Software\Classes\CLSID\$Script:CLSID\InprocServer32" -NameTypeValueArray @(
        @('(Default)', 'String', "$appxPath\MRVLFCProvider.dll"),
        @('ThreadingModel', 'String', 'Free') )
}

Function Remove-AppxTrustedAppsRegistryKey {

    $appxPolicyRegKey = 'HKLM:\Software\Policies\Microsoft\Windows\Appx'
    $appxPolicyRegValName = 'AllowAllTrustedApps'

    # If user previously had this registry key, set it back to what it was before. Otherwise, just delete the registry key.
    if ($Script:savedAppxPolicyRegValData -ne $null) {
        Add-ManyToRegistryWriteOver -Path $appxPolicyRegKey -NameTypeValueArray @(
            @($appxPolicyRegValName, 'DWord', $Script:savedAppxPolicyRegValData) )
    } else {
        Remove-ItemProperty -Path $appxPolicyRegKey -Name $appxPolicyRegValName
    }
}

Function Copy-CmdletsToProgramData {
    # Check for ProgramData directories with old versions and remove them
    $oldPaths = (Get-ChildItem $env:ProgramData | Where-Object { ($_.Name).StartsWith('MRVLFCProvider_') }).FullName
    foreach ($oldPath in $oldPaths) {
        Remove-Item $oldPath -Recurse -Force
    }

    # Copy cmdlets to ProgramData
    $appxPkg = Get-AppxPackageWrapper -AppxName 'MRVLFCProvider'
    $cmdletsPath = $appxPkg.InstallLocation + '\Cmdlets\'
    if (Test-Path $cmdletsPath) {
        $cmdletsDestPath = "$env:ProgramData\$($appxPkg.PackageFullName)"
        Invoke-IOWrapper -Expression "Copy-Item -Path `"$cmdletsPath`" -Destination $cmdletsDestPath -Recurse -Force" `
            -SuccessMessage "Cmdlets copied to '$cmdletsDestPath'." `
            -FailMessage "Failed to copy FC cmdlets."
    }
}


Function Add-CmdletsPathToPSModulePath {
    #$p = [Environment]::GetEnvironmentVariable("PSModulePath")
    #Out-Log -Data "PSModulePath = '$p'" -Level 1
    # This function adds cmdlet module path to system PSModulePath environment variable permanently.
    # Out-Log -Data "PSModulePath = '$appxCmdletsPath' to PSModulePath." -Level 1
    $appxCmdletsPath = (Get-AppxPackageWrapper -AppxName 'MRVLFCProvider').InstallLocation + '\Cmdlets'
    if (Test-RegistryValue -Path 'HKLM:\System\CurrentControlSet\Control\Session Manager\Environment' -Value 'PSModulePath') {
        $currPSModulePathArr = [Environment]::GetEnvironmentVariable("PSModulePath").Split(';')
        #Out-Log -Data "currPSModulePathArr '$currPSModulePathArr'" -Level 1
        $newPSModulePathArr = @()
        # Remove any old paths with MRVLFCProvider from PSModulePath
        foreach ($path in $currPSModulePathArr) {
            if (-not $path.Contains('MRVLFCProvider')) {
                # Out-Log -Data "path '$path'" -Level 1
                $newPSModulePathArr += $path
            }
        }
        # Add new cmdlet path to PSModulePath
        $newPSModulePathArr += $appxCmdletsPath  # Skip for uninstall script
        $newPSModulePathStr = $newPSModulePathArr -join ';'

        #Out-Log -Data "new PSModulePath '$newPSModulePathArr'" -Level 1
        # Write PSModulePath to registry and set local session variable
        Out-Log -Data (& setx /s $env:COMPUTERNAME PSModulePath $newPSModulePathStr /m 2>&1) -Level 3
        $env:PSModulePath = $newPSModulePathStr
        Out-Log -Data "Added '$appxCmdletsPath' to PSModulePath." -Level 1

    } else {
        # No PSModulePath registry key/value exists, so don't worry about modifying it. Just create a new one.
        Out-Log -Data (& setx /s $env:COMPUTERNAME PSModulePath $appxCmdletsPath /m 2>&1) -Level 3
        $env:PSModulePath = $appxCmdletsPath
        Out-Log -Data "Added '$appxCmdletsPath' to PSModulePath." -Level 1
    }
}

Function Set-WinRMConfiguration {
    Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $SetWinRMConfigurationResponse )
    if ($SetWinRMConfigurationResponse) {
        Out-Log -Data (& winrm set winrm/config/client '@{AllowUnencrypted="true"}' 2>&1) -Level 3
        Out-Log -Data (& winrm set winrm/config/client '@{TrustedHosts="*"}' 2>&1) -Level 3
        Out-Log -Data 'WinRM successfully configured.' -Level 1
    }
}


Function Test-PowerShellVersion {
    # This function tests the version of PowerShell / Windows Management Framework and prompts the user
      # to download a newer version if necessary.
    if ($PSVersionTable.PSVersion.Major -ge 4) { return }

    Out-Log -Data 'Failed to install FastLinq REST API PowerKit: Windows Management Framework 4.0+ required.' -ForegroundColor Red -Level 0
    Out-Log -Data ' Please install WMF 4.0 or higher and run this script again.' -ForegroundColor Red -Level 0
    Out-Log -Data ' URL for WMF 4.0: https://www.microsoft.com/en-us/download/details.aspx?id=40855' -ForegroundColor Red -Level 0
    Out-Log -Data ' URL for WMF 5.0: https://www.microsoft.com/en-us/download/details.aspx?id=50395' -ForegroundColor Red -NoNewline -Level 0

    $title = 'Download Windows Management Framework 4.0+'
    $message = 'Windows Management Framework 4.0+ is required for FastLinq REST API PowerKit. Which WMF version would you like to download?'
    $4 = New-Object System.Management.Automation.Host.ChoiceDescription '&4.0', `
        'Windows Management Framework 4.0 (https://www.microsoft.com/en-us/download/details.aspx?id=40855)'
    $5 = New-Object System.Management.Automation.Host.ChoiceDescription '&5.0', `
        'Windows Management Framework 5.0 (https://www.microsoft.com/en-us/download/details.aspx?id=50395'
    $none = New-Object System.Management.Automation.Host.ChoiceDescription '&None', `
        'None'
    $options = [System.Management.Automation.Host.ChoiceDescription[]]($4, $5, $none)
    $tempResult = $host.ui.PromptForChoice($title, $message, $options, 1)
    if ($tempResult -eq 0) {
        Start-Process 'https://www.microsoft.com/en-us/download/details.aspx?id=40855'
    } elseif ($tempResult -eq 1) {
        Start-Process 'https://www.microsoft.com/en-us/download/details.aspx?id=50395'
    }
    # TODO - Add script exiting message.
    Exit
}

Function Enable-WindowsFeatures {
    # This function enables the Windows features required for REST API to work.
    # This function does not check if the Windows feature is already enabled or not,
    # but enabling an already-enabled feature is not harmful.
    if ( $SkipEnablingWindowsFeaturesDEBUG) { return }
    Out-Log -Data 'Installing Management OData Service...' -Level 1

    $allFeatureNames = 'ManagementOdata, WCF-HTTP-Activation45, IIS-BasicAuthentication, IIS-WindowsAuthentication, IIS-WebServerManagementTools, IIS-ManagementConsole, IIS-ManagementScriptingTools, IIS-ManagementService, IIS-IIS6ManagementCompatibility, IIS-Metabase, IIS-WMICompatibility, IIS-LegacyScripts, IIS-LegacySnapIn' -split ', '
    for ($i=0; $i -lt $allFeatureNames.Count; $i++) {
        $featureName = $allFeatureNames[$i]
        Write-Progress -Activity 'Enabling required Windows features' -Status "Enabling $featureName ($($i+1) of $($allFeatureNames.Count))" -PercentComplete ($i / $allFeatureNames.Count * 100)
        Out-Log -Data (Enable-WindowsOptionalFeature -Online -FeatureName $featureName -All -NoRestart 2>&1) -Level 3
    }
    Write-Progress -Activity "Completed" -Completed
}

Function Copy-DependentLibsAndFilesToWbemPath 
{
    $appxPkg = Get-AppxPackageWrapper -AppxName 'MRVLFCProvider'
    $dependentLibPath = $appxPkg.InstallLocation

    if (Test-Path $dependentLibPath)
    {
        $dependentLibDestPath = "C:\Windows\System32\wbem"
        
        # Copy ql2xhai2.dll to "C:\Windows\System32\wbem"
        Invoke-IOWrapper -Expression "Copy-Item -Path `"$dependentLibPath\ql2xhai2.dll`" -Destination `"$dependentLibDestPath`" -Recurse -Force" `
            -SuccessMessage "Copied dependent library to Wbem path : '$dependentLibDestPath'." `
            -FailMessage "Failed to copy dependent library : '$dependentLibDestPath'. Please uninstall the old version if present. Please stop 'WMI Provider Host' if running."
            

        # Copy adapters_adapter_provider.properties to "C:\Windows\System32\wbem"
        Invoke-IOWrapper -Expression "Copy-Item -Path `"$dependentLibPath\adapters_adapter_provider.properties`" -Destination `"$dependentLibDestPath`" -Recurse -Force" `
            -SuccessMessage "Copied dependent file - adapters_adapter_provider.properties to Wbem path : '$dependentLibDestPath'." `
            -FailMessage "Failed to copy dependent file - adapters_adapter_provider.properties : '$dependentLibDestPath'. Please uninstall the old version if present. Please stop 'WMI Provider Host' if running."
            
            
        # Copy adapters_cna_provider.properties to "C:\Windows\System32\wbem"
        Invoke-IOWrapper -Expression "Copy-Item -Path `"$dependentLibPath\adapters_cna_provider.properties`" -Destination `"$dependentLibDestPath`" -Recurse -Force" `
            -SuccessMessage "Copied dependent file - adapters_cna_provider.properties to Wbem path : '$dependentLibDestPath'." `
            -FailMessage "Failed to copy dependent file - adapters_cna_provider.properties : '$dependentLibDestPath'. Please uninstall the old version if present. Please stop 'WMI Provider Host' if running."
            
            
        # Copy nvramdefs to "C:\Windows\System32\wbem"
        Invoke-IOWrapper -Expression "Copy-Item -Path `"$dependentLibPath\nvramdefs`" -Destination `"$dependentLibDestPath\nvramdefs`" -Recurse -Force" `
            -SuccessMessage "Copied dependent files - nvramdefs to Wbem path : '$dependentLibDestPath'." `
            -FailMessage "Failed to copy dependent files - nvramdefs : '$dependentLibDestPath'. Please uninstall the old version if present. Please stop 'WMI Provider Host' if running."              
    }
}

Function Install-WebServiceEndPoint {
    # This function configures IIS and sets up the web service endpoint and is an adaptation of the
    # 'SetupIISConfig.ps1' from the 'Windows Powershell role-based OData Web Service sample' project.
    # This is a massive function containing several helper functions within it.
    Param (
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Site,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Path,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Cfgfile,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Port,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $App,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $AppPool,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Svc,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Schema,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $DispatchXml,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Rbac,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $CustomPluginAssembly
    )

    Function Backup-IISConfig {
        $dt = Get-Date
        $backupID = "MODataIISSetup_$($dt.Year)-$($dt.Month)-$($dt.Day)-$($dt.Hour)-$($dt.Minute)-$($dt.Second)"
        Out-Log -Data (& $Script:appcmd add backup $backupID 2>&1) -Level 2
    }

    Function Copy-FilesToCorrectLocation {
        if (-not (Test-Path $Path)) {
            Out-Log -Data (New-Item -Path $Path -ItemType Container 2>&1) -Level 3
        }
        Copy-Item -Path $Cfgfile                -Destination (Join-Path $Path 'web.config')
        Copy-Item -Path $Svc                    -Destination $Path
        Copy-Item -Path $Schema                 -Destination $Path
        Copy-Item -Path $DispatchXml            -Destination $Path
        Copy-Item -Path $Rbac                   -Destination (Join-Path $Path 'RbacConfiguration.xml')
        Copy-Item -Path $CustomPluginAssembly   -Destination $Path
    }

    Function Invoke-ActionAllSites {
        Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Action )
        $siteInfoArray = (& $Script:appcmd list site)
        if ($siteInfoArray -eq $null) { return }
        foreach ($siteInfo in $siteInfoArray.Split("`n")) {
            $siteName = $siteInfo.split('"')[1]
            Invoke-ActionOneSite -SiteAction $Action -SiteName $siteName
        }
    }

    Function Invoke-ActionOneSite {
        Param (
            [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $SiteAction,
            [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $SiteName
        )
        Out-Log -Data (& $Script:appcmd $SiteAction site $SiteName 2>&1) -Level 2
    }

    Function New-SiteID {
        # Determine an unused ID number to use.
        $siteInfoArray = (& $Script:appcmd list site)
        if ($siteInfoArray -eq $null) { return 1 }
        $largestId = 0
        foreach ($siteInfo in $siteInfoArray.Split("`n")) {
            $i = $siteInfo.IndexOf('id:') + 3
            $c = $siteInfo.IndexOf(',', $i) - $i
            $id = $siteInfo.Substring($i, $c)
            if ($id -gt $largestId) { $largestId = $id }
        }
        return [int]$largestId + 1
    }

    Function Unlock-AuthSections {
        Out-Log -Data (& $Script:appcmd unlock config -section:access 2>&1) -Level 2
        Out-Log -Data (& $Script:appcmd unlock config -section:anonymousAuthentication 2>&1) -Level 2
        Out-Log -Data (& $Script:appcmd unlock config -section:basicAuthentication 2>&1) -Level 2
        Out-Log -Data (& $Script:appcmd unlock config -section:windowsAuthentication 2>&1) -Level 2
    }

    Function Set-RequiredAuthentication {
        Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $EffVdir )
        Out-Log -Data (& $Script:appcmd set config $EffVdir /section:system.webServer/security/authentication/anonymousAuthentication /enabled:false /commit:apphost 2>&1) -Level 2
        Out-Log -Data (& $Script:appcmd set config $EffVdir /section:system.webServer/security/authentication/basicAuthentication /enabled:true /commit:apphost 2>&1) -Level 2
        Out-Log -Data (& $Script:appcmd set config $EffVdir /section:system.webServer/security/authentication/windowsAuthentication /enabled:true /commit:apphost 2>&1) -Level 2
    }

    Function New-FirewallRule {
        Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $FirewallPort )
        # Remove any existing firewall rules
        if ((Get-NetFirewallRule | Where-Object { ($_.DisplayName).StartsWith('MOData_IIS_Port') }) -ne $null) {
            Out-Log -Data (Remove-NetFirewallRule -DisplayName 'MOData_IIS_Port') -Level 3
        }
        # Add new firewall rule
        Out-Log -Data (New-NetFirewallRule -DisplayName 'MOData_IIS_Port' -Direction Inbound -Action Allow -Protocol TCP -LocalPort $FirewallPort) -Level 3
    }

    # TODO Check if all the params above which are files exist before continuing
    # TODO Check if IIS is installed before continuing

    Out-Log -Data 'Setting up web service endpoint ...' -Level 1

    $appxPkg = Get-AppxPackageWrapper -AppxName 'MRVLFCProviderREST'
    $restFilesPath = "$env:ProgramData\$($appxPkg.PackageFullName)"
    Push-Location -Path $restFilesPath


    Out-Log -Data (& $Script:appcmd delete apppool MOData 2>&1) -Level 2
    Out-Log -Data (& $Script:appcmd delete site $Site 2>&1) -Level 2
    Backup-IISConfig
    Copy-FilesToCorrectLocation

    Invoke-ActionAllSites -Action 'Stop'

    Out-Log -Data (& $Script:appcmd stop apppool /apppool.name:DefaultAppPool 2>&1) -Level 2
    Out-Log -Data (& $Script:appcmd start apppool /apppool.name:DefaultAppPool 2>&1) -Level 2

    $siteID = New-SiteID

    Out-Log -Data (& $Script:appcmd add apppool /name:$AppPool 2>&1) -Level 2
    Out-Log -Data (& $Script:appcmd set apppool /apppool.name:$AppPool /managedRuntimeVersion:"v4.0" 2>&1) -Level 2
    Out-Log -Data (& $Script:appcmd add site /name:$Site /id:$siteID /bindings:http://*:$Port /physicalPath:$env:SystemDrive\inetpub\wwwroot 2>&1) -Level 2

    Out-Log -Data (& $Script:appcmd set site /site.name:$Site /[path=`'/`'].applicationPool:$AppPool 2>&1) -Level 2
    Out-Log -Data (& $Script:appcmd delete app $site/$app 2>&1) -Level 2
    Out-Log -Data (& $Script:appcmd add app /site.name:$site /path:/$app /physicalPath:$path 2>&1) -Level 2
    Out-Log -Data (& $Script:appcmd set app /app.name:$site/$app /applicationPool:$AppPool 2>&1) -Level 2

    Unlock-AuthSections
    $effVdir = "$site"
    Set-RequiredAuthentication -EffVdir $effVdir

    Invoke-ActionOneSite -SiteAction 'Start' -SiteName $Site

    New-FirewallRule -FirewallPort $port

    Invoke-ActionAllSites -Action 'Start'

    Out-Log -Data 'Web Service endpoint is setup. The source root URI is:' -Level 0
    Out-Log -Data " http://localhost:$Port/$Site/$($Svc.Substring(2, $Svc.Length - 2))/" -Level 0 -ForegroundColor Gray

    Pop-Location

    Start-Sleep -Seconds 5
}

Function Add-AccountToIIS {
    Param ( [Parameter(Mandatory=$true)] [AllowNull()] $Credential )
    if ($Credential -eq $null) {
        Out-Log -Data "No account added to IIS.`n" -Level 1
        return
    }
    Out-Log -Data (& $Script:appcmd set config MODataSvc/MODataSvc -section:system.web/identity /impersonate:false 2>&1) -Level 2

    Out-Log -Data (& $Script:appcmd set apppool MOData /processModel.identityType:SpecificUser 2>&1) -Level 2
    Out-Log -Data (& $Script:appcmd set apppool MOData /processModel.userName:$($Credential.UserName) 2>&1) -Level 2
    Out-Log -Data (& $Script:appcmd set apppool MOData /processModel.password:$(ConvertFrom-SecureToPlain -SecurePassword $Credential.Password) 2>&1) -Level 2

    Out-Log -Data (& $Script:appcmd start apppool /apppool.name:MOData 2>&1) -Level 2

    Out-Log -Data "Account '$($Credential.UserName)' added to IIS." -Level 1

    $encodedAuthString = Convert-CredentialToHeaderAuthorizationString -Credential $Credential
    Out-Log -Data 'Use this string in the REST API request header:' -Level 0
    Out-Log -Data " Authorization: Basic $encodedAuthString" -Level 0 -ForegroundColor Gray
}

Function Test-RESTAPI {
    Param ( [Parameter(Mandatory=$true)] [AllowNull()] $Credential )
    if ($Credential -eq $null) { return }

    $encodedAuthString = Convert-CredentialToHeaderAuthorizationString -Credential $Credential
    $headers = @{
        'Authorization' = "Basic $encodedAuthString";
        'Content-Type' = 'application/json';
        'Accept' = 'application/json,application/xml';
    }
    $url = "http://localhost:7000/MODataSvc/Microsoft.Management.OData.svc/QLGCFCAdapter?"

    Out-Log -Data "Testing REST API with $url" -ForegroundColor Cyan -Level 1

    try {
        $response = (Invoke-RestMethod -Method Get -Uri $url -Headers $headers -Credential $Credential).value
        $response = ($response | Out-String).Trim()
        Out-Log -Data "`n$response`n" -Level 1
    } catch {
        $Global:errRest = $_
        Out-Log -Data "REST API test failed" -ForegroundColor Red -Level 0
        Out-Log -Data " StatusCode: $($_.Exception.Response.StatusCode.value__)" -ForegroundColor Red -Level 0
        Out-Log -Data " StatusDescription: $($_.Exception.Response.StatusDescription)" -ForegroundColor Red -Level 0
    }
}

Function Import-AllCmdlets {
    # This function imports all MRVLFC cmdlets into the current PowerShell session.
    # This function is necessary to update any cmdlet and cmdlet help changes that have occurred from one PowerKit
    # version to another. One consequence of force importing the MRVLFC cmdlets is the user will be prompted if they
    # want to run software from an unpublished publisher when this function is called rather than the first time
    # intellisense is invoked after the PowerKit is installed. Note that this prompt only occurs if it is the user's
    # first time installing the PowerKit. Of course, in this scenario this function call is not necessary, since the
    # cmdlets will not need to be refreshed.
    # TODO - Determine if this is the first time PowerKit has been installed / if user already trusts QLGC publisher.
    $moduleNames = (Get-Module -ListAvailable | Where-Object { $_.Name.StartsWith('QLGCFC_') }).Name
    foreach ($moduleName in $moduleNames) {
        Import-Module $moduleName -Force
    }
}

Function Test-LastExitCode {
    Param (
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $ExitCode,
        [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Message,
        [Parameter(Mandatory=$false)] [AllowNull()] [Switch] $ExitScriptIfBadErrorCode
    )
    if ($ExitCode -eq 0) { return; }

    if (-not $ExitScriptIfBadErrorCode) {
        Out-Log -Data "WARNING: $Message" -ForegroundColor Yellow -Level 1
    } else {
        Out-Log -Data "ERROR: $Message" -ForegroundColor Red -Level 1
    }
}

Function Install-FCPowerKit {
    Param ( $response0, $response1, $response2 )
    # Encapulate installer logic here
    #--------------------------------------------------------------------------------------------------
    # Globals
    $Script:CLSID = '{A31B8A5E-8B6A-421C-8BB1-F85C9C34E659}'
    $Script:savedAppxPolicyRegValData = $null
    $cmdletsAppxFileName = Get-CmdletsAppxFileName
    # $restAppxFileName = '.\MRVLFCProviderREST-WindowsServer.appx'
    $Script:appcmd = "$env:SystemRoot\system32\inetsrv\appcmd.exe"
    $Script:netsh = "$env:windir\system32\netsh.exe"
    $Script:Verbose = 1


    #--------------------------------------------------------------------------------------------------
    # User Input
    Test-ExecutionPolicy
    if ((Get-AppxPackage -Name 'MRVLFCProvider') -ne $null) {
       Test-LastExitCode -ExitCode 1 -Message "Could not install PowerKit because it is already installed! Please uninstall the PowerKit first." -ExitScriptIfBadErrorCode
    }
    else {

        $response0 = Set-WinRMConfigurationPrompt

        #$response1 = Install-PowerShellODataPrompt
        #$response2 = Add-AccountToIISPrompt -InstallPSODataResponse $response1
        #$response0 = $WinRMConfiguration
        #$response1 = $Configure_IIS_OData_RESTAPI
        #$response2 = $AddAccountToIIS
        #$cred = $Credential


        #--------------------------------------------------------------------------------------------------
        # Script - Cmdlets
        $Script:CurrentInstallation = 'MRVL FC PowerKit'
        Add-AppxTrustedAppsRegistryKey
        Remove-CLSIDRegistryPaths
        Add-AppxPackageWrapper -AppxFileName $cmdletsAppxFileName
        Add-CLSIDRegistryPathsAndMofCompInstall
        Remove-AppxTrustedAppsRegistryKey
        Copy-DependentLibsAndFilesToWbemPath
        Copy-CmdletsToProgramData
        Add-CmdletsPathToPSModulePath
        Set-WinRMConfiguration -SetWinRMConfigurationResponse $response0
        Out-Log -Data "Successfully installed MarvellQLogicFCPowerKit.`n" -ForegroundColor Green -Level 0
     
        #--------------------------------------------------------------------------------------------------
        Import-AllCmdlets
        Out-Log -Data "Note: if this was an upgrade from a previous version, you may need to restart the system for the new provider to be loaded." -ForegroundColor Yellow -Level 0
    }
}

Export-ModuleMember -function Install-FCPowerKit



# SIG # Begin signature block
# MIIqIAYJKoZIhvcNAQcCoIIqETCCKg0CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAe/46IPIpg8zyP
# kdU5/q53F2pZT5KdAnq6/5kbRyJsXaCCDoswggawMIIEmKADAgECAhAIrUCyYNKc
# TJ9ezam9k67ZMA0GCSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNV
# BAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0yMTA0MjkwMDAwMDBaFw0z
# NjA0MjgyMzU5NTlaMGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwg
# SW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcg
# UlNBNDA5NiBTSEEzODQgMjAyMSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
# ggIKAoICAQDVtC9C0CiteLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYwn6SOaNhc9es0
# JAfhS0/TeEP0F9ce2vnS1WcaUk8OoVf8iJnBkcyBAz5NcCRks43iCH00fUyAVxJr
# Q5qZ8sU7H/Lvy0daE6ZMswEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1hz1RGeiQIXhF
# LqGfLOEYwhrMxe6TSXBCMo/7xuoc82VokaJNTIIRSFJo3hC9FFdd6BgTZcV/sk+F
# LEikVoQ11vkunKoAFdE3/hoGlMJ8yOobMubKwvSnowMOdKWvObarYBLj6Na59zHh
# 3K3kGKDYwSNHR7OhD26jq22YBoMbt2pnLdK9RBqSEIGPsDsJ18ebMlrC/2pgVItJ
# wZPt4bRc4G/rJvmM1bL5OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYoX7BzzosmJQay
# g9Rc9hUZTO1i4F4z8ujo7AqnsAMrkbI2eb73rQgedaZlzLvjSFDzd5Ea/ttQokbI
# YViY9XwCFjyDKK05huzUtw1T0PhH5nUwjewwk3YUpltLXXRhTT8SkXbev1jLchAp
# QfDVxW0mdmgRQRNYmtwmKwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZYIpkVMHMIRro
# OBl8ZhzNeDhFMJlP/2NPTLuqDQhTQXxYPUez+rbsjDIJAsxsPAxWEQIDAQABo4IB
# WTCCAVUwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg67Y7+F8Rhvv+
# YXsIiGX0TkIwHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0P
# AQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUFBwEBBGswaTAk
# BggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAC
# hjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9v
# dEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5j
# b20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAcBgNVHSAEFTATMAcGBWeBDAED
# MAgGBmeBDAEEATANBgkqhkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6PvDqZ01bgAhql
# +Eg08yy25nRm95RysQDKr2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V1T9J9Ce7FoFF
# UP2cvbaF4HZ+N3HLIvdaqpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+3NiAGhEZGM1h
# mYFW9snjdufE5BtfQ/g+lP92OT2e1JnPSt0o618moZVYSNUa/tcnP/2Q0XaG3Ryw
# YFzzDaju4ImhvTnhOE7abrs2nfvlIVNaw8rpavGiPttDuDPITzgUkpn13c5Ubdld
# AhQfQDN8A+KVssIhdXNSy0bYxDQcoqVLjc1vdjcshT8azibpGL6QB7BDf5WIIIJw
# 8MzK7/0pNVwfiThV9zeKiwmhywvpMRr/LhlcOXHhvpynCgbWJme3kuZOX956rEnP
# LqR0kq3bPKSchh/jwVYbKyP/j7XqiHtwa+aguv06P0WmxOgWkVKLQcBIhEuWTatE
# QOON8BUozu3xGFYHKi8QxAwIZDwzj64ojDzLj4gLDb879M4ee47vtevLt/B3E+bn
# KD+sEq6lLyJsQfmCXBVmzGwOysWGw/YmMwwHS6DTBwJqakAwSEs0qFEgu60bhQji
# WQ1tygVQK+pKHJ6l/aCnHwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0YW6/aOImYIbq
# yK+p/pQd52MbOoZWeE4wggfTMIIFu6ADAgECAhAPk5/xw2E4WPvGz6i0rW23MA0G
# CSqGSIb3DQEBCwUAMGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwg
# SW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcg
# UlNBNDA5NiBTSEEzODQgMjAyMSBDQTEwHhcNMjEwODA2MDAwMDAwWhcNMjMwODA4
# MjM1OTU5WjCBtjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFDAS
# BgNVBAcTC1NhbnRhIENsYXJhMSQwIgYDVQQKExtNYXJ2ZWxsIFNlbWljb25kdWN0
# b3IsIEluYy4xCzAJBgNVBAsTAklUMSQwIgYDVQQDExtNYXJ2ZWxsIFNlbWljb25k
# dWN0b3IsIEluYy4xIzAhBgkqhkiG9w0BCQEWFHJvZ2VybGl1QG1hcnZlbGwuY29t
# MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr4bpumsFGhFqGG7Bgas7
# ZEuy0kaczWPLM50MUrBRRxgqpmrBYMUPhsmXu7PdttPwGlcp7G3Q50kAPEra4nce
# LK+vc8GYD96BttuVAxJiBsfq5Abe1iR5+1ZtKrequpNCDuihZpT6KhBW8nvFj4IY
# +9ei2whK4xke2+DtisyHa3e/8hIopBvG0VoZlDrDjxYtR+bFbGaCRCJglkC8QcNI
# x4TBWiuq2h4MZ08kfqeozCRKz8Ov7QB9si8OqrPX4ttPE6tFw9q/P1jFRFQ4hVJY
# PfuMgv8OHdesPFM9N27VUl3cZAvD8lQDqBDrY4W9TW4NEq75ChCi8JbC9OaK+Nfp
# ssU0o0kGpKPJXcmNkpXULLClfyD6MxciT4QBfxx0p/KSOD5D2bsr5T8tVOtARVl1
# bV3bxfxJXnaZ29ELwUq4DSa38j48MsnFCckv60/MGV6P4tAi0lolX8Pvtgwtov7K
# x3KJemAql5VzKsR99prWLTNqqEbXBOflk684uad27hy6gjaQ3taR1szFjxy0c1uO
# YvuT4OJgMEzKMFyL1AG0dshOi1qnMdeSH5ncHota/iGpIelRZxMP7juCP6HKVc4g
# bXsfrbfHh6NlZTaVLMFxLVKgO6uTgCM2kHDssRX20BV5gAPslq0XJrE37QWrO+zH
# E9JOKT5wmrA/PfTxx3T+9FUCAwEAAaOCAicwggIjMB8GA1UdIwQYMBaAFGg34Ou2
# O/hfEYb7/mF7CIhl9E5CMB0GA1UdDgQWBBS+sjWymf3OAbJHv2KMcCcJvXPlVDAf
# BgNVHREEGDAWgRRyb2dlcmxpdUBtYXJ2ZWxsLmNvbTAOBgNVHQ8BAf8EBAMCB4Aw
# EwYDVR0lBAwwCgYIKwYBBQUHAwMwgbUGA1UdHwSBrTCBqjBToFGgT4ZNaHR0cDov
# L2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdS
# U0E0MDk2U0hBMzg0MjAyMUNBMS5jcmwwU6BRoE+GTWh0dHA6Ly9jcmw0LmRpZ2lj
# ZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNIQTM4
# NDIwMjFDQTEuY3JsMD4GA1UdIAQ3MDUwMwYGZ4EMAQQBMCkwJwYIKwYBBQUHAgEW
# G2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzCBlAYIKwYBBQUHAQEEgYcwgYQw
# JAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBcBggrBgEFBQcw
# AoZQaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0
# Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5jcnQwDAYDVR0TAQH/BAIw
# ADANBgkqhkiG9w0BAQsFAAOCAgEAbZWAAFjUMBcc2V8y09EaiEk9JrKZzaf3gDNk
# +jWkGPh7XwGiFFpo8dDRUvw2c40/+I7IWaUrfz9HyCZcrqfLAMguvAWq6+8DrEBC
# 4uuJL18NuGu7y4yEdY4sKmDUb8U6rdkmTyI4+UhxAsIw/Eug3HnW4CYOUdWlDWcQ
# StEIGI3zsbLzX59Czylvkmpb+rGJm2Cnu5n8cWuPe7axqd5dI//llr4/9OdSwau6
# vFwZZ6BqKpBtMrMiihxLCj8RQb9uPOHDa5US5MOCTbl9nXmy7KxmH9oRGOS5ZUYc
# z+scn8KY0hyEsvSjNsRnUkBhDqfYCm9ou9zp4Bbr18CmYZ2TxKLmuWsXiT8lp+VA
# QF+skDjvY8bfMUdQDU4aVQXddcDKR/5BUJ4ksbldVVqlqFNn34Ge5D05YMx3S654
# U27V/vdjdrITJiQT3x2l7ASh4RxpUtY59G4Bnx2HL/pHeCcWjsj6DdPBUF5UJpvc
# PTkegweOzvRuYZdf7In4kw6xqqaexTferopUgM+qH2ojZ19Pn3H6RWGyou/8mWQK
# CjxfQP2DPd4KD4Vlk9P5V25VHpH9z+H8hB0XxsTH4QRK6LbHhDw28D33Q9KsDCCd
# toaTA2ohZDNKls6dB6KF9bEDxtkZlIAh6iGxvcY1Bs/lH8QPIHxOVCWzyhlPeqA9
# QwxDRcgxghrrMIIa5wIBATB9MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdp
# Q2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNp
# Z25pbmcgUlNBNDA5NiBTSEEzODQgMjAyMSBDQTECEA+Tn/HDYThY+8bPqLStbbcw
# DQYJYIZIAWUDBAIBBQCggeQwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYK
# KwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEICii5OH+
# Bd2pu349GzjnUFDajYdYUHXfJi3sNViGEEa8MHgGCisGAQQBgjcCAQwxajBooEyA
# SgBNAGEAcgB2AGUAbABsACAAUQBMAG8AZwBpAGMAIABGAGkAYgByAGUAIABDAGgA
# YQBuAG4AZQBsACAAUABvAHcAZQByAEsAaQB0oRiAFmh0dHA6Ly93d3cubWFydmVs
# bC5jb20wDQYJKoZIhvcNAQEBBQAEggIAlDholXL8NXu+KulZ+pvLwXE5Y93JYZEP
# +jv80ZGlTKPJjhMq1+j4iLwQ09UdFAbpcZuxfARw7RIE1TWrIf9sBvM59wLTdBs7
# 5Ekf+DqeUhm3odb3ejcul8NuDRCGNQiShFmkit0oM/gQQx/z1LCopWo1eOoG6BeW
# 9Ltoe7UUDoBROP/VWdmlqyYh/Xst+bhEL2dVNyKr7s0pbuG/QozdNTnA3MkMnvfF
# AaUGiTfP1gS1IuvA/qvp0+CE4tnupbE1wzGkLxq8JQiPjMMPVAzvA23bMWS0QWNK
# iJZpAUyK1kJfi5It2oZDOh5k07AMr7qfzmkR0zyyEbbrc8/d4mYp5ddxM+WX4TKk
# 5ogz8uyGnzIzCjycWk1splkoXQgqWxHrH9Sd/4e0Gy6zX+CmqC+2sGRdgvrQO3S0
# a1C7aAX0Y+AUDRfoMAcADw+qXvFXpTwMGNiDfumOtMAFps367gIDn3bxhQXlSm+b
# iMkGZh28t6iH8Ouqcgrayey8Fw+2OIHAHJDlSVpLq++sh23LKFJ1miowVVrSMwg6
# NgYrXwbxlL+OE/EOdbyh9x4OG/bQ8/1RcxkX58IDpDcxmeCAc8Mr/xp020f01OMD
# PDBDFQOf2Vu6KAH1IgVcY2Nrf4cnpMiqqsgGHHW1SF729oM6KrBwmW90Spe7KTKj
# EC8O4Y8YC7ahghdYMIIXVAYKKwYBBAGCNwMDATGCF0QwghdABgkqhkiG9w0BBwKg
# ghcxMIIXLQIBAzEPMA0GCWCGSAFlAwQCAQUAMGgGCyqGSIb3DQEJEAEEoFkEVzBV
# AgEBBglghkgBhv1sBwEwITAJBgUrDgMCGgUABBTI6Vx467sttRPm9NXbF/9WOUO+
# jQIRAK32ditV6a8ReLzcutxspawYDzIwMjIwNzI3MDY1MzQxWqCCEzEwggbGMIIE
# rqADAgECAhAKekqInsmZQpAGYzhNhpedMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNV
# BAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNl
# cnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwHhcN
# MjIwMzI5MDAwMDAwWhcNMzMwMzE0MjM1OTU5WjBMMQswCQYDVQQGEwJVUzEXMBUG
# A1UEChMORGlnaUNlcnQsIEluYy4xJDAiBgNVBAMTG0RpZ2lDZXJ0IFRpbWVzdGFt
# cCAyMDIyIC0gMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALkqliOm
# XLxf1knwFYIY9DPuzFxs4+AlLtIx5DxArvurxON4XX5cNur1JY1Do4HrOGP5PIhp
# 3jzSMFENMQe6Rm7po0tI6IlBfw2y1vmE8Zg+C78KhBJxbKFiJgHTzsNs/aw7ftwq
# HKm9MMYW2Nq867Lxg9GfzQnFuUFqRUIjQVr4YNNlLD5+Xr2Wp/D8sfT0KM9CeR87
# x5MHaGjlRDRSXw9Q3tRZLER0wDJHGVvimC6P0Mo//8ZnzzyTlU6E6XYYmJkRFMUr
# DKAz200kheiClOEvA+5/hQLJhuHVGBS3BEXz4Di9or16cZjsFef9LuzSmwCKrB2N
# O4Bo/tBZmCbO4O2ufyguwp7gC0vICNEyu4P6IzzZ/9KMu/dDI9/nw1oFYn5wLOUr
# sj1j6siugSBrQ4nIfl+wGt0ZvZ90QQqvuY4J03ShL7BUdsGQT5TshmH/2xEvkgMw
# zjC3iw9dRLNDHSNQzZHXL537/M2xwafEDsTvQD4ZOgLUMalpoEn5deGb6GjkagyP
# 6+SxIXuGZ1h+fx/oK+QUshbWgaHK2jCQa+5vdcCwNiayCDv/vb5/bBMY38ZtpHlJ
# rYt/YYcFaPfUcONCleieu5tLsuK2QT3nr6caKMmtYbCgQRgZTu1Hm2GV7T4LYVrq
# PnqYklHNP8lE54CLKUJy93my3YTqJ+7+fXprAgMBAAGjggGLMIIBhzAOBgNVHQ8B
# Af8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAg
# BgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwHwYDVR0jBBgwFoAUuhbZ
# bU2FL3MpdpovdYxqII+eyG8wHQYDVR0OBBYEFI1kt4kh/lZYRIRhp+pvHDaP3a8N
# MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdp
# Q2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcmwwgZAG
# CCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy
# dC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9E
# aWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcnQw
# DQYJKoZIhvcNAQELBQADggIBAA0tI3Sm0fX46kuZPwHk9gzkrxad2bOMl4IpnENv
# AS2rOLVwEb+EGYs/XeWGT76TOt4qOVo5TtiEWaW8G5iq6Gzv0UhpGThbz4k5HXBw
# 2U7fIyJs1d/2WcuhwupMdsqh3KErlribVakaa33R9QIJT4LWpXOIxJiA3+5Jlbez
# zMWn7g7h7x44ip/vEckxSli23zh8y/pc9+RTv24KfH7X3pjVKWWJD6KcwGX0ASJl
# x+pedKZbNZJQfPQXpodkTz5GiRZjIGvL8nvQNeNKcEiptucdYL0EIhUlcAZyqUQ7
# aUcR0+7px6A+TxC5MDbk86ppCaiLfmSiZZQR+24y8fW7OK3NwJMR1TJ4Sks3Kkzz
# XNy2hcC7cDBVeNaY/lRtf3GpSBp43UZ3Lht6wDOK+EoojBKoc88t+dMj8p4Z4A2U
# KKDr2xpRoJWCjihrpM6ddt6pc6pIallDrl/q+A8GQp3fBmiW/iqgdFtjZt5rLLh4
# qk1wbfAs8QcVfjW05rUMopml1xVrNQ6F1uAszOAMJLh8UgsemXzvyMjFjFhpr6s9
# 4c/MfRWuFL+Kcd/Kl7HYR+ocheBFThIcFClYzG/Tf8u+wQ5KbyCcrtlzMlkI5y2S
# oRoR/jKYpl0rl+CL05zMbbUNrkdjOEcXW28T2moQbh9Jt0RbtAgKh1pZBHYRoad3
# AhMcMIIGrjCCBJagAwIBAgIQBzY3tyRUfNhHrP0oZipeWzANBgkqhkiG9w0BAQsF
# ADBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL
# ExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJv
# b3QgRzQwHhcNMjIwMzIzMDAwMDAwWhcNMzcwMzIyMjM1OTU5WjBjMQswCQYDVQQG
# EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0
# IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMIICIjAN
# BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxoY1BkmzwT1ySVFVxyUDxPKRN6mX
# UaHW0oPRnkyibaCwzIP5WvYRoUQVQl+kiPNo+n3znIkLf50fng8zH1ATCyZzlm34
# V6gCff1DtITaEfFzsbPuK4CEiiIY3+vaPcQXf6sZKz5C3GeO6lE98NZW1OcoLevT
# sbV15x8GZY2UKdPZ7Gnf2ZCHRgB720RBidx8ald68Dd5n12sy+iEZLRS8nZH92GD
# Gd1ftFQLIWhuNyG7QKxfst5Kfc71ORJn7w6lY2zkpsUdzTYNXNXmG6jBZHRAp8By
# xbpOH7G1WE15/tePc5OsLDnipUjW8LAxE6lXKZYnLvWHpo9OdhVVJnCYJn+gGkcg
# Q+NDY4B7dW4nJZCYOjgRs/b2nuY7W+yB3iIU2YIqx5K/oN7jPqJz+ucfWmyU8lKV
# EStYdEAoq3NDzt9KoRxrOMUp88qqlnNCaJ+2RrOdOqPVA+C/8KI8ykLcGEh/FDTP
# 0kyr75s9/g64ZCr6dSgkQe1CvwWcZklSUPRR8zZJTYsg0ixXNXkrqPNFYLwjjVj3
# 3GHek/45wPmyMKVM1+mYSlg+0wOI/rOP015LdhJRk8mMDDtbiiKowSYI+RQQEgN9
# XyO7ZONj4KbhPvbCdLI/Hgl27KtdRnXiYKNYCQEoAA6EVO7O6V3IXjASvUaetdN2
# udIOa5kM0jO0zbECAwEAAaOCAV0wggFZMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYD
# VR0OBBYEFLoW2W1NhS9zKXaaL3WMaiCPnshvMB8GA1UdIwQYMBaAFOzX44LScV1k
# TN8uZz/nupiuHA9PMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcD
# CDB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj
# ZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t
# L0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0
# cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmww
# IAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUA
# A4ICAQB9WY7Ak7ZvmKlEIgF+ZtbYIULhsBguEE0TzzBTzr8Y+8dQXeJLKftwig2q
# KWn8acHPHQfpPmDI2AvlXFvXbYf6hCAlNDFnzbYSlm/EUExiHQwIgqgWvalWzxVz
# jQEiJc6VaT9Hd/tydBTX/6tPiix6q4XNQ1/tYLaqT5Fmniye4Iqs5f2MvGQmh2yS
# vZ180HAKfO+ovHVPulr3qRCyXen/KFSJ8NWKcXZl2szwcqMj+sAngkSumScbqyQe
# JsG33irr9p6xeZmBo1aGqwpFyd/EjaDnmPv7pp1yr8THwcFqcdnGE4AJxLafzYeH
# JLtPo0m5d2aR8XKc6UsCUqc3fpNTrDsdCEkPlM05et3/JWOZJyw9P2un8WbDQc1P
# tkCbISFA0LcTJM3cHXg65J6t5TRxktcma+Q4c6umAU+9Pzt4rUyt+8SVe+0KXzM5
# h0F4ejjpnOHdI/0dKNPH+ejxmF/7K9h+8kaddSweJywm228Vex4Ziza4k9Tm8heZ
# Wcpw8De/mADfIBZPJ/tgZxahZrrdVcA6KYawmKAr7ZVBtzrVFZgxtGIJDwq9gdkT
# /r+k0fNX2bwE+oLeMt8EifAAzV3C+dAjfwAL5HYCJtnwZXZCpimHCUcr5n8apIUP
# /JiW9lVUKx+A+sDyDivl1vupL0QVSucTDh3bNzgaoSv27dZ8/DCCBbEwggSZoAMC
# AQICEAEkCvseOAuKFvFLcZ3008AwDQYJKoZIhvcNAQEMBQAwZTELMAkGA1UEBhMC
# VVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0
# LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTIy
# MDYwOTAwMDAwMFoXDTMxMTEwOTIzNTk1OVowYjELMAkGA1UEBhMCVVMxFTATBgNV
# BAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8G
# A1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MIICIjANBgkqhkiG9w0BAQEF
# AAOCAg8AMIICCgKCAgEAv+aQc2jeu+RdSjwwIjBpM+zCpyUuySE98orYWcLhKac9
# WKt2ms2uexuEDcQwH/MbpDgW61bGl20dq7J58soR0uRf1gU8Ug9SH8aeFaV+vp+p
# VxZZVXKvaJNwwrK6dZlqczKU0RBEEC7fgvMHhOZ0O21x4i0MG+4g1ckgHWMpLc7s
# Xk7Ik/ghYZs06wXGXuxbGrzryc/NrDRAX7F6Zu53yEioZldXn1RYjgwrt0+nMNlW
# 7sp7XeOtyU9e5TXnMcvak17cjo+A2raRmECQecN4x7axxLVqGDgDEI3Y1DekLgV9
# iPWCPhCRcKtVgkEy19sEcypukQF8IUzUvK4bA3VdeGbZOjFEmjNAvwjXWkmkwuap
# oGfdpCe8oU85tRFYF/ckXEaPZPfBaYh2mHY9WV1CdoeJl2l6SPDgohIbZpp0yt5L
# HucOY67m1O+SkjqePdwA5EUlibaaRBkrfsCUtNJhbesz2cXfSwQAzH0clcOP9yGy
# shG3u3/y1YxwLEFgqrFjGESVGnZifvaAsPvoZKYz0YkH4b235kOkGLimdwHhD5QM
# IR2yVCkliWzlDlJRR3S+Jqy2QXXeeqxfjT/JvNNBERJb5RBQ6zHFynIWIgnffEx1
# P2PsIV/EIFFrb7GrhotPwtZFX50g/KEexcCPorF+CiaZ9eRpL5gdLfXZqbId5RsC
# AwEAAaOCAV4wggFaMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOzX44LScV1k
# TN8uZz/nupiuHA9PMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA4G
# A1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDCDB5BggrBgEFBQcBAQRt
# MGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEF
# BQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJl
# ZElEUm9vdENBLmNydDBFBgNVHR8EPjA8MDqgOKA2hjRodHRwOi8vY3JsMy5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMCAGA1UdIAQZMBcw
# CAYGZ4EMAQQCMAsGCWCGSAGG/WwHATANBgkqhkiG9w0BAQwFAAOCAQEAmhYCpQHv
# gfsNtFiyeK2oIxnZczfaYJ5R18v4L0C5ox98QE4zPpA854kBdYXoYnsdVuBxut5e
# xje8eVxiAE34SXpRTQYy88XSAConIOqJLhU54Cw++HV8LIJBYTUPI9DtNZXSiJUp
# Q8vgplgQfFOOn0XJIDcUwO0Zun53OdJUlsemEd80M/Z1UkJLHJ2NltWVbEcSFCRf
# JkH6Gka93rDlkUcDrBgIy8vbZol/K5xlv743Tr4t851Kw8zMR17IlZWt0cu7KgYg
# +T9y6jbrRXKSeil7FAM8+03WSHF6EBGKCHTNbBsEXNKKlQN2UVBT1i73SkbDrhAs
# cUywh7YnN0RgRDGCA3YwggNyAgEBMHcwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoT
# DkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJT
# QTQwOTYgU0hBMjU2IFRpbWVTdGFtcGluZyBDQQIQCnpKiJ7JmUKQBmM4TYaXnTAN
# BglghkgBZQMEAgEFAKCB0TAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwHAYJ
# KoZIhvcNAQkFMQ8XDTIyMDcyNzA2NTM0MVowKwYLKoZIhvcNAQkQAgwxHDAaMBgw
# FgQUhQjzhlFcs9MHfba0t8B/G0peQd4wLwYJKoZIhvcNAQkEMSIEICEMkb7oHyEw
# AlscZvL9vHHiBleqHQXBGUQdaDDnopnnMDcGCyqGSIb3DQEJEAIvMSgwJjAkMCIE
# IJ2mkBXDScbBiXhFujWCrXDIj6QpO9tqvpwr0lOSeeY7MA0GCSqGSIb3DQEBAQUA
# BIICAA+I5Dc+S778ETXVSQEuhkRVCyeIQueD0k87DQfoS4n3qu9+goIL7RQ8BAay
# wmIX94aUQ5Bpt04ZalmwJ+txTPszF2WbeJt3VOmaMRgWhnTCNzly6RscoUSsglVL
# 58eClDmoY61LRKerlQYig1waOh5OcDUht6ibSbQr9gHQCSEkZhRvBQ8JZx8y8Xmn
# rtvjROJD5RgEryptlWHjDMI1DyenRltML+K5yhruBA7q9gvTkxHZCsriT/AgTmc9
# Q+uf2HhJz7yrq+7M1baEx03ryE2V/ozzjFcQomkS22Zee4hz0kGd3F25Bk6DEO5M
# 1IY5y5MABx4bgkuSXuU87LPzZuVGLlPQSXLqW1X7EZDTF4MrdLdLcjw6YXDig1IN
# 47rx9neJ/vzD7Dn9fIJa/Bw4BOkdykN+clHq3/bNdetmOdQBZ+2g2hsisNoaY5Sr
# PVbROuOMFGwxRC9yZ7ZnyP/07+tsz3hP8Az9wuEB/IOPOaVdZcSn/CUtAuyS1qqA
# g8zc8fvpXoQYJyxWyIK4Xyj7A/AgPos3PwMHkzPwZwcB/k9BWpj39UqacmYTSZsx
# 7IKUtay9LW/BgMT7wOYkreQYYKvN8ATmHd27rxxlMKBhXt6ofyNtt57d3KA4WwC0
# b+NoPJDVd9yrdp+ECSfvh2CWZtjKdSOHsK3I8oGAI05A23oh
# SIG # End signature block