Paths.psm1

#########################################################################################
#
# Copyright (c) Microsoft Corporation. All rights reserved.
#
# Licensed under the MIT license.
#
# PSSwagger Module
#
#########################################################################################

Microsoft.PowerShell.Core\Set-StrictMode -Version Latest
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath Utilities.psm1) -DisableNameChecking
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath SwaggerUtils.psm1) -DisableNameChecking
. "$PSScriptRoot\PSSwagger.Constants.ps1" -Force
Microsoft.PowerShell.Utility\Import-LocalizedData  LocalizedData -filename PSSwagger.Resources.psd1

function Get-SwaggerSpecPathInfo
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [PSObject]
        $JsonPathItemObject,

        [Parameter(Mandatory=$true)]
        [PSCustomObject] 
        $PathFunctionDetails,

        [Parameter(Mandatory = $true)]
        [hashTable]
        $swaggerDict,

        [Parameter(Mandatory = $true)]
        [hashtable]
        $SwaggerMetaDict,

        [Parameter(Mandatory = $true)]
        [hashtable]
        $DefinitionFunctionsDetails,

        [Parameter(Mandatory=$true)]
        [hashtable]
        $ParameterGroupCache,

        [Parameter(Mandatory=$false)]
        [PSCustomObject]
        $PSMetaJsonObject
    )

    Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
    $UseAzureCsharpGenerator = $SwaggerMetaDict['UseAzureCsharpGenerator']
    $EndpointRelativePath = $JsonPathItemObject.Name

    $PSMetaPathJsonObject = $null
    if($PSMetaJsonObject) {
        if(Get-Member -InputObject $PSMetaJsonObject.paths -Name $EndpointRelativePath) {
            $PSMetaPathJsonObject = $PSMetaJsonObject.paths.$EndpointRelativePath
        }
        elseif(Get-Member -InputObject $PSMetaJsonObject.'x-ms-paths' -Name $EndpointRelativePath) {
            $PSMetaPathJsonObject = $PSMetaJsonObject.'x-ms-paths'.$EndpointRelativePath
        }
    }

    # First get path level common parameters, if any, which will be common to all operations in this swagger path.
    $PathCommonParameters = @{}
    if(Get-Member -InputObject $JsonPathItemObject.value -Name 'Parameters')
    {
        $PSMetaParametersJsonObject = $null
        if($PSMetaPathJsonObject -and (Get-Member -InputObject $PSMetaPathJsonObject -Name 'parameters')) {
            $PSMetaParametersJsonObject = $PSMetaPathJsonObject.'parameters'
        }

        $GetPathParamInfo_params = @{
            JsonPathItemObject = $JsonPathItemObject.Value
            SwaggerDict = $swaggerDict
            DefinitionFunctionsDetails = $DefinitionFunctionsDetails
            ParameterGroupCache = $ParameterGroupCache
            ParametersTable = $PathCommonParameters
            PSMetaParametersJsonObject = $PSMetaParametersJsonObject
        }
        Get-PathParamInfo @GetPathParamInfo_params
    }

    $JsonPathItemObject.value.PSObject.Properties | ForEach-Object {
        $longRunningOperation = $false
        $operationType = $_.Name
        if (((Get-Member -InputObject $_.Value -Name 'x-ms-long-running-operation') -and $_.Value.'x-ms-long-running-operation')) {
            $longRunningOperation = $true
        }

        $x_ms_pageableObject = $null
        if (((Get-Member -InputObject $_.Value -Name 'x-ms-pageable') -and $_.Value.'x-ms-pageable')) {
            $x_ms_pageableObject = @{}
            if (((Get-Member -InputObject $_.Value.'x-ms-pageable' -Name 'operationName') -and $_.Value.'x-ms-pageable'.'operationName')) {
                $x_ms_pageableObject['operationName'] = $_.Value.'x-ms-pageable'.'operationName'
            }

            if (((Get-Member -InputObject $_.Value.'x-ms-pageable' -Name 'itemName') -and $_.Value.'x-ms-pageable'.'itemName')) {
                $x_ms_pageableObject['itemName'] = $_.Value.'x-ms-pageable'.'itemName'
            }

            if ((Get-Member -InputObject $_.Value.'x-ms-pageable' -Name 'nextLinkName')) {
                if ($_.Value.'x-ms-pageable'.'nextLinkName') {
                    $x_ms_pageableObject['nextLinkName'] = $_.Value.'x-ms-pageable'.'nextLinkName'
                } else {
                    $x_ms_pageableObject = $null
                }
            }
        }

        if(Get-Member -InputObject $_.Value -Name 'security')
        {
            $operationSecurityObject = $_.Value.'security'
        } elseif ($swaggerDict.ContainsKey('Security')) {
            $operationSecurityObject = $swaggerDict['Security']
        } else {
            $operationSecurityObject = $null
        }

        $cmdletInfoOverrides = @()
        $PSMetaOperationJsonObject = $null
        if($PSMetaPathJsonObject -and
           (Get-Member -InputObject $PSMetaPathJsonObject -Name $operationType)) {
            $PSMetaOperationJsonObject = $PSMetaPathJsonObject.$operationType
        }
        
        if($PSMetaOperationJsonObject -and
           (Get-Member -InputObject $PSMetaOperationJsonObject -Name 'x-ps-cmdlet-infos')) {
            $PSMetaOperationJsonObject.'x-ps-cmdlet-infos' | ForEach-Object {
                $cmdletInfoOverrides += @{
                    Name = $_.name
                }
            }
        }
        elseif ((Get-Member -InputObject $_.Value -Name 'x-ps-cmdlet-infos') -and $_.Value.'x-ps-cmdlet-infos') {
            foreach ($cmdletMetadata in $_.Value.'x-ps-cmdlet-infos') {
                $cmdletInfoOverride = @{}
                if ((Get-Member -InputObject $cmdletMetadata -Name 'name') -and $cmdletMetadata.name) {
                    $cmdletInfoOverride['name'] = $cmdletMetadata.name
                }

                $cmdletInfoOverrides += $cmdletInfoOverride
            }
        }

        if(Get-Member -InputObject $_.Value -Name 'OperationId')
        {
            $operationId = $_.Value.operationId
            Write-Verbose -Message ($LocalizedData.GettingSwaggerSpecPathInfo -f $operationId)

            $FunctionDescription = ""
            if((Get-Member -InputObject $_.value -Name 'description') -and $_.value.description) {
                $FunctionDescription = $_.value.description 
            }

            $FunctionSynopsis = ''
            if((Get-Member -InputObject $_.value -Name 'Summary') -and $_.value.Summary) {
                $FunctionSynopsis = $_.value.Summary 
            }
            
            $ParametersTable = @{}
            # Add Path common parameters to the operation's parameters list.
            $PathCommonParameters.GetEnumerator() | ForEach-Object {
                # Cloning the common parameters object so that some values can be updated.
                $PathCommonParamDetails = $_.Value.Clone()
                if($PathCommonParamDetails.ContainsKey('OriginalParameterName') -and $PathCommonParamDetails.OriginalParameterName)
                {
                    $PathCommonParamDetails['OriginalParameterName'] = ''
                }
                $ParametersTable[$_.Key] = $PathCommonParamDetails
            }

            $PSMetaParametersJsonObject = $null
            if($PSMetaOperationJsonObject -and (Get-Member -InputObject $PSMetaOperationJsonObject -Name 'parameters')) {
                $PSMetaParametersJsonObject = $PSMetaOperationJsonObject.'parameters'
            }

            $GetPathParamInfo_params2 = @{
                JsonPathItemObject = $_.value
                SwaggerDict = $swaggerDict
                DefinitionFunctionsDetails = $DefinitionFunctionsDetails
                ParameterGroupCache = $ParameterGroupCache
                ParametersTable = $ParametersTable
                PSMetaParametersJsonObject = $PSMetaParametersJsonObject
            }
            Get-PathParamInfo @GetPathParamInfo_params2

            $responses = ""
            if((Get-Member -InputObject $_.value -Name 'responses') -and $_.value.responses) {
                $responses = $_.value.responses 
            }

            if($cmdletInfoOverrides)
            {
                $commandNames = $cmdletInfoOverrides
            } else {
                $commandNames = Get-PathCommandName -OperationId $operationId
            }
            
            # Priority of a parameterset will be used to determine the default parameterset of a cmdlet.
            $Priority = 0
            $ParametersCount = Get-HashtableKeyCount -Hashtable $ParametersTable
            if($ParametersCount) {
                # Priority for parameter sets with mandatory parameters starts at 100
                $Priority = 100

                $ParametersTable.GetEnumerator() | ForEach-Object {
                    if($_.Value.ContainsKey('Mandatory') -and $_.Value.Mandatory -eq '$true') {
                        $Priority++
                    }
                }

                # If there are no mandatory parameters, use the parameter count as the priority.
                if($Priority -eq 100) {
                    $Priority = $ParametersCount
                }
            }

            $ParameterSetDetail = @{
                Description          = $FunctionDescription
                Synopsis             = $FunctionSynopsis
                ParameterDetails     = $ParametersTable
                Responses            = $responses
                OperationId          = $operationId
                OperationType        = $operationType
                EndpointRelativePath = $EndpointRelativePath
                PathCommonParameters = $PathCommonParameters
                Priority             = $Priority
                'x-ms-pageable'      = $x_ms_pageableObject
            }

            if ((Get-Member -InputObject $_.Value -Name 'x-ms-odata') -and $_.Value.'x-ms-odata') {
                # Currently only the existence of this property is really important, but might as well save the value
                $ParameterSetDetail.'x-ms-odata' = $_.Value.'x-ms-odata'
            }

            # There's probably a better way to do this...
            $opIdValues = $operationId -split "_",2
            if(-not $opIdValues -or ($opIdValues.Count -ne 2)) {
                $approximateVerb = $operationId
            } else {
                $approximateVerb = $opIdValues[1]
                if ((-not $UseAzureCsharpGenerator) -and 
                    (Test-OperationNameInDefinitionList -Name $opIdValues[0] -SwaggerDict $swaggerDict))
                { 
                    $ParameterSetDetail['UseOperationsSuffix'] = $true
                }
            }

            $commandNames | ForEach-Object {
                $FunctionDetails = @{}
                if ($PathFunctionDetails.ContainsKey($_.name)) {
                    $FunctionDetails = $PathFunctionDetails[$_.name]
                } else {
                    $FunctionDetails['CommandName'] = $_.name
                    $FunctionDetails['x-ms-long-running-operation'] = $longRunningOperation
                }

                if ($operationSecurityObject) {
                    $FunctionDetails['Security'] = $operationSecurityObject
                }

                $ParameterSetDetails = @()
                if ($FunctionDetails.ContainsKey('ParameterSetDetails')) {
                    $ParameterSetDetails = $FunctionDetails['ParameterSetDetails']
                } 

                $ParameterSetDetails += $ParameterSetDetail
                $FunctionDetails['ParameterSetDetails'] = $ParameterSetDetails
                $PathFunctionDetails[$_.name] = $FunctionDetails
            }
        }
        elseif(-not ((Get-Member -InputObject $_ -Name 'Name') -and ($_.Name -eq 'Parameters')))
        {
            $Message = $LocalizedData.UnsupportedSwaggerProperties -f ('JsonPathItemObject', $($_.Value | Out-String))
            Write-Warning -Message $Message
        }
    }
}

function New-SwaggerSpecPathCommand
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        [hashtable]
        $PathFunctionDetails,

        [Parameter(Mandatory = $true)]
        [hashtable]
        $SwaggerMetaDict,

        [Parameter(Mandatory = $true)]
        [hashtable]
        $SwaggerDict,

        [Parameter(Mandatory=$true)]
        [hashtable]
        $DefinitionFunctionsDetails,

        [Parameter(Mandatory=$false)]
        [AllowEmptyString()]
        [string]
        $PSHeaderComment
    )
    
    Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    $FunctionsToExport = @()
    Preprocess-PagingOperations -PathFunctionDetails $PathFunctionDetails
    $PathFunctionDetails.GetEnumerator() | ForEach-Object {
        $FunctionsToExport += New-SwaggerPath -FunctionDetails $_.Value `
                                              -SwaggerMetaDict $SwaggerMetaDict `
                                              -SwaggerDict $SwaggerDict `
                                              -PathFunctionDetails $PathFunctionDetails `
                                              -DefinitionFunctionsDetails $DefinitionFunctionsDetails `
                                              -PSHeaderComment $PSHeaderComment
    }

    return $FunctionsToExport
}

<# Mark any operations as paging operations if they're the target of any operation's x-ms-pageable.operationName property.
These operations will not generate -Page and -Paging, even though they're marked as pageable.
These are single page operations and should never be unrolled (in the case of -not -Paging) or accept IPage parameters (in the case of -Page) #>

function Preprocess-PagingOperations {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [hashtable]
        $PathFunctionDetails
    )

    $PathFunctionDetails.GetEnumerator() | ForEach-Object {
        $_.Value.ParameterSetDetails | ForEach-Object {
            $parameterSetDetail = $_
            if ($parameterSetDetail.ContainsKey('x-ms-pageable') -and $parameterSetDetail.'x-ms-pageable') {
                if ($parameterSetDetail.'x-ms-pageable'.ContainsKey('operationName')) {
                    $matchingPath = $PathFunctionDetails.GetEnumerator() | Where-Object { $_.Value.ParameterSetDetails | Where-Object { $_.OperationId -eq $parameterSetDetail.'x-ms-pageable'.'operationName'} } | Select-Object -First 1
                    $matchingPath.Value['IsNextPageOperation'] = $true
                }
            }
        }
    }
}

function Add-UniqueParameter {
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        [hashtable]
        $ParameterDetails,
        
        [Parameter(Mandatory=$true)]
        [hashtable]
        $CandidateParameterDetails,

        [Parameter(Mandatory=$true)]
        [string]
        $OperationId,

        [Parameter(Mandatory=$true)]
        [hashtable]
        $ParametersToAdd,

        [Parameter(Mandatory=$true)]
        [hashtable]
        $ParameterHitCount
    )

    Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    $parameterName = $CandidateParameterDetails.Name
    if($parameterDetails.IsParameter) {
        if (-not $parameterHitCount.ContainsKey($parameterName)) {
            $parameterHitCount[$parameterName] = 0
        }

        $parameterHitCount[$parameterName]++
        if (-not ($parametersToAdd.ContainsKey($parameterName))) {
            $parametersToAdd[$parameterName] = @{
                # We can grab details like Type, Name, ValidateSet from any of the parameter definitions
                Details = $CandidateParameterDetails
                ParameterSetInfo = @{$OperationId = @{
                    Name = $OperationId
                    Mandatory = $CandidateParameterDetails.Mandatory
                }}
            }
        } else {
            $parametersToAdd[$parameterName].ParameterSetInfo[$OperationId] = @{
                                                                        Name = $OperationId
                                                                        Mandatory = $CandidateParameterDetails.Mandatory
                                                                    }
        }
    }
}

function New-SwaggerPath
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        [hashtable]
        $FunctionDetails,

        [Parameter(Mandatory = $true)]
        [hashtable]
        $SwaggerMetaDict,

        [Parameter(Mandatory = $true)]
        [hashtable]
        $SwaggerDict,

        [Parameter(Mandatory = $true)]
        [hashtable]
        $PathFunctionDetails,

        [Parameter(Mandatory=$true)]
        [hashtable]
        $DefinitionFunctionsDetails,
        
        [Parameter(Mandatory=$false)]
        [AllowEmptyString()]
        [string]
        $PSHeaderComment
    )

    Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    $commandName = $FunctionDetails.CommandName
    $parameterSetDetails = $FunctionDetails['ParameterSetDetails']
    $isLongRunningOperation = $FunctionDetails.ContainsKey('x-ms-long-running-operation') -and $FunctionDetails.'x-ms-long-running-operation'
    $isNextPageOperation = $FunctionDetails.ContainsKey('IsNextPageOperation') -and $FunctionDetails.'IsNextPageOperation'
    $info = $SwaggerDict['Info']
    $namespace = $info['NameSpace']
    $models = $info['Models']
    $clientName = '$' + $info['ClientTypeName']
    $UseAzureCsharpGenerator = $SwaggerMetaDict['UseAzureCsharpGenerator']
    
    $description = ''
    $synopsis = ''
    $paramBlock = ''
    $paramHelp = ''
    $parametersToAdd = @{}
    $flattenedParametersOnPSCmdlet = @{}
    $parameterHitCount = @{}
    $globalParameters = @()
    $x_ms_pageableObject = $null
    foreach ($parameterSetDetail in $parameterSetDetails) {
        if ($parameterSetDetail.ContainsKey('x-ms-pageable') -and $parameterSetDetail.'x-ms-pageable' -and (-not $isNextPageOperation)) {
            if ($x_ms_pageableObject -and 
                $x_ms_pageableObject.ContainsKey('ReturnType') -and 
                ($x_ms_pageableObject.ReturnType -ne 'NONE') -and
                ($x_ms_pageableObject.ReturnType -ne $parameterSetDetail.ReturnType)) {
                Write-Warning -Message ($LocalizedData.MultiplePageReturnTypes -f ($commandName))
                $x_ms_pageableObject.ReturnType = 'NONE'
            } elseif (-not $x_ms_pageableObject) {
                $x_ms_pageableObject = $parameterSetDetail.'x-ms-pageable'
                $x_ms_pageableObject['ReturnType'] = $parameterSetDetail.ReturnType
                if($parameterSetDetail.ContainsKey('PSCmdletOutputItemType')) {
                    $x_ms_pageableObject['PSCmdletOutputItemType'] = $parameterSetDetail.PSCmdletOutputItemType
                }
                if ($x_ms_pageableObject.Containskey('operationName')) {
                    # Search for the cmdlet with a parameter set with the given operationName
                    $pagingFunctionDetails = $PathFunctionDetails.GetEnumerator() | Where-Object { $_.Value.ParameterSetDetails | Where-Object { $_.OperationId -eq $x_ms_pageableObject.operationName }} | Select-Object -First 1
                    if (-not $pagingFunctionDetails) {
                        throw ($LocalizedData.FailedToFindPagingOperation -f ($($x_ms_pageableObject.OperationName), $commandName))
                    }

                    $pagingParameterSet = $pagingFunctionDetails.Value.ParameterSetDetails | Where-Object { $_.OperationId -eq $x_ms_pageableObject.operationName }
                    $unmatchedParameters = @()
                    # This list of parameters works for when -Page is called...
                    $cmdletArgsPageParameterSet = ''
                    # ...and this list of parameters works for when unrolling paged results (when -Paging is not used)
                    $cmdletArgsNoPaging = ''
                    foreach ($pagingParameterEntry in $pagingParameterSet.ParameterDetails.GetEnumerator()) {
                        $pagingParameter = $pagingParameterEntry.Value
                        # Ignore parameters that are readonly or have a constant value
                        if ($pagingParameter.ContainsKey('ReadOnlyGlobalParameter') -and $pagingParameter.ReadOnlyGlobalParameter) {
                            continue
                        }

                        if ($pagingParameter.ContainsKey('ConstantValue') -and $pagingParameter.ConstantValue) {
                            continue
                        }

                        $matchingCurrentParameter = $parameterSetDetail.ParameterDetails.GetEnumerator() | Where-Object { $_.Value.Name -eq $pagingParameter.Name } | Select-Object -First 1
                        if ($matchingCurrentParameter) {
                            $cmdletArgsPageParameterSet += "-$($pagingParameter.Name) `$$($pagingParameter.Name) "
                            $cmdletArgsNoPaging += "-$($pagingParameter.Name) `$$($pagingParameter.Name) "
                        } else {
                            $unmatchedParameters += $pagingParameter
                            $cmdletArgsPageParameterSet += "-$($pagingParameter.Name) `$Page.NextPageLink "
                            $cmdletArgsNoPaging += "-$($pagingParameter.Name) `$result.NextPageLink "
                        }
                    }

                    if ($unmatchedParameters.Count -ne 1) {
                        throw ($LocalizedData.InvalidPagingOperationSchema -f ($commandName, $pagingFunctionDetails.Value.CommandName))
                    }

                    $x_ms_pageableObject['Cmdlet'] = $pagingFunctionDetails.Value.CommandName
                    $x_ms_pageableObject['CmdletArgsPage'] = $cmdletArgsPageParameterSet.Trim()
                    $x_ms_pageableObject['CmdletArgsPaging'] = $cmdletArgsNoPaging.Trim()
                } else {
                    $x_ms_pageableObject['Operations'] = $parameterSetDetail.Operations
                    $x_ms_pageableObject['MethodName'] = "$($parameterSetDetail.MethodName.Substring(0, $parameterSetDetail.MethodName.IndexOf('WithHttpMessagesAsync')))NextWithHttpMessagesAsync"
                }
            }
        }

        $parameterSetDetail.ParameterDetails.GetEnumerator() | ForEach-Object {
            $parameterDetails = $_.Value
            $parameterRequiresAdding = $true
            if ($parameterDetails.ContainsKey('x_ms_parameter_location') -and ('client' -eq $parameterDetails.'x_ms_parameter_location')) {
                # Check if a global has been added already
                if ($parametersToAdd.ContainsKey("$($parameterDetails.Name)Global")) {
                    $parameterRequiresAdding = $false
                } elseif ($parameterDetails.ContainsKey('ReadOnlyGlobalParameter') -and $parameterDetails.ReadOnlyGlobalParameter) {
                    $parameterRequiresAdding = $false
                } else {
                    $globalParameterName = $parameterDetails.Name
                    $globalParameterValue = "```$$($parameterDetails.Name)"
                    if ($parameterDetails.ContainsKey('ConstantValue') -and $parameterDetails.ConstantValue) {
                        # A parameter with a constant value doesn't need to be in the parameter block
                        $parameterRequiresAdding = $false
                        $globalParameterValue = $parameterDetails.ConstantValue
                    }
                    
                    $globalParameters += $globalParameterName
                }
            }

            if ($parameterRequiresAdding) {
                $AddUniqueParameter_params = @{
                    ParameterDetails = $parameterDetails
                    OperationId = $parameterSetDetail.OperationId
                    ParametersToAdd = $parametersToAdd
                    ParameterHitCount = $parameterHitCount
                }

                if ($parameterDetails.ContainsKey('x_ms_parameter_grouping_group')) {
                    foreach ($parameterDetailEntry in $parameterDetails.'x_ms_parameter_grouping_group'.GetEnumerator()) {
                        $AddUniqueParameter_params['CandidateParameterDetails'] = $parameterDetailEntry.Value
                        Add-UniqueParameter @AddUniqueParameter_params
                    }
                } elseif($parameterDetails.ContainsKey('FlattenOnPSCmdlet') -and $parameterDetails.FlattenOnPSCmdlet) {
                    $DefinitionName = ($parameterDetails.Type -split '[.]')[-1]
                    if($DefinitionFunctionsDetails.ContainsKey($DefinitionName)) {
                        $DefinitionDetails = $DefinitionFunctionsDetails[$DefinitionName]
                        $flattenedParametersOnPSCmdlet[$parameterDetails.Name] = $DefinitionDetails
                        $DefinitionDetails.ParametersTable.GetEnumerator() | ForEach-Object {
                            $AddUniqueParameter_params['CandidateParameterDetails'] = $_.value
                            Add-UniqueParameter @AddUniqueParameter_params
                        }
                    }
                    else{
                        Throw ($LocalizedData.InvalidPSMetaFlattenParameter -f ($parameterDetails.Name,$parameterDetails.Type))
                    }
                } else {
                    $AddUniqueParameter_params['CandidateParameterDetails'] = $parameterDetails
                    Add-UniqueParameter @AddUniqueParameter_params
                }
            } else {
                # This magic string is here to distinguish local vs global parameters with the same name, e.g. in the Azure Resources API
                $parametersToAdd["$($parameterDetails.Name)Global"] = $null
            }
        }
    }
    $topParameterToAdd = $null
    $skipParameterToAdd = $null
    $pagingBlock = ''
    $pagingOperationName = ''
    $NextLinkName = 'NextLink'
    $pagingOperations = ''
    $Cmdlet = ''
    $CmdletParameter = ''
    $CmdletArgs = ''
    $pageType = 'Array'
    $PSCmdletOutputItemType = ''
    $resultBlockStr = $resultBlockNoPaging
    if ($x_ms_pageableObject) {
        if ($x_ms_pageableObject.ReturnType -ne 'NONE') {
            $pageType = $x_ms_pageableObject.ReturnType
            if($x_ms_pageableObject.ContainsKey('PSCmdletOutputItemType')) {
                $PSCmdletOutputItemType = $x_ms_pageableObject.PSCmdletOutputItemType                
            }
        }

        if ($x_ms_pageableObject.ContainsKey('Operations')) {
            $pagingOperations = $x_ms_pageableObject.Operations
            $pagingOperationName = $x_ms_pageableObject.MethodName
        } else {
            $Cmdlet = $x_ms_pageableObject.Cmdlet
            $CmdletArgs = $x_ms_pageableObject.CmdletArgsPaging
        }

        if ($x_ms_pageableObject.ContainsKey('NextLinkName') -and $x_ms_pageableObject.NextLinkName) {
            $NextLinkName = $x_ms_pageableObject.NextLinkName
        }

        $topParameterToAdd = @{
            Details = @{
                Name = 'Top'
                Type = 'int'
                Mandatory = '$false'
                Description = 'Return the top N items as specified by the parameter value. Applies after the -Skip parameter.'
                IsParameter = $true
                ValidateSet = $null
                ExtendedData = @{
                    Type = 'int'
                    HasDefaultValue = $true
                    DefaultValue = -1
                }
            }
            ParameterSetInfo = @{}
        }

        $skipParameterToAdd = @{
            Details = @{
                Name = 'Skip'
                Type = 'int'
                Mandatory = '$false'
                Description = 'Skip the first N items as specified by the parameter value.'
                IsParameter = $true
                ValidateSet = $null
                ExtendedData = @{
                    Type = 'int'
                    HasDefaultValue = $true
                    DefaultValue = -1
                }
            }
            ParameterSetInfo = @{}
        }
    }

    # Process security section
    $AuthenticationCommand = ""
    $AuthenticationCommandArgumentName = ''
    $hostOverrideCommand = ''
    $AddHttpClientHandler = $false
    $securityParametersToAdd = @()
    $PowerShellCodeGen = $SwaggerMetaDict['PowerShellCodeGen']

    # CustomAuthCommand and HostOverrideCommand are not required for Arm Services
    if (($PowerShellCodeGen['ServiceType'] -ne 'azure') -and ($PowerShellCodeGen['ServiceType'] -eq 'azure_stack')) {
        if ($PowerShellCodeGen['CustomAuthCommand']) {
            $AuthenticationCommand = $PowerShellCodeGen['CustomAuthCommand']
        }
        if ($PowerShellCodeGen['HostOverrideCommand']) {
            $hostOverrideCommand = $PowerShellCodeGen['HostOverrideCommand']
        }
    }

    # If the auth function hasn't been set by metadata, try to discover it from the security and securityDefinition objects in the spec
    if (-not $AuthenticationCommand -and -not $UseAzureCsharpGenerator) {
        if ($FunctionDetails.ContainsKey('Security')) {
            # For now, just take the first security object
            if ($FunctionDetails.Security.Count -gt 1) {
                Write-Warning ($LocalizedData.MultipleSecurityTypesNotSupported -f $commandName)
            }
            $firstSecurityObject = Get-Member -InputObject $FunctionDetails.Security[0] -MemberType NoteProperty
            # If there's no security object, we don't care about the security definition object
            if ($firstSecurityObject) {
                # If there is one, we need to know the definition
                if (-not $swaggerDict.ContainsKey("SecurityDefinitions")) {
                    throw $LocalizedData.SecurityDefinitionsObjectMissing
                }

                $securityDefinitions = $swaggerDict.SecurityDefinitions
                $securityDefinition = $securityDefinitions.$($firstSecurityObject.Name)
                if (-not $securityDefinition) {
                    throw ($LocalizedData.SpecificSecurityDefinitionMissing -f ($firstSecurityObject.Name))
                }

                if (-not (Get-Member -InputObject $securityDefinition -Name 'type')) {
                    throw ($LocalizedData.SecurityDefinitionMissingProperty -f ($firstSecurityObject.Name, 'type'))
                }

                $type = $securityDefinition.type
                if ($type -eq 'basic') {
                    # For Basic Authentication, allow the user to pass in a PSCredential object.
                    $credentialParameter = @{
                        Details = @{
                            Name = 'Credential'
                            Type = 'PSCredential'
                            Mandatory = '$true'
                            Description = 'User credentials.'
                            IsParameter = $true
                            ValidateSet = $null
                            ExtendedData = @{
                                Type = 'PSCredential'
                                HasDefaultValue = $false
                            }
                        }
                        ParameterSetInfo = @{}
                    }
                    $securityParametersToAdd += @{
                        Parameter = $credentialParameter
                        IsConflictingWithOperationParameter = $false
                    }
                    # If the service is specified to not issue authentication challenges, we can't rely on HttpClientHandler
                    if ($PowerShellCodeGen['NoAuthChallenge'] -and ($PowerShellCodeGen['NoAuthChallenge'] -eq $true)) {
                        $AuthenticationCommand = 'param([pscredential]$Credential) Get-AutoRestCredential -Credential $Credential'
                        $AuthenticationCommandArgumentName = 'Credential'
                    } else {
                        # Use an empty service client credentials object because we're using HttpClientHandler instead
                        $AuthenticationCommand = 'Get-AutoRestCredential'
                        $AddHttpClientHandler = $true
                    }
                } elseif ($type -eq 'apiKey') {
                    if (-not (Get-Member -InputObject $securityDefinition -Name 'name')) {
                        throw ($LocalizedData.SecurityDefinitionMissingProperty -f ($firstSecurityObject.Name, 'name'))
                    }

                    if (-not (Get-Member -InputObject $securityDefinition -Name 'in')) {
                        throw ($LocalizedData.SecurityDefinitionMissingProperty -f ($firstSecurityObject.Name, 'in'))
                    }

                    $name = $securityDefinition.name
                    $in = $securityDefinition.in
                    # For API key authentication, the user should supply the API key, but the in location and the name are generated from the spec
                    # In addition, we'd be unable to authenticate without the API key, so make it mandatory
                    $credentialParameter = @{
                        Details = @{
                            Name = 'APIKey'
                            Type = 'string'
                            Mandatory = '$true'
                            Description = 'API key given by service owner.'
                            IsParameter = $true
                            ValidateSet = $null
                            ExtendedData = @{
                                Type = 'string'
                                HasDefaultValue = $false
                            }
                        }
                        ParameterSetInfo = @{}
                    }
                    $securityParametersToAdd += @{
                        Parameter = $credentialParameter
                        IsConflictingWithOperationParameter = $false
                    }
                    $AuthenticationCommand = "param([string]`$APIKey) Get-AutoRestCredential -APIKey `$APIKey -Location '$in' -Name '$name'"
                    $AuthenticationCommandArgumentName = 'APIKey'
                } else {
                    Write-Warning -Message ($LocalizedData.UnsupportedAuthenticationType -f ($type))
                }
            }
        }
    }

    if (-not $AuthenticationCommand -and -not $UseAzureCsharpGenerator) {
        # At this point, there was no supported security object or overridden auth function, so assume no auth
        $AuthenticationCommand = 'Get-AutoRestCredential'
    }

    $nonUniqueParameterSets = @()
    foreach ($parameterSetDetail in $parameterSetDetails) {
        # Add parameter sets to paging parameter sets
        if ($topParameterToAdd -and $parameterSetDetail.ContainsKey('x-ms-pageable') -and $parameterSetDetail.'x-ms-pageable' -and (-not $isNextPageOperation)) {
            $topParameterToAdd.ParameterSetInfo[$parameterSetDetail.OperationId] = @{
                Name = $parameterSetDetail.OperationId
                Mandatory = '$false'
            }
        }

        if ($skipParameterToAdd -and $parameterSetDetail.ContainsKey('x-ms-pageable') -and $parameterSetDetail.'x-ms-pageable' -and (-not $isNextPageOperation)) {
            $skipParameterToAdd.ParameterSetInfo[$parameterSetDetail.OperationId] = @{
                Name = $parameterSetDetail.OperationId
                Mandatory = '$false'
            }
        }

        # Test for uniqueness of parameters
        $parameterSetDetail.ParameterDetails.GetEnumerator() | ForEach-Object {
            $parameterDetails = $_.Value
            # Check if the paging parameters are conflicting
            # Note that this has to be moved elsewhere to be more generic, but this is temporarily located here to solve this scenario for paging at least
            if ($topParameterToAdd -and $parameterDetails.Name -eq 'Top') {
                $topParameterToAdd = $null
                # If the parameter is not OData, full paging support isn't possible.
                if (-not $parameterDetails.ExtendedData.ContainsKey('IsODataParameter') -or -not $parameterDetails.ExtendedData.IsODataParameter) {
                    Write-Warning -Message ($LocalizedData.ParameterConflictAndResult -f ('Top', $commandName, $parameterSetDetail.OperationId, $LocalizedData.PagingNotFullySupported))
                }
            }

            if ($skipParameterToAdd -and $parameterDetails.Name -eq 'Skip') {
                $skipParameterToAdd = $null
                # If the parameter is not OData, full paging support isn't possible.
                if (-not $parameterDetails.ExtendedData.ContainsKey('IsODataParameter') -or -not $parameterDetails.ExtendedData.IsODataParameter) {
                    Write-Warning -Message ($LocalizedData.ParameterConflictAndResult -f ('Skip', $commandName, $parameterSetDetail.OperationId, $LocalizedData.PagingNotFullySupported))
                }
            }

            foreach ($additionalParameter in $securityParametersToAdd) {
                if ($parameterDetails.Name -eq $additionalParameter.Parameter.Details.Name) {
                    $additionalParameter.IsConflictingWithOperationParameter = $true
                    Write-Warning -Message ($LocalizedData.ParameterConflictAndResult -f ($additionalParameter.Parameter.Details.Name, $commandName, $parameterSetDetail.OperationId, $LocalizedData.CredentialParameterNotSupported))
                }
            }
            
            if ($parameterHitCount[$parameterDetails.Name] -eq 1) {
                # continue here brings us back to the top of the $parameterSetDetail.ParameterDetails.GetEnumerator() | ForEach-Object loop
                continue
            }
        }

        # At this point none of the parameters in this set are unique
        $nonUniqueParameterSets += $parameterSetDetail
    }

    if ($topParameterToAdd) {
        $parametersToAdd[$topParameterToAdd.Details.Name] = $topParameterToAdd
    }

    if ($skipParameterToAdd) {
        $parametersToAdd[$skipParameterToAdd.Details.Name] = $skipParameterToAdd
    }

    foreach ($additionalParameter in $securityParametersToAdd) {
        if (-not $additionalParameter.IsConflictingWithOperationParameter) {
            $parametersToAdd[$additionalParameter.Parameter.Details.Name] = $additionalParameter.Parameter
        }
    }
    
    if ($topParameterToAdd -and $skipParameterToAdd) {
        $resultBlockStr = $executionContext.InvokeCommand.ExpandString($resultBlockWithSkipAndTop)
    } elseif ($topParameterToAdd -and -not $skipParameterToAdd) {
        $resultBlockStr = $executionContext.InvokeCommand.ExpandString($resultBlockWithTop)
    } elseif (-not $topParameterToAdd -and $skipParameterToAdd) {
        $resultBlockStr = $executionContext.InvokeCommand.ExpandString($resultBlockWithSkip)
    }

    $getTaskResult = $executionContext.InvokeCommand.ExpandString($getTaskResultBlock)

    if ($pagingOperations) {
        if ($topParameterToAdd) {
            $pagingBlock = $executionContext.InvokeCommand.ExpandString($PagingBlockStrFunctionCallWithTop)
        } else {
            $pagingBlock = $executionContext.InvokeCommand.ExpandString($PagingBlockStrFunctionCall)
        }
    } elseif ($Cmdlet) {
        if ($topParameterToAdd) {
            $pagingBlock = $executionContext.InvokeCommand.ExpandString($PagingBlockStrCmdletCallWithTop)
        } else {
            $pagingBlock = $executionContext.InvokeCommand.ExpandString($PagingBlockStrCmdletCall)
        }
    }

    # For description, we're currently using the default parameter set's description, since concatenating multiple descriptions doesn't ever really work out well.
    if ($nonUniqueParameterSets.Length -gt 1) {
        # Pick the highest priority set among $nonUniqueParameterSets, but really it doesn't matter, cause...
        # Print warning that this generated cmdlet has ambiguous parameter sets
        $defaultParameterSet = $nonUniqueParameterSets | Sort-Object {$_.Priority} | Select-Object -First 1
        $DefaultParameterSetName = $defaultParameterSet.OperationId
        $description = $defaultParameterSet.Description
        $synopsis = $defaultParameterSet.Synopsis
        Write-Warning -Message ($LocalizedData.CmdletHasAmbiguousParameterSets -f ($commandName))
    } elseif ($nonUniqueParameterSets.Length -eq 1) {
        # If there's only one non-unique, we can prevent errors by making this the default
        $DefaultParameterSetName = $nonUniqueParameterSets[0].OperationId
        $description = $nonUniqueParameterSets[0].Description
        $synopsis = $nonUniqueParameterSets[0].Synopsis
    } else {
        # Pick the highest priority set among all sets
        $defaultParameterSet = $parameterSetDetails | Sort-Object @{e = {$_.Priority -as [int] }} | Select-Object -First 1
        $DefaultParameterSetName = $defaultParameterSet.OperationId
        $description = $defaultParameterSet.Description
        $synopsis = $defaultParameterSet.Synopsis        
    }

    $oDataExpression = ""
    $oDataExpressionBlock = ""
    # Variable used to replace in function body
    $parameterGroupsExpressionBlock = ""
    # Variable used to store all group expressions, concatenate, then store in $parameterGroupsExpressionBlock
    $parameterGroupsExpressions = @{}
    $parametersToAdd.GetEnumerator() | ForEach-Object {
        $parameterToAdd = $_.Value
        if ($parameterToAdd) {
            $parameterName = $parameterToAdd.Details.Name
            $AllParameterSetsString = ''
            foreach ($parameterSetInfoEntry in $parameterToAdd.ParameterSetInfo.GetEnumerator()) {
                $parameterSetInfo = $parameterSetInfoEntry.Value
                $isParamMandatory = $parameterSetInfo.Mandatory
                $ParameterSetPropertyString = ", ParameterSetName = '$($parameterSetInfo.Name)'"
                if ($AllParameterSetsString) {
                    # Two tabs
                    $AllParameterSetsString += [Environment]::NewLine + " " + $executionContext.InvokeCommand.ExpandString($parameterAttributeString)
                } else {
                    $AllParameterSetsString = $executionContext.InvokeCommand.ExpandString($parameterAttributeString)
                }
            }

            if (-not $AllParameterSetsString) {
                $isParamMandatory = $parameterToAdd.Details.Mandatory
                $ParameterSetPropertyString = ""
                $AllParameterSetsString = $executionContext.InvokeCommand.ExpandString($parameterAttributeString)
            }

            $paramName = "`$$parameterName" 
            $ValidateSetDefinition = $null
            if ($parameterToAdd.Details.ValidateSet)
            {
                $ValidateSetString = $parameterToAdd.Details.ValidateSet
                $ValidateSetDefinition = $executionContext.InvokeCommand.ExpandString($ValidateSetDefinitionString)
            }

            $parameterDefaultValueOption = ""
            $paramType = "$([Environment]::NewLine) "
            if ($parameterToAdd.Details.ContainsKey('ExtendedData')) {
                if ($parameterToAdd.Details.ExtendedData.ContainsKey('IsODataParameter') -and $parameterToAdd.Details.ExtendedData.IsODataParameter) {
                    $paramType = "[$($parameterToAdd.Details.Type)]$paramType"
                    $oDataExpression += " if (`$$parameterName) { `$oDataQuery += `"&```$$parameterName=`$$parameterName`" }" + [Environment]::NewLine
                } else {
                    # Assuming you can't group ODataQuery parameters
                    if ($parameterToAdd.Details.ContainsKey('x_ms_parameter_grouping') -and $parameterToAdd.Details.'x_ms_parameter_grouping') {
                        $parameterGroupPropertyName = $parameterToAdd.Details.Name
                        $groupName = $parameterToAdd.Details.'x_ms_parameter_grouping'
                        $fullGroupName = $parameterToAdd.Details.ExtendedData.GroupType
                        if ($parameterGroupsExpressions.ContainsKey($groupName)) {
                            $parameterGroupsExpression = $parameterGroupsExpressions[$groupName]
                        } else {
                            $parameterGroupsExpression = $executionContext.InvokeCommand.ExpandString($parameterGroupCreateExpression)
                        }

                        $parameterGroupsExpression += [Environment]::NewLine + $executionContext.InvokeCommand.ExpandString($parameterGroupPropertyExpression)
                        $parameterGroupsExpressions[$groupName] = $parameterGroupsExpression
                    }
                    
                    if ($parameterToAdd.Details.ExtendedData.Type) {
                        $paramType = "[$($parameterToAdd.Details.ExtendedData.Type)]$paramType"
                        if ($parameterToAdd.Details.ExtendedData.HasDefaultValue) {
                            if ($parameterToAdd.Details.ExtendedData.DefaultValue) {
                                if ([NullString]::Value -eq $parameterToAdd.Details.ExtendedData.DefaultValue) {
                                    $parameterDefaultValue = "[NullString]::Value"
                                } elseif ("System.String" -eq $parameterToAdd.Details.ExtendedData.Type) {
                                    $parameterDefaultValue = "`"$($parameterToAdd.Details.ExtendedData.DefaultValue)`""
                                } else {
                                    $parameterDefaultValue = "$($parameterToAdd.Details.ExtendedData.DefaultValue)"
                                }
                            } else {
                                $parameterDefaultValue = "`$null"
                            }

                            $parameterDefaultValueOption = $executionContext.InvokeCommand.ExpandString($parameterDefaultValueString)
                        }
                    }
                }

                $paramBlock += $executionContext.InvokeCommand.ExpandString($parameterDefString)
                $pDescription = $parameterToAdd.Details.Description
                $paramHelp += $executionContext.InvokeCommand.ExpandString($helpParamStr)
            } elseif($parameterToAdd.Details.Containskey('Type')) {
                $paramType = "[$($parameterToAdd.Details.Type)]$paramType"

                $paramblock += $executionContext.InvokeCommand.ExpandString($parameterDefString)
                $pDescription = $parameterToAdd.Details.Description
                $paramHelp += $executionContext.InvokeCommand.ExpandString($helpParamStr)
            } else {
                Write-Warning ($LocalizedData.ParameterMissingFromAutoRestCode -f ($parameterName, $commandName))
            }
        }
    }

    foreach ($parameterGroupsExpressionEntry in $parameterGroupsExpressions.GetEnumerator()) {
        $parameterGroupsExpressionBlock += $parameterGroupsExpressionEntry.Value + [Environment]::NewLine
    }

    if ($oDataExpression) {
        $oDataExpression = $oDataExpression.Trim()
        $oDataExpressionBlock = $executionContext.InvokeCommand.ExpandString($oDataExpressionBlockStr)
    }

    $paramBlock = $paramBlock.TrimEnd().TrimEnd(",")
    $commandHelp = $executionContext.InvokeCommand.ExpandString($helpDescStr)
    if ($isLongRunningOperation) {
        if ($paramBlock) {
            $ParamBlockReplaceStr = $paramBlock + ",`r`n" + $AsJobParameterString
        } else {
            $ParamBlockReplaceStr = $AsJobParameterString
        }

        $PathFunctionBody = $executionContext.InvokeCommand.ExpandString($PathFunctionBodyAsJob)
    } else {
        $ParamBlockReplaceStr = $paramBlock
        $PathFunctionBody = $executionContext.InvokeCommand.ExpandString($PathFunctionBodySynch)
    }

    $functionBodyParams = @{
        ParameterSetDetails               = $parameterSetDetails
        ODataExpressionBlock              = $oDataExpressionBlock
        ParameterGroupsExpressionBlock    = $parameterGroupsExpressionBlock
        SwaggerDict                       = $SwaggerDict
        SwaggerMetaDict                   = $SwaggerMetaDict
        FlattenedParametersOnPSCmdlet     = $flattenedParametersOnPSCmdlet
    }
    if($AuthenticationCommand) {
        $functionBodyParams['AuthenticationCommand'] = $AuthenticationCommand
        $functionBodyParams['AuthenticationCommandArgumentName'] = $AuthenticationCommandArgumentName
    }
    if($AddHttpClientHandler) {
        $functionBodyParams['AddHttpClientHandler'] = $AddHttpClientHandler
    }    
    if($hostOverrideCommand) {
        $functionBodyParams['hostOverrideCommand'] = $hostOverrideCommand
    }
    if($globalParameters) {
        $functionBodyParams['GlobalParameters'] = $globalParameters
    }
                           
    $pathGenerationPhaseResult = Get-PathFunctionBody @functionBodyParams
    $bodyObject = $pathGenerationPhaseResult.BodyObject

    $body = $bodyObject.Body
    if($PSCmdletOutputItemType){
        $fullPathDataType = $PSCmdletOutputItemType
        $outputTypeBlock = $executionContext.InvokeCommand.ExpandString($outputTypeStr)
    }
    else {
        $outputTypeBlock = $bodyObject.OutputTypeBlock        
    }

    if ($UseAzureCsharpGenerator) {
        $dependencyInitFunction = "Initialize-PSSwaggerDependencies -Azure"
    } else {
        $dependencyInitFunction = "Initialize-PSSwaggerDependencies"
    }
    
    $CommandString = $executionContext.InvokeCommand.ExpandString($advFnSignatureForPath)
    $GeneratedCommandsPath = Join-Path -Path (Join-Path -Path $SwaggerMetaDict['outputDirectory'] -ChildPath $GeneratedCommandsName) `
                                       -ChildPath 'SwaggerPathCommands'

    if(-not (Test-Path -Path $GeneratedCommandsPath -PathType Container)) {
        $null = New-Item -Path $GeneratedCommandsPath -ItemType Directory
    }

    $CommandFilePath = Join-Path -Path $GeneratedCommandsPath -ChildPath "$commandName.ps1"
    Out-File -InputObject @($PSHeaderComment, $CommandString) -FilePath $CommandFilePath -Encoding ascii -Force -Confirm:$false -WhatIf:$false

    Write-Verbose -Message ($LocalizedData.GeneratedPathCommand -f $commandName)

    return $commandName
}

function Set-ExtendedCodeMetadata {
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        [string]
        $MainClientTypeName,

        [Parameter(Mandatory=$true)]
        [string]
        $CliXmlTmpPath
    )

    $resultRecord = @{
        VerboseMessages = @()
        ErrorMessages = @()
        WarningMessages = @()
    }
    
    $resultRecord.VerboseMessages += $LocalizedData.ExtractingMetadata

    $PathFunctionDetails = Import-CliXml -Path $CliXmlTmpPath
    $errorOccurred = $false
    $PathFunctionDetails.GetEnumerator() | ForEach-Object {
        $FunctionDetails = $_.Value
        $ParameterSetDetails = $FunctionDetails['ParameterSetDetails']
        foreach ($parameterSetDetail in $ParameterSetDetails) {
            if ($errorOccurred) {
                return
            }
            
            $operationId = $parameterSetDetail.OperationId
            $methodNames = @()
            $operations = ''
            $operationsWithSuffix = ''
            $opIdValues = $operationId -split '_',2 
            if(-not $opIdValues -or ($opIdValues.count -ne 2)) {
                $methodNames += $operationId + 'WithHttpMessagesAsync'
                $methodNames += $operationId + 'Method' + 'WithHttpMessagesAsync'
            } else {            
                $operationName = $opIdValues[0]
                $operationType = $opIdValues[1]
                $operations = ".$operationName"
                if ($parameterSetDetail['UseOperationsSuffix'] -and $parameterSetDetail['UseOperationsSuffix'])
                { 
                    $operationsWithSuffix = $operations + 'Operations'
                }

                $methodNames += $operationType + 'WithHttpMessagesAsync'
                # When OperationType value conflicts with a definition name, AutoREST generates method name by adding Method to the OperationType.
                $methodNames += $operationType + 'Method' + 'WithHttpMessagesAsync'
            }

            $parameterSetDetail['Operations'] = $operations

            # For some reason, moving this out of this loop causes issues
            $clientType = $MainClientTypeName -as [Type]
            if (-not $clientType) {
                $resultRecord.ErrorMessages += $LocalizedData.ExpectedServiceClientTypeNotFound -f ($MainClientTypeName)
                Export-CliXml -InputObject $resultRecord -Path $CliXmlTmpPath
                $errorOccurred = $true
                return
            }

            # Process global parameters
            $paramObject = $parameterSetDetail.ParameterDetails
            $clientType.GetProperties() | ForEach-Object {
                $propertyName = $_.Name
                $matchingParamDetail = $paramObject.GetEnumerator() | Where-Object { $_.Value.Name -eq $propertyName } | Select-Object -First 1 -ErrorAction Ignore
                if ($matchingParamDetail) {
                    $setSingleParameterMetadataParms = @{
                                                            CommandName = $FunctionDetails['CommandName']
                                                            Name = $matchingParamDetail.Value.Name
                                                            HasDefaultValue = $false
                                                            IsGrouped = $false
                                                            Type = $_.PropertyType
                                                            MatchingParamDetail = $matchingParamDetail.Value
                                                            ResultRecord = $resultRecord
                                                        }
                    if (-not (Set-SingleParameterMetadata @setSingleParameterMetadataParms))
                    {
                        Export-CliXml -InputObject $ResultRecord -Path $CliXmlTmpPath
                        $errorOccurred = $true
                        return
                    }
                }
            }

            if ($operationsWithSuffix) {
                $operationName = $operationsWithSuffix.Substring(1)
                $propertyObject = $clientType.GetProperties() | Where-Object { $_.Name -eq $operationName } | Select-Object -First 1 -ErrorAction Ignore
                if (-not $propertyObject) {
                    # The Operations suffix logic isn't rock solid, so this is safety check.
                    $operationName = $operations.Substring(1)
                    $propertyObject = $clientType.GetProperties() | Where-Object { $_.Name -eq $operationName } | Select-Object -First 1 -ErrorAction Ignore
                    if (-not $propertyObject) {
                        $resultRecord.ErrorMessages += $LocalizedData.ExpectedOperationsClientTypeNotFound -f ($operationName, $clientType)
                        Export-CliXml -InputObject $resultRecord -Path $CliXmlTmpPath
                        $errorOccurred = $true
                        return
                    }
                } else {
                    $parameterSetDetail['Operations'] = $operationsWithSuffix
                }

                $clientType = $propertyObject.PropertyType
            } elseif ($operations) {
                $operationName = $operations.Substring(1)
                $propertyObject = $clientType.GetProperties() | Where-Object { $_.Name -eq $operationName } | Select-Object -First 1 -ErrorAction Ignore
                if (-not $propertyObject) {
                    $resultRecord.ErrorMessages += $LocalizedData.ExpectedOperationsClientTypeNotFound -f ($operationName, $clientType)
                    Export-CliXml -InputObject $resultRecord -Path $CliXmlTmpPath
                    $errorOccurred = $true
                    return
                }

                $clientType = $propertyObject.PropertyType
            }

            $methodInfo = $clientType.GetMethods() | Where-Object {$MethodNames -contains $_.Name} | Select-Object -First 1
            if (-not $methodInfo) {
                $resultRecord.ErrorMessages += $LocalizedData.ExpectedMethodOnTypeNotFound -f (($MethodNames -join ', or '), $clientType)
                Export-CliXml -InputObject $resultRecord -Path $CliXmlTmpPath
                $errorOccurred = $true
                return
            }
            $parameterSetDetail['MethodName'] = $methodInfo.Name

            # Process output type
            $returnType = $methodInfo.ReturnType
            if ($returnType.Name -eq 'Task`1') {
                $returnType = $returnType.GenericTypeArguments[0]
            }

            if ($returnType.Name -eq 'AzureOperationResponse`1') {
                $returnType = $returnType.GenericTypeArguments[0]
            }

            # Note: ReturnType and PSCmdletOutputItemType are currently used for Swagger operations which supports x-ms-pageable.
            if (($returnType.Name -eq 'IPage`1') -and $returnType.GenericTypeArguments) {
                $PSCmdletOutputItemTypeString = Convert-GenericTypeToString -Type $returnType.GenericTypeArguments[0]
                $parameterSetDetail['PSCmdletOutputItemType'] = $PSCmdletOutputItemTypeString.Trim('[]')
            }
            $parameterSetDetail['ReturnType'] = Convert-GenericTypeToString -Type $returnType

            $ParamList = @()
            $oDataQueryFound = $false
            $methodInfo.GetParameters() | Sort-Object -Property Position | ForEach-Object {
                $hasDefaultValue = $_.HasDefaultValue
                # All Types should be converted to their string names, otherwise the CLI XML gets too large
                $type = $_.ParameterType.ToString()
                $metadata = @{
                    Name = Get-PascalCasedString -Name $_.Name
                    HasDefaultValue = $hasDefaultValue
                    Type = $type
                }

                $matchingParamDetail = $paramObject.GetEnumerator() | Where-Object { $_.Value.Name -eq $metadata.Name } | Select-Object -First 1 -ErrorAction Ignore
                if ($matchingParamDetail) {
                    # Not all parameters in the code is present in the Swagger spec (autogenerated parameters like CustomHeaders or ODataQuery parameters)
                    $matchingParamDetail = $matchingParamDetail[0].Value
                    if ($matchingParamDetail.ContainsKey('x_ms_parameter_grouping_group')) {
                        # Look through this parameter group's parameters and extract the individual metadata
                        $paramToAdd = "`$$($matchingParamDetail.Name)"
                        $parameterGroupType = $_.ParameterType
                        $parameterGroupType.GetProperties() | ForEach-Object {
                            $parameterGroupProperty = $_
                            $matchingGroupedParameterDetailEntry = $matchingParamDetail.'x_ms_parameter_grouping_group'.GetEnumerator() | Where-Object { $_.Value.Name -eq $parameterGroupProperty.Name } | Select-Object -First 1 -ErrorAction Ignore
                            if ($matchingGroupedParameterDetailEntry) {
                                $setSingleParameterMetadataParms = @{
                                    CommandName = $FunctionDetails['CommandName']
                                    Name = $matchingParamDetail.Name
                                    HasDefaultValue = $false
                                    IsGrouped = $true
                                    Type = $_.PropertyType
                                    MatchingParamDetail = $matchingGroupedParameterDetailEntry.Value
                                    ResultRecord = $resultRecord
                                }

                                if (-not (Set-SingleParameterMetadata @setSingleParameterMetadataParms))
                                {
                                    Export-CliXml -InputObject $ResultRecord -Path $CliXmlTmpPath
                                    $errorOccurred = $true
                                    return
                                }

                                $matchingGroupedParameterDetailEntry.Value.ExtendedData.GroupType = $parameterGroupType.ToString()
                            }
                        }
                    } else {
                        # Single parameter
                        $setSingleParameterMetadataParms = @{
                            CommandName = $FunctionDetails['CommandName']
                            Name = $_.Name
                            HasDefaultValue = $hasDefaultValue
                            IsGrouped = $false
                            Type = $_.ParameterType
                            MatchingParamDetail = $matchingParamDetail
                            ResultRecord = $resultRecord
                        }

                        if ($hasDefaultValue) {
                            $setSingleParameterMetadataParms['DefaultValue'] = $_.DefaultValue
                        }

                        if (-not (Set-SingleParameterMetadata @setSingleParameterMetadataParms))
                        {
                            Export-CliXml -InputObject $ResultRecord -Path $CliXmlTmpPath
                            $errorOccurred = $true
                            return
                        }

                        $paramToAdd = $matchingParamDetail.ExtendedData.ParamToAdd
                    }
                    
                    $ParamList += $paramToAdd
                } else {
                    if ($metadata.Type.StartsWith("Microsoft.Rest.Azure.OData.ODataQuery``1")) {
                        if ($oDataQueryFound) {
                            $resultRecord.ErrorMessages += ($LocalizedData.MultipleODataQueriesOneFunction -f ($operationId))
                            Export-CliXml -InputObject $resultRecord -Path $CliXmlTmpPath
                            return
                        } else {
                            # Escape backticks
                            $oDataQueryType = $metadata.Type.Replace("``", "````")
                            $ParamList += "`$(if (`$oDataQuery) { New-Object -TypeName `"$oDataQueryType`" -ArgumentList `$oDataQuery } else { `$null })"
                            $oDataQueryFound = $true
                        }
                    }
                }
            }
            
            if ($parameterSetDetail.ContainsKey('x-ms-odata') -and $parameterSetDetail.'x-ms-odata') {
                $paramObject.GetEnumerator() | ForEach-Object {
                    $paramDetail = $_.Value
                    if (-not $paramDetail.ContainsKey('ExtendedData')) {
                        $metadata = @{
                            IsODataParameter = $true
                        }

                        $paramDetail.ExtendedData = $metadata
                    }
                }
            }

            $parameterSetDetail['ExpandedParamList'] = $ParamList -Join ", "
        }

        if ($errorOccurred) {
            return
        }
    }

    $resultRecord.Result = $PathFunctionDetails
    Export-CliXml -InputObject $resultRecord -Path $CliXmlTmpPath
}

function Convert-GenericTypeToString {
    param(
        [Parameter(Mandatory=$true)]
        [Type]$Type
    )

    if (-not $Type.IsGenericType) {
        return $Type.FullName
    }

    $genericTypeStr = ''
    foreach ($genericTypeArg in $Type.GenericTypeArguments) {
        $genericTypeStr += "$(Convert-GenericTypeToString -Type $genericTypeArg),"
    }

    $genericTypeStr = $genericTypeStr.Substring(0, $genericTypeStr.Length - 1)
    return "$($Type.FullName.Substring(0, $Type.FullName.IndexOf('`')))[$genericTypeStr]"
}

function Set-SingleParameterMetadata {
    param(
        [Parameter(Mandatory=$true)]
        [string]
        $CommandName,

        [Parameter(Mandatory=$true)]
        [string]
        $Name,

        [Parameter(Mandatory=$true)]
        [bool]
        $HasDefaultValue,

        [Parameter(Mandatory=$true)]
        [bool]
        $IsGrouped,

        [Parameter(Mandatory=$true)]
        [System.Type]
        $Type,

        [Parameter(Mandatory=$true)]
        [hashtable]
        $MatchingParamDetail,

        [Parameter(Mandatory=$true)]
        [hashtable]
        $ResultRecord,

        [Parameter(Mandatory=$false)]
        [object]
        $DefaultValue
    )
    
    $name = Get-PascalCasedString -Name $_.Name
    $metadata = @{
                    Name = $name
                    HasDefaultValue = $HasDefaultValue
                    Type = $Type.ToString()
                    ParamToAdd = "`$$name"
                }

    if ($HasDefaultValue) {
        # Setting this default value actually matter, but we might as well
        if ("System.String" -eq $metadata.Type) {
            if ($DefaultValue -eq $null) {
                $metadata.HasDefaultValue = $false
                # This is the part that works around PS automatic string coercion
                $metadata.ParamToAdd = "`$(if (`$PSBoundParameters.ContainsKey('$($metadata.Name)')) { $($metadata.ParamToAdd) } else { [NullString]::Value })"
            }
        } elseif ("System.Nullable``1[System.Boolean]" -eq $metadata.Type) {
            if($DefaultValue -ne $null) {
                $DefaultValue = "`$$DefaultValue"
            }

            $metadata.Type = "switch"
        } else {
            $DefaultValue = $_.DefaultValue
            if (-not ($_.ParameterType.IsValueType) -and $DefaultValue) {
                $ResultRecord.ErrorMessages += $LocalizedData.ReferenceTypeDefaultValueNotSupported -f ($metadata.Name, $metadata.Type, $CommandName)
                return $false
            }
        }

        $metadata['DefaultValue'] = $DefaultValue
    } else {
        if ('$false' -eq $matchingParamDetail.Mandatory -and (-not $IsGrouped)) {
            # This happens in the case of optional path parameters, even if the path parameter is at the end
            $ResultRecord.WarningMessages += ($LocalizedData.OptionalParameterNowRequired -f ($metadata.Name, $CommandName))
        }
    }

    $MatchingParamDetail['ExtendedData'] = $metadata
    return $true
}

function Get-TemporaryCliXmlFilePath {
    param(
        [Parameter(Mandatory=$true)]
        [string]
        $FullClientTypeName
    )

    $random = [Guid]::NewGuid().Guid
    $filePath = Join-Path -Path (Get-XDGDirectory -DirectoryType Cache) -ChildPath "$FullClientTypeName.$random.xml"
    return $filePath
}
# SIG # Begin signature block
# MIIarQYJKoZIhvcNAQcCoIIanjCCGpoCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU5/h5hXHsrJVILE1EFjztnO2a
# sMOgghWAMIIEwjCCA6qgAwIBAgITMwAAALwLLhp7irHHkQAAAAAAvDANBgkqhkiG
# 9w0BAQUFADB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G
# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSEw
# HwYDVQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EwHhcNMTYwOTA3MTc1ODQ3
# WhcNMTgwOTA3MTc1ODQ3WjCBsjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEMMAoGA1UECxMDQU9DMScwJQYDVQQLEx5uQ2lwaGVyIERTRSBFU046
# MTJCNC0yRDVGLTg3RDQxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNl
# cnZpY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrW5IBRZQaAQPT
# HTCSXDRgGi/lbqVTqt3Mp5XqqbEkIZowQp8M/Gyv+1TmRpbFaQIQ4oQ7AqZRsvd+
# PMGtZjo6vUBRyeLKpnHq1a9XYeiGkoGaJu/98Ued3Z+sFD45bhzi6tLzY6kq98KI
# YqK7XsI76kqVU3oIyiETzzoANwuXUNSnm9lAN3l/G8xgDm/3qBWMSjkBvg2GeZ57
# 3WqYP6fImkO9U0bRtuIr6mybzvXUUO+rg6hhdrEnLGI4QQ7frEWReYeyMlgjC7VR
# aJy2gomkh+sEmxxivphgOuJrtPgUhdIlyTkUTtyudNUd/6gTE4zt9TsmFf5wGCsx
# pbZqKFW3AgMBAAGjggEJMIIBBTAdBgNVHQ4EFgQUyHJk5pJfz0FWFyn1nlRJFHyq
# /vcwHwYDVR0jBBgwFoAUIzT42VJGcArtQPt2+7MrsMM1sw8wVAYDVR0fBE0wSzBJ
# oEegRYZDaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv
# TWljcm9zb2Z0VGltZVN0YW1wUENBLmNybDBYBggrBgEFBQcBAQRMMEowSAYIKwYB
# BQUHMAKGPGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljcm9z
# b2Z0VGltZVN0YW1wUENBLmNydDATBgNVHSUEDDAKBggrBgEFBQcDCDANBgkqhkiG
# 9w0BAQUFAAOCAQEAPiS9UtetZjCdEkaanFlC+NU/Ti+PUD6O+P6yCASPI6+qK20t
# B16FXJg7rXRee3c/E2wcyWuxeL/0oLkj4LunxQoDDhoOjM9w9SnrWjki/kbkEdbg
# i1Pl4ebDSu+6Six3fdRrLowgkQwXxkCoUWwyFS9dL5BbC5lSzHlOiXiWVlc94vr3
# 9sMaoqsxl6A6Ud9YvbohYuiKJsdpSrLW97wXO66h+Cx289JckOmomW1Zum3ppfgp
# +5lJJBxySomU08S8G5QOOrvjO4KsQ55eHHVWJXhnGL+zhghaSf5TIQuDdohDOnNb
# +FImqnwn3++hmpbkAVWdFUNDNlJemia/hMH9vzCCBOswggPToAMCAQICEzMAAAF4
# JVq1zSPGX5UAAQAAAXgwDQYJKoZIhvcNAQEFBQAweTELMAkGA1UEBhMCVVMxEzAR
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjEjMCEGA1UEAxMaTWljcm9zb2Z0IENvZGUgU2ln
# bmluZyBQQ0EwHhcNMTcwODExMjAxMTE1WhcNMTgwODExMjAxMTE1WjCBgjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEMMAoGA1UECxMDQU9DMR4w
# HAYDVQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUA
# A4IBDwAwggEKAoIBAQCZbh1TVaudsrIbXUPPB9c8S+E+dKSbskHKYlG6SGTH8jhT
# hpuvGiAO87F2b9GHVN+DvszaMkGy/xQgHaGEJLfpnb3kpakic7E0bjDHdG4KnHRb
# no/wfUkGLfS79o+cw//RY8Ck6yE+0czDBcxp0Gbw5JyGP+KFqvzRR/3Tv3nt/5x0
# 5ZnEOHYP+eDVikDvn/DH+oxxtiCfX3tkvtm/yX4eOb47YdmYKQjCgz2+Nil/lupY
# vU0QFIjvke3jshqQINDng/vO9ys2qA0ex/q5hlLKQTST99dGoM86Ge6F723ReToq
# KnGLN8kiCG7uNapOAIQrpCHZq96CVumiaA5ZvxU9AgMBAAGjggFgMIIBXDATBgNV
# HSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUjuhtD3FD7tk/RKloJFX05cpgLjcw
# UQYDVR0RBEowSKRGMEQxDDAKBgNVBAsTA0FPQzE0MDIGA1UEBRMrMjI5ODAzKzFh
# YmY5ZTVmLWNlZDAtNDJlNi1hNjVkLWQ5MzUwOTU5ZmUwZTAfBgNVHSMEGDAWgBTL
# EejK0rQWWAHJNy4zFha5TJoKHzBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3Js
# Lm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNDb2RTaWdQQ0FfMDgt
# MzEtMjAxMC5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY0NvZFNpZ1BDQV8wOC0zMS0y
# MDEwLmNydDANBgkqhkiG9w0BAQUFAAOCAQEAYnG/oHG/xgZYR8NAMHZ/vE9GM0e4
# 7YdhuTea2uY7pSGwM707wp8Wan0Fa6evK1PWfcd/XNOh2BpEv5o8RmKDoEsG0ECP
# 13Jug7cklfKreBVHQ+Djg43VVFLZpuo7aOAVK6wjlcnpPUtn+SfH9K0aM2FjDKVJ
# FW6XFKXBat5R+Zp6uOxWTxpSeMTeDC5zF6IY6ogR1uzU+9EQoRlAvkwX6po+exEL
# nMLr4++P+fqOxIU+PODIoB8ijClAqwwKvLlMPa3qlrNHt+LweTMu7lvGC/RA18wU
# zzXAeomuZ03blUw+bkOiVgWOk4S0RN7EnW7zjJV8gd/+G2dbToUi1cB/fTCCBbww
# ggOkoAMCAQICCmEzJhoAAAAAADEwDQYJKoZIhvcNAQEFBQAwXzETMBEGCgmSJomT
# 8ixkARkWA2NvbTEZMBcGCgmSJomT8ixkARkWCW1pY3Jvc29mdDEtMCsGA1UEAxMk
# TWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTEwMDgzMTIy
# MTkzMloXDTIwMDgzMTIyMjkzMloweTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjEjMCEGA1UEAxMaTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0Ew
# ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCycllcGTBkvx2aYCAgQpl2
# U2w+G9ZvzMvx6mv+lxYQ4N86dIMaty+gMuz/3sJCTiPVcgDbNVcKicquIEn08Gis
# TUuNpb15S3GbRwfa/SXfnXWIz6pzRH/XgdvzvfI2pMlcRdyvrT3gKGiXGqelcnNW
# 8ReU5P01lHKg1nZfHndFg4U4FtBzWwW6Z1KNpbJpL9oZC/6SdCnidi9U3RQwWfjS
# jWL9y8lfRjFQuScT5EAwz3IpECgixzdOPaAyPZDNoTgGhVxOVoIoKgUyt0vXT2Pn
# 0i1i8UU956wIAPZGoZ7RW4wmU+h6qkryRs83PDietHdcpReejcsRj1Y8wawJXwPT
# AgMBAAGjggFeMIIBWjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTLEejK0rQW
# WAHJNy4zFha5TJoKHzALBgNVHQ8EBAMCAYYwEgYJKwYBBAGCNxUBBAUCAwEAATAj
# BgkrBgEEAYI3FQIEFgQU/dExTtMmipXhmGA7qDFvpjy82C0wGQYJKwYBBAGCNxQC
# BAweCgBTAHUAYgBDAEEwHwYDVR0jBBgwFoAUDqyCYEBWJ5flJRP8KuEKU5VZ5KQw
# UAYDVR0fBEkwRzBFoEOgQYY/aHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9j
# cmwvcHJvZHVjdHMvbWljcm9zb2Z0cm9vdGNlcnQuY3JsMFQGCCsGAQUFBwEBBEgw
# RjBEBggrBgEFBQcwAoY4aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0
# cy9NaWNyb3NvZnRSb290Q2VydC5jcnQwDQYJKoZIhvcNAQEFBQADggIBAFk5Pn8m
# Rq/rb0CxMrVq6w4vbqhJ9+tfde1MOy3XQ60L/svpLTGjI8x8UJiAIV2sPS9MuqKo
# VpzjcLu4tPh5tUly9z7qQX/K4QwXaculnCAt+gtQxFbNLeNK0rxw56gNogOlVuC4
# iktX8pVCnPHz7+7jhh80PLhWmvBTI4UqpIIck+KUBx3y4k74jKHK6BOlkU7IG9KP
# cpUqcW2bGvgc8FPWZ8wi/1wdzaKMvSeyeWNWRKJRzfnpo1hW3ZsCRUQvX/TartSC
# Mm78pJUT5Otp56miLL7IKxAOZY6Z2/Wi+hImCWU4lPF6H0q70eFW6NB4lhhcyTUW
# X92THUmOLb6tNEQc7hAVGgBd3TVbIc6YxwnuhQ6MT20OE049fClInHLR82zKwexw
# o1eSV32UjaAbSANa98+jZwp0pTbtLS8XyOZyNxL0b7E8Z4L5UrKNMxZlHg6K3RDe
# ZPRvzkbU0xfpecQEtNP7LN8fip6sCvsTJ0Ct5PnhqX9GuwdgR2VgQE6wQuxO7bN2
# edgKNAltHIAxH+IOVN3lofvlRxCtZJj/UBYufL8FIXrilUEnacOTj5XJjdibIa4N
# XJzwoq6GaIMMai27dmsAHZat8hZ79haDJLmIz2qoRzEvmtzjcT3XAH5iR9HOiMm4
# GPoOco3Boz2vAkBq/2mbluIQqBC0N1AI1sM9MIIGBzCCA++gAwIBAgIKYRZoNAAA
# AAAAHDANBgkqhkiG9w0BAQUFADBfMRMwEQYKCZImiZPyLGQBGRYDY29tMRkwFwYK
# CZImiZPyLGQBGRYJbWljcm9zb2Z0MS0wKwYDVQQDEyRNaWNyb3NvZnQgUm9vdCBD
# ZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDcwNDAzMTI1MzA5WhcNMjEwNDAzMTMw
# MzA5WjB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSEwHwYD
# VQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EwggEiMA0GCSqGSIb3DQEBAQUA
# A4IBDwAwggEKAoIBAQCfoWyx39tIkip8ay4Z4b3i48WZUSNQrc7dGE4kD+7Rp9FM
# rXQwIBHrB9VUlRVJlBtCkq6YXDAm2gBr6Hu97IkHD/cOBJjwicwfyzMkh53y9Gcc
# LPx754gd6udOo6HBI1PKjfpFzwnQXq/QsEIEovmmbJNn1yjcRlOwhtDlKEYuJ6yG
# T1VSDOQDLPtqkJAwbofzWTCd+n7Wl7PoIZd++NIT8wi3U21StEWQn0gASkdmEScp
# ZqiX5NMGgUqi+YSnEUcUCYKfhO1VeP4Bmh1QCIUAEDBG7bfeI0a7xC1Un68eeEEx
# d8yb3zuDk6FhArUdDbH895uyAc4iS1T/+QXDwiALAgMBAAGjggGrMIIBpzAPBgNV
# HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQjNPjZUkZwCu1A+3b7syuwwzWzDzALBgNV
# HQ8EBAMCAYYwEAYJKwYBBAGCNxUBBAMCAQAwgZgGA1UdIwSBkDCBjYAUDqyCYEBW
# J5flJRP8KuEKU5VZ5KShY6RhMF8xEzARBgoJkiaJk/IsZAEZFgNjb20xGTAXBgoJ
# kiaJk/IsZAEZFgltaWNyb3NvZnQxLTArBgNVBAMTJE1pY3Jvc29mdCBSb290IENl
# cnRpZmljYXRlIEF1dGhvcml0eYIQea0WoUqgpa1Mc1j0BxMuZTBQBgNVHR8ESTBH
# MEWgQ6BBhj9odHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0
# cy9taWNyb3NvZnRyb290Y2VydC5jcmwwVAYIKwYBBQUHAQEESDBGMEQGCCsGAQUF
# BzAChjhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY3Jvc29m
# dFJvb3RDZXJ0LmNydDATBgNVHSUEDDAKBggrBgEFBQcDCDANBgkqhkiG9w0BAQUF
# AAOCAgEAEJeKw1wDRDbd6bStd9vOeVFNAbEudHFbbQwTq86+e4+4LtQSooxtYrhX
# AstOIBNQmd16QOJXu69YmhzhHQGGrLt48ovQ7DsB7uK+jwoFyI1I4vBTFd1Pq5Lk
# 541q1YDB5pTyBi+FA+mRKiQicPv2/OR4mS4N9wficLwYTp2OawpylbihOZxnLcVR
# DupiXD8WmIsgP+IHGjL5zDFKdjE9K3ILyOpwPf+FChPfwgphjvDXuBfrTot/xTUr
# XqO/67x9C0J71FNyIe4wyrt4ZVxbARcKFA7S2hSY9Ty5ZlizLS/n+YWGzFFW6J1w
# lGysOUzU9nm/qhh6YinvopspNAZ3GmLJPR5tH4LwC8csu89Ds+X57H2146SodDW4
# TsVxIxImdgs8UoxxWkZDFLyzs7BNZ8ifQv+AeSGAnhUwZuhCEl4ayJ4iIdBD6Svp
# u/RIzCzU2DKATCYqSCRfWupW76bemZ3KOm+9gSd0BhHudiG/m4LBJ1S2sWo9iaF2
# YbRuoROmv6pH8BJv/YoybLL+31HIjCPJZr2dHYcSZAI9La9Zj7jkIeW1sMpjtHhU
# BdRBLlCslLCleKuzoJZ1GtmShxN1Ii8yqAhuoFuMJb+g74TKIdbrHk/Jmu5J4PcB
# ZW+JC33Iacjmbuqnl84xKf8OxVtc2E0bodj6L54/LlUWa8kTo/0xggSXMIIEkwIB
# ATCBkDB5MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSMwIQYD
# VQQDExpNaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQQITMwAAAXglWrXNI8ZflQAB
# AAABeDAJBgUrDgMCGgUAoIGwMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwG
# CisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMCMGCSqGSIb3DQEJBDEWBBRgaR35
# xaCjydVQp9Xyacht8pvtYzBQBgorBgEEAYI3AgEMMUIwQKAWgBQAUABvAHcAZQBy
# AFMAaABlAGwAbKEmgCRodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vUG93ZXJTaGVs
# bCAwDQYJKoZIhvcNAQEBBQAEggEAbtltf0aVQX69gw1vgVkNtIU0UR7qhxE8Mk0T
# 7JZopXy63g46asVsiqXd5Z2hYePR0HKHw3rEuNCGuJa/29u4e8s3qngar+Wn69o3
# qMffkE5LJNlXUButKUCC8weVKWO0Iptqj8z57iOHOvc0ADDPuscFMoBgrS2VJG+e
# XLBScqrBWyIONu00H5PL2mNBM0TXDHV8KFALBd7gsz9EOzPMf39VJuurAkyutBg1
# pTUKfRlhRiovyHdBvpxTGN6q5hI+b3pOk2okLH3pqwvL28hacDumw67k/gx5EfLE
# Uo1RlNQk2hVHpbSSQ5UcYs10TGWhGPcKB2Z/X69GrLNFDtskNaGCAigwggIkBgkq
# hkiG9w0BCQYxggIVMIICEQIBATCBjjB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMSEwHwYDVQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EC
# EzMAAAC8Cy4ae4qxx5EAAAAAALwwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzEL
# BgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE3MTAxMDIwNDc0OFowIwYJKoZI
# hvcNAQkEMRYEFPpp56JXeeiFxftf47lVeOgZxJ4IMA0GCSqGSIb3DQEBBQUABIIB
# AGcMrq6HDk/U9P3oHyx/eackYG0vhwkMKpd/oW/BG8irl9thHcaJPVWM9+Rbb7kI
# pXEH5kgMSOTlUdSeWpSaVSl6q5CLIUMgmwoMQI6HTvyG78J2kmiN7SSn26ZVPlYL
# NZD3dYnUG9GZogHnAaDs1mZFQyi8BSl+hJ6tdYjplxBV91eYkCLF5KprKPGn9DAa
# ajOOlanFvFRhINXqckp9z5IK5dLLvA92jUvYRyZ04YC+VDNYKuJ1llYT0SH3JJQv
# R8W90oaNonw5yn5jLAKhQrmAL5bUwA0cV9/1uWyrj7O3PLHelZuhaZ/bsfycttwn
# PJjeSsalOIVdYWpzkj7XcVQ=
# SIG # End signature block