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
}
