Microsoft.PowerShell.ODataAdapter.ps1

Import-LocalizedData LocalizedData -FileName Microsoft.PowerShell.ODataUtilsStrings.psd1


# Add .NET classes used by the module
Add-Type -TypeDefinition $script:BaseClassDefinitions

#########################################################
# Generates PowerShell module containing client side
# proxy cmdlets that can be used to interact with an
# OData based server side endpoint.
#########################################################
function ExportODataEndpointProxy 
{
    param
    (
        [string] $Uri,
        [string] $OutputModule,
        [string] $MetadataUri,
        [PSCredential] $Credential,
        [string] $CreateRequestMethod,
        [string] $UpdateRequestMethod,
        [string] $CmdletAdapter,
        [Hashtable] $ResourceNameMapping,
        [switch] $Force,
        [Hashtable] $CustomData,
        [switch] $AllowClobber,
        [switch] $AllowUnsecureConnection,
        [Hashtable] $Headers,
        [string] $ProgressBarStatus,
        [System.Management.Automation.PSCmdlet] $PSCmdlet
    )

    [xml] $metadataXML = GetMetaData $MetadataUri $PSCmdlet $Credential $Headers

    [ODataUtils.Metadata] $metaData = ParseMetadata $metadataXML $MetadataUri $CmdletAdapter $PSCmdlet

    VerifyMetaData $MetadataUri $metaData $AllowClobber.IsPresent $PSCmdlet $progressBarStatus $CmdletAdapter $CustomData $ResourceNameMapping
                
    GenerateClientSideProxyModule $metaData $MetadataUri $Uri $OutputModule $CreateRequestMethod $UpdateRequestMethod $CmdletAdapter $ResourceNameMapping $CustomData $ProgressBarStatus $PSCmdlet
}

#########################################################
# ParseMetaData is a helper function used to parse the
# metadata to convert it in to an object structure for
# further consumption during proxy generation.
#########################################################
function ParseMetaData 
{
    param
    (
        [xml]    $metadataXml,
        [string] $metaDataUri,
        [string] $cmdletAdapter,
        [System.Management.Automation.PSCmdlet] $callerPSCmdlet
    )

    # $metaDataUri is already validated at the cmdlet layer.
    if($callerPSCmdlet -eq $null) { throw ($LocalizedData.ArguementNullError -f "PSCmdlet", "ParseMetadata") }

    if($metadataXml -eq $null)
    {
        $errorMessage = ($LocalizedData.InValidXmlInMetadata -f $metaDataUri)
        $exception = [System.InvalidOperationException]::new($errorMessage, $_.Exception)
        $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyInvalidMetadataUriFormat" $null ([System.Management.Automation.ErrorCategory]::InvalidArgument) $exception $metaDataUri
        $callerPSCmdlet.ThrowTerminatingError($errorRecord)
    }

    Write-Verbose $LocalizedData.VerboseParsingMetadata

    # Check the OData version in the fetched metadata to make sure that
    # OData version (and hence the protocol) used in the metadata is
    # supported by the adatapter used for executing the generated
    # proxy cmdlets.
    if(($metadataXML -ne $null) -and ($metadataXML.Edmx -ne $null))
    {
        if($null -eq $metadataXML.Edmx.Version)
        {
            $errorMessage = ($LocalizedData.ODataVersionNotFound -f $MetadataUri)
            $exception = [System.InvalidOperationException]::new($errorMessage)
            $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyODataVersionNotFound" $null ([System.Management.Automation.ErrorCategory]::InvalidData) $exception $MetadataUri
            $callerPSCmdlet.ThrowTerminatingError($errorRecord)
        }

        $metaDataVersion = New-Object -TypeName System.Version -ArgumentList @($metadataXML.Edmx.Version)

        # When we support plug-in model, We would have to fetch the
        # $minSupportedVersionString & $maxSupportedVersionString
        # from the plug-in instead of using an hardcoded value.
        $minSupportedVersionString = '1.0'
        $maxSupportedVersionString = '3.0'
        $minSupportedVersion = New-Object -TypeName System.Version -ArgumentList @($minSupportedVersionString)
        $maxSupportedVersion = New-Object -TypeName System.Version -ArgumentList @($maxSupportedVersionString)

        $minVersionComparisonResult = $minSupportedVersion.CompareTo($metaDataVersion)
        $maxVersionComparisonResult = $maxSupportedVersion.CompareTo($metaDataVersion)

        if(-not($minVersionComparisonResult -lt $maxVersionComparisonResult))
        {
            $errorMessage = ($LocalizedData.ODataVersionNotSupported -f $metadataXML.Edmx.Version, $MetadataUri, $minSupportedVersionString, $maxSupportedVersionString, $CmdletAdapter)
            $exception = [System.NotSupportedException]::new($errorMessage)
            $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyODataVersionNotSupported" $null ([System.Management.Automation.ErrorCategory]::InvalidData) $exception $MetadataUri
            $callerPSCmdlet.ThrowTerminatingError($errorRecord)
        }
    }
    else
    {
        $errorMessage = ($LocalizedData.InValidMetadata -f $MetadataUri)
        $exception = [System.InvalidOperationException]::new($errorMessage)
        $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyInvalidMetadata" $null ([System.Management.Automation.ErrorCategory]::InvalidData) $exception $MetadataUri
        $callerPSCmdlet.ThrowTerminatingError($errorRecord)
    }

    foreach ($schema in $MetadataXML.Edmx.DataServices.Schema)
    {
        if (($schema -ne $null) -and [string]::IsNullOrEmpty($schema.NameSpace ))
        {
            $callerPSCmdlet = $callerPSCmdlet -as [System.Management.Automation.PSCmdlet]
            $errorMessage = ($LocalizedData.InValidSchemaNamespace -f $metaDataUri)
            $exception = [System.InvalidOperationException]::new($errorMessage)
            $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyInvalidSchemaNamespace" $null ([System.Management.Automation.ErrorCategory]::InvalidArgument) $exception $metaDataUri
            $callerPSCmdlet.ThrowTerminatingError($errorRecord)
        }
    }

    $metaData = New-Object -TypeName ODataUtils.Metadata
    
    # this is a processing queue for those types that require base types that haven't been defined yet
    $entityAndComplexTypesQueue = @{}

    foreach ($schema in $metadataXml.Edmx.DataServices.Schema)
    {
        if ($schema -eq $null)
        {
            Write-Error $LocalizedData.EmptySchema
            continue
        }

        if ($metadata.Namespace -eq $null)
        {
            $metaData.Namespace = $schema.Namespace
        }

        foreach ($entityType in $schema.EntityType)
        {
            $baseType = $null

            if ($entityType.BaseType -ne $null)
            {
                # add it to the processing queue
                $baseType = GetBaseType $entityType $metaData
                if ($baseType -eq $null)
                {
                    $entityAndComplexTypesQueue[$entityType.BaseType] += @(@{type='EntityType'; value=$entityType})
                    continue
                }
            }

            [ODataUtils.EntityType] $newType = ParseMetadataTypeDefinition $entityType $baseType $metaData $schema.Namespace $true
            $metaData.EntityTypes += $newType
            AddDerivedTypes $newType $entityAndComplexTypesQueue $metaData $schema.Namespace
        }

        foreach ($complexType in $schema.ComplexType)
        {
            $baseType = $null

            if ($complexType.BaseType -ne $null)
            {
                # add it to the processing queue
                $baseType = GetBaseType $complexType $metaData
                if ($baseType -eq $null)
                {
                    $entityAndComplexTypesQueue[$entityType.BaseType] += @(@{type='ComplexType'; value=$complexType})
                    continue
                }
            }

            [ODataUtils.EntityType] $newType = ParseMetadataTypeDefinition $complexType $baseType $metaData $schema.Namespace $false
            $metaData.ComplexTypes += $newType
            AddDerivedTypes $newType $entityAndComplexTypesQueue $metaData $schema.Namespace
        }
    }

    foreach ($schema in $metadataXml.Edmx.DataServices.Schema)
    {
        foreach ($entityContainer in $schema.EntityContainer)
        {
            if ($entityContainer.IsDefaultEntityContainer)
            {
                $metaData.DefaultEntityContainerName = $entityContainer.Name
            }

            $entityTypeToEntitySetMapping = @{};
            foreach ($entitySet in $entityContainer.EntitySet)
            {
                $entityType = $metaData.EntityTypes | Where-Object { $_.Name -eq $entitySet.EntityType.Split('.')[-1] }
                $entityTypeName = $entityType.Name

                if($entityTypeToEntitySetMapping.ContainsKey($entityTypeName))
                {
                    $existingEntitySetName = $entityTypeToEntitySetMapping[$entityTypeName]

                    $errorMessage = ($LocalizedData.EntityNameConflictError -f $metaDataUri, $existingEntitySetName, $entitySet.Name, $entityTypeName)
                    $exception = [System.NotSupportedException]::new($errorMessage)
                    $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyEntityTypeMappingError" $null ([System.Management.Automation.ErrorCategory]::InvalidData) $exception $metaDataUri
                    $callerPSCmdlet.ThrowTerminatingError($errorRecord)
                }
                else
                {
                    $entityTypeToEntitySetMapping.Add($entityTypeName, $entitySet.Name)
                }

                $newEntitySet = [ODataUtils.EntitySet] @{
                    "Namespace" = $schema.Namespace;
                    "Name" = $entitySet.Name;
                    "Type" = $entityType;
                }

                $metaData.EntitySets += $newEntitySet
            }
        }
    }

    foreach ($schema in $metadataXml.Edmx.DataServices.Schema)
    {
        foreach ($association in $schema.Association)
        {
            $newAssociationType = [ODataUtils.AssociationType] @{
                "Namespace" = $schema.Namespace;
                "EndType1" = $metaData.EntityTypes | Where-Object { $_.Name -eq $association.End[0].Type.Split('.')[-1] };
                "NavPropertyName1" = $association.End[0].Role;
                "Multiplicity1" = $association.End[0].Multiplicity;

                "EndType2" = $metaData.EntityTypes | Where-Object { $_.Name -eq $association.End[1].Type.Split('.')[-1] };
                "NavPropertyName2" = $association.End[1].Role;
                "Multiplicity2" = $association.End[1].Multiplicity;
            }

            $newAssociation = [ODataUtils.AssociationSet] @{
                "Namespace" = $schema.Namespace;
                "Name" = $association.Name;
                "Type" = $newAssociationType;
            }
            
            $metaData.Associations += $newAssociation
        }
    }

    foreach ($schema in $metadataXml.Edmx.DataServices.Schema)
    {
        foreach ($action in $schema.EntityContainer.FunctionImport)
        {
            # HttpMethod is only used for legacy Service Operations
            if ($action.HttpMethod -eq $null)
            {
                if ($action.IsSideEffecting -ne $null)
                {
                    $isSideEffecting = $action.IsSideEffecting
                }
                else
                {
                    $isSideEffecting = $true
                }

                $newAction = [ODataUtils.Action] @{
                    "Namespace" = $schema.Namespace;
                    "Verb" = $action.Name;
                    "IsSideEffecting" = $isSideEffecting;
                    "IsBindable" = $action.IsBindable;
                    # we don't care about IsAlwaysBindable, since we populate actions information from $metaData
                    # so we can't know the state of the entity
                }
                
                # Actions are always SideEffecting, otherwise it's an OData function
                if ($newAction.IsSideEffecting -ne $false)
                {
                    foreach ($parameter in $action.Parameter)
                    {
                        if ($parameter.Nullable -ne $null)
                        {
                            $parameterIsNullable = [System.Convert]::ToBoolean($parameter.Nullable);
                        }

                        $newParameter = [ODataUtils.TypeProperty] @{
                            "Name" = $parameter.Name;
                            "TypeName" = $parameter.Type;
                            "IsNullable" = $parameterIsNullable
                        }

                        $newAction.Parameters += $newParameter
                    }

                    # IsBindable means it operates on Entity/ies
                    if ($newAction.IsBindable)
                    {
                        $regex = "Collection\((.+)\)"

                        if ($newAction.Parameters[0].TypeName -match $regex)
                        {
                            # action operating on a collection of entities
                            $insideTypeName = Convert-ODataTypeToCLRType $Matches[1]

                            $newAction.EntitySet = $metaData.EntitySets | Where-Object { ($_.Type.Namespace + "." + $_.Type.Name) -eq $insideTypeName }
                            $newAction.IsSingleInstance = $false
                        }
                        else
                        {
                            # actions operating on a single instance
                            $newAction.EntitySet = $metaData.EntitySets | Where-Object { ($_.Type.Namespace + "." + $_.Type.Name) -eq $newAction.Parameters[0].TypeName }

                            $newAction.IsSingleInstance = $true
                        }
                    }

                    $metaData.Actions += $newAction
                }
            }
        }
    }

    $metaData
}

#########################################################
# VerifyMetaData is a helper function used to validate
# the processed metadata to make sure client side proxy
# can be created for the supplied metadata.
#########################################################
function VerifyMetaData 
{
    param
    (
        [string]    $metaDataUri,
        [ODataUtils.Metadata]  $metaData,
        [boolean]   $allowClobber,
        [System.Management.Automation.PSCmdlet] $callerPSCmdlet,
        [string]    $progressBarStatus,
        [string]    $cmdletAdapter,
        [Hashtable] $customData,
        [Hashtable] $resourceNameMapping
    )

    # $metaDataUri & $cmdletAdapter is already validated at the cmdlet layer.
    if($metaData -eq $null) { throw ($LocalizedData.ArguementNullError -f "metadata", "VerifyMetaData") }
    if($callerPSCmdlet -eq $null) { throw ($LocalizedData.ArguementNullError -f "PSCmdlet", "VerifyMetaData") }
    if($progressBarStatus -eq $null) { throw ($LocalizedData.ArguementNullError -f "ProgressBarStatus", "VerifyMetaData") }

    Write-Verbose $LocalizedData.VerboseVerifyingMetadata

    if ($metadata.EntitySets.Count -le 0)
    {
        $errorMessage = ($LocalizedData.NoEntitySets -f $metaDataUri)
        $exception = [System.InvalidOperationException]::new($errorMessage)
        $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyInvalidMetaDataUri" $null ([System.Management.Automation.ErrorCategory]::InvalidArgument) $exception $metaDataUri
        $callerPSCmdlet.ThrowTerminatingError($errorRecord)
    }

    if ($metadata.EntityTypes.Count -le 0)
    {
        $errorMessage = ($LocalizedData.NoEntityTypes -f $metaDataUri)
        $exception = [System.InvalidOperationException]::new($errorMessage)
        $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyInvalidMetaDataUri" $null ([System.Management.Automation.ErrorCategory]::InvalidArgument) $exception $metaDataUri
        $callerPSCmdlet.ThrowTerminatingError($errorRecord)
    }
    
    # All the generated proxy cmdlets would have the following paramters added.
    # The ODataAdpter has the default implementation on how to handle the
    # scenario when these parameters are used during proxy invocations.
    # The default implementaion can be overridden using adapter derivation model.
    $reservedProperties = @("Filter", "IncludeTotalResponseCount", "OrderBy", "Select", "Skip", "Top", "ConnectionUri", "CertificateThumbprint", "Credential")
    $validEntitySets = @()
    $sessionCommands = Get-Command -All
    
    foreach ($entitySet in $metaData.EntitySets)
    {
        if ($entitySet.Type -eq $null)
        {
            $errorMessage = ($LocalizedData.EntitySetUndefinedType -f $metaDataUri, $entitySet.Name)
            $exception = [System.InvalidOperationException]::new($errorMessage)
            $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyInvalidMetaDataUri" $null ([System.Management.Automation.ErrorCategory]::InvalidArgument) $exception $metaDataUri
            $callerPSCmdlet.ThrowTerminatingError($errorRecord)
        }

        if ($cmdletAdapter -eq "NetworkControllerAdapter" -And $customData -And $customData.Contains($entitySet.Name) -eq $false)
        {
            continue
        }

        $hasConflictingProperty = $false
        $hasConflictingCommand = $false

        $entityAndNavigationProperties = (GetAllProperties $entitySet.Type) + (GetAllProperties $entitySet.Type -IncludeOnlyNavigationProperties)
        foreach($entityProperty in $entityAndNavigationProperties)
        {
            if($reservedProperties.Contains($entityProperty.Name))
            {
                $hasConflictingProperty = $true
                if(!$allowClobber)
                {
                    # Write Error message and skip current Entity Set.
                    $errorMessage = ($LocalizedData.SkipEntitySetProxyCreation -f $entitySet.Name, $entitySet.Type.Name, $entityProperty.Name)
                    $exception = [System.InvalidOperationException]::new($errorMessage)
                    $errorRecord = CreateErrorRecordHelper "ODataEndpointDefaultPropertyCollision" $null ([System.Management.Automation.ErrorCategory]::InvalidOperation) $exception $metaDataUri
                    $callerPSCmdlet.WriteError($errorRecord)
                }
                else
                {                    
                    $warningMessage = ($LocalizedData.EntitySetProxyCreationWithWarning -f $entitySet.Name, $entityProperty.Name, $entitySet.Type.Name)
                    $callerPSCmdlet.WriteWarning($warningMessage)
                }
            }
        }

        foreach($currentCommand in $sessionCommands)
        {
            # The generated command Noun can be set using ResourceNameMapping
            $generatedCommandName = $entitySet.Name
            if ($resourceNameMapping -And $resourceNameMapping.Contains($entitySet.Name)) {
                $generatedCommandName = $resourceNameMapping[$entitySet.Name]
            }

            if(($currentCommand.Noun -ne $null -and $currentCommand.Noun -eq $generatedCommandName) -and 
            ($currentCommand.Verb -eq "Get" -or 
            $currentCommand.Verb -eq "Set" -or 
            $currentCommand.Verb -eq "New" -or 
            $currentCommand.Verb -eq "Remove"))
            {
                $hasConflictingCommand = $true
                VerifyMetadataHelper $LocalizedData.SkipEntitySetConflictCommandCreation `
                $LocalizedData.EntitySetConflictCommandCreationWithWarning `
                $entitySet.Name $currentCommand.Name $metaDataUri $allowClobber $callerPSCmdlet
            }
        }

        foreach($currentAction in $metaData.Actions)
        {
            $actionCommand = "Invoke-" + "$($entitySet.Name)$($currentAction.Verb)"
        
            foreach($currentCommand in $sessionCommands)
            {
                if($actionCommand -eq $currentCommand.Name)
                {
                    $hasConflictingCommand = $true
                    VerifyMetadataHelper $LocalizedData.SkipEntitySetConflictCommandCreation `
                    $LocalizedData.EntitySetConflictCommandCreationWithWarning $entitySet.Name `
                    $currentCommand.Name $metaDataUri $allowClobber $callerPSCmdlet
                }
            }
        }

        if(!($hasConflictingProperty -or $hasConflictingCommand)-or $allowClobber)
        {
            $validEntitySets += $entitySet
        }
    }
    
    if ($cmdletAdapter -ne "NetworkControllerAdapter") {
    
        $metaData.EntitySets = $validEntitySets
    
        $validServiceActions = @()        
        $hasConflictingServiceActionCommand = $true
        foreach($currentAction in $metaData.Actions)
        {
            $serviceActionCommand = "Invoke-" + "$($currentAction.Verb)"
    
            foreach($currentCommand in $sessionCommands)
            {
                if($serviceActionCommand -eq $currentCommand.Name)
                {
                    $hasConflictingServiceActionCommand = $true
                    VerifyMetadataHelper $LocalizedData.SkipConflictServiceActionCommandCreation `
                    $LocalizedData.ConflictServiceActionCommandCreationWithWarning $entitySet.Name `
                    $currentCommand.Name $metaDataUri $allowClobber $callerPSCmdlet
                }
            }
    
            if(!$hasConflictingServiceActionCommand -or $allowClobber)
            {
                $validServiceActions += $currentAction
            }
        }
    
        $metaData.Actions = $validServiceActions
    }
    
    # Update Progress bar.
    ProgressBarHelper "Export-ODataEndpointProxy" $progressBarStatus 5 20 1  1
}

#########################################################
# GenerateClientSideProxyModule is a helper function used
# to generate a PowerShell module that serves as a client
# side proxy for interacting with the server side
# OData endpoint. The proxy module conatins proxy cmdlets
# implemneted in CDXML modules and they are exposed
# through module manifest as nested modules.
# One CDXML module is created for each EntitySet
# described in the metadata. Each CDXML module contains
# CRUD & Service Action specific proxy cmdlets targeting
# the underlying EntityType. There is 1:M mapping between
# EntitySet & its underlying EntityTypes (i.e., all
# entities with in the single EntitySet will be of the
# same EntityType but there can be multiple entities
# of the same type with in an EntitySet).
#########################################################
function GenerateClientSideProxyModule 
{
    param
    (
        [ODataUtils.Metadata] $metaData,
        [string]    $metaDataUri,
        [string]    $uri,
        [string]    $outputModule,
        [string]    $createRequestMethod,
        [string]    $updateRequestMethod,    
        [string]    $cmdletAdapter,   
        [Hashtable] $resourceNameMapping,  
        [Hashtable] $customData,
        [string]    $progressBarStatus,
        [System.Management.Automation.PSCmdlet] $callerPSCmdlet
    )

    # $uri, $outputModule, $metaDataUri, $createRequestMethod, $updateRequestMethod, & $cmdletAdapter is already validated at the cmdlet layer.
    if($metaData -eq $null) { throw ($LocalizedData.ArguementNullError -f "metadata", "GenerateClientSideProxyModule") }
    if($callerPSCmdlet -eq $null) { throw ($LocalizedData.ArguementNullError -f "PSCmdlet", "GenerateClientSideProxyModule") }
    if($progressBarStatus -eq $null) { throw ($LocalizedData.ArguementNullError -f "ProgressBarStatus", "GenerateClientSideProxyModule") }

    # This function performs the following set of tasks
    # while creating the client side proxy module:
    # 1. If the server side endpoint exposes comlex types,
    # the client side proxy complex types are created
    # as C# class in ComplexTypeDefinations.psm1
    # 2. Creates proxy cmdlets for CRUD opreations.
    # 3. Creates proxy cmdlets for Serice action opreations.
    # 4. Creates module manifest.

    Write-Verbose ($LocalizedData.VerboseSavingModule -f $outputModule)

    $typeDefinationFileName = "ComplexTypeDefinitions.psm1"
    $complexTypeMapping = GenerateComplexTypeDefination $metaData $metaDataUri $outputModule $typeDefinationFileName $cmdletAdapter $callerPSCmdlet

    ProgressBarHelper "Export-ODataEndpointProxy" $progressBarStatus 20 20 1  1

    $complexTypeFileDefinationPath = Join-Path -Path $outputModule -ChildPath $typeDefinationFileName

    if(Test-Path -Path $complexTypeFileDefinationPath)
    {
        $proxyFile = New-Object -TypeName System.IO.FileInfo -ArgumentList $complexTypeFileDefinationPath | Get-Item
        if($callerPSCmdlet -ne $null)
        { 
            $callerPSCmdlet.WriteObject($proxyFile)
        }
    }
    
    $currentEntryCount = 0
    foreach ($entitySet in $metaData.EntitySets)
    {
        $currentEntryCount += 1
        if ($cmdletAdapter -eq "NetworkControllerAdapter" -And $customData -And $customData.Contains($entitySet.Name) -eq $false)
        {
            ProgressBarHelper "Export-ODataEndpointProxy" $progressBarStatus 40 20 $metaData.EntitySets.Count  $currentEntryCount
            continue
        }
         
        GenerateCRUDProxyCmdlet $entitySet $metaData $uri $outputModule $createRequestMethod $updateRequestMethod $cmdletAdapter $resourceNameMapping $customData $complexTypeMapping "Export-ODataEndpointProxy" $progressBarStatus 40 20 $metaData.EntitySets.Count $currentEntryCount $callerPSCmdlet
    }

    GenerateServiceActionProxyCmdlet $metaData $uri "$outputModule\ServiceActions.cdxml" $complexTypeMapping $progressBarStatus $callerPSCmdlet

    $moduleDirInfo = [System.IO.DirectoryInfo]::new($outputModule)
    $moduleManifestName = $moduleDirInfo.Name + ".psd1"
    GenerateModuleManifest $metaData $outputModule\$moduleManifestName @($typeDefinationFileName, 'ServiceActions.cdxml') $resourceNameMapping $progressBarStatus $callerPSCmdlet
}

#########################################################
# GenerateCRUDProxyCmdlet is a helper function used
# to generate Get, Set, New & Remove proxy cmdlet.
# The proxy cmdlet is generated in the CDXML
# compliant format.
#########################################################
function GenerateCRUDProxyCmdlet 
{
    param
    (
        [ODataUtils.EntitySet] $entitySet,
        [ODataUtils.Metadata] $metaData,
        [string] $uri,
        [string] $outputModule,
        [string] $createRequestMethod,
        [string] $UpdateRequestMethod,
        [string] $cmdletAdapter,
        [Hashtable] $resourceNameMapping,  
        [Hashtable] $customData,
        [Hashtable] $compexTypeMapping,
        [string] $progressBarActivityName,
        [string] $progressBarStatus,
        [double] $previousSegmentWeight,
        [double] $currentSegmentWeight,
        [int] $totalNumberofEntries,
        [int] $currentEntryCount,
        [System.Management.Automation.PSCmdlet] $callerPSCmdlet
    )

    # $uri, $outputModule, $metaDataUri, $createRequestMethod, $updateRequestMethod, & $cmdletAdapter is already validated at the cmdlet layer.
    if($entitySet -eq $null) { throw ($LocalizedData.ArguementNullError -f "EntitySet", "GenerateClientSideProxyModule") }
    if($metaData -eq $null) { throw ($LocalizedData.ArguementNullError -f "metadata", "GenerateClientSideProxyModule") }
    if($callerPSCmdlet -eq $null) { throw ($LocalizedData.ArguementNullError -f "PSCmdlet", "GenerateClientSideProxyModule") }
    if($progressBarStatus -eq $null) { throw ($LocalizedData.ArguementNullError -f "ProgressBarStatus", "GenerateClientSideProxyModule") }

    $entitySetName = $entitySet.Name 
    if(($resourceNameMapping -ne $null) -and 
    $resourceNameMapping.ContainsKey($entitySetName))
    {
        $entitySetName = $resourceNameMapping[$entitySetName]
    }
    else
    {
        $entitySetName = $entitySet.Type.Name
    }

    $Path = "$OutputModule\$entitySetName.cdxml"

    $xmlWriter = New-Object System.XMl.XmlTextWriter($Path,$Null)

    if ($xmlWriter -eq $null)
    {
        throw ($LocalizedData.XmlWriterInitializationError -f $entitySet.Name)
    }

    $xmlWriter = SaveCDXMLHeader $xmlWriter $uri $entitySet.Name $entitySetName $cmdletAdapter

    # Get the keys depending on whether the url contains variables or not
    if ($CmdletAdapter -ne "NetworkControllerAdapter")
    {
        $keys = (GetAllProperties $entitySet.Type) | Where-Object { $_.IsKey }
    }
    else
    {
        $name = $entitySet.Name
        $keys = GetKeys $entitySet $customData.$name 'Get'
    }

    $navigationProperties = GetAllProperties $entitySet.Type -IncludeOnlyNavigationProperties

    GenerateGetProxyCmdlet $xmlWriter $metaData $keys $navigationProperties $cmdletAdapter $compexTypeMapping

    $nonKeyProperties = (GetAllProperties $entitySet.Type) | ? { -not $_.isKey }
    $nullableProperties = $nonKeyProperties | ? { $_.isNullable }
    $nonNullableProperties = $nonKeyProperties | ? { -not $_.isNullable }

    $xmlWriter.WriteStartElement('StaticCmdlets')

        $keyProperties = $keys

        # Do operations specifically needed for NetworkController cmdlets
        if ($CmdletAdapter -eq "NetworkControllerAdapter")
        {
            $keyProperties = GetKeys $entitySet $customData.$name 'New'
            $additionalProperties = GetNetworkControllerAdditionalProperties $navigationProperties $metaData
            $nullableProperties = UpdateNetworkControllerSpecificProperties $nullableProperties $additionalProperties $keyProperties $true
            $nonNullableProperties = UpdateNetworkControllerSpecificProperties $nonNullableProperties $additionalProperties $keyProperties $false
        }

        GenerateNewProxyCmdlet $xmlWriter $metaData $keyProperties $nonNullableProperties $nullableProperties $navigationProperties $cmdletAdapter $compexTypeMapping

        if ($CmdletAdapter -ne "NetworkControllerAdapter")
        {
            GenerateSetProxyCmdlet $xmlWriter $keyProperties $nonKeyProperties $compexTypeMapping
        }

        if ($CmdletAdapter -eq "NetworkControllerAdapter")
        {
            $keyProperties = GetKeys $entitySet $customData.$name 'Remove'
        }

        GenerateRemoveProxyCmdlet $xmlWriter $metaData $keyProperties $navigationProperties $cmdletAdapter $compexTypeMapping

        $entityActions = $metaData.Actions | Where-Object { ($_.EntitySet.Namespace -eq $entitySet.Namespace) -and ($_.EntitySet.Name -eq $entitySet.Name) }

        if ($entityActions.Length -gt 0)
        {
            foreach($action in $entityActions)
            {
                $xmlWriter = GenerateActionProxyCmdlet $xmlWriter $metaData $action $entitySet.Name $true $keys $complexTypeMapping
            }
        }

    $xmlWriter.WriteEndElement()

    $xmlWriter.WriteStartElement('CmdletAdapterPrivateData')

        $xmlWriter.WriteStartElement('Data')
        $xmlWriter.WriteAttributeString('Name', 'EntityTypeName')
        $xmlWriter.WriteString("$($entitySet.Type.Namespace).$($entitySet.Type.Name)")
        $xmlWriter.WriteEndElement()
        $xmlWriter.WriteStartElement('Data')
        $xmlWriter.WriteAttributeString('Name', 'EntitySetName')
        $xmlWriter.WriteString("$($entitySet.Namespace).$($entitySet.Name)")
        $xmlWriter.WriteEndElement()

        # Add the customUri to privateData
        if ($CmdletAdapter -eq "NetworkControllerAdapter")
        {
            $xmlWriter.WriteStartElement('Data')
            $xmlWriter.WriteAttributeString('Name', "CustomUriSuffix")
            $xmlWriter.WriteString($CustomData.$name)
            $xmlWriter.WriteEndElement()
        }

        # Add CreateRequestMethod and UpdateRequestMethod to privateData
        $xmlWriter.WriteStartElement('Data')
        $xmlWriter.WriteAttributeString('Name', 'CreateRequestMethod')
        $xmlWriter.WriteString("$CreateRequestMethod")
        $xmlWriter.WriteEndElement()

        $xmlWriter.WriteStartElement('Data')
        $xmlWriter.WriteAttributeString('Name', 'UpdateRequestMethod')
        $xmlWriter.WriteString("$UpdateRequestMethod")
        $xmlWriter.WriteEndElement()

    $xmlWriter.WriteEndElement()

    SaveCDXMLFooter $xmlWriter

    ProcessStreamHelper ($LocalizedData.VerboseSavedCDXML -f $($entitySetName), $Path) $progressBarActivityName $progressBarStatus $previousSegmentWeight $currentSegmentWeight $totalNumberofEntries $currentEntryCount $Path $callerPSCmdlet
}

#########################################################
# GenerateGetProxyCmdlet is a helper function used
# to generate Get-* proxy cmdlet. The proxy cmdlet is
# generated in the CDXML compliant format.
#########################################################
function GenerateGetProxyCmdlet 
{
    param
    (
        [System.XMl.XmlTextWriter] $xmlWriter,
        [ODataUtils.Metadata] $metaData, 
        [object[]]  $keys,
        [object[]]  $navigationProperties,
        [string]    $cmdletAdapter,
        [Hashtable] $complexTypeMapping
    )
    
    # $cmdletAdapter is already validated at the cmdlet layer.
    if($xmlWriter -eq $null) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "GenerateGetProxyCmdlet") }
    if($metaData -eq $null) { throw ($LocalizedData.ArguementNullError -f "metadata", "GenerateGetProxyCmdlet") }

    $xmlWriter.WriteStartElement('InstanceCmdlets')
        $xmlWriter.WriteStartElement('GetCmdletParameters')
            $xmlWriter.WriteAttributeString('DefaultCmdletParameterSet', 'Default')

            # adding key parameters and association parameters to QueryableProperties, each in a different parameter set
            # to be used by GET cmdlet
            if (($keys -ne $null -and $keys.Length -gt 0) -or (($navigationProperties -ne $null -and $navigationProperties.Length -gt 0) -and $cmdletAdapter -ne "NetworkControllerAdapter"))
            {
                $xmlWriter.WriteStartElement('QueryableProperties')
                $position = 0
                
                $keys | ? { $_ -ne $null } | % {
                    $xmlWriter.WriteStartElement('Property')
                    $xmlWriter.WriteAttributeString('PropertyName', $_.Name)

                        $xmlWriter.WriteStartElement('Type')
                        $PSTypeName = Convert-ODataTypeToCLRType $_.TypeName $complexTypeMapping
                        $xmlWriter.WriteAttributeString('PSType', $PSTypeName)
                        $xmlWriter.WriteEndElement()

                        $xmlWriter.WriteStartElement('RegularQuery')
                            $xmlWriter.WriteStartElement('CmdletParameterMetadata')
                            $xmlWriter.WriteAttributeString('PSName', $_.Name)
                            $xmlWriter.WriteAttributeString('CmdletParameterSets', 'Default')
                            $xmlWriter.WriteAttributeString('IsMandatory', $_.IsMandatory.ToString().ToLower())
                            $xmlWriter.WriteAttributeString('Position', $position)
                            if($_.IsMandatory)
                            {
                                $xmlWriter.WriteAttributeString('ValueFromPipelineByPropertyName', 'true')
                            }
                            $xmlWriter.WriteEndElement()
                        $xmlWriter.WriteEndElement()
                    $xmlWriter.WriteEndElement()

                    $position++
                }

                # This behaviour is different for NetworkController specific cmdlets.
                if ($CmdletAdapter -ne "NetworkControllerAdapter")
                {
                    $navigationProperties | ? { $_ -ne $null } | % {
                    $associatedType = GetAssociatedType $metaData $_
                    $associatedEntitySet = GetEntitySetForEntityType $metaData $associatedType
                    $nvgProperty = $_

                        (GetAllProperties $associatedType)  | ? { $_.IsKey } | % {
                            $xmlWriter.WriteStartElement('Property')
                            $xmlWriter.WriteAttributeString('PropertyName', $associatedEntitySet.Name + ':' + $_.Name + ':Key')

                                $xmlWriter.WriteStartElement('Type')
                                $PSTypeName = Convert-ODataTypeToCLRType $_.TypeName $complexTypeMapping
                                $xmlWriter.WriteAttributeString('PSType', $PSTypeName)
                                $xmlWriter.WriteEndElement()

                                $xmlWriter.WriteStartElement('RegularQuery')
                                    $xmlWriter.WriteStartElement('CmdletParameterMetadata')
                                    $xmlWriter.WriteAttributeString('PSName', 'Associated' + $nvgProperty.Name + $_.Name)
                                    $xmlWriter.WriteAttributeString('CmdletParameterSets', $nvgProperty.AssociationName)
                                    $xmlWriter.WriteAttributeString('IsMandatory', 'true')
                                    $xmlWriter.WriteAttributeString('ValueFromPipelineByPropertyName', 'true')
                                    $xmlWriter.WriteEndElement()
                                $xmlWriter.WriteEndElement()
                            $xmlWriter.WriteEndElement()
                        }
                    }
                    

                    # Add Query Parameters (i.e., Top, Skip, OrderBy, Filter) to the generated Get-* cmdlets.
                    $queryParameters = 
                    @{
                        "Filter" = "Edm.String";
                        "IncludeTotalResponseCount" = "switch";
                        "OrderBy" = "Edm.String";
                        "Select" = "Edm.String";  
                        "Skip" = "Edm.Int32"; 
                        "Top" = "Edm.Int32";
                    }

                    foreach($currentQueryParameter in $queryParameters.Keys)
                    {
                        $xmlWriter.WriteStartElement('Property')
                        $xmlWriter.WriteAttributeString('PropertyName', "QueryOption:" + $currentQueryParameter)
                        $xmlWriter.WriteStartElement('Type')
                        $PSTypeName = Convert-ODataTypeToCLRType $queryParameters[$currentQueryParameter]
                        $xmlWriter.WriteAttributeString('PSType', $PSTypeName)
                        $xmlWriter.WriteEndElement()
                        $xmlWriter.WriteStartElement('RegularQuery')
                        $xmlWriter.WriteStartElement('CmdletParameterMetadata')
                        $xmlWriter.WriteAttributeString('PSName', $currentQueryParameter)

                        if($queryParameters[$currentQueryParameter] -eq "Edm.String")
                        {
                            $xmlWriter.WriteStartElement('ValidateNotNullOrEmpty')
                            $xmlWriter.WriteEndElement()
                        }

                        if($queryParameters[$currentQueryParameter] -eq "Edm.Int32")
                        {
                            $minValue = 1
                            # For Skip Query parameter we want to support 0 as the
                            # minimum skip value in order to support client side paging.
                            if($currentQueryParameter -eq 'Skip')
                            {
                                $minValue = 0
                            }
                            $xmlWriter.WriteStartElement('ValidateRange')
                            $xmlWriter.WriteAttributeString('Min', $minValue)
                            $xmlWriter.WriteAttributeString('Max', [int]::MaxValue)
                            $xmlWriter.WriteEndElement()
                        }

                        $xmlWriter.WriteEndElement()
                        $xmlWriter.WriteEndElement()
                        $xmlWriter.WriteEndElement()
                    }
                }

                $xmlWriter.WriteEndElement()
            }

        $xmlWriter.WriteEndElement()

        $xmlWriter.WriteStartElement('GetCmdlet')
            $xmlWriter.WriteStartElement('CmdletMetadata')
            $xmlWriter.WriteAttributeString('Verb', 'Get')
            $xmlWriter.WriteEndElement()
        $xmlWriter.WriteEndElement()

    $xmlWriter.WriteEndElement()
}

#########################################################
# GenerateNewProxyCmdlet is a helper function used
# to generate New-* proxy cmdlet. The proxy cmdlet is
# generated in the CDXML compliant format.
#########################################################
function GenerateNewProxyCmdlet 
{
    param
    (
        [System.XMl.XmlTextWriter] $xmlWriter,
        [ODataUtils.Metadata] $metaData,
        [object[]]  $keyProperties,
        [object[]]  $nonNullableProperties,
        [object[]]  $nullableProperties,
        [object[]]  $navigationProperties,
        [string]    $cmdletAdapter,
        [Hashtable] $complexTypeMapping
    )

    # $cmdletAdapter is already validated at the cmdlet layer.
    if($xmlWriter -eq $null) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "GenerateNewProxyCmdlet") }
    if($metaData -eq $null) { throw ($LocalizedData.ArguementNullError -f "metadata", "GenerateNewProxyCmdlet") }

    $xmlWriter.WriteStartElement('Cmdlet')
        $xmlWriter.WriteStartElement('CmdletMetadata')
        $xmlWriter.WriteAttributeString('Verb', 'New')
        $xmlWriter.WriteAttributeString('DefaultCmdletParameterSet', 'Default')
        $xmlWriter.WriteAttributeString('ConfirmImpact', 'Medium')
        $xmlWriter.WriteEndElement()

        $xmlWriter.WriteStartElement('Method')
        $xmlWriter.WriteAttributeString('MethodName', 'Create')
        $xmlWriter.WriteAttributeString('CmdletParameterSet', 'Default')
        
        AddParametersNode $xmlWriter $keyProperties $nonNullableProperties $nullableProperties $null $true $true $complexTypeMapping
        $xmlWriter.WriteEndElement()

        # This behaviour is different for NetworkControllerCmdlets
        if ($CmdletAdapter -ne "NetworkControllerAdapter")
        {
            $navigationProperties | ? { $_ -ne $null } | % {
                $associatedType = GetAssociatedType $metaData $_
                $associatedEntitySet = GetEntitySetForEntityType $metaData $associatedType

                $xmlWriter.WriteStartElement('Method')
                $xmlWriter.WriteAttributeString('MethodName', "Association:Create:$($associatedEntitySet.Name)")
                $xmlWriter.WriteAttributeString('CmdletParameterSet', $_.Name)
                    
                $associatedKeys = ((GetAllProperties $associatedType) | ? { $_.isKey })

                AddParametersNode $xmlWriter $associatedKeys $keyProperties $null "Associated$($_.Name)" $true $true $complexTypeMapping
                $xmlWriter.WriteEndElement()
            }
        }
    
        $xmlWriter.WriteEndElement()
}

#########################################################
# GenerateRemoveProxyCmdlet is a helper function used
# to generate Remove-* proxy cmdlet. The proxy cmdlet is
# generated in the CDXML compliant format.
#########################################################
function GenerateRemoveProxyCmdlet 
{
    param
    (

        [System.XMl.XmlTextWriter] $xmlWriter,
        [ODataUtils.Metadata] $metaData,
        [object[]] $keyProperties,
        [object[]] $navigationProperties,
        [string] $cmdletAdapter,
        [Hashtable] $complexTypeMapping
    )

    # $metaData, $cmdletAdapter & $cmdletAdapter are already validated at the cmdlet layer.
    if($xmlWriter -eq $null) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "GenerateRemoveProxyCmdlet") }
    if($metaData -eq $null) { throw ($LocalizedData.ArguementNullError -f "metadata", "GenerateRemoveProxyCmdlet") }

    $xmlWriter.WriteStartElement('Cmdlet')
        $xmlWriter.WriteStartElement('CmdletMetadata')
        $xmlWriter.WriteAttributeString('Verb', 'Remove')
        $xmlWriter.WriteAttributeString('DefaultCmdletParameterSet', 'Default')
        $xmlWriter.WriteAttributeString('ConfirmImpact', 'Medium')
        $xmlWriter.WriteEndElement()

        $xmlWriter.WriteStartElement('Method')
        $xmlWriter.WriteAttributeString('MethodName', 'Delete')
        $xmlWriter.WriteAttributeString('CmdletParameterSet', 'Default')

            # This behaviour is different for NetworkControllerCmdlets
            if ($CmdletAdapter -eq "NetworkControllerAdapter")
            {
                # Add etag for NetworkControllerCmdlets
                $otherProperties = @([ODataUtils.TypeProperty] @{
                    "Name" = "Etag";
                    "TypeName" = "Edm.String";
                    "IsNullable" = $true;
                })

                AddParametersNode $xmlWriter $keyProperties $null $otherProperties $null $true $true $complexTypeMapping
            }
            else
            {
                AddParametersNode $xmlWriter $keyProperties $null $null $null $true $true $complexTypeMapping
            }

        $xmlWriter.WriteEndElement()

        # This behaviour is different for NetworkControllerCmdlets
        if ($CmdletAdapter -ne "NetworkControllerAdapter")
        {
            $navigationProperties | ? { $_ -ne $null } | % {

                $associatedType = GetAssociatedType $metaData $_
                $associatedEntitySet = GetEntitySetForEntityType $metaData $associatedType

                $xmlWriter.WriteStartElement('Method')
                $xmlWriter.WriteAttributeString('MethodName', "Association:Delete:$($associatedEntitySet.Name)")
                $xmlWriter.WriteAttributeString('CmdletParameterSet', $_.Name)
                
                    $associatedType = GetAssociatedType $metaData $_
                    $associatedKeys = ((GetAllProperties $associatedType) | ? { $_.isKey })

                AddParametersNode $xmlWriter $associatedKeys $keyProperties $null "Associated$($_.Name)" $true $true $complexTypeMapping
                $xmlWriter.WriteEndElement()
            }
        }
    $xmlWriter.WriteEndElement()
}

#########################################################
# GenerateActionProxyCmdlet is a helper function used
# to generate Invoke-* proxy cmdlet. These proxy cmdlets
# support Instance/Service level actions. They are
# generated in the CDXML compliant format.
#########################################################
function GenerateActionProxyCmdlet 
{
    param
    (
        [System.Xml.XmlWriter]    $xmlWriter,
        [ODataUtils.Metadata]     $metaData,
        [ODataUtils.Action]       $action,
        [string]                  $noun,
        [bool]                    $isInstanceAction,
        [ODataUtils.TypeProperty] $keys,
        [Hashtable]               $complexTypeMapping
    )

    # $metaData is already validated at the cmdlet layer.
    if($xmlWriter -eq $null) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "GenerateActionProxyCmdlet") }
    if($metaData -eq $null) { throw ($LocalizedData.ArguementNullError -f "metadata", "GenerateActionProxyCmdlet") }
    if($action -eq $null) { throw ($LocalizedData.ArguementNullError -f "Action", "GenerateActionProxyCmdlet") }
    if($noun -eq $null) { throw ($LocalizedData.ArguementNullError -f "Noun", "GenerateActionProxyCmdlet") }

    $xmlWriter.WriteStartElement('Cmdlet')

        $xmlWriter.WriteStartElement('CmdletMetadata')
        $xmlWriter.WriteAttributeString('Verb', 'Invoke')
        $xmlWriter.WriteAttributeString('Noun', "$($noun)$($action.Verb)")
        $xmlWriter.WriteAttributeString('ConfirmImpact', 'Medium')
        $xmlWriter.WriteEndElement()

        $xmlWriter.WriteStartElement('Method')
        $xmlWriter.WriteAttributeString('MethodName', "Action:$($action.Verb):$($action.EntitySet.Name)")

            $xmlWriter.WriteStartElement('Parameters')

            $keys | ? { $_ -ne $null } | % {
                $xmlWriter.WriteStartElement('Parameter')
                $xmlWriter.WriteAttributeString('ParameterName', $_.Name + ':Key')

                    $xmlWriter.WriteStartElement('Type')
                    $PSTypeName = Convert-ODataTypeToCLRType $_.TypeName $complexTypeMapping
                    $xmlWriter.WriteAttributeString('PSType', $PSTypeName)
                    $xmlWriter.WriteEndElement()

                    $xmlWriter.WriteStartElement('CmdletParameterMetadata')
                    $xmlWriter.WriteAttributeString('PSName', $_.Name)
                    $xmlWriter.WriteAttributeString('IsMandatory', 'true')
                    $xmlWriter.WriteAttributeString('ValueFromPipelineByPropertyName', 'true')
                    $xmlWriter.WriteEndElement()
                $xmlWriter.WriteEndElement()
            }

            $i = -1
            foreach ($parameter in $action.Parameters)
            {
                $i++

                # for Instance actions, first parameter is Entity Set which we refer to using keys
                if ($isInstanceAction -and ($i -eq 0))
                {
                    continue
                }

                $xmlWriter.WriteStartElement('Parameter')
                $xmlWriter.WriteAttributeString('ParameterName', $parameter.Name)

                    $xmlWriter.WriteStartElement('Type')
                    $PSTypeName = Convert-ODataTypeToCLRType $parameter.TypeName
                    $xmlWriter.WriteAttributeString('PSType', $PSTypeName)
                    $xmlWriter.WriteEndElement()

                    $xmlWriter.WriteStartElement('CmdletParameterMetadata')
                    $xmlWriter.WriteAttributeString('PSName', $parameter.Name)
                    if (-not $parameter.IsNullable)
                    {
                        $xmlWriter.WriteAttributeString('IsMandatory', 'true')
                        $xmlWriter.WriteAttributeString('ValueFromPipelineByPropertyName', 'true')
                    }
                    $xmlWriter.WriteEndElement()
                $xmlWriter.WriteEndElement()
            }

            # Add -Force parameter to Service Action cmdlets.
            AddParametersNode $xmlWriter $null $null $null $null $true $false $complexTypeMapping
            $xmlWriter.WriteEndElement()
        $xmlWriter.WriteEndElement()

    $xmlWriter.WriteEndElement()

    $xmlWriter
}

#########################################################
# GenerateServiceActionProxyCmdlet is a helper function
# used to generate Invoke-* proxy cmdlet. These proxy
# cmdlets support all Service-level actions. They are
# generated in the CDXML compliant format.
#########################################################
function GenerateServiceActionProxyCmdlet 
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ODataUtils.Metadata] $metaData,
        [Parameter(Mandatory=$true)]
        [string] $uri,
        [Parameter(Mandatory=$true)]
        [string] $path,
        [Hashtable] $complexTypeMapping,
        [string] $progressBarStatus,
        [System.Management.Automation.PSCmdlet] $callerPSCmdlet
    )

    # $uri is already validated at the cmdlet layer.
    if($metaData -eq $null) { throw ($LocalizedData.ArguementNullError -f "metadata", "GenerateServiceActionProxyCmdlet") }

    $xmlWriter = New-Object System.XMl.XmlTextWriter($path,$Null)

    if ($xmlWriter -eq $null)
    {
        throw $LocalizedData.XmlWriterInitializationError -f "ServiceActions"
    }

    $xmlWriter = SaveCDXMLHeader $xmlWriter $uri 'ServiceActions' 'ServiceActions'

    $actions = $metaData.Actions | Where-Object { $_.EntitySet -eq $null }

    if ($actions.Length -gt 0)
    {
        $xmlWriter.WriteStartElement('StaticCmdlets')

        foreach ($action in $actions)
        {
            $xmlWriter = GenerateActionProxyCmdlet $xmlWriter $metaData $action '' $false $null $complexTypeMapping
        }

        $xmlWriter.WriteEndElement()
    }

    $xmlWriter.WriteStartElement('CmdletAdapterPrivateData')
    $xmlWriter.WriteStartElement('Data')
    $xmlWriter.WriteAttributeString('Name', 'Namespace')
    $xmlWriter.WriteString("$($EntitySet.Namespace)")
    $xmlWriter.WriteEndElement()
    $xmlWriter.WriteEndElement()

    SaveCDXMLFooter $xmlWriter

    ProcessStreamHelper ($LocalizedData.VerboseSavedServiceActions -f $path) "Export-ODataEndpointProxy" $progressBarStatus 60 20 1 1 $path $callerPSCmdlet
}

#########################################################
# GenerateModuleManifest is a helper function used
# to generate a wrapper module manifest file. The
# generated module manifest is persisted to the disk at
# the specified OutPutModule path. When the module
# manifest is imported, the following comands will
# be imported:
# 1. Get, Set, New & Remove proxy cmdlets.
# 2. If the server side Odata endpoint exposes complex
# types, then the corresponding client side proxy
# complex types imported.
# 3. Service Action proxy cmdlets.
#########################################################
function GenerateModuleManifest 
{
    param
    (
        [ODataUtils.Metadata] $metaData,
        [String]              $modulePath,
        [string[]]            $additionalModules,
        [Hashtable]           $resourceNameMapping,
        [string]              $progressBarStatus,
        [System.Management.Automation.PSCmdlet] $callerPSCmdlet
    )

    if($metaData -eq $null) { throw ($LocalizedData.ArguementNullError -f "metadata", "GenerateModuleManifest") }
    if($modulePath -eq $null) { throw ($LocalizedData.ArguementNullError -f "ModulePath", "GenerateModuleManifest") }
    if($progressBarStatus -eq $null) { throw ($LocalizedData.ArguementNullError -f "ProgressBarStatus", "GenerateModuleManifest") }

    $NestedModules = @()
    foreach ($entitySet in $metaData.EntitySets)
    {
        $entitySetName = $entitySet.Name 
        if(($resourceNameMapping -ne $null) -and 
        $resourceNameMapping.ContainsKey($entitySetName))
        {
            $entitySetName = $resourceNameMapping[$entitySetName]
        }
        else
        {
            $entitySetName = $entitySet.Type.Name
        }

        $NestedModules += "$OutputModule\$($entitySetName).cdxml"
    }

    New-ModuleManifest -Path $modulePath -NestedModules ($AdditionalModules + $NestedModules)

    ProcessStreamHelper ($LocalizedData.VerboseSavedModuleManifest -f $modulePath) "Export-ODataEndpointProxy" $progressBarStatus 80 20 1 1 $modulePath $callerPSCmdlet
}

#########################################################
# GetBaseType is a helper function used to fetch the
# base type of the given type.
#########################################################
function GetBaseType 
{
    param
    (
        [System.Xml.XmlElement] $metadataEntityDefinition,
        [ODataUtils.Metadata] $metaData
    )

    if ($metadataEntityDefinition -ne $null -and 
    $metaData -ne $null -and 
    $metadataEntityDefinition.BaseType -ne $null)
    {
        $baseType = $metaData.EntityTypes | Where {$_.Namespace+"."+$_.Name -eq $metadataEntityDefinition.BaseType}
        if ($baseType -eq $null)
        {
            $baseType = $metaData.ComplexTypes | Where {$_.Namespace+"."+$_.Name -eq $metadataEntityDefinition.BaseType}
        }
    }

    if ($baseType -ne $null)
    {
        $baseType[0]
    }
}

#########################################################
# AddDerivedTypes is a helper function used to process
# derived types of a newly added type, that were
# previously waiting in the queue.
#########################################################
function AddDerivedTypes 
{
    param
    (
        [ODataUtils.EntityType] $baseType,
        [Hashtable]$entityAndComplexTypesQueue,    
        [ODataUtils.Metadata] $metaData,
        [string] $namespace
    )

    # $metaData is already validated at the cmdlet layer.
    if($baseType -eq $null) { throw ($LocalizedData.ArguementNullError -f "BaseType", "AddDerivedTypes") }
    if($entityAndComplexTypesQueue -eq $null) { throw ($LocalizedData.ArguementNullError -f "EntityAndComplexTypesQueue", "AddDerivedTypes") }
    if($namespace -eq $null) { throw ($LocalizedData.ArguementNullError -f "Namespace", "AddDerivedTypes") }

    $baseTypeFullName = $baseType.Namespace + '.' + $baseType.Name

    if ($entityAndComplexTypesQueue.ContainsKey($baseTypeFullName))
    {
        foreach ($type in $entityAndComplexTypesQueue[$baseTypeFullName])
        {
            if ($type.type -eq 'EntityType')
            {
                $newType = ParseMetadataTypeDefinition ($type.value) $baseType $metaData $namespace $true
                $metaData.EntityTypes += $newType
            }
            else
            {
                $newType = ParseMetadataTypeDefinition ($type.value) $baseType $metaData $namespace $false
                $metaData.ComplexTypes += $newType
            }

            AddDerivedTypes $newType $entityAndComplexTypesQueue $metaData $namespace
        }
    }
}

#########################################################
# ParseMetadataTypeDefinition is a helper function used
# to parse types definitions element of metadata xml.
#########################################################
function ParseMetadataTypeDefinition 
{
    param
    (
        [Parameter(Mandatory=$true)]
        [System.Xml.XmlElement] $metadataEntityDefinition,
        [ODataUtils.EntityType] $baseType,
        [ODataUtils.Metadata] $metaData,
        [string] $namespace,
        [bool] $isEntity
    )

    # $metaData is already validated at the cmdlet layer.
    if($metadataEntityDefinition -eq $null) { throw ($LocalizedData.ArguementNullError -f "MetadataEntityDefinition", "ParseMetadataTypeDefinition") }
    if($namespace -eq $null) { throw ($LocalizedData.ArguementNullError -f "Namespace", "ParseMetadataTypeDefinition") }

    $newEntityType = [ODataUtils.EntityType] @{
        "Namespace" = $namespace;
        "Name" = $metadataEntityDefinition.Name;
        "IsEntity" = $isEntity;
        "BaseType" = $baseType;
    }

    # properties defined on EntityType
    $newEntityType.EntityProperties = $metadataEntityDefinition.Property | % {
        if ($_ -ne $null)
        {
            if ($_.Nullable -ne $null)
            {
                $newPropertyIsNullable = [System.Convert]::ToBoolean($_.Nullable)
            }
            else
            {
                $newPropertyIsNullable = $true
            }

            [ODataUtils.TypeProperty] @{
                "Name" = $_.Name;
                "TypeName" = $_.Type;
                "IsNullable" = $newPropertyIsNullable;
            }
        }
    }

    # navigation properties defined on EntityType
    $newEntityType.NavigationProperties = $metadataEntityDefinition.NavigationProperty | % {
        if ($_ -ne $null)
        {
            ($AssociationNamespace, $AssociationName) = SplitNamespaceAndName $_.Relationship
            [ODataUtils.NavigationProperty] @{
                "Name" = $_.Name;
                "FromRole" = $_.FromRole;
                "ToRole" = $_.ToRole;
                "AssociationNamespace" = $AssociationNamespace;
                "AssociationName" = $AssociationName;
            }
        }
    }

    foreach ($entityTypeKey in $metadataEntityDefinition.Key.PropertyRef)
    {
        ((GetAllProperties $newEntityType) | Where-Object { $_.Name -eq $entityTypeKey.Name }).IsKey = $true
    }

    $newEntityType
}

#########################################################
# GetAllProperties is a helper function used to fetch
# the entity properties or navigation properties of
# the entity type as well as that of complete base
# type hierarchy.
#########################################################
function GetAllProperties 
{
    param
    (
        [ODataUtils.EntityType] $entityType,
        [switch] $IncludeOnlyNavigationProperties 
    )

    if($entityType -eq $null) { throw ($LocalizedData.ArguementNullError -f "EntityType", "GetAllProperties") }

    $requestedProperties = @()

    # Populate EntityType property from current EntityType as well
    # as from the corresponding base types recursively if
    # $IncludeOnlyNavigationProperties switch parameter is used then follow
    # the same routine for navigation properties.
    $currentEntityType = $entityType
    while($currentEntityType -ne $null)
    {
        if($IncludeOnlyNavigationProperties.IsPresent)
        {
            $chosenProperties = $currentEntityType.NavigationProperties
        }
        else
        {
            $chosenProperties = $currentEntityType.EntityProperties
        }

        $requestedProperties += $chosenProperties
        $currentEntityType = $currentEntityType.BaseType
    }

    return $requestedProperties
}

#########################################################
# SplitNamespaceAndName is a helper function used
# to split Namespace and actual Name.
# e.g. "a.b.c" is namespace "a.b" and name "c"
#########################################################
function SplitNamespaceAndName 
{
    param
    (
        [string] $fullyQualifiedName
    )

    if($fullyQualifiedName -eq $null) { throw ($LocalizedData.ArguementNullError -f "FUllyQualifiedName", "SplitNamespaceAndName") }

    $sa = $fullyQualifiedName -split "(.*)\.(.*)"

    if ($sa.Length -gt 1)
    {
        # return Namespace
        $sa[1]

        # return Name
        $sa[2]
    }
    else
    {
        # return Namespace
        ""

        # return Name
        $sa[0]
    }
}

#########################################################
# GetEntitySetForEntityType is a helper function used
# to fetch EntitySet for a given EntityType by
# searching the inheritance hierarchy in the
# supplied metadata.
#########################################################
function GetEntitySetForEntityType 
{
    param
    (
        [ODataUtils.Metadata] $metaData,
        [ODataUtils.EntityType] $entityType
    )

    # $metaData is already validated at the cmdlet layer.
    if($entityType -eq $null) { throw ($LocalizedData.ArguementNullError -f "EntityType", "GetEntitySetForEntityType") }

    $result = $metaData.EntitySets | ? { ($_.Type.Namespace -eq $entityType.Namespace) -and ($_.Type.Name -eq $entityType.Name) }

    if (($result.Count -eq 0) -and ($entityType.BaseType -ne $null))
    {
        GetEntitySetForEntityType $metaData $entityType.BaseType
    }
    elseif ($result.Count -gt 1)
    {
        throw ($LocalizedData.WrongCountEntitySet -f (($entityType.Namespace + "." + $entityType.Name), $result.Count))
    }

    $result
}

#########################################################
# ProcessStreamHelper is a helper function that performs
# the following utility tasks:
# 1. Writes verobose messsages to the stream.
# 2. Writes FileInfo objects for the proxy modules
# saved to the disk. This is done to keep the user
# experience in consistent with Export-PSSession.
# 3. Updates progess bar.
#########################################################
function ProcessStreamHelper 
{
    param
    (
        [string] $verboseMessage,
        [string] $progressBarActivityName,
        [string] $status,
        [double] $previousSegmentWeight,
        [double] $currentSegmentWeight,
        [int]    $totalNumberofEntries,
        [int]    $currentEntryCount,
        [string] $path,
        [System.Management.Automation.PSCmdlet] $callerPSCmdlet
    )

    Write-Verbose -Message $verboseMessage
    ProgressBarHelper $progressBarActivityName $status $previousSegmentWeight $currentSegmentWeight $totalNumberofEntries $currentEntryCount
    $proxyFile = New-Object -TypeName System.IO.FileInfo -ArgumentList $path | Get-Item
    if($callerPSCmdlet -ne $null)
    {
        $callerPSCmdlet.WriteObject($proxyFile)
    }
}

#########################################################
# GetAssociatedType is a helper function used
# to fetch associated instance's EntityType
# for a given Navigation property in the
# supplied metadata.
#########################################################
function GetAssociatedType 
{
    param
    (
        [ODataUtils.Metadata] $Metadata,
        [ODataUtils.NavigationProperty] $navProperty
    )

    # $metaData is already validated at the cmdlet layer.
    if($navProperty -eq $null) { throw ($LocalizedData.ArguementNullError -f "NavigationProperty", "GetAssociatedType") }

    $associationName = $navProperty.AssociationName
    $association = $Metadata.Associations | ? { $_.Name -eq $associationName }
    $associationType = $association.Type

    if ($associationType.Count -lt 1)
    {
        throw ($LocalizedData.AssociationNotFound -f $associationName)
    }
    elseif ($associationType.Count -gt 1)
    {
        throw ($LocalizedData.TooManyMatchingAssociationTypes -f $associationType.Count, $associationName)
    }

    if ($associationType.NavPropertyName1 -eq $navProperty.ToRole)
    {
        $associatedType = $associationType.EndType1
    }
    elseif ($associationType.NavPropertyName2 -eq $navProperty.ToRole)
    {
        $associatedType = $associationType.EndType2
    }
    else
    {
        throw ($LocalizedData.ZeroMatchingAssociationTypes -f $navProperty.ToRole, $association.Name)
    }

    # return associated EntityType
    $associatedType
}

#########################################################
# AddParametersNode is a helper function used
# to add parameters to the generated proxy cmdlet,
# based on mandatoryProperties and otherProperties.
# PrefixForKeys is used by associations to append a
# prefix to PowerShell parameter name.
#########################################################
function AddParametersNode 
{
    param
    (
        [Parameter(Mandatory=$true)]
        [System.Xml.XmlWriter]       $xmlWriter,
        [ODataUtils.TypeProperty[]]  $keyProperties,
        [ODataUtils.TypeProperty[]]  $mandatoryProperties,
        [ODataUtils.TypeProperty[]]  $otherProperties,
        [string]    $prefixForKeys,
        [boolean]   $addForceParameter,
        [boolean]   $addParametersElement,
        [Hashtable] $complexTypeMapping
    )

    if($xmlWriter -eq $null) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "AddParametersNode") }

    if(($keyProperties.Length -gt 0) -or 
       ($mandatoryProperties.Length -gt 0) -or 
       ($otherProperties.Length -gt 0) -or
       ($addForceParameter))
    {
        if($addParametersElement)
        {
            $xmlWriter.WriteStartElement('Parameters')
        }

        $pos = 0

        if ($keyProperties -ne $null)
        {
            $pos = AddParametersCDXML $xmlWriter $keyProperties $pos $true $prefixForKeys ":Key" $complexTypeMapping
        }

        if ($mandatoryProperties -ne $null)
        {
            $pos = AddParametersCDXML $xmlWriter $mandatoryProperties $pos $true $null $null $complexTypeMapping
        }

        if ($otherProperties -ne $null)
        {
            $pos = AddParametersCDXML $xmlWriter $otherProperties $pos $false $null $null $complexTypeMapping
        }

        if($addForceParameter)
        {
            $forceParameter = [ODataUtils.TypeProperty] @{
                "Name" = "Force";
                "TypeName" = "switch";
                "IsNullable" = $false
            }

            $pos = AddParametersCDXML $xmlWriter $forceParameter $pos $false $null $null $complexTypeMapping
        }

        if($addParametersElement)
        {
            $xmlWriter.WriteEndElement()
        }
    }
}

#########################################################
# AddParametersNode is a helper function used
# to add Parameter node to CDXML based on properties.
# Prefix is appended to PS parameter names, used for
# associations. Suffix is appended to all parameter
# names, for ex. to differentiate keys. returns new $pos
#########################################################
function AddParametersCDXML 
{
    param
    (
        [Parameter(Mandatory=$true)]
        [System.Xml.XmlWriter] $xmlWriter,
        [ODataUtils.TypeProperty[]] $properties,
        [Parameter(Mandatory=$true)]
        [int] $pos,
        [bool] $isMandatory,
        [string] $prefix,
        [string] $suffix,
        [Hashtable] $compleTypeMapping
    )

    $properties | ? { $_ -ne $null } | % {
        $xmlWriter.WriteStartElement('Parameter')
        $xmlWriter.WriteAttributeString('ParameterName', $_.Name + $suffix)
            $xmlWriter.WriteStartElement('Type')
            $PSTypeName = Convert-ODataTypeToCLRType $_.TypeName $compleTypeMapping
            $xmlWriter.WriteAttributeString('PSType', $PSTypeName)
            $xmlWriter.WriteEndElement()

            $xmlWriter.WriteStartElement('CmdletParameterMetadata')
            $xmlWriter.WriteAttributeString('PSName', $prefix + $_.Name)
            $xmlWriter.WriteAttributeString('IsMandatory', ($isMandatory).ToString().ToLowerInvariant())
            $xmlWriter.WriteAttributeString('Position', $pos)
            if($isMandatory)
            {
                $xmlWriter.WriteAttributeString('ValueFromPipelineByPropertyName', 'true')
            }
            $xmlWriter.WriteEndElement()
        $xmlWriter.WriteEndElement()

        $pos++
    }

    $pos
}

#########################################################
# GenerateComplexTypeDefination is a helper function used
# to generate comlplex type defination from the metadata.
#########################################################
function GenerateComplexTypeDefination 
{
    param
    (
        [ODataUtils.Metadata] $metaData,
        [string] $metaDataUri,
        [string] $OutputModule,
        [string] $typeDefinationFileName,
        [string] $cmdletAdapter,
        [System.Management.Automation.PSCmdlet] $callerPSCmdlet
    )

    #metadataUri, $OutputModule & $cmdletAdapter are already validated at the cmdlet layer.
    if($typeDefinationFileName -eq $null) { throw ($LocalizedData.ArguementNullError -f "TypeDefinationFileName", "GenerateComplexTypeDefination") }
    if($metaData -eq $null) { throw ($LocalizedData.ArguementNullError -f "metadata", "GenerateComplexTypeDefination") }
    if($callerPSCmdlet -eq $null) { throw ($LocalizedData.ArguementNullError -f "PSCmdlet", "GenerateComplexTypeDefination") }

    $Path = "$OutputModule\$typeDefinationFileName"

    # We are currently generating classes for EntityType & ComplexType
    # defination exposed in the metadata.
    $typesToBeGenerated = $metaData.EntityTypes+$metadata.ComplexTypes

    if($typesToBeGenerated -ne $null -and $typesToBeGenerated.Count -gt 0)
    {
        $complexTypeMapping = @{}
        $entityTypeNameSpaceMapping = @{}

        foreach ($entityType in $typesToBeGenerated)
        {
            if ($entityType -ne $null)
            {
                $entityTypeFullName = $entityType.Namespace + '.' + $entityType.Name
                if(!$complexTypeMapping.ContainsKey($entityTypeFullName))
                {
                    $complexTypeMapping.Add($entityTypeFullName, $entityType.Name)
                }

                if(!$entityTypeNameSpaceMapping.ContainsKey($entityType.Namespace))
                {
                    $entityTypes = @()
                    $entityTypeNameSpaceMapping.Add($entityType.Namespace, $entityTypes)
                }

                $entityTypeNameSpaceMapping[$entityType.Namespace] += $entityType
            }
        }

        if($entityTypeNameSpaceMapping.Count -gt 0)
        {
$output = @"
`$typeDefinitions = @"
using System;
using System.Management.Automation;
 
"@


            foreach($currentNameSpace in $entityTypeNameSpaceMapping.Keys)
            {
                $entityTypes = $entityTypeNameSpaceMapping[$currentNameSpace]

                $output += "`r`nnamespace $(ValidateComplexTypeIdentifier $currentNameSpace $true $metaDataUri $callerPSCmdlet)`r`n{"
                
                foreach ($entityType in $entityTypes)
                {
                    $entityTypeFullName = (ValidateComplexTypeIdentifier $entityType.Namespace $true $metaDataUri $callerPSCmdlet) + '.' + $entityType.Name
                    Write-Verbose ($LocalizedData.VerboseAddingTypeDefinationToGeneratedModule -f $entityTypeFullName, "$OutputModule\$typeDefinationFileName")

                    if($entityType.BaseType -ne $null)
                    {
                        $entityBaseFullName = (ValidateComplexTypeIdentifier $entityType.BaseType.Namespace $true $metaDataUri $callerPSCmdlet) + '.' + (ValidateComplexTypeIdentifier $entityType.BaseType.Name $false $metaDataUri $callerPSCmdlet)
                        $output += "`r`n public class $(ValidateComplexTypeIdentifier $entityType.Name $false $metaDataUri $callerPSCmdlet) : $($entityBaseFullName)`r`n {"
                    }
                    else
                    {
                        $output += "`r`n public class $(ValidateComplexTypeIdentifier $entityType.Name $false $metaDataUri $callerPSCmdlet)`r`n {"
                    }

                    $properties = $null

                    for($index = 0; $index -lt $entityType.EntityProperties.Count; $index++)
                    {
                        $property = $entityType.EntityProperties[$index]
                        $typeName = Convert-ODataTypeToCLRType $property.TypeName $complexTypeMapping
                        $properties += "`r`n public $typeName $(ValidateComplexTypeIdentifier $property.Name $false $metaDataUri $callerPSCmdlet);"
                    }

                    # Navigation properties are treated like any other property for NetworkController scenario.
                    if ($cmdletAdapter -eq "NetworkControllerAdapter")
                    {
                        for($index = 0; $index -lt $entityType.NavigationProperties.Count; $index++)
                        {
                            $property = $entityType.NavigationProperties[$index]
                            $navigationTypeName = GetNavigationPropertyTypeName $property $metaData
                            $typeName = Convert-ODataTypeToCLRType $navigationTypeName $complexTypeMapping
                            $properties += "`r`n public $typeName $(ValidateComplexTypeIdentifier $property.Name $false $metaDataUri $callerPSCmdlet);"
                        }           
                    }

                    $output += $properties
                    $output += "`r`n }`r`n"
                }

                $output += "}`r`n"
            }
            $output += """@`r`n"

            $output += "Add-Type -TypeDefinition `$typeDefinitions `r`n"
            $output | Out-File -FilePath $Path
            Write-Verbose ($LocalizedData.VerboseSavedTypeDefinationModule -f $typeDefinationFileName, $OutputModule)
        }
     }

     return $complexTypeMapping
}

# Creating a single instace of CSharpCodeProvider that would be used
# for Identifier validation in the ValidateComplexTypeIdentifier helper method.
$cSharpCodeProvider = [Microsoft.CSharp.CSharpCodeProvider]::new()

#########################################################
# ValidateComplexTypeIdentifier is a helper function to
# make sure that the type names defined in the
# metadata are valid C# Identifier names. This validation
# is performed to make sure that there are no security
# threat from importing the generated complex type
# (which is created using the metadata file).
# This method return the identifier name if its a
# valid identifier, else a terminating error in thrown.
#########################################################
function ValidateComplexTypeIdentifier 
{
    param
    (
        [string] $identifierName,
        [bool]   $isNameSpaceName,
        [string] $metaDataUri,
        [System.Management.Automation.PSCmdlet] $callerPSCmdlet
    )

    if($callerPSCmdlet -eq $null) { throw ($LocalizedData.ArguementNullError -f "PSCmdlet", "ValidateComplexTypeIdentifier") }

    if($isNameSpaceName)
    {
        $independentIdentifiers = $identifierName.Split('.')
        $result = $true
        foreach($currentIdentifier in $independentIdentifiers)
        {
            if(![System.CodeDom.Compiler.CodeGenerator]::IsValidLanguageIndependentIdentifier($currentIdentifier))
            {
                $result = $false
                break
            }
        }
    }
    else
    {
        $result = $cSharpCodeProvider.IsValidIdentifier($identifierName)
    }

    if(!$result)
    {
        $errorMessage = ($LocalizedData.InValidIdentifierInMetadata -f $metaDataUri, $identifierName)
        $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyInvalidIdentifier" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidData) $null $identifierName
        $callerPSCmdlet.ThrowTerminatingError($errorRecord)
    }
    else
    {
        return $identifierName
    }
}

#########################################################
# GetKeys is a helper function used to
# return the keys for the entity if customUri
# is specified.
#########################################################
function GetKeys 
{
    param
    (
        [ODataUtils.EntitySet] $entitySet,
        [string] $customUri,
        [string] $actionName
    )

    # Get the original keys
    $key = (GetAllProperties $entitySet.Type) | Where-Object { $_.IsKey }

    # Get the keys with delimiters
    $keys = $customUri -split "/" | % {
        if ($_ -match '{*}')
        {
            [ODataUtils.TypeProperty] @{
                "Name" = $_.Substring($_.IndexOf('{')+1,$_.IndexOf('}')-$_.IndexOf('{')-1);
                "TypeName" = "Edm.String";
                "IsNullable" = $false;
                "IsMandatory" = $true;
            }
        }
        elseif ($_ -match '\[*\]')
        {
            if ($ActionName -eq 'Get') {
                [ODataUtils.TypeProperty] @{
                    "Name" = $_.Substring($_.IndexOf('[')+1,$_.IndexOf(']')-$_.IndexOf('[')-1);
                    "TypeName" = "Edm.String";
                    "IsNullable" = $false;
                    "IsMandatory" = $false;
                }
            }
            else {
                [ODataUtils.TypeProperty] @{
                    "Name" = $_.Substring($_.IndexOf('[')+1,$_.IndexOf(']')-$_.IndexOf('[')-1);
                    "TypeName" = "Edm.String";
                    "IsNullable" = $false;
                    "IsMandatory" = $true;
                }
            }
        }
    }

    # Now combine the two keys and avoid duplication
    # Make a list of names already present in the new keys
    # Foreach old key check if that key is present in the new keyList
    # Else add the key to new key list
    $keyParams = $keys | ForEach-Object {$_.Name}
    
    if ($keyParams -eq $null -Or $keyParams.Count -eq 0) {
        $keys = $key
    }
    else {
        if ($keyParams.Count -eq 1) {
            $keys = @($keys)
        }

        $key | ForEach-Object {
            if ($keyParams.Contains($_.Name) -eq $false)
            {
                $keys += $_
            }
        }
    }

    $keys
}

#########################################################
# GetNetworkControllerAdditionalProperties is a helper
# function used to fetch network controller specific
# additional properties.
#########################################################
function GetNetworkControllerAdditionalProperties 
{
    param 
    (
        $navigationProperties,
        $metaData
    )

    # Additional properties contains the types present as navigation properties

    $additionalProperties = $navigationProperties | ? { $_ -ne $null } | %{
        $typeName = GetNavigationPropertyTypeName $_ $metaData

        if ($_.Name -eq "Properties") {
            $isNullable = $false
        }
        else {
            $isNullable = $true
        }

        [ODataUtils.TypeProperty] @{
            "Name" = $_.Name;
            "TypeName" = $typeName
            "IsNullable" = $isNullable;
        }
    }
   
    # Add etag to the additionalProperties

    if ($additionalProperties -ne $null)
    {
        if ($additionalProperties.Count -eq 1) {
            $additionalProperties = @($additionalProperties)
        }

        $additionalProperties += [ODataUtils.TypeProperty] @{
            "Name" = "Etag";
            "TypeName" = "Edm.String";
            "IsNullable" = $true;
        }
    }
    else
    {
      $additionalProperties = [ODataUtils.TypeProperty] @{
            "Name" = "Etag";
            "TypeName" = "Edm.String";
            "IsNullable" = $true;
        }  
    } 

    $additionalProperties
}

#########################################################
# UpdateNetworkControllerSpecificProperties is a
# helper function used to append additionalProperties
# to nullable/nonNullable Properties. This is network controller
# specific logic.
#########################################################
function UpdateNetworkControllerSpecificProperties 
{
    param 
    (
        $nullableProperties,
        $additionalProperties,
        $keyProperties,
        $isNullable
    )

    if ($isNullable) {
        $additionalProperties = $additionalProperties | ? { $_.isNullable }
    }
    else {
        $additionalProperties = $additionalProperties | ? { -not $_.isNullable }
    }

    if ($nullableProperties -eq $null)
    {
        $nullableProperties = $additionalProperties
    }
    else {
        if ($nullableProperties.Count -eq 1) {
               $nullableProperties = @($nullableProperties)
        }
        if ($additionalProperties -ne $null) {
            $nullableProperties += $additionalProperties
        }
    }

    if ($nullableProperties -ne $null -And $keyProperties -ne $null)
    {
        if ($keyProperties.Count -eq 1) {
            $keyProperties = @($keyProperties)
        }

        $keys = $keyProperties | ForEach-Object {$_.Name} 

        if ($keys.Count -eq 1) {
            $keys = @($keys)
        }

        $nullableProperties = $nullableProperties | Where-Object {$keys.Contains($_.Name) -eq $false}
    }

    $nullableProperties
}

#########################################################
# GetNavigationPropertyTypeName is a
# helper function used to fetch the type corresponding
# to navigation property in this metadata. This is
# network controller specific logic.
#########################################################
function GetNavigationPropertyTypeName 
{
    param 
    (
        $navigationProperty,
        $metaData
    )

    foreach($association in $metaData.Associations)
    {
        if ($association.Name -ne $navigationProperty.AssociationName -Or $association.Namespace -ne $navigationProperty.AssociationNamespace)
        {
            continue
        }

        # Now get the type for this association

        if ($association.Type.NavPropertyName1 -eq $navigationProperty.Name)
        {
            $type = $association.Type.EndType1
            $multiplicity = $association.Type.Multiplicity1
        }
        elseif ($associationType.NavPropertyName2 -eq $navigationProperty.Name)
        {
            $type = $association.Type.EndType2
            $multiplicity = $association.Type.Multiplicity2
        }
        
        break
    }

    $fullName = $type.Namespace + '.' + $type.Name
    
    # Check the multiplicity and convert to array if needed
    if ($multiplicity -eq "*")
    {
        $fullName = "Collection($fullName)"
    }

    $fullName
}

# SIG # Begin signature block
# MIIj+AYJKoZIhvcNAQcCoIIj6TCCI+UCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAE9nrOh/zgPoEa
# c9iTtgD5GqHso4X+lsOKH2nm9prFjaCCDYEwggX/MIID56ADAgECAhMzAAABA14l
# HJkfox64AAAAAAEDMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMTgwNzEyMjAwODQ4WhcNMTkwNzI2MjAwODQ4WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDRlHY25oarNv5p+UZ8i4hQy5Bwf7BVqSQdfjnnBZ8PrHuXss5zCvvUmyRcFrU5
# 3Rt+M2wR/Dsm85iqXVNrqsPsE7jS789Xf8xly69NLjKxVitONAeJ/mkhvT5E+94S
# nYW/fHaGfXKxdpth5opkTEbOttU6jHeTd2chnLZaBl5HhvU80QnKDT3NsumhUHjR
# hIjiATwi/K+WCMxdmcDt66VamJL1yEBOanOv3uN0etNfRpe84mcod5mswQ4xFo8A
# DwH+S15UD8rEZT8K46NG2/YsAzoZvmgFFpzmfzS/p4eNZTkmyWPU78XdvSX+/Sj0
# NIZ5rCrVXzCRO+QUauuxygQjAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUR77Ay+GmP/1l1jjyA123r3f3QP8w
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDM3OTY1MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAn/XJ
# Uw0/DSbsokTYDdGfY5YGSz8eXMUzo6TDbK8fwAG662XsnjMQD6esW9S9kGEX5zHn
# wya0rPUn00iThoj+EjWRZCLRay07qCwVlCnSN5bmNf8MzsgGFhaeJLHiOfluDnjY
# DBu2KWAndjQkm925l3XLATutghIWIoCJFYS7mFAgsBcmhkmvzn1FFUM0ls+BXBgs
# 1JPyZ6vic8g9o838Mh5gHOmwGzD7LLsHLpaEk0UoVFzNlv2g24HYtjDKQ7HzSMCy
# RhxdXnYqWJ/U7vL0+khMtWGLsIxB6aq4nZD0/2pCD7k+6Q7slPyNgLt44yOneFuy
# bR/5WcF9ttE5yXnggxxgCto9sNHtNr9FB+kbNm7lPTsFA6fUpyUSj+Z2oxOzRVpD
# MYLa2ISuubAfdfX2HX1RETcn6LU1hHH3V6qu+olxyZjSnlpkdr6Mw30VapHxFPTy
# 2TUxuNty+rR1yIibar+YRcdmstf/zpKQdeTr5obSyBvbJ8BblW9Jb1hdaSreU0v4
# 6Mp79mwV+QMZDxGFqk+av6pX3WDG9XEg9FGomsrp0es0Rz11+iLsVT9qGTlrEOla
# P470I3gwsvKmOMs1jaqYWSRAuDpnpAdfoP7YO0kT+wzh7Qttg1DO8H8+4NkI6Iwh
# SkHC3uuOW+4Dwx1ubuZUNWZncnwa6lL2IsRyP64wggd6MIIFYqADAgECAgphDpDS
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVzTCCFckCAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAQNeJRyZH6MeuAAAAAABAzAN
# BglghkgBZQMEAgEFAKCBvDAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgK35vRqlR
# oIh8U+1GTDe6aX2FuE1D5Zk1izQcE0CQNRswUAYKKwYBBAGCNwIBDDFCMECgFoAU
# AFAAbwB3AGUAcgBTAGgAZQBsAGyhJoAkaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L1Bvd2VyU2hlbGwgMA0GCSqGSIb3DQEBAQUABIIBALrWrcvM+NxD1Qd7l1lIgH/I
# hchFtf+sgYUJAfLRG079bFcFkwkbFNGW93IklXoFiPd7jPCIvYnczTaw3rmgRGJJ
# s3vB66TYVleET0JBqC1noCc4DYCBkcRyIBWHVxPQfir1fyLLj0yyk9ZP/4SWLJgu
# I52FjKakE23y5laF8WE3V7uiu6AWHO2HC8SWbJpe7c/CNJ5bnKD/cSOZLj0e9q1m
# 2eGIJk6cYglu8E8nhtySRpaqcnJNS3kzVH+3fzjfuk3PUdMHTbBUv5yuwZA8hANv
# 90Oz4imfKc63hkeep3FLzxwFylS9nOs2Yjj3P1qnLCdZ7I/C4SnJAKLc9UW4SUmh
# ghNJMIITRQYKKwYBBAGCNwMDATGCEzUwghMxBgkqhkiG9w0BBwKgghMiMIITHgIB
# AzEPMA0GCWCGSAFlAwQCAQUAMIIBPAYLKoZIhvcNAQkQAQSgggErBIIBJzCCASMC
# AQEGCisGAQQBhFkKAwEwMTANBglghkgBZQMEAgEFAAQgec7nNyRG4WECDbCh+4fL
# LEanxO6XsKQKROvEGc/bn5MCBltjOJ+BhBgTMjAxODA4MDkyMDE2MDAuMzEyWjAH
# AgEBgAIB9KCBuKSBtTCBsjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0
# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
# dGlvbjEMMAoGA1UECxMDQU9DMScwJQYDVQQLEx5uQ2lwaGVyIERTRSBFU046MTJC
# NC0yRDVGLTg3RDQxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZp
# Y2Wggg7NMIIGcTCCBFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCB
# iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMp
# TWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAw
# NzAxMjEzNjU1WhcNMjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UE
# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z
# b2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQ
# Q0EgMjAxMDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6
# IOz8E5f1+n9plGt0VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsa
# lR3OCROOfGEwWbEwRA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8k
# YDJYYEbyWEeGMoQedGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRn
# EnIaIYqvS2SJUGKxXf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWa
# yrGo8noqCjHw2k4GkbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURd
# XhacAQVPIk0CAwEAAaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQW
# BBTVYzpcijGQ80N7fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMA
# QTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbL
# j+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1p
# Y3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0w
# Ni0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIz
# LmNydDCBoAYDVR0gAQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUH
# AgEWMWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVs
# dC5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkA
# XwBTAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN
# 4sbgmD+BcQM9naOhIW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj
# +bzta1RXCCtRgkQS+7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/
# xn/yN31aPxzymXlKkVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaP
# WSm8tv0E4XCfMkon/VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jns
# GUpxY517IW3DnKOiPPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWA
# myI4ILUl5WTs9/S/fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99
# g/DhO3EJ3110mCIIYdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mB
# y6cJrDm77MbL2IK0cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4S
# KfXAL1QnIffIrE7aKLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYov
# G8chr1m1rtxEPJdQcdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7v
# DaBQNdrvCScc1bN+NR4Iuto229Nfj950iEkSMIIE2TCCA8GgAwIBAgITMwAAAKdk
# XcUoDwE9RAAAAAAApzANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEG
# A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj
# cm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFt
# cCBQQ0EgMjAxMDAeFw0xNjA5MDcxNzU2NTJaFw0xODA5MDcxNzU2NTJaMIGyMQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMQwwCgYDVQQLEwNBT0Mx
# JzAlBgNVBAsTHm5DaXBoZXIgRFNFIEVTTjoxMkI0LTJENUYtODdENDElMCMGA1UE
# AxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCCASIwDQYJKoZIhvcNAQEB
# BQADggEPADCCAQoCggEBAKbqGlDAX0/SU/mh7AR8DdcgVzEhUdjH7gMKLnjOiy4i
# I2uWNhAfClF6heddFP5zstfSfGNVwQq4xpQ06LiJ2RecCbEYoZE/Q1ZVVtlbxU/W
# g37k6BCIG0x6G5Ci1LUC09H4v5vO/zVq1S6OTJNGerN9TlLQGBHYzJfMTJTkcotQ
# /OrSUgkQOIMRuJYDMThcQY06KYcRWBOjDq19rd765m7K0Lihc22+tamlZ0lrB3x5
# iLRmNvIxJzgU4jUfwSddeMjiKjJrZ4FGUwXWe0M1akQw7bsh7TdJf7JECZvaWcAr
# PpL4DsAmSxeK7wjTVOeMC4+KjVL2oHmoT2YGMrmLKvECAwEAAaOCARswggEXMB0G
# A1UdDgQWBBR8DUoLfftP/14QLD15byBNQlCIXTAfBgNVHSMEGDAWgBTVYzpcijGQ
# 80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jv
# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNUaW1TdGFQQ0FfMjAxMC0wNy0w
# MS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0YVBDQV8yMDEwLTA3LTAxLmNy
# dDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0GCSqGSIb3DQEB
# CwUAA4IBAQCaxiRL55eyUgFkhw/iQpMoVnl29PkKFWdNeg6JgDw/M0LejnLyXKbz
# eLO5sIXSjyXt24q94Sx9VmAEEs8n82N4+EZUVEoW2mvqXLv7hnpsXn9Nc2KgNp2w
# 3rPpzxJijPrwnGwkDGuILpGXvt9OMoxcbieEJ3mbncvf6OwRCwAJXHovaiX20vY6
# KodtR3bMVa9OtvHVehqz+ZwdCEgo/XH/XgebJUKwo/gL/WHSX7p06GHV8+LgRL8p
# DeOK8G1djFri5Q4NgQfRK47SeVCZrQGiyzUBur4AQoMjcbYdYIeqjFv+e+1whg4I
# cbVXhS+UECNttB+8o0a35RZLrvCutrpOoYIDdzCCAl8CAQEwgeKhgbikgbUwgbIx
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xDDAKBgNVBAsTA0FP
# QzEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNOOjEyQjQtMkQ1Ri04N0Q0MSUwIwYD
# VQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiUKAQEwCQYFKw4DAhoF
# AAMVAOSCLp0yNm10ADIPHsEhtA3rDHfZoIHBMIG+pIG7MIG4MQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMQwwCgYDVQQLEwNBT0MxJzAlBgNVBAsT
# Hm5DaXBoZXIgTlRTIEVTTjoyNjY1LTRDM0YtQzVERTErMCkGA1UEAxMiTWljcm9z
# b2Z0IFRpbWUgU291cmNlIE1hc3RlciBDbG9jazANBgkqhkiG9w0BAQUFAAIFAN8W
# hU8wIhgPMjAxODA4MDkwOTE4MDdaGA8yMDE4MDgxMDA5MTgwN1owdzA9BgorBgEE
# AYRZCgQBMS8wLTAKAgUA3xaFTwIBADAKAgEAAgJAaQIB/zAHAgEAAgIZZjAKAgUA
# 3xfWzwIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMBoAowCAIBAAID
# FuNgoQowCAIBAAIDB6EgMA0GCSqGSIb3DQEBBQUAA4IBAQCdbcMkRb1GC1I/6Tjs
# VTtAo54wL3MDL4WbAndfaLqAkUFCyeYwtaDv1GjkTGC9BUsRQW+oHqJfELZfe2Ap
# CRzeL7LOj8u7ZT6/EeWx/dXJcSc2/BtudkJivLq//Oa0iSGvDxSv1h8j5iDI1I2p
# MEHgMlPpdmRmlDTPRZA4d7Nh6a87xZpsMrFzupvGLkVvrJn74XR6gFSI84yLm9vz
# 7VTFpUDO27Ox3oonyiNv8J81lTU5VHbw6vEKkacXNOnTIBixTsrSNYrTXKCXiiBS
# 3AQLE5UKwyXpw23Y1eR7j34XM70yhsyeUESxWffCoEnlid1V2Is+eue/HmMepD/u
# UpefMYIC9TCCAvECAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAC
# EzMAAACnZF3FKA8BPUQAAAAAAKcwDQYJYIZIAWUDBAIBBQCgggEyMBoGCSqGSIb3
# DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgvyLnNN3i4NUrqsXS
# rRpIwCPPyC9gT1XadbLqXRxvybkwgeIGCyqGSIb3DQEJEAIMMYHSMIHPMIHMMIGx
# BBTkgi6dMjZtdAAyDx7BIbQN6wx32TCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0
# YW1wIFBDQSAyMDEwAhMzAAAAp2RdxSgPAT1EAAAAAACnMBYEFMYQYeerrRVKJmU3
# apVO8otPBc8JMA0GCSqGSIb3DQEBCwUABIIBAIbTkf+808yjVI1WtaN0oBe02mV3
# iMZ3KVpuF8uhIHZ0VFIcFnCZaWbHTneufO2KOsymMrZUEtg1bFYSD2JKOYxpVGj5
# fcTiTaiEXb3cLKYeyonvKE1373oU0gA13V+yaMdmytqzQD5MM9z5laOuqpRN6x1j
# PhfpPjAeUIujNS+sJ09X0IYL+8BMLXX29ys63+eN0RWoXubqg032+7zOHS8SayHP
# afoBkpRKOUjg1izVWtq4kRTcXkclLzY06lEoTa8zSjY+9XHsCumwo0OUEUrTtVt6
# RMGHY2i7jGsK8qzlvG6dCKfRu7vnuTbelSUHQHm5yipA+zI69xtRhYk1YAU=
# SIG # End signature block