File "Microsoft.PowerShell.ODataAdapter.ps1"

Full Path: C:/Windows/System32/WindowsPowerShell/v1.0/Modules/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataAdapter.ps1
File size: 167.09 KB
MIME-type: text/plain
Charset: 8 bit

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





# Add .NET classes used by the module

Add-Type -TypeDefinition $global: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

}