PSDesiredStateConfiguration.psm1

###########################################################
#
# 'PSDesiredStateConfiguration' logic module
#
###########################################################
data LocalizedData
{
    # culture="en-US"
    ConvertFrom-StringData -StringData @'
    CheckSumFileExists = File '{0}' already exists. Please specify -Force parameter to overwrite existing checksum files.
    CreateChecksumFile = Create checksum file '{0}'
    OverwriteChecksumFile = Overwrite checksum file '{0}'
    OutpathConflict = (ERROR) Cannot create directory '{0}'. A file exists with the same name.
    InvalidConfigPath = (ERROR) Invalid configuration path '{0}' specified.
    InvalidOutpath = (ERROR) Invalid OutPath '{0}' specified.
    InvalidConfigurationName = Invalid Configuration Name '{0}' is specified. Standard names may only contain letters (a-z, A-Z), numbers (0-9), and underscore (_). The name may not be null or empty, and should start with a letter.
    InvalidResourceSpecification = Found more than one resource named '{0}'. Please use the module specification to be more specific.
    UnsupportedResourceImplementation = The resource '{0}' implemented as '{1}' is not supported by Invoke-DscResource.
    NoValidConfigFileFound = No valid config files (mof,zip) were found.
    InputFileNotExist=File {0} doesn't exist.
    FileReadError=Error Reading file {0}.
    MatchingFileNotFound=No matching file found.
    CertificateFileReadError=Error Reading certificate file {0}.
    CertificateStoreReadError=Error Reading certificate store for {0}.
    CannotCreateOutputPath=Invalid Configuration name and output path combination :{0}. Please make sure output parameter is a valid path segment.
    ConflictingDuplicateResource=A conflict was detected between resources '{0}' and '{1}' in node '{2}'. Resources have identical key properties but there are differences in the following non-key properties: '{3}'. Values '{4}' don't match values '{5}'. Please update these property values so that they are identical in both cases.
    ConfiguratonDataNeedAllNodes=ConfigurationData parameter need to have property AllNodes.
    ConfiguratonDataAllNodesNeedHashtable=ConfigurationData parameter property AllNodes needs to be a collection.
    AllNodeNeedToBeHashtable=all elements of AllNodes need to be hashtable and has a property 'NodeName'.
    DuplicatedNodeInConfigurationData=There are duplicated NodeNames '{0}' in the configurationData passed in.
    EncryptedToPlaintextNotAllowed=Converting and storing encrypted passwords as plain text is not recommended. For more information on securing credentials in MOF file, please refer to MSDN blog: http://go.microsoft.com/fwlink/?LinkId=393729
    DomainCredentialNotAllowed=It is not recommended to use domain credential for node '{0}'. In order to suppress the warning, you can add a property named 'PSDscAllowDomainUser' with a value of $true to your DSC configuration data for node '{0}'.
    NestedNodeNotAllowed=Defining node '{0}' inside the current node '{1}' is not allowed since node definitions cannot be nested. Please move the definition for node '{0}' to the top level of the configuration '{2}'.
    FailToProcessNode=An exception was raised while processing Node '{0}': {1}
    LocalHostNodeNotAllowed=Defining a 'localhost' node in the configuration '{0}' is not allowed since the configuration already contains one or more resource definitions that are not associated with any nodes.
    InvalidMOFDefinition=Invalid MOF definition for node '{0}': {1}
    RequiredResourceNotFound=Resource '{0}' required by '{1}' does not exist. Please ensure that the required resource exists and the name is properly formed.
    ReferencedManagerNotFound=Download Manager '{0}' referenced by '{1}' does not exist. Please ensure that the referenced download manager exists and the name is properly formed.
    ReferencedResourceSourceNotFound=Resource Repository '{0}' referenced by '{1}' does not exist. Please ensure that the referenced resource repository exists and the name is properly formed.
    DependsOnLinkTooDeep=DependsOn link exceeded max depth limitation '{0}'.
    DependsOnLoopDetected=Circular DependsOn exists '{0}'. Please make sure there are no circular reference.
    FailToProcessConfiguration=Errors occurred while processing configuration '{0}'.
    FailToProcessProperty={0} error processing property '{1}' OF TYPE '{2}': {3}
    NodeNameIsRequired=Node processing is skipped since the node name is empty.
    ConvertValueToPropertyFailed=Cannot convert '{0}' to type '{1}' for property '{2}' in resource '{3}'.
    ResourceNotFound=The term '{0}' is not recognized as the name of a {1}.
    GetDscResourceInputName=The Get-DscResource input '{0}' parameter value is '{1}'.
    ResourceNotMatched=Skipping resource '{0}' as it does not match the requested name.
    InitializingClassCache=Initializing class cache
    LoadingDefaultCimKeywords=Loading default CIM keywords
    GettingModuleList=Getting module list
    CreatingResourceList=Creating resource list
    CreatingResource=Creating resource '{0}'.
    SchemaFileForResource=Schema file name for resource {0}
    UnsupportedReservedKeyword=The '{0}' keyword is not supported in this version of the language.
    UnsupportedReservedProperty=The '{0}' property is not supported in this version of the language.
    MetaConfigurationHasMoreThanOneLocalConfigurationManager=The meta configuration for node '{0}' contain more than one definitions for LocalConfigurationManager which is not allowed.
    MetaConfigurationSettingsMissing=The settings definition for node '{0}' is missing. A default empty settings definition is added for the node.
    ConflictInExclusiveResources=The partial configuration '{0}' and '{1}' have coflicting exclusive resource declarations.
    ReferencedModuleNotExist=The referenced module '{0}' does not exist on the machine. Please use Get-DscResource to find out what exists on the machine.
    ReferencedResourceNotExist=The referenced resource '{0}' does not exist on the machine. Please use Get-DscResource to find out what exists on the machine.
    ReferencedModuleResourceNotExist=The referenced module\resource '{0}' does not exist on the machine. Please use Get-DscResource to find out what exists on the machine.
    DuplicatedResourceInModules=The referenced resource '{0}' exists in module {1} and module {2} on the machine. Please make sure it exists in only one module.
    CannotConvertStringToBool=Cannot convert value "System.String" to type "System.Boolean". Boolean parameters accept only Boolean values and numbers, such as $True, $False, 1 or 0.
    NoModulesPresent=There are no modules present in the system with the given module specification.
    ImportDscResourceWarningForInbuiltResource=The configuration '{0}' is loading one or more built-in resources without explicitly importing associated modules. Add Import-DscResource -ModuleName 'PSDesiredStateConfiguration' to your configuration to avoid this message.
    PasswordTooLong=An error occurred during encryption of a password in node '{0}'. Most likely the password entered is too long to be encrypted using the selected certificate. Please either use a shorter password or select a certificate with a larger key.
    PsDscRunAsCredentialNotSupport=The 'PsDscRunAsCredential' property is not currently support when using Invoke-DscResource.
'@

}
Set-StrictMode -Off

# In case localized resource is not available we revert back to English as defined in LocalizedData section so ignore the error instead of showing it to user.
Import-LocalizedData  -BindingVariable LocalizedData -FileName PSDesiredStateConfiguration.Resource.psd1 -ErrorAction Ignore

Import-Module $PSScriptRoot/helpers/DscResourceInfo.psm1

# Set DSC HOME environment variable.
$env:DSC_HOME = "$PSScriptRoot/Configuration"

$script:V1MetaConfigPropertyList = @('ConfigurationModeFrequencyMins', 'RebootNodeIfNeeded', 'ConfigurationMode', 'ActionAfterReboot', 'RefreshMode', 'CertificateID', 'ConfigurationID', 'DownloadManagerName', 'DownloadManagerCustomData', 'RefreshFrequencyMins', 'AllowModuleOverwrite', 'DebugMode', 'Credential')
$script:DirectAccessMetaConfigPropertyList = @('AllowModuleOverWrite', 'CertificateID', 'ConfigurationDownloadManagers', 'ResourceModuleManagers', 'DebugMode', 'RebootNodeIfNeeded', 'RefreshMode', 'ConfigurationAgent')

#################################################################
# Code to determin what version related info is needed
# and fixup the configuration document is it is already generated
#################################################################
function Generate-VersionInfo
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Scope="Function", Target="*")]
    param(
        [Parameter(Mandatory)]
        $KeywordData,
        [Parameter(Mandatory)]
        [Hashtable]
        $Value
    )

    $SystemProperties = @('ResourceID', 'SourceInfo', 'ModuleName', 'ModuleVersion')
    $HasAdditionalProperty = $false
    foreach ($key in $KeywordData.Keys)
    {
        if (($Value.Contains($key)) -and ($script:V1MetaConfigPropertyList -notcontains $key) -and ($SystemProperties -notcontains $key))
        {
            $HasAdditionalProperty = $true
            break
        }
    }

    if($HasAdditionalProperty)
    {
        Set-PSMetaConfigVersionInfoV2
    }
    else
    {
        $script:PSMetaConfigDocumentInstVersionInfo['MinimumCompatibleVersion'] = ($script:PSMetaConfigDocumentInstVersionInfo['MinimumCompatibleVersion'], "1.0.0" | Measure-Object -Maximum).Maximum
    }

    $script:PSMetaConfigDocumentInstVersionInfo['CompatibleVersionAdditionalProperties'] = @('MSFT_DSCMetaConfiguration:StatusRetentionTimeInDays')
    $script:PSMetaConfigurationProcessed = $true
}

# indicate whether meta configuration is processed before document instance
function Set-PSMetaConfigDocInsProcessedBeforeMeta
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Scope="Function")]
    param()
    $Script:PSMetaConfigDocInsProcessedBeforeMeta = $true
}

function Get-PSMetaConfigurationProcessed
{
    return $script:PSMetaConfigurationProcessed
}

function Get-PSMetaConfigDocumentInstVersionInfo
{
    return $script:PSMetaConfigDocumentInstVersionInfo
}

function Set-PSMetaConfigVersionInfoV2
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Scope="Function")]
    param()
    $script:PSMetaConfigDocumentInstVersionInfo['MinimumCompatibleVersion'] = '2.0.0'
    if($Script:PSMetaConfigDocInsProcessedBeforeMeta) #fixup configuration document instance version info
    {
        [string]$data = Get-MofInstanceText '$OMI_ConfigurationDocument1ref'
        $Script:NoNameNodeInstanceAliases['$OMI_ConfigurationDocument1ref'] = $data -replace 'MinimumCompatibleVersion = "1.0.0"', 'MinimumCompatibleVersion = "2.0.0"'
        Set-PSDefaultConfigurationDocument $Script:NoNameNodeInstanceAliases['$OMI_ConfigurationDocument1ref']
    }
}

function Get-CompatibleVersionAddtionaPropertiesStr
{
    '{'
    if($script:PSMetaConfigDocumentInstVersionInfo['CompatibleVersionAdditionalProperties'])
    {
        $len = @($script:PSMetaConfigDocumentInstVersionInfo['CompatibleVersionAdditionalProperties']).Length
        foreach ($e in @($script:PSMetaConfigDocumentInstVersionInfo['CompatibleVersionAdditionalProperties']))
        {
                "`"$e`"" + $(if (--$len -gt 0)
                {
                    ', '
                }
                else
                {
                    ''
                }
            )
        }
    }
    '}'
}

###########################################################
# The MOF generation code
###########################################################

#
# This scriptblock takes a type name and a list of properties and produces
# the MOF source text to define an instance of that type
#
function ConvertTo-MOFInstance
{
    param (
        [Parameter(Mandatory)]
        [string]
        $Type,
        [Parameter(Mandatory)]
        [AllowNull()]
        [hashtable]
        $Properties
    )


    # remove ModuleVersion, this will be handled during final validation.
    if($properties.ContainsKey('ModuleName') -and  $properties['ModuleName'] -ieq 'PsDesiredStateConfiguration')
    {
        $script:PsDscModuleVersion = $properties['ModuleVersion']
        $properties.Remove('ModuleVersion')
    }
    if($properties.ContainsKey('PsDscRunAsCredential'))
    {
        $script:PsDscCompatibleVersion = "2.0.0"
    }
    # Look up the property definitions for this keyword.
    $PropertyTypes = [System.Management.Automation.Language.DynamicKeyword]::GetKeyword($Type).Properties

    # and the CIM type name to use since the keyword might be an alias.
    $ResourceName = [System.Management.Automation.Language.DynamicKeyword]::GetKeyword($Type).ResourceName

    if($script:IsMetaConfig -and ($ResourceName -eq 'MSFT_DSCMetaConfigurationV2'))
    {
        Generate-VersionInfo $PropertyTypes $Properties
    }

    #
    # Function to convert .NET datetime object to MOF datetime string format
    # We're not using [System.Management.ManagementDateTimeConverter]::ToDmtfDateTime()
    # because it has known bugs which are not going to be fixed.
    #
    function ConvertTo-MofDateTimeString ([datetime] $d)
    {
        $utcOffset = ($d -$d.ToUniversalTime()).TotalMinutes
        $utcOffsetString = if ($utcOffset -ge 0)
        {
            '+'
        }
        else
        {
            '-'
        }
        $utcOffsetString += ([System.Math]::Abs(($utcOffset)).ToString().PadLeft(3,'0'))
        '{0}{1}' -f
        $d.ToString('yyyyMMddHHmmss.ffffff'),
        $utcOffsetString
    }

    #
    # Utility routine to find if username specified is
    # a domain user.
    #

    function IsDomainUser()
    {
        param(
            [Parameter(Mandatory)]
            [string]
            $UserName
        )
        # if username contains '\' example: domain\username or '@' example: username@mydomain.com
        # it may not be a local user.
        if( -not( $UserName.Contains('\') -or $UserName.Contains('@')))
        {
            # it is a local user
            return $false
        }
        elseif( $UserName.Contains('@'))
        {
            return $true
        }
        else
        {
            # In case of '\', domain name can be local machine.
            $result = $UserName.Split('\')
            if( $result.Count -ge 2)
            {
                $domain = $result[0]
                $localMachineNames = @("localhost","127.0.0.1","::1")
                $isDomainUser = $true
                if( $localMachineNames -icontains $domain)
                {
                    $isDomainUser = $false
                }
                return $isDomainUser
            }
        }
        $true
    }

    #
    # Utility routine to render a property
    # as a string in MOF syntax.
    #
    function stringify ($Value, $asArray = $false, $targetType = [string])
    {
        $result = if ($Value -is [array] -or $asArray)
        {
            '{'
            $len = @($Value).Length
            foreach ($e in $Value)
            {
                ' ' + (stringify $e -targetType $targetType) +
                $(if (--$len -gt 0)
                    {
                        ','
                    }
                    else
                    {
                        ''
                    }
                )
            }
            '}'
        }
        elseif ($Value -is [PSCredential] )
        {
            # If the input object is a PSCredential, turn it into an MSFT_Credential with an encrypted password.
            $clearText = [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::GetStringFromSecureString($Value.Password)
            $newValue = @{
                UserName = $Value.UserName
                Password = $clearText
            }
            # Recurse to build the object.
            ConvertTo-MOFInstance MSFT_Credential $newValue
        }
        elseif ($Value -is [System.Collections.Hashtable])
        {
            # Collect the individual strings
            $elementsAsStrings = foreach ($p in $Value.GetEnumerator())
            {
                ConvertTo-MOFInstance MSFT_KeyValuePair @{
                    Key   = $p.Key
                    Value = $p.Value
                }
            }
            # Produce a single formatted string.
            ' ' + ($elementsAsStrings -join ",`n ") + "`n"
        }
        elseif ($Value -is [ScriptBlock] )
        {
            # Find all $using: variables used in the script and replace them with normal variables
            $scriptText = "$Value"
            # Need to create a new scriptblock so the extent offsets are correct
            $scriptAst = [scriptblock]::Create($scriptText).Ast
            # get the $using: variable asts into an array
            $variables = $scriptAst.FindAll({
                    param ($ast)
                    $ast.GetType().FullName -match 'VariableExpressionAst' -and
                    $ast.Extent.Text -match '^\$using:'
                }
            , $true).ToArray()

            # do the substitutions in reverse order
            [Array]::Reverse($variables)
            $variables | ForEach-Object -Process {
                $start = $_.Extent.StartOffset
                $length = $_.Extent.EndOffset - $start
                $newName = '$' + $_.VariablePath.UserPath
                $scriptText = $scriptText.Remove($start, $length).Insert($start, $newName)
            }

            $completeScript = ''
            # generate assignement statements to set the variable values on the other side
            # using serialized values passed from the local environment
            $varNames = @($variables).VariablePath.UserPath | Sort-Object -Unique
            foreach ($v in $varNames)
            {
                # If the ScriptBlock was defined in a module, then use the module for lookups
                if ($Value.Module)
                {
                    $var = $Value.Module.SessionState.PSVariable.Get($v)
                }
                else
                {
                    # Otherwise look up in the callers context
                    $var = $ExecutionContext.SessionState.Module.GetVariableFromCallersModule($v)
                }

                if ($var)
                {
                    $varValue = $var.Value
                    # Skip null values but preserve empty arrays and strings for type propigation
                    if ($null -ne $varValue)
                    {
                        # Pass strings quoted; amn explicit type check is needed because -is recognizes too many things as strings
                        if ($varValue -is [string])
                        {
                            $completeScript += "`$$v ='" + ($varValue -replace "'", "''") + "'`n"
                        }
                        else
                        {
                            # Serialize everything else
                            $serializedValue = [System.Management.Automation.PSSerializer]::Serialize($varValue) -replace "'", "''"
                            $completeScript += "`$$v = [System.Management.Automation.PSSerializer]::Deserialize('$serializedValue')`n"
                        }
                    }
                }
            }

            # Merge in the actual scriptblock body
            $completeScript += $scriptText

            # Quote the string so it's suitable to embed in the MOF file...
            '"' + ($completeScript -replace '\\', '\\' -replace "[`r]*`n", '\n'  -replace '"', '\"') + '"'
        }
        elseif ($targetType -eq [datetime])
        {
            # If the target is a datetime, convert the argument to a datetime and then render that
            # as a DMTF datetime...
            '"' + (ConvertTo-MofDateTimeString $Value) + '"'
        }
        elseif ($targetType -eq [double])
        {
            # MOF syntax requires reals to always have a decimal point so add
            # so add one if the string representation does contain one.
            [string] $dblAsString = [double] $Value
            if ( -not $dblAsString.Contains('.') )
            {
                $dblAsString += '.0'
            }
            $dblAsString
        }
        elseif ($targetType -eq [char])
        {
            # A char16 is encode as a single quoted character
            "'$Value'"
        }
        elseif ($targetType -eq [int64])
        {
            [int64] $Value
        }
        elseif ($targetType -eq [uint64])
        {
            [uint64] $Value
        }
        elseif ($targetType -eq [bool])
        {
            if($Value -is [string])
            {
                $errorMessage = $LocalizedData.CannotConvertStringToBool
                ThrowError -ExceptionName 'System.ArgumentException' -ExceptionMessage $errorMessage -ExceptionObject $Value -ErrorId 'CannotConvertStringToBool' -ErrorCategory InvalidArgument
            }
            else
            {
                [bool]$Value
            }
        }
        elseif ($targetType -ne [string])
        {
            # Cast the value to the target type...
            $Value -as $targetType
        }
        elseif ($Value -is [string] -and -not $InstanceAliases[$Value] )
        {
            '"' + ($Value -replace '\\', '\\' -replace "`r?`n", '\n'  -replace '"', '\"') + '"'
        }
        elseif ($null -eq $Value)
        {
            'NULL'
        }
        elseif (($targetType -eq [string]) -and  ($Value -isnot [string]))
        {
            # Cast value to string if it is not already a string, this is for covering cases like when a user assign an integer while the
            # CIM property type is string
            '"' + ($Value -replace '\\', '\\' -replace "`r?`n", '\n'  -replace '"', '\"') + '"'
        }
        else
        {
            $Value
        }

        $result -join "`n"
    }

    Write-Debug -Message " BEGIN MOF GENERATION FOR $Type"

    # Generate the MOF instance alias to use for the current node
    if ( (Get-PSCurrentConfigurationNode) )
    {
        if($null -eq $Script:NodeTypeRefCount[ (Get-PSCurrentConfigurationNode) ])
        {
            $Script:NodeTypeRefCount[ (Get-PSCurrentConfigurationNode) ] =
            New-Object -TypeName 'System.Collections.Generic.Dictionary[string, System.Collections.Generic.Dictionary[string,int]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
        }

        $MofAliasString = '$' + $ResourceName + ++$Script:NodeTypeRefCount[ (Get-PSCurrentConfigurationNode) ][$ResourceName] + 'ref'
        $InstanceAliases = $Script:NodeInstanceAliases[ (Get-PSCurrentConfigurationNode) ]
    }
    else # Generate the MOF instance alias to use for the default (unnamed) node.
    {
        $MofAliasString = '$' + $ResourceName + ++$Script:NoNameNodeTypeRefCount[$ResourceName] + 'ref'
        $InstanceAliases = $Script:NoNameNodeInstanceAliases
    }

    # Start generating the MOF source text for this instance
    $result = "instance of $ResourceName as $MofAliasString`n{`n"

    #special case psdesiredstateConfiguration module. Insert '0.0' as module if user hasn't explicilty asked for the version
    if( $Properties.ContainsKey("ModuleName") -and ($Properties["ModuleName"] -ieq 'PsDesiredStateConfiguration'))
    {
        if(-not $Script:ExplicitlyImportedModules.ContainsKey('PsDesiredStateConfiguration'))
        {
            $script:ShowImportDscResourceWarning = $true
        }
    }

    # generate the property definitions
    $oldOFS = $OFS
    $OFS = ' '
    $result += try
    {
        if ($Properties -and $Properties.Count)
        {
            foreach ($p in $Properties.GetEnumerator())
            {
                Write-Debug -Message " Generating property data for '$($p.Name)' = '$($p.Value)'"
                $targetTypeName = $PropertyTypes[$p.Name].TypeConstraint

                # see if the target type is an array
                $asArray = $p.Name -eq 'DependsOn' -or $targetTypeName -match 'Array'

                # Convert the CIM typename to the appropriate .NET type to use
                # to convert the input object into an appropriately encoded string
                # using the PowerShell type conversion semantics.
                switch -regex ($targetTypeName)
                {
                    # unsigned integer types
                    '^sint[0-9]{1,2}'
                    {
                        $targetType = [int64]
                        break
                    }
                    # Single 16 bit character (note - this type is deprecated and removed in MOFv3
                    '^char16'
                    {
                        $targetType = [char]
                        break
                    }
                    # signed integer types
                    '^uint[0-9]{0,2}'
                    {
                        $targetType = [uint64]
                        break
                    }
                    # reals
                    '^real32|^real64'
                    {
                        $targetType = [double]
                        break
                    }
                    # boolean
                    '^boolean'
                    {
                        $targetType = [bool]
                    }
                    # datetime
                    'datetime'
                    {
                        $targetType = [datetime]
                    }
                    # everything else render directly as a string...
                    default
                    {
                        $targetType = [string]
                    }
                }

                #
                # If the scalar target types is a credential, then we need to encrypt the Password property value
                # before generating the MOF text
                #
                if(($Type -match 'MSFT_Credential') -and $p.Name -match 'Password')
                {
                    # For MSFT_Credential we'll have a password that may need to be encrypted depending
                    # on the availability of a key. This may need to change to the base class of MSFT_WindowCredential
                    # if we're using the class to refer to non-Windows machines where a Domain may be irrelevant.

                    $p.Name + ' = ' + (stringify -value (Get-EncryptedPassword $p.Value) -asArray $asArray -targetType  $targetType ) + ";`n"
                }
                else
                {
                    #embeded instances cannot be null
                    if($null -eq $p.Value -and $PropertyTypes[$p.Name].TypeConstraint -eq 'Instance')
                    {
                        $errorMessage = $LocalizedData.ConvertValueToPropertyFailed -f @('$null', $Type, $p.Name, $ResourceName)
                        $errorMessage += Get-PositionInfo $Properties['SourceInfo']
                        $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
                        Write-Error -Exception $exception -Message $errorMessage  -Category InvalidArgument -ErrorId FailToProcessProperty
                        Update-ConfigurationErrorCount
                    }
                    if($p.Value -is [PSCredential])
                    {
                        [bool] $PSDscAllowPlainTextPassword = $false
                        [bool] $PSDscAllowDomainUser = $false
                        [bool] $PSDscDomainUser = IsDomainUser -username $p.Value.UserName


                        if($Node -and $selectedNodesData)
                        {
                            if($selectedNodesData -is [array])
                            {
                                foreach($target in $selectedNodesData)
                                {
                                    if($target['NodeName'] -and $target['NodeName'] -eq $Node)
                                    {
                                        $currentNode = $target
                                    }
                                }
                            }
                            else
                            {
                                $currentNode = $selectedNodesData
                            }
                        }
                        # where user need to specify properties for resources not in a node,
                        # they can do it through localhost nodeName in $allNodes
                        elseif($allnodes -and $allnodes.AllNodes)
                        {
                            foreach($target in $allnodes.AllNodes)
                            {
                                if($target['NodeName'] -and $target['NodeName'] -eq 'localhost')
                                {
                                    $currentNode = $target
                                }
                            }
                        }

                        if($currentNode)
                        {
                            # PSDscAllowDomainUser set to true would indicate that we want to allow
                            # domain credential. It takes precedence over PSDscAllowPlainTextPassword behavior
                            if($currentNode['PSDscAllowDomainUser'])
                            {
                                $PSDscAllowDomainUser = $currentNode['PSDscAllowDomainUser']
                            }
                            if($PSDscDomainUser -and (-not $PSDscAllowDomainUser))
                            {
                                if($null -eq $Script:NodeUsingDomainCred)
                                {
                                    $Script:NodeUsingDomainCred = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,bool]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
                                }
                                $Script:NodeUsingDomainCred[$currentNode['NodeName']] = $true
                            }
                            # PSDscAllowPlainTextPassword set to true would indicate that we want to allow the
                            # automatic conversion of the encrypted password to plaintext if a certificate or
                            # certificate id is not specified.
                            # if a certid or cert file is specified, it taks precedence over PSDscAllowPlainTextPassword
                            if($currentNode['PSDscAllowPlainTextPassword'])
                            {
                                $PSDscAllowPlainTextPassword = $currentNode['PSDscAllowPlainTextPassword']
                            }

                            $certificateid = $currentNode['CertificateID']

                            if ( -not $certificateid)
                            {
                                # CertificateFile is the public key file
                                $certificatefile = $currentNode['CertificateFile']

                                if (( -not $certificatefile) -and (-not $PSDscAllowPlainTextPassword))
                                {
                                    $errorMessage = $($LocalizedData.EncryptedToPlaintextNotAllowed)
                                    ThrowError -ExceptionName 'System.InvalidOperationException' -ExceptionMessage $errorMessage -ExceptionObject $p -ErrorId 'InvalidPathSpecified' -ErrorCategory InvalidOperation
                                }
                            }

                            if($currentNode['NodeName'] -and ($certificatefile -or $certificateid))
                            {
                                $Script:NodesPasswordEncrypted[$currentNode['NodeName']] = $true
                            }

                            $p.Name + ' = ' + (stringify -value $p.Value -asArray $asArray -targetType  $targetType ) + ";`n"
                        }
                        else
                        {
                            $errorMessage = $($LocalizedData.EncryptedToPlaintextNotAllowed)
                            ThrowError -ExceptionName 'System.InvalidOperationException' -ExceptionMessage $errorMessage -ExceptionObject $p -ErrorId 'InvalidPathSpecified' -ErrorCategory InvalidOperation
                        }
                    }
                    else
                    {
                        if ($targetTypeName -notmatch 'Array' -or $p.Value.Count)
                        {
                            $p.Name + ' = ' + (stringify -value $p.Value -asArray $asArray -targetType  $targetType ) + ";`n"
                        }
                    }
                }
            }
        }
        else
        {
            if ($Type -notmatch 'OMI_ConfigurationDocument')
            {
                "// This instance definition of $Type had no properties`n"
            }
            Write-Debug -Message " ENCOUNTERED INSTANCE OF TYPE '$Type' WITH NO PROPERTIES"
        }
    }
    catch
    {
        $errorMessage = $_.Exception.Message + (Get-PositionInfo $Properties['SourceInfo'])
        $errorMessage = $LocalizedData.FailToProcessProperty -f @($_.Exception.GetType().FullName, $p.Name, $Type, $errorMessage)
        $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
        Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId FailToProcessProperty
        Update-ConfigurationErrorCount
    }
    finally
    {
        $OFS = $oldOFS
    }

    #
    # Add extra information about Author, GenerationHost, GenerationDate and Name if they are not specified
    #
    if ($Type -match 'OMI_ConfigurationDocument' -and $Properties)
    {
        if (-not $Properties.ContainsKey('Author'))
        {
            $result += " Author = `"$([system.environment]::UserName)`";`n"
        }

        if (-not $Properties.ContainsKey('GenerationDate'))
        {
            $result += " GenerationDate = `"$(Get-Date)`";`n"
        }

        if (-not $Properties.ContainsKey('GenerationHost'))
        {
            $result += " GenerationHost = `"$([system.environment]::MachineName)`";`n"
        }

        # todo: report error is configuration name does't match
        if (-not $Properties.ContainsKey('Name'))
        {
            $result += " Name = `"$(Get-PSTopConfigurationName)`";`n"
        }
    }

    #
    # Append the completed mof instance text to the overall document
    #
    $instanceText = "`n" + $result + "`n};`n"

    #
    # Record and return the alias for that document
    #
    Write-Debug -Message " Added alias $MofAliasString to InstanceAliases array for node '$(Get-PSCurrentConfigurationNode)'"

    if ( Get-PSCurrentConfigurationNode )
    {
        if($null -eq $Script:NodeInstanceAliases[ (Get-PSCurrentConfigurationNode) ])
        {
            $Script:NodeInstanceAliases[ (Get-PSCurrentConfigurationNode) ] = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,string]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
            $Script:NodeResourceIdAliases[ (Get-PSCurrentConfigurationNode) ] = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,string]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
        }

        $Script:NodeInstanceAliases[ (Get-PSCurrentConfigurationNode) ][$MofAliasString] = $instanceText

        if($Properties.ContainsKey('ResourceID'))
        {
            $Script:NodeResourceIdAliases[ (Get-PSCurrentConfigurationNode) ][$Properties['ResourceID']] = $MofAliasString
        }
    }
    else
    {
        $Script:NoNameNodeInstanceAliases[$MofAliasString] = $instanceText

        if($Properties.ContainsKey('ResourceID'))
        {
            $Script:NoNameNodeResourceIdAliases[$Properties['ResourceID']] = $MofAliasString
        }
    }

    # todo: we can check error for duplicated alias in a node and report it here
    # because this can live acrose configurationelement calls
    Write-Debug -Message " MOF GENERATION COMPLETED FOR $Type"
    $MofAliasString
}

#
#
# Returns the MOF text for the instance that
# corresponds to the aliasId
#
function Get-MofInstanceText
{
    param (
        [Parameter(Mandatory)]
        [string]
        $aliasId
    )

    if ( Get-PSCurrentConfigurationNode )
    {
        $Script:NodeInstanceAliases[ (Get-PSCurrentConfigurationNode) ][$aliasId]
    }
    else
    {
        $Script:NoNameNodeInstanceAliases[$aliasId]
    }
}

#
# Get the position message based on the SourceMetadata
#
function Get-PositionInfo
{
    param(
        [string]
        $sourceMetadata
    )

    $positionMessage = ''
    if ($sourceMetadata)
    {
        $positionMessage = "`nAt"
        $infoItems = $sourceMetadata -split '::'

        # File name may be empty
        if ($infoItems[0])
        {
            $positionMessage += " $($infoItems[0]):$($infoItems[1])"
        }
        else
        {
            $positionMessage += " line:$($infoItems[1])"
        }

        $positionMessage += " char:$($infoItems[2])"
        $positionMessage += "`n+ $($infoItems[3])"
    }

    $positionMessage
}


###################################################################################
#
# A function that implements the 'Node' keyword logic. The Node keyword accumulates
# the resource instances to define for a specific node or nodes. It's passed into the
# configuration statement using the ScriptBlock.InvokeWithContext() method
#
function Node
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPositionalParameters", "", Scope="Function", Target="*")]
    [OutputType([void])]
    param (
        [Parameter(Mandatory)]
        $KeywordData,       # Not used in this function....
        [string[]]
        $Name,              # the list of nodes to process in this call
        [Parameter(Mandatory)]
        [ScriptBlock]
        $Value,             # The scriptblock that generates the configuration in each node.
        [Parameter(Mandatory)]
        $sourceMetadata     # Not used in this function
    )


    if (-not $Name)
    {
        Write-Debug -Message 'The name parameter was empty, no nodes generated'
        return
    }

    Write-Debug -Message "*PROCESSING STARTED FOR NODE SET {$(@($Name) -join ',')} "

    # Save any global level resources and initialize for the resources defined for this node.
    $Script:PSOuterConfigurationNodes.Push( (Get-PSCurrentConfigurationNode) )
    $OldNodeResources = $Script:NodeResources
    $OldNodeKeys = $Script:NodeKeys

    try
    {
        $OuterNode = $Script:PSOuterConfigurationNodes.Peek()
        if(( $OuterNode -eq [string]::Empty) -or
        (($OuterNode -ne [string]::Empty) -and ($Name -contains $OuterNode)))
        {
            #
            # Set up a map from node name to data
            #
            $nodeDataMap = @{}

            #
            # Copy the AllNodes data into the map
            #

            if($script:ConfigurationData)
            {
                $script:ConfigurationData.AllNodes | ForEach-Object -Process {
                    $nodeDataMap[$_.NodeName] = $_
                }
            }

            #
            # Create the SelectedNodes list for this Node statement
            #
            $selectedNodesData = foreach ($nn in $Name)
            {
                # If there is no data for this node, create a dummy node
                # with at least the node name
                if ( -not $nodeDataMap[$nn] )
                {
                    $nodeDataMap[$nn] = @{
                        NodeName = $nn
                    }
                }
                $nodeDataMap[$nn]
            }

            foreach ($Node in $Name)
            {
                if(($OuterNode -ne [string]::Empty) -and ($OuterNode -ne $Node))
                {
                    continue
                }

                Set-PSCurrentConfigurationNode $Node

                if( $Script:NodesInThisConfiguration[$Node] )
                {
                    $Script:NodeResources = $Script:NodesInThisConfiguration[$Node]
                }
                else
                {
                    $Script:NodesInThisConfiguration[$Node] = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,[string[]]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
                    $Script:NodeResources = $Script:NodesInThisConfiguration[$Node]
                }

                #this for tracking referenced configuration managers
                if( $Script:NodesManagerInThisConfiguration[$Node] )
                {
                    $Script:NodeManager = $Script:NodesManagerInThisConfiguration[$Node]
                }
                else
                {
                    $Script:NodesManagerInThisConfiguration[$Node] = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,string[]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
                    $Script:NodeManager = $Script:NodesManagerInThisConfiguration[$Node]
                }

                #this for tracking exclusive resources
                if( $Script:NodesExclusiveResourcesInThisConfiguration[$Node] )
                {
                    $Script:NodeExclusiveResources = $Script:NodesExclusiveResourcesInThisConfiguration[$Node]
                }
                else
                {
                    $Script:NodesExclusiveResourcesInThisConfiguration[$Node] = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,string[]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
                    $Script:NodeExclusiveResources = $Script:NodesExclusiveResourcesInThisConfiguration[$Node]
                }

                #this for tracking referenced Resource Module Source
                if( $Script:NodesResourceSourceInThisConfiguration[$Node] )
                {
                    $Script:NodeResourceSource = $Script:NodesResourceSourceInThisConfiguration[$Node]
                }
                else
                {
                    $Script:NodesResourceSourceInThisConfiguration[$Node] = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,string[]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
                    $Script:NodeResourceSource = $Script:NodesResourceSourceInThisConfiguration[$Node]
                }

                if(-not $Script:NodeTypeRefCount[$Node])
                {
                    $Script:NodeTypeRefCount[$Node] = New-Object -TypeName 'System.Collections.Generic.Dictionary[string, System.Collections.Generic.Dictionary[string,int]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
                    $Script:NodeInstanceAliases[$Node] = New-Object -TypeName 'System.Collections.Generic.Dictionary[string, System.Collections.Generic.Dictionary[string,string]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
                    $Script:NodeResourceIdAliases[$Node] = New-Object -TypeName 'System.Collections.Generic.Dictionary[string, System.Collections.Generic.Dictionary[string,string]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
                }

                if(-not $script:NodesKeysInThisConfiguration[$Node])
                {
                    $script:NodesKeysInThisConfiguration[$Node] = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,System.Collections.Generic.HashSet[string]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
                }
                $Script:NodeKeys = $script:NodesKeysInThisConfiguration[$Node]

                #
                # Set up the context variable list.
                #
                $variablesToDefine = @(
                    New-Object -TypeName PSVariable -ArgumentList ('SelectedNodes', $selectedNodesData)
                    New-Object -TypeName PSVariable -ArgumentList ('Node', $nodeDataMap[$Node])
                    New-Object -TypeName PSVariable -ArgumentList ('NodeName', $Node)
                )

                # Initialize dictionary to detect duplicate resources
                if ($null -eq $Script:DuplicateResources)
                {
                    $Script:DuplicateResources = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,System.Collections.Generic.Dictionary[string,System.Collections.Generic.List[System.Collections.Hashtable]]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
                }

                if (-not $Script:DuplicateResources.ContainsKey($Name))
                {
                    $Script:DuplicateResources[$Name] = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,System.Collections.Generic.List[System.Collections.Hashtable]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
                }

                try
                {
                    # Evaluate the node's business logic scriptblock
                    Write-Debug -Message "*$Node : NODE PROCESSING STARTED FOR THIS NODE."
                    $Value.InvokeWithContext($functionsToDefine, $variablesToDefine)
                }
                catch [System.Management.Automation.MethodInvocationException]
                {
                    # Write the unwrapped exception message
                    $pscmdlet.CommandRuntime.WriteError((Get-InnerMostErrorRecord $_))
                    Update-ConfigurationErrorCount
                }

                # Display warning if domain credential is used in the configuration.
                if($Script:NodeUsingDomainCred.ContainsKey($Node)  -and $Script:NodeUsingDomainCred[$Node])
                {
                    $nameMessage = $LocalizedData.DomainCredentialNotAllowed -f @($Node, $Node)
                    $UserWarningPreference = $WarningPreference
                    if($null -ne $ArgsToBody['WarningAction'])
                    {
                        $UserWarningPreference = $ArgsToBody['WarningAction']
                    }
                    Write-Warning -Message $nameMessage -WarningAction $UserWarningPreference
                }

                # Validate make sure all of the required resources are defined
                # if so, add the DependsOn fields for all resources
                ValidateNodeResources
                #
                # Fixup ModuleVersion
                #
                Update-ModuleVersion $Script:NodeResources $Script:NodeInstanceAliases[$Node] $Script:NodeResourceIdAliases[$Node]

                # Validate make sure all of the referenced download managers are defined
                ValidateNodeManager

                # Validate make sure all of the referenced resource source are defined
                ValidateNodeResourceSource

                # Validate make sure no conflict between exclusive resources
                # also validate exclusive resource exist
                ValidateNodeExclusiveResources

                # Validate make sure all of the required resources are defined
                ValidateNoCircleInNodeResources

                Write-Debug -Message "*$Node : NODE PROCESSING COMPLETED FOR THIS NODE. configuration errors encountered so far: $(Get-ConfigurationErrorCount)"
            }
        }
        else
        {
            $errorMessage = $LocalizedData.NestedNodeNotAllowed -f @(($Name -join ','), (Get-PSCurrentConfigurationNode), $Script:PSConfigurationName)
            $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
            Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId NestedNodeNotAllowed
            Update-ConfigurationErrorCount
        }
    }
    catch
    {
        $errorMessage = $_.Exception.Message + (Get-PositionInfo $sourceMetadata)
        $errorMessage = $LocalizedData.FailToProcessNode -f @($Node, $errorMessage)
        $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
        Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId FailToProcessNode
        Update-ConfigurationErrorCount
    }
    finally
    {
        Set-PSCurrentConfigurationNode  ($Script:PSOuterConfigurationNodes.Pop())
        $Script:NodeResources = $OldNodeResources
        $Script:NodeKeys = $OldNodeKeys
        Write-Debug -Message "*NODE STATEMENT PROCESSING COMPLETED. Configuration errors encountered so far: $(Get-ConfigurationErrorCount)"
    }
}

#
# Utility used to track the number of errors encountered while processing the configuration
#
function Update-ConfigurationErrorCount
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Scope="Function")]
    [OutputType([void])]
    param()

    $Script:PSConfigurationErrors++
}

function Get-ConfigurationErrorCount
{
    [OutputType([int])]
    param()

    $Script:PSConfigurationErrors
}

function Get-ComplexResourceQualifier
{
    [OutputType([string])]
    param()

   # walk the call stack to get at all of the enclosing configuration resource IDs
    $stackedConfigs = @(Get-PSCallStack |
        Where-Object -FilterScript { ($null -ne $_.InvocationInfo.MyCommand) -and ($_.InvocationInfo.MyCommand.CommandType -eq 'Configuration') })

    $complexResourceQualifier = $null
    # keep all but the top-most
    if(@($stackedConfigs).Length -ge 3)
    {
        $stackedConfigs = $stackedConfigs[1..(@($stackedConfigs).Length - 2)]
        # and build the complex resource ID suffix.
        $complexResourceQualifier = ( $stackedConfigs | foreach-Object -Process { '[' + $_.Command + ']' + $_.InvocationInfo.BoundParameters['InstanceName'] } ) -join '::'
    }

    return $complexResourceQualifier
 }

#
# A function to set the document text for default (unnamed) node
#
function Set-PSDefaultConfigurationDocument
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Scope="Function")]
    [OutputType([void])]
    param (
        [Parameter()]
        [string]
        $documentText = ''
    )

    $Script:PSDefaultConfigurationDocument = $documentText
}

#
# Get the text associated with the default (unnamed) node.
#
function Get-PSDefaultConfigurationDocument
{
    [OutputType([string])]
    param()

    return $Script:PSDefaultConfigurationDocument
}

##################################################
#
# Returns the name of the configuration currently being configured
#
function Get-PSTopConfigurationName
{
    [OutputType([string])]
    param()

    return $Script:PSTopConfigurationName
}

function Set-PSTopConfigurationName
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Scope="Function")]
    [OutputType([void])]
    param (
        [Parameter()]
        [string]
        $Name
    )

    $Script:PSTopConfigurationName = $Name
}

##################################################
#
# Returns the name of the node currently being configured
#
function Get-PSCurrentConfigurationNode
{
    [OutputType([string])]
    param()

    return $Script:PSCurrentConfigurationNode
}

function Set-PSCurrentConfigurationNode
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Scope="Function")]
    [OutputType([void])]
    param (
        [Parameter()]
        [string]
        $nodeName
    )

    $Script:PSCurrentConfigurationNode = $nodeName
}

#################################################

function Set-NodeResources
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Scope="Function")]
    [OutputType([void])]
    param (
        [Parameter(Mandatory)]
        [string]
        $resourceId,
        [Parameter(Mandatory)]
        [AllowNull()]
        [AllowEmptyCollection()]
        [string[]]
        $requiredResourceList
    )

    if ($null -eq $Script:NodeResources)
    {
        $Script:NodeResources = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,[string[]]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
    }
    $Script:NodeResources[$resourceId] = $requiredResourceList
}

function Test-NodeResources
{
    [OutputType([bool])]
    param (
        [Parameter(Mandatory)]

        [string]
        $resourceId
    )

    if ( -not $Script:NodeResources)
    {
        $false
    }
    else
    {
        $Script:NodeResources.ContainsKey($resourceId)
    }
}

#
# update a mapping of a partial configuration resource and the managers it referenced
#
function Set-NodeManager
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Scope="Function")]
    [OutputType([void])]
    param (
        [Parameter(Mandatory)]
        [string]
        $resourceId,
        [Parameter(Mandatory)]
        [AllowNull()]
        [string[]]
        $referencedManagers
    )

    if ($null -eq $Script:NodeManager)
    {
        $Script:NodeManager = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,string[]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
    }
    $Script:NodeManager[$resourceId] = $referencedManagers
}

#
#
#
function Test-NodeManager
{
    [OutputType([bool])]
    param (
        [Parameter(Mandatory)]

        [string]
        $resourceId
    )

    if ( -not $Script:NodeManager)
    {
        $false
    }
    else
    {
        $Script:NodeManager.ContainsKey($resourceId)
    }
}

#
# update a mapping of a partial configuration resource and the managers it referenced
#
function Set-NodeResourceSource
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Scope="Function")]
    [OutputType([void])]
    param (
        [Parameter(Mandatory)]
        [string]
        $resourceId,
        [Parameter(Mandatory)]
        [AllowNull()]
        [string[]]
        $referencedResourceSources
    )

    if ($null -eq $Script:NodeResourceSource)
    {
        $Script:NodeResourceSource = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,string[]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
    }
    $Script:NodeResourceSource[$resourceId] = $referencedResourceSources
}

#
#
#
function Test-NodeResourceSource
{
    [OutputType([bool])]
    param (
        [Parameter(Mandatory)]

        [string]
        $resourceId
    )

    if ( -not $Script:NodeResourceSource)
    {
        $false
    }
    else
    {
        $Script:NodeResourceSource.ContainsKey($resourceId)
    }
}

#
# update a mapping of a partial configuration resource and the managers it referenced
# resource format can be moduleName\* moduleName\resourceName and resourceName
# this is validated during mof generation/cananic stage
#
function Set-NodeExclusiveResources
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Scope="Function")]
    [OutputType([string[]])]
    param (
        [Parameter(Mandatory)]
        [string]
        $resourceId,
        [Parameter(Mandatory)]
        [AllowNull()]
        [string[]]
        $exclusiveResource
    )

    $Script:NodeExclusiveResources[$resourceId] = $exclusiveResource
    return $exclusiveResource
}


function Add-NodeKeys
{
    [OutputType([void])]
    param (
        [Parameter(Mandatory)]
        [string]
        $ResourceKey,
        [parameter(Mandatory)]
        [string]
        $keywordName
    )

    if ($null -eq $Script:NodeKeys)
    {
        $Script:NodeKeys = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,System.Collections.Generic.HashSet[string]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
    }

    if( -not $Script:NodeKeys.ContainsKey($keywordName))
    {
        $Script:NodeKeys[$keywordName] = New-Object -TypeName 'System.Collections.Generic.HashSet[string]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
    }

    if(-not $Script:NodeKeys[$keywordName].Contains($ResourceKey))
    {
        $null = $Script:NodeKeys[$keywordName].Add($ResourceKey)
    }

}

###########################################################
#
# A function to verify there's no duplicate conflicting resources in the configuration
#
# Conflict detecting algorithm works by going through all previuosly visited resources of same type and checking whether previously visited
# resource contains any properties which currently analyzed resource does not have or whether
# they have same properties but with different values. If that's the case, we mark that either key or non-key properties don't match.
# After that we check whether currently analyzed resource contains properties which the previously analyzed resource did not have at all. If that's the case we mark that
# non-key properties don't match (since all key properties were covered in the first phase).
#
# Once we processed all previous resources, we return error about duplicate conflicting resources if and only if key properties match and non key properties don't match.
#
###########################################################
function Test-ConflictingResources
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Scope="Function")]
    [OutputType([void])]
    param (
        [string]
            $keyword,
        [parameter(Mandatory)]
        [Hashtable]
            $properties,
        [Parameter(Mandatory)]
            $keywordData
    )
    $resourceID = $properties['ResourceID']
    $sourceInfo = $properties['SourceInfo']

    $nonConflictingKeywords = @("PartialConfiguration", "WaitForAny", "WaitForSome", "WaitForAll")
    if ($keyword -in $nonConflictingKeywords)
    {
        return
    }

    # Get name of the current node
    $currentNodeName = Get-PSCurrentConfigurationNode
    if (-not $currentNodeName)
    {
        $currentNodeName = 'localhost'
    }

    # Initialize $Script:DuplicateResources if not already initialized
    if ($null -eq $Script:DuplicateResources)
    {
        $Script:DuplicateResources = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,System.Collections.Generic.Dictionary[string,System.Collections.Generic.List[System.Collections.Hashtable]]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
    }

    if (-not $Script:DuplicateResources.ContainsKey($currentNodeName))
    {
        $Script:DuplicateResources[$currentNodeName] = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,System.Collections.Generic.List[System.Collections.Hashtable]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
    }

    if ( -not $Script:DuplicateResources[$currentNodeName].ContainsKey($keyword))
    {
        $Script:DuplicateResources[$currentNodeName][$keyword] = New-Object 'System.Collections.Generic.List[System.Collections.Hashtable]'
    }

    # Find if current resource is duplicate and conflicting.
    foreach($resource in $Script:DuplicateResources[$currentNodeName][$keyword])
    {
        $keyPropertiesMatch = $true
        $nonKeyPropertiesMatch = $true
        $unmatchedNonKeyPropertiesNames = ""
        $unmatchedNonKeyPropertiesCurrentValues = ""
        $unmatchedNonKeyPropertiesPreviousValues = ""

        # For every property in previously analyzed resource
        foreach($property in $resource.Keys)
        {
            $nonConflictingProperties = @("DependsOn", "ResourceID", "SourceInfo", "ModuleVersion")
            if ($property -in $nonConflictingProperties)
            {
                continue
            }

            # If currently analyzed resource does not have the property
            if ( -not $properties.ContainsKey($property))
            {
                $nonKeyPropertiesMatch = $false
                $unmatchedNonKeyPropertiesNames += $property.ToString() + ';'
                # Calling ToString() explicitly so that the value is not converted to Boolean/Integer
                if ($resource[$property])
                {
                    $unmatchedNonKeyPropertiesPreviousValues += $resource[$property].ToString() + ';'
                }
                else
                {
                    $unmatchedNonKeyPropertiesPreviousValues = $unmatchedNonKeyPropertiesPreviousValues.ToString() + 'NULL;'
                }
                $unmatchedNonKeyPropertiesCurrentValues = $unmatchedNonKeyPropertiesCurrentValues.ToString() + 'NULL;'
            }
            # If currently analyzed resource has property, but with different value
            elseif ( $resource[$property] -ne $properties[$property] )
            {
                # If it's a key property
                if ($keywordData.Properties[$property].IsKey)
                {
                    $keyPropertiesMatch = $false
                    break
                }
                # If it's a non-key property
                else
                {
                    $nonKeyPropertiesMatch = $false
                    $unmatchedNonKeyPropertiesNames += $property.ToString() + ';'
                    if ($resource[$property])
                    {
                        $unmatchedNonKeyPropertiesPreviousValues += $resource[$property].ToString() + ';'
                    }
                    else
                    {
                        $unmatchedNonKeyPropertiesPreviousValues = $unmatchedNonKeyPropertiesPreviousValues.ToString() + 'NULL;'
                    }

                    if ($properties[$property])
                    {
                        $unmatchedNonKeyPropertiesCurrentValues += $properties[$property].ToString() + ';'
                    }
                    else
                    {
                        $unmatchedNonKeyPropertiesCurrentValues = $unmatchedNonKeyPropertiesCurrentValues.ToString() + 'NULL;'
                    }
                }
            }
        }

        if ($keyPropertiesMatch)
        {
            foreach($property in $properties.Keys)
            {
                # If previously analyzed resource does not have property
                if ((-not $resource.containsKey($property)) -and ($property -ne "ModuleVersion"))
                {
                    $nonKeyPropertiesMatch = $false
                    $unmatchedNonKeyPropertiesNames += $property.ToString() + ';'
                    $unmatchedNonKeyPropertiesPreviousValues = $unmatchedNonKeyPropertiesPreviousValues.ToString() + 'NULL;'
                    if ($properties[$property])
                    {
                        $unmatchedNonKeyPropertiesCurrentValues += $properties[$property].ToString() + ';'
                    }
                    else
                    {
                        $unmatchedNonKeyPropertiesCurrentValues = $unmatchedNonKeyPropertiesCurrentValues.ToString() + 'NULL;'
                    }
                }
            }
        }

        # If resource is duplicate and conflicting we should return error
        if ($keyPropertiesMatch -and (-not $nonKeyPropertiesMatch))
        {
            $unmatchedNonKeyPropertiesNames = $unmatchedNonKeyPropertiesNames.Substring(0, $unmatchedNonKeyPropertiesNames.Length-1)
            $unmatchedNonKeyPropertiesPreviousValues = $unmatchedNonKeyPropertiesPreviousValues.Substring(0, $unmatchedNonKeyPropertiesPreviousValues.Length-1)
            $unmatchedNonKeyPropertiesCurrentValues = $unmatchedNonKeyPropertiesCurrentValues.Substring(0, $unmatchedNonKeyPropertiesCurrentValues.Length-1)

            $previousResourceIdentifier = $resource["ResourceID"] + " (" + $resource["SourceInfo"] + ")"
            $currentResourceIdentifier = $resourceID + " (" + $sourceInfo + ")"
            $errorMessage = $LocalizedData.ConflictingDuplicateResource -f @($previousResourceIdentifier, $currentResourceIdentifier, $currentNodeName, $unmatchedNonKeyPropertiesNames, $unmatchedNonKeyPropertiesPreviousValues, $unmatchedNonKeyPropertiesCurrentValues)
            $exception = New-Object System.InvalidOperationException $errorMessage
            Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId ConflictingDuplicateResource
            Update-ConfigurationErrorCount
        }
    }

    $Script:DuplicateResources[$currentNodeName][$keyword].Add($properties)

}

#################################################
#
# A function to get the innermost error record.
#
function Get-InnerMostErrorRecord
{
    param (
        [Parameter(Mandatory)]
        [System.Management.Automation.ErrorRecord]
        $ErrorRecord
    )

    $exception = $ErrorRecord.Exception
    while ($exception.InnerException -and $exception.InnerException.ErrorRecord)
    {
        $exception = $exception.InnerException
        $ErrorRecord = $exception.ErrorRecord
    }
    $ErrorRecord
}

###########################################################
#
# A function to set up all of the module-scoped state variables.
#
function Initialize-ConfigurationRuntimeState
{
    param (
        [Parameter()]
        [string]
        $ConfigurationName = ''
    )

    # The overall name of the configuration being processed
    [string] $Script:PSConfigurationName = $ConfigurationName

    # The ModuleVersion used for PsDesiredStateConfiguration module.
    [string] $Script:PsDscModuleVersion = "0.0"

    # The compatibility version for the document..
    [string] $Script:PsDscCompatibleVersion = "1.0.0"

    # The list of modules explicitly imported using import-dscresorce
    $Script:ExplicitlyImportedModules = New-Object -TypeName 'System.Collections.Generic.Dictionary[String,String]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    # For the current configuration, this contains the name of the node currently being processed
    [string] $Script:PSCurrentConfigurationNode = ''

    # For the current node, this contains a map of resource instance to resource prerequisites (DependsOn resources).
    [System.Collections.Generic.Dictionary[string,string[]]] `
    $Script:NodeResources = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,[string[]]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    # For the current node, this contains a map of partial configuration instance to configuration managers.
    [System.Collections.Generic.Dictionary[string,string]] `
    $Script:NodeManager = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,string[]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    # For the current node, this contains a map of partial configuration instance to Resource Source.
    [System.Collections.Generic.Dictionary[string,string]] `
    $Script:NodeResourceSource = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,string[]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    # For the current node, this contains a map of partial configuration instance to its exclusive resources.
    [System.Collections.Generic.Dictionary[string,string[]]] `
    $Script:NodeExclusiveResources = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,string[]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    # For all nodes except the unnamed node, this contains the per-node per-type reference counter used to generate the CIM aliases for each instance
    [System.Collections.Generic.Dictionary[string, System.Collections.Generic.Dictionary[string,int]]] `
    $Script:NodeTypeRefCount = New-Object -TypeName 'System.Collections.Generic.Dictionary[string, System.Collections.Generic.Dictionary[string,int]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    # For all nodes except the unnamed node, this maps the node name to the node's table of alias to mof text mappings.
    [System.Collections.Generic.Dictionary[string,System.Collections.Generic.Dictionary[string,string]]] `
    $Script:NodeInstanceAliases = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,System.Collections.Generic.Dictionary[string,string]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    # For all nodes except the unnamed node, this maps the node name to the node's table of resourceID to mof text mappings.
    [System.Collections.Generic.Dictionary[string,System.Collections.Generic.Dictionary[string,string]]] `
    $Script:NodeResourceIdAliases = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,System.Collections.Generic.Dictionary[string,string]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    # For all nodes except the unnamed node, this maps the node name to the node's map of resource instance to resource prerequisites
    [System.Collections.Generic.Dictionary[string,object]] `
    $Script:NodesInThisConfiguration = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,object]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    # For all nodes except the unnamed node, this maps the node name to the node's map of partial configuration resource instance to reference resource source.
    [System.Collections.Generic.Dictionary[string,object]] `
    $Script:NodesResourceSourceInThisConfiguration = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,object]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    # For all nodes except the unnamed node, this maps the node name to the node's map of partial configuration resource instance to reference configuraiton manager.
    [System.Collections.Generic.Dictionary[string,object]] `
    $Script:NodesManagerInThisConfiguration = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,object]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    # For all nodes except the unnamed node, this maps the node name to the node's map of resource instance to exclusive resource
    [System.Collections.Generic.Dictionary[string,object]] `
    $Script:NodesExclusiveResourcesInThisConfiguration = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,object]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    # For the unnamed (default) node, this contains a map of resource instance to resource prerequisites (required resources).
    [System.Collections.Generic.Dictionary[String,String[]]] `
    $Script:NoNameNodesResources = New-Object -TypeName 'System.Collections.Generic.Dictionary[String,String[]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    # For the unnamed (default) node, this contains a map of partial configuration resource instance to reference configuration manager.
    [System.Collections.Generic.Dictionary[String,String]] `
    $Script:NoNameNodeManager = New-Object -TypeName 'System.Collections.Generic.Dictionary[String,String[]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    # For the unnamed (default) node, this contains a map of partial configuration resource instance to reference resource source.
    [System.Collections.Generic.Dictionary[String,String]] `
    $Script:NoNameNodeResourceSource = New-Object -TypeName 'System.Collections.Generic.Dictionary[String,String[]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    # For the unnamed (default) node, this contains a map of partial configuration resource instance to its exclusive resources.
    [System.Collections.Generic.Dictionary[String,String[]]] `
    $Script:NoNameNodeExclusiveResources = New-Object -TypeName 'System.Collections.Generic.Dictionary[String,String[]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    # For the unnamed (default) node, this contains the per-node per-type reference counter used to generate the CIM aliases for each instance
    [System.Collections.Generic.Dictionary[String,int]] `
    $Script:NoNameNodeTypeRefCount = New-Object -TypeName 'System.Collections.Generic.Dictionary[String,int]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    # For the unnamed (default) node, this maps the node name to the node's table of alias to mof text mappings.
    # Alias to mof text mapping for the unnamed node.
    [System.Collections.Generic.Dictionary[string,string]] `
    $Script:NoNameNodeInstanceAliases = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,string]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    # For the unnamed (default) node, this maps the node name to the node's table of resourceId to mof text mappings.
    # Alias to mof text mapping for the unnamed node.
    [System.Collections.Generic.Dictionary[string,string]] `
    $Script:NoNameNodeResourceIdAliases = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,string]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    #dictionary to save whether a node has encrypted password
    [System.Collections.Generic.Dictionary[string,bool]] `
    $Script:NodesPasswordEncrypted = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,bool]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    # For all nodes, contain information about node using domain credential or not
    [System.Collections.Generic.Dictionary[string,bool]] `
    $Script:NodeUsingDomainCred = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,bool]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    [System.Collections.Generic.Stack[String]] $Script:PSOuterConfigurationNodes = New-Object -TypeName 'System.Collections.Generic.Stack[String]'

    # The number of errors that have occurred while processing this configuration.
    [int] $Script:PSConfigurationErrors = 0

    # Set up a variable to hold a top-level OMI_ConfigurationDocument value
    # which will be used by all nodes if it's present.
    [string]    $Script:PSDefaultConfigurationDocument = ''


    # Set up a hastable to hold user specified info for OMI_ConfigurationDocument value for meta config
    # we will need update it last after processing meta config to figure out what V2 property it uses
    [hashtable]    $script:PSMetaConfigDocumentInstVersionInfo = @{}

    [bool] $Script:PSMetaConfigDocInsProcessedBeforeMeta = $false
    [bool] $script:PSMetaConfigurationProcessed = $false

    # For all nodes except the unnamed node, this maps the node name to the node's map of keys of resources.
    [System.Collections.Generic.Dictionary[string,object]] `
    $script:NodesKeysInThisConfiguration = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,object]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    # For the current node, this contains keys of resource instances.
    [System.Collections.Generic.Dictionary[string,System.Collections.Generic.HashSet[string]]] `
    $Script:NodeKeys = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,System.Collections.Generic.HashSet[string]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    # For the unnamed (default) node, this contains keys of resource instances.
    [System.Collections.Generic.Dictionary[String,System.Collections.Generic.HashSet[string]]] `
    $Script:NoNameNodeKeys = New-Object -TypeName 'System.Collections.Generic.Dictionary[String,System.Collections.Generic.HashSet[string]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    # For the current configuration, $Script:DuplicateResources["Type"] contains list with properties of all resources of the specific type
    [System.Collections.Generic.Dictionary[string,System.Collections.Generic.Dictionary[string,System.Collections.Generic.List[System.Collections.Hashtable]]]] `
    $Script:DuplicateResources = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,System.Collections.Generic.Dictionary[string,System.Collections.Generic.List[System.Collections.Hashtable]]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)

    # Show Import-DscResource warning if in-build resources are used in the Configuration without Import-DscResource statement.
    [bool] $script:ShowImportDscResourceWarning = $false
}

#
# Then call it to enact the default state. This happens
# only once during the module load. Thereafter the
# configuration function is responsible for resetting state.
#
Initialize-ConfigurationRuntimeState

# make sure configuration data format:
# 1. Is a hashtable
# 2. Has a collection AllNodes
# 3. Allnodes is an collection of Hashtable
# 4. Each element of Allnodes has NodeName
# 5. We will copy values from NodeName="*" to all node if they don't exist
function ValidateUpdate-ConfigurationData
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Scope="Function", Target="*")]
    param (
        [Parameter()]
        [hashtable]
        $ConfigurationData
    )

    if( -not $ConfigurationData.ContainsKey('AllNodes'))
    {
        $errorMessage = $LocalizedData.ConfiguratonDataNeedAllNodes
        $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
        Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId ConfiguratonDataNeedAllNodes
        return $false
    }

    if($ConfigurationData.AllNodes -isnot [array])
    {
        $errorMessage = $LocalizedData.ConfiguratonDataAllNodesNeedHashtable
        $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
        Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId ConfiguratonDataAllNodesNeedHashtable
        return $false
    }

    $nodeNames = New-Object -TypeName 'System.Collections.Generic.HashSet[string]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
    foreach($Node in $ConfigurationData.AllNodes)
    {
        if($Node -isnot [hashtable] -or -not $Node.NodeName)
        {
            $errorMessage = $LocalizedData.AllNodeNeedToBeHashtable
            $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
            Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId ConfiguratonDataAllNodesNeedHashtable
            return $false
        }

        if($nodeNames.Contains($Node.NodeName))
        {
            $errorMessage = $LocalizedData.DuplicatedNodeInConfigurationData -f $Node.NodeName
            $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
            Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId DuplicatedNodeInConfigurationData
            return $false
        }

        if($Node.NodeName -eq '*')
        {
            $AllNodeSettings = $Node
        }
        [void] $nodeNames.Add($Node.NodeName)
    }

    if($AllNodeSettings)
    {
        foreach($Node in $ConfigurationData.AllNodes)
        {
            if($Node.NodeName -ne '*')
            {
                foreach($nodeKey in $AllNodeSettings.Keys)
                {
                    if(-not $Node.ContainsKey($nodeKey))
                    {
                        $Node.Add($nodeKey, $AllNodeSettings[$nodeKey])
                    }
                }
            }
        }

        $ConfigurationData.AllNodes = @($ConfigurationData.AllNodes | Where-Object -FilterScript {
                $_.NodeName -ne '*'
            }
        )
    }

    return $true
}

##############################################################
#
# Checks to see if a module defining composite resources should be reloaded
# based the last write time of the schema file. Returns true if the file exists
# and the last modified time was either not recorded or has change.
#
function Test-ModuleReloadRequired
{
    [OutputType([bool])]
    param (
        [Parameter(Mandatory)]
        [string]
        $SchemaFilePath
    )

    if (-not $SchemaFilePath -or  $SchemaFilePath -notmatch '\.schema\.psm1$')
    {
        # not a composite res
        return $false
    }

    # If the path doesn't exist, then we can't reload it.
    # Note: this condition is explicitly not an error for this function.
    if ( -not (Test-Path $SchemaFilePath))
    {
        if ($schemaFileLastUpdate.ContainsKey($SchemaFilePath))
        {
            $schemaFileLastUpdate.Remove($SchemaFilePath)
        }
        return $false
    }

    # If we have a modified date, then return it.
    if ($schemaFileLastUpdate.ContainsKey($SchemaFilePath))
    {
        if ( (Get-Item $SchemaFilePath).LastWriteTime -eq $schemaFileLastUpdate[$SchemaFilePath] )
        {
            return $false
        }
        else
        {
            return $true
        }
    }

    # Otherwise, record the last write time and return true.
    $script:schemaFileLastUpdate[$SchemaFilePath] = (Get-Item $SchemaFilePath).LastWriteTime
    $true
}
# Holds the schema file to lastwritetime mapping.
[System.Collections.Generic.Dictionary[string,DateTime]] $script:schemaFileLastUpdate =
New-Object -TypeName 'System.Collections.Generic.Dictionary[string,datetime]'

###########################################################
# Configuration keyword implementation
###########################################################
#
# Implements the 'configuration' keyword logic that accumulates and writes
# out the generated DSC MOF files
#
function Configuration
{
    # suppress global:ConfigurationData
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "", Scope="Function", Target="*")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSProvideCommentHelp", "", Scope="Function", Target="*")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPositionalParameters", "", Scope="Function", Target="*")]
    [CmdletBinding(HelpUri = 'http://go.microsoft.com/fwlink/?LinkId=517195')]
    param (
        # there are extra [] around Tuple arguments
        [System.Collections.Generic.List[System.Tuple[[string[]], [Microsoft.PowerShell.Commands.ModuleSpecification[]], [Version]]]]
        $ResourceModuleTuplesToImport,
        $OutputPath = '.',
        $Name,
        [scriptblock]
        $Body,
        [hashtable]
        $ArgsToBody,
        [hashtable]
        $ConfigurationData = @{
            AllNodes = @()
        },
        [string]
        $InstanceName = ''
    )


    function ConvertModuleDefnitionToModuleInfo
    {
        [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPositionalParameters", "", Scope="Function", Target="*")]
        param(
            [Microsoft.PowerShell.Commands.ModuleSpecification[]]$moduleToImport,
            [Version]$moduleVersion = $null
        )

        if (-not $moduleToImport) {
            return $null
        }

        $moduleToImport | Foreach-Object -Process {
            $versionToUse = $_.Version
            if( [string]::IsNullOrEmpty($versionToUse))
            {
                $versionToUse = $_.RequiredVersion
            }
            $Script:ExplicitlyImportedModules[ $_.Name ] = $versionToUse
        }
        $moduleInfos = Get-Module -ListAvailable -FullyQualifiedName $moduleToImport | Sort-Object -Property Version -Descending

        return $moduleInfos
    }

    try
    {
        Write-Debug -Message "BEGIN CONFIGURATION '$Name' PROCESSING: OutputPath: '$OutputPath'"

        if ($Name -inotmatch  '^[a-z][a-z0-9_]*$')
        {
            $errorId = 'InvalidConfigurationName'
            $errorMessage = $($LocalizedData.InvalidConfigurationName) -f ${Name}
            $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
            Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId $errorId
        }

        $script:IsMetaConfig = $false
        foreach($attri in $Body.Attributes)
        {
            if ($attri.GetType() -eq [System.Management.Automation.DscLocalConfigurationManagerAttribute])
            {
                $script:IsMetaConfig = $true
                $Script:PSMetaConfigDocInsProcessedBeforeMeta = $false
                $script:PSMetaConfigurationProcessed = $false
                break
            }
        }

        # True if this is the top-most level of the configuration statement.
        #
        [bool] $topLevel = $false

        if ($Script:PSConfigurationName -eq '')
        {
            Write-Debug -Message " $Name : TOP-LEVEL CONFIGURATION STARTED"
            $topLevel = $true
            Set-PSTopConfigurationName $Name
            #
            # Define a dictionary to hold the "driver" functions that will be created for each CIM class/keyword.
            # This dictionary is passed into the body scriptblock, defining these functions in the body scope
            # which simplifies cleanup.
            #
            $script:functionsToDefine = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,ScriptBlock]'([System.StringComparer]::OrdinalIgnoreCase)

            if($null -eq $ConfigurationData)
            {
                $ConfigurationData = @{
                    AllNodes = @()
                }
            }

            $dataValidated = ValidateUpdate-ConfigurationData $ConfigurationData

            if (-not $dataValidated)
            {
                Update-ConfigurationErrorCount
                Write-Debug -Message 'ConfigurationData validation failed'
                return
            }
            else
            {
                $script:ConfigurationData = $ConfigurationData
            }

            if($OutputPath -eq '.' -or $null -eq $OutputPath -or $OutputPath -eq '')
            {
                $OutputPath = ".\$Name"
            }

            # Load the default CIM keyword/function definitions set, populating the function collection
            # with the default functions.
            [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::LoadDefaultCimKeywords($functionsToDefine)

            # Set up the rest of the configuration runtime state.
            Initialize-ConfigurationRuntimeState $Name

            # Create the output directory only if it does not exist.
            # If it exists then the document MOF files will overwrite
            # any existing MOF files with the same name but otherwise
            # leave contents of existing directory alone.
            $ConfigurationOutputDirectory = "$OutputPath"
            if (!(Test-Path $ConfigurationOutputDirectory))
            {
                $mkdirError = $null
                New-Item -ErrorVariable mkdirError -ItemType Directory -Force -Path $ConfigurationOutputDirectory  > $null 2> $null
                if (! $?)
                {
                    Update-ConfigurationErrorCount
                }

                if ($mkdirError)
                {
                    $errorId = 'InvalidOutputPath'
                    $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation
                    $errorMessage = $($LocalizedData.CannotCreateOutputPath) -f ${ConfigurationOutputDirectory}
                    $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
                    $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception, $errorId, $errorCategory, $null
                    Write-Error $ErrorRecord
                    foreach ($e in $mkdirError)
                    {
                        Write-Error -Exception $e.Exception
                        Update-ConfigurationErrorCount
                    }
                }
            }

            #
            # Add the utility functions used by the resource implementation functions.
            #
            $functionsToDefine.Add('Get-MofInstanceText',                ${function:Get-MofInstanceText} )
            $functionsToDefine.Add('ConvertTo-MOFInstance',              ${function:ConvertTo-MOFInstance} )
            $functionsToDefine.Add('Update-ConfigurationErrorCount',     ${function:Update-ConfigurationErrorCount} )
            $functionsToDefine.Add('Get-ConfigurationErrorCount',        ${function:Get-ConfigurationErrorCount} )
            $functionsToDefine.Add('Set-PSDefaultConfigurationDocument', ${function:Set-PSDefaultConfigurationDocument} )
            $functionsToDefine.Add('Get-PSDefaultConfigurationDocument', ${function:Get-PSDefaultConfigurationDocument} )
            $functionsToDefine.Add('Get-PSCurrentConfigurationNode',     ${function:Get-PSCurrentConfigurationNode} )
            $functionsToDefine.Add('Set-NodeResources',                  ${function:Set-NodeResources} )
            $functionsToDefine.Add('Test-NodeResources',                 ${function:Test-NodeResources} )
            $functionsToDefine.Add('Set-NodeManager',                    ${function:Set-NodeManager} )
            $functionsToDefine.Add('Test-NodeManager',                   ${function:Test-NodeManager} )
            $functionsToDefine.Add('Set-NodeResourceSource',             ${function:Set-NodeResourceSource} )
            $functionsToDefine.Add('Test-NodeResourceSource',            ${function:Test-NodeResourceSource} )
            $functionsToDefine.Add('Set-NodeExclusiveResources',         ${function:Set-NodeExclusiveResources} )
            $functionsToDefine.Add('Add-NodeKeys',                       ${function:Add-NodeKeys} )
            $functionsToDefine.Add('Test-ConflictingResources',          ${function:Test-ConflictingResources} )
            $functionsToDefine.Add('Set-PSMetaConfigDocInsProcessedBeforeMeta', ${function:Set-PSMetaConfigDocInsProcessedBeforeMeta} )
            $functionsToDefine.Add('Get-PSMetaConfigDocumentInstVersionInfo', ${function:Get-PSMetaConfigDocumentInstVersionInfo} )
            $functionsToDefine.Add('Get-PSMetaConfigurationProcessed', ${function:Get-PSMetaConfigurationProcessed} )
            $functionsToDefine.Add('Set-PSMetaConfigVersionInfoV2', ${function:Set-PSMetaConfigVersionInfoV2})

            #
            # Add the node keyword implementation function which must be module qualified even though
            # it's not exported from the module because the parsing logic always adds the module name
            # to the command call.
            #
            $functionsToDefine.Add('PSDesiredStateConfiguration\node',   ${function:Node})

            Write-Debug -Message " $Name : $($functionsToDefine.Count) type handler functions loaded."
            Write-Debug -Message " $Name TOP-LEVEL INITIALIZATION COMPLETED"
        }
        else
        {
            Write-Debug -Message " $Name : NESTED CONFIGURATION STARTED"
        }

        #
        # Load all of the required resource definition modules
        #

        foreach ($tuple in $ResourceModuleTuplesToImport)
        {
            $res = $tuple.Item1
            $modulesInfo = ConvertModuleDefnitionToModuleInfo -moduleToImport $tuple.Item2 -moduleVersion $tuple.Item3

            if (-not $modulesInfo) {
                # Module name is not specified. Try to load resource from all available modules.
                $modulesInfo = Get-Module -ListAvailable
            }

            foreach ($mod in $modulesInfo) {

                $null = ImportClassResourcesFromModule -Module $mod -Resources $res -functionsToDefine $functionsToDefine
                $dscResourcesPath = Join-Path -Path $mod.ModuleBase -ChildPath 'DscResources'
                if(Test-Path $dscResourcesPath)
                {
                    foreach($requiredResource in $res)
                    {
                        if ($requiredResource.Contains('*')) {
                            # we historically resolve wildcards by Get-Item File System rules.
                            # We don't support wildcards resolutions for Friendly names.
                            foreach ($resource in Get-ChildItem -Path $dscResourcesPath -Directory -Name -Filter $requiredResource)
                            {
                                $null = ImportCimAndScriptKeywordsFromModule -Module $mod -Resource $resource -functionsToDefine $functionsToDefine
                            }
                        } else {
                            # ImportCimAndScriptKeywordsFromModule takes care about resolving $requiredResources names to ClassNames or FriendlyNames.
                            $null = ImportCimAndScriptKeywordsFromModule -Module $mod -Resource $requiredResource -functionsToDefine $functionsToDefine
                        }
                    }
                }
                elseif ($moduleInfos.Count -eq 1)
                {
                    $modules.Add($moduleInfos)
                }
            }
        }

        if (-not (Get-PSCurrentConfigurationNode))
        {
            # A dictionary maps a resourceId to its list of required resources list for any resources
            # defined outside of a node statement
            $Script:NodeResources = $Script:NoNameNodesResources
            $Script:NodeKeys = $Script:NoNameNodeKeys
            $Script:NodeManager = $Script:NoNameNodeManager
            $Script:NodeExclusiveResources = $Script:NoNameNodeExclusiveResources
            $Script:NodeResourceSource = $Script:NoNameNodeResourceSource

        }
        else
        {
            $Script:NodeResources = $Script:NodesInThisConfiguration[(Get-PSCurrentConfigurationNode)]
            $Script:NodeManager = $Script:NodesManagerInThisConfiguration[(Get-PSCurrentConfigurationNode)]
            $Script:NodeResourceSource = $Script:NodesResourceSourceInThisConfiguration[(Get-PSCurrentConfigurationNode)]
            $Script:NodeExclusiveResources = $Script:NodesExclusiveResourcesInThisConfiguration[(Get-PSCurrentConfigurationNode)]
        }

        #
        # Evaluate the configuration statement body which will generate the resource definitons
        # for this configuration.
        #
        Write-Debug -Message " $Name : Evaluating configuration statement body..."
        try
        {
            $variablesToDefine = @(
                #
                # Figure out the "type" of this resource, with is the name of the driver function that was called.
                #
                New-Object -TypeName PSVariable -ArgumentList ('ConfigurationData', $script:ConfigurationData )
                New-Object -TypeName PSVariable -ArgumentList ('MyTypeName', $ExecutionContext.SessionState.Module.GetVariableFromCallersModule('MyInvocation').Value.MyCommand.Name)
                New-Object -TypeName PSVariable -ArgumentList ('IsMetaConfig', $script:IsMetaConfig)
                New-Object -TypeName PSVariable -ArgumentList ('V1MetaConfigPropertyList', $script:V1MetaConfigPropertyList)
                if($script:ConfigurationData)
                {
                    New-Object -TypeName PSVariable -ArgumentList ('AllNodes', $script:ConfigurationData.AllNodes)
                }
            )

            $variablesToDefine += foreach ($key in $ArgsToBody.Keys)
            {
                #
                # we need to process dependsOn seperately to
                # 1. combined depends on with possible upper level configuration statement (composite resource case
                # 2. in case of 1, we also need to fix up the dependson to append the suffix of ::$complexResourceQualifier similar in func:Test-DependsOn in CimDSCParser
                #
                if($key -ne 'DependsOn')
                {
                    New-Object -TypeName PSVariable -ArgumentList ($key, $ArgsToBody[$key])
                }
            }

            if($DependsOn -or $ArgsToBody['DependsOn'])
            {
                $complexResourceQualifier = Get-ComplexResourceQualifier
                if($complexResourceQualifier)
                {
                    $updatedDependsOn = foreach($DependsOnVar in @($ArgsToBody['DependsOn']))
                    {
                        if($DependsOnVar)
                        {
                            "$DependsOnVar::$complexResourceQualifier"
                        }
                    }
                    $DependsOn = @($DependsOn) + @($updatedDependsOn)
                }
                else
                {
                    $DependsOn = @($DependsOn) + @($ArgsToBody['DependsOn'])
                }

                $variablesToDefine += New-Object -TypeName PSVariable -ArgumentList('DependsOn', $DependsOn)
            }

            $null = $Body.InvokeWithContext($functionsToDefine, $variablesToDefine)
        }
        catch [System.Management.Automation.MethodInvocationException]
        {
            Write-Debug -Message " $Name : Top level exception: $($_ | Out-String)"
            # Write the unwrapped exception message
            $pscmdlet.CommandRuntime.WriteError((Get-InnerMostErrorRecord $_))
            Update-ConfigurationErrorCount
        }

        #
        # write the generated files to disk and return the resulting files to stdout.
        #

        if( $topLevel )
        {
            if($Script:NoNameNodeInstanceAliases.Count -gt 0)
            {
                if ($Script:NodeInstanceAliases.ContainsKey('localhost'))
                {
                    $errorMessage = $LocalizedData.LocalHostNodeNotAllowed -f "$Name"
                    $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
                    Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId LocalHostNodeNotAllowed
                    Update-ConfigurationErrorCount
                }

                #
                # Fixup DependsOn
                #
                ValidateNoNameNodeResources
                Update-DependsOn $Script:NoNameNodesResources $Script:NoNameNodeInstanceAliases $Script:NoNameNodeResourceIdAliases


                Write-Debug -Message " $Name : Evaluation completed, validating the generated resource set."
                ValidateNodeResources
                Write-Debug -Message " $Name Validation completed."

                #
                # Fixup ModuleVersion
                #
                Update-ModuleVersion $Script:NoNameNodesResources $Script:NoNameNodeInstanceAliases $Script:NoNameNodeResourceIdAliases

                Write-Debug -Message " $Name : Evaluation completed, validating the generated resource set has no circle."
                ValidateNoCircleInNodeResources
                Write-Debug -Message " $Name Validation circle completed."

                if($script:IsMetaConfig)
                {
                    # Validate make sure all of the referenced download managers are defined
                    Write-Debug -Message " $Name : Evaluation completed, validating the generated resource set reference download manager."
                    ValidateNodeManager
                    Write-Debug -Message " $Name Validation completed."

                    # Validate make sure all of the referenced resource source are defined
                    Write-Debug -Message " $Name : Evaluation completed, validating the generated resource set reference resource source."
                    ValidateNodeResourceSource
                    Write-Debug -Message " $Name Validation completed."

                    # Validate make sure new conflict between exclusive resources
                    Write-Debug -Message " $Name : Evaluation completed, validating no conflict between exclusive resources."
                    ValidateNodeExclusiveResources
                    Write-Debug -Message " $Name Validation completed."

                    Write-MetaConfigFile $Name 'localhost' $Script:NoNameNodeInstanceAliases
                }
                else
                {
                    Update-ConfigurationDocumentRef $Script:NoNameNodesResources $Script:NoNameNodeInstanceAliases $Script:NoNameNodeResourceIdAliases $Name
                    #
                    # Write the mof instance texts to files
                    #
                    Write-NodeMOFFile $Name 'localhost' $Script:NoNameNodeInstanceAliases
                }

                # If no script-level $ConfigurationData variable is set, this code
                # tries to get it first, from a global PowerShell ConfigurationData variable,
                # then if that doesn't work it trys the environment
                # variable $ENV:ConfigurationData when is expected to contain a JSON string
                # that will be converted to objects.
                #
                if (-not $script:ConfigurationData)
                {
                    $script:ConfigurationData = try
                    {
                        if ($global:ConfigurationData)
                        {
                            $global:ConfigurationData
                        }
                        elseif ($ENV:ConfigurationData)
                        {
                            $ENV:ConfigurationData | ConvertFrom-Json
                        }
                        else
                        {
                            @()
                        }
                    }
                    catch
                    {
                        Write-Error $_
                        Update-ConfigurationErrorCount
                        @()
                    }
                }
            }

            if($script:ShowImportDscResourceWarning)
            {
                $message = $LocalizedData.ImportDscResourceWarningForInbuiltResource -f @(Get-PSTopConfigurationName)
                $ImportDscResourceWarningPreference = $WarningPreference
                if($null -ne $ArgsToBody['WarningAction'])
                {
                    $ImportDscResourceWarningPreference = $ArgsToBody['WarningAction']
                }
                Write-Warning -Message $message -WarningAction $ImportDscResourceWarningPreference
            }

            #
            # write using the top-level hashtable
            #
            foreach($mofNode in $Script:NodeInstanceAliases.Keys)
            {
                #
                # Fixup DependsOn
                #
                Update-DependsOn $Script:NodesInThisConfiguration[$mofNode] $Script:NodeInstanceAliases[$mofNode] $Script:NodeResourceIdAliases[$mofNode]

                if($script:IsMetaConfig)
                {
                    Write-MetaConfigFile $Name $mofNode $Script:NodeInstanceAliases[$mofNode]
                }
                else
                {
                    Update-ConfigurationDocumentRef $Script:NodesInThisConfiguration[$mofNode] $Script:NodeInstanceAliases[$mofNode] $Script:NodeResourceIdAliases[$mofNode] $Name
                    #
                    # Write the mof instance texts to files
                    #
                    Write-NodeMOFFile $Name $mofNode $Script:NodeInstanceAliases[$mofNode]
                }
            }

            if ($Script:PSConfigurationErrors -gt 0)
            {
                $errorMessage = $LocalizedData.FailToProcessConfiguration -f "$Name"
                ThrowError -ExceptionName 'System.InvalidOperationException' -ExceptionMessage $errorMessage -ExceptionObject "$Name" -ErrorId 'FailToProcessConfiguration' -ErrorCategory InvalidOperation
            }
        }
    }
    finally
    {
        if($topLevel)
        {
            Write-Debug -Message " CONFIGURATION $Name : DOING TOP-LEVEL CLEAN UP"
            [System.Management.Automation.Language.DynamicKeyword]::Reset()
            [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::ClearCache()

            Initialize-ConfigurationRuntimeState
        }
        Write-Debug -Message "END CONFIGURATION '$Name' PROCESSING. OutputPath: '$OutputPath'"
    }
}
Export-ModuleMember -Function Configuration

function Update-ModuleVersion
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Scope="Function")]
    [OutputType([void])]
    param(
        [Parameter(Mandatory)]
        [System.Collections.Generic.Dictionary[String,String[]]]
        $NodeResources,

        [Parameter(Mandatory)]
        [System.Collections.Generic.Dictionary[string,string]]
        $NodeInstanceAliases,

        [Parameter(Mandatory)]
        [System.Collections.Generic.Dictionary[string,string]]
        $NodeResourceIdAliases
    )

    $moduleVersionValue = '0.0'

    # explicit import-dscresource with version for psdesiredstateconfiguration module was not done.
    if( $Script:ExplicitlyImportedModules.ContainsKey('PsDesiredStateConfiguration') -and
        (-not [string]::IsNullOrEmpty($Script:ExplicitlyImportedModules['PsDesiredStateConfiguration'])))
    {
        $moduleVersionValue= $script:PsDscModuleVersion
    }
    # generating compatible document.
    elseif($script:PsDscCompatibleVersion -eq '1.0.0')
    {
        $moduleVersionValue='1.0'
    }

    foreach($resourceId in $NodeResources.keys)
    {
        $alias = $NodeResourceIdAliases[$resourceId]
        $instanceText = $NodeInstanceAliases[$alias]
        $curlyPosition = $instanceText.LastIndexOf('}')
        if(($curlyPosition -gt 0) -and ($instanceText -imatch 'ModuleName[\s]*=[\s]*["]PsDesiredStateConfiguration["]') -and
          ($instanceText -inotmatch 'ModuleVersion[\s]*='))
        {
            $first = $instanceText.Substring(0, $curlyPosition)

            $moduleVersionstring = "ModuleVersion = "
            $moduleVersionstring += "`"$moduleVersionValue`"" + ";"
            $NodeInstanceAliases[$alias] = $first + $moduleVersionstring + "`r`n};"
        }
    }
}

function Update-DependsOn
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Scope="Function")]
    [OutputType([void])]
    param(
        [Parameter(Mandatory)]
        [System.Collections.Generic.Dictionary[String,String[]]]
        $NodeResources,

        [Parameter(Mandatory)]
        [System.Collections.Generic.Dictionary[string,string]]
        $NodeInstanceAliases,

        [Parameter(Mandatory)]
        [System.Collections.Generic.Dictionary[string,string]]
        $NodeResourceIdAliases
    )

    foreach($resourceId in $NodeResources.keys)
    {
        $alias = $NodeResourceIdAliases[$resourceId]
        $needAdd = $false
        $instanceText = $NodeInstanceAliases[$alias]
        if($NodeResources[$resourceId])
        {
            $curlyPosition = $instanceText.LastIndexOf('}')
            if(($curlyPosition -gt 0) -and ($instanceText -notmatch 'dependsOn[\s]*='))
            {
                $needAdd = $true
                $first = $instanceText.Substring(0, $curlyPosition)

                $dependsOn = "DependsOn = {`r`n"
                $len = @($NodeResources[$resourceId]).Length
                $dependsOn += foreach ($resourceId in $NodeResources[$resourceId])
                {
                    ' ' + "`"$($resourceId -replace '\\', '\\' -replace '"', '\"')`"" +
                    $(if (--$len -gt 0)
                        {
                            ",`r`n"
                        }
                        else
                        {
                            ''
                        }
                    )
                }
                $dependsOn += '};'
            }
        }

        if($needAdd)
        {
            $NodeInstanceAliases[$alias] = $first + $dependsOn + "`r`n};"
        }
    }
}

#
# add a reference to each resource to point to the OMI_ConfigurationDocument
# so it can be differiencated after merging of partical configurations
#
function Update-ConfigurationDocumentRef
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Scope="Function")]
    [OutputType([void])]
    param(
        [Parameter(Mandatory)]
        [System.Collections.Generic.Dictionary[String,String[]]]
        $NodeResources,

        [Parameter(Mandatory)]
        [System.Collections.Generic.Dictionary[string,string]]
        $NodeInstanceAliases,

        [Parameter(Mandatory)]
        [System.Collections.Generic.Dictionary[string,string]]
        $NodeResourceIdAliases,

        [Parameter(Mandatory)]
        [string]
        $ConfigurationName
    )

    foreach($resourceId in $NodeResources.keys)
    {
        $alias = $NodeResourceIdAliases[$resourceId]
        $instanceText = $NodeInstanceAliases[$alias]
        $needAdd = $false
        $curlyPosition = $instanceText.LastIndexOf('}')
        if($curlyPosition -gt 0)
        {
            $needAdd = $true
            $first = $instanceText.Substring(0, $curlyPosition).TrimEnd()

            $ConfigurationNameRef = "`r`n ConfigurationName = `"$ConfigurationName`";"
        }

        if($needAdd)
        {
            $NodeInstanceAliases[$alias] = $first + $ConfigurationNameRef + "`r`n};"
        }
    }
}

function ImportClassResourcesFromModule
{
    param (
        [Parameter(Mandatory)]
        [PSModuleInfo]
        $Module,

        [Parameter(Mandatory)]
        [System.Collections.Generic.List[string]]
        $Resources,

        [System.Collections.Generic.Dictionary[string, scriptblock]]
        $functionsToDefine
    )

    $resourcesFound = [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::ImportClassResourcesFromModule($Module, $Resources, $functionsToDefine)
    return ,$resourcesFound
}

function ImportCimAndScriptKeywordsFromModule
{
    param (
        [Parameter(Mandatory)]
        $Module,

        [Parameter(Mandatory)]
        $resource,

        $functionsToDefine
    )

    trap
    {
        continue
    }

    $SchemaFilePath = $null
    $oldCount = $functionsToDefine.Count

    $keywordErrors = New-Object -TypeName 'System.Collections.ObjectModel.Collection[System.Exception]'

    $foundCimSchema = [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::ImportCimKeywordsFromModule(
    $Module, $resource, [ref] $SchemaFilePath, $functionsToDefine, $keywordErrors)

    foreach($ex in $keywordErrors)
    {
        Write-Error -Exception $ex
        if($ex.InnerException)
        {
            Write-Error -Exception $ex.InnerException
        }
    }

    $functionsAdded = $functionsToDefine.Count - $oldCount
    Write-Debug -Message " $Name : PROCESSING RESOURCE FILE: Added $functionsAdded type handler functions from '$SchemaFilePath'"

    $SchemaFilePath = $null
    $oldCount = $functionsToDefine.Count

    $foundScriptSchema = [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::ImportScriptKeywordsFromModule(
    $Module, $resource, [ref] $SchemaFilePath, $functionsToDefine )

    $functionsAdded = $functionsToDefine.Count - $oldCount
    Write-Debug -Message " $Name : PROCESSING RESOURCE FILE: Added $functionsAdded type handler functions from '$SchemaFilePath'"

    if ($foundScriptSchema -and $SchemaFilePath)
    {
        $resourceDirectory = Split-Path $SchemaFilePath
        if($null -ne $resourceDirectory)
        {
            Import-Module -Force: (Test-ModuleReloadRequired $SchemaFilePath) -Verbose:$false -Name $resourceDirectory -Global -ErrorAction SilentlyContinue
        }
    }

    return $foundCimSchema -or $foundScriptSchema
}

#
# A function to write the MOF instance texts of a node to files as meta config
#
function Write-MetaConfigFile
{
    # nodeConfigurationDocument incorrectly shows up as unused
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "", Target="", Scope="Function")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPositionalParameters", "", Scope="Function", Target="*")]
    param(
        [string]
        $ConfigurationName,

        [string]
        $mofNode,

        [System.Collections.Generic.Dictionary[string,string]]
        $mofNodeHash

    )

    # Set up prefix for both the configuration and metaconfiguration documents.
    $nodeDoc = "/*`n@TargetNode='$mofNode'`n" + "@GeneratedBy=$([system.environment]::UserName)`n@GenerationDate=$(Get-Date)`n@GenerationHost=$([system.environment]::MachineName)`n*/`n"
    $nodeConfigurationDocument = $null
    [int]$nodeDocCount = 0
    $resourceManagers = $null
    $resourceManagersCount = 0
    $reportManagers = $null
    $reportManagersCount = 0
    $downloadManagers = $null
    $downloadManagersCount = 0
    $localConfigManager = $null
    $partialConfiguratons = $null
    $partialConfigurationCount = 0

    foreach($mofTypeName in $mofNodeHash.Keys)
    {
        if($mofTypeName -match 'OMI_ConfigurationDocument')
        {
            $nodeConfigurationDocument = $mofNodeHash[$mofTypeName]
        }

        if($mofTypeName -match 'MSFT_WebDownloadManager' -or $mofTypeName -match 'MSFT_FileDownloadManager')
        {
            $r = $mofNodeHash[$mofTypeName] -match '\$\w*'
            if($r)
            {
                if($downloadManagersCount++ -gt 0)
                {
                    $downloadManagers += ",`n"
                }
                $downloadManagers += ' ' + $Matches[0]
            }
        }

        if($mofTypeName -match 'MSFT_WebResourceManager' -or $mofTypeName -match 'MSFT_FileResourceManager')
        {
            $r = $mofNodeHash[$mofTypeName] -match '\$\w*'
            if($r)
            {
                if($resourceManagersCount++ -gt 0)
                {
                    $resourceManagers += ",`n"
                }
                $resourceManagers += ' ' +$Matches[0]
            }
        }

        if($mofTypeName -match 'MSFT_OaaSReportManager' -or $mofTypeName -match 'MSFT_WebReportManager')
        {
            $r = $mofNodeHash[$mofTypeName] -match '\$\w*'
            if($r)
            {
                if($reportManagersCount++ -gt 0)
                {
                    $reportManagers += ",`n"
                }
                $reportManagers += ' ' +$Matches[0]
            }
        }

        #MSFT_PartialConfiguration
        if($mofTypeName -match 'MSFT_PartialConfiguration')
        {
            $r = $mofNodeHash[$mofTypeName] -match '\$\w*'
            if($r)
            {
                if($partialConfigurationCount++ -gt 0)
                {
                    $partialConfiguratons += ",`n"
                }
                $partialConfiguratons += ' ' +$Matches[0]
            }
        }

        if($mofTypeName -notmatch 'MSFT_DSCMetaConfiguration')
        {
            $nodeDoc += $mofNodeHash[$mofTypeName]
        }
        else
        {
            if($null -eq $localConfigManager)
            {
                # save the localConfigManager which need to be fixed up to add additional manager info as embedded resources
                $localConfigManager = $mofNodeHash[$mofTypeName] -replace 'MSFT_DSCMetaConfigurationV2', 'MSFT_DSCMetaConfiguration'
            }
            else
            {
                $errorMessage = $LocalizedData.MetaConfigurationHasMoreThanOneLocalConfigurationManager -f @($mofNode)
                $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
                Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId InvalidMOFDefinition
                Update-ConfigurationErrorCount
                return
            }
        }

        $nodeDocCount++
    }

    if($null -eq $localConfigManager)
    {
        # Print verbose message that empty settings definition is added.
        $emptySettingVerboseMessage = $LocalizedData.MetaConfigurationSettingsMissing -f @($mofNode)
        Write-Verbose -Message $emptySettingVerboseMessage

        # Assign default settings
        $localConfigManager = "`ninstance of MSFT_DSCMetaConfiguration as `$MSFT_DSCMetaConfiguration1ref `n{`n};"
    }

    # fixup to add embedded instances
    $nodeDoc += Update-LocalConfigManager $localConfigManager $resourceManagers $reportManagers $downloadManagers $partialConfiguratons


    $nodeOutfile = "$ConfigurationOutputDirectory/$($mofNode).meta.mof"

    # add/update OMI_ConfigurationDocument of meta config
    if ($nodeDoc -notmatch 'OMI_ConfigurationDocument')
    {
        if (Get-PSDefaultConfigurationDocument)
        {
            Write-Debug -Message " ${ConfigurationName}: Adding OMI_ConfigurationDocument from $(Get-PSDefaultConfigurationDocument)"
            $nodeDoc += Get-PSDefaultConfigurationDocument
        }
        else
        {
            Write-Debug -Message " ${ConfigurationName}: Adding missing OMI_ConfigurationDocument element to the document"
            if($Script:NodesPasswordEncrypted[$mofNode])
            {
                $nodeDoc += "`ninstance of OMI_ConfigurationDocument`n{`n Version=`"2.0.0`";`n MinimumCompatibleVersion = `"$($script:PSMetaConfigDocumentInstVersionInfo['MinimumCompatibleVersion'])`";`n CompatibleVersionAdditionalProperties= $(Get-CompatibleVersionAddtionaPropertiesStr);`n Author=`"$([system.environment]::UserName)`";`n GenerationDate=`"$(Get-Date)`";`n GenerationHost=`"$([system.environment]::MachineName)`";`n ContentType=`"PasswordEncrypted`";`n Name=`"$(Get-PSTopConfigurationName)`";`n};"
            }
            else
            {
                $nodeDoc += "`ninstance of OMI_ConfigurationDocument`n{`n Version=`"2.0.0`";`n MinimumCompatibleVersion = `"$($script:PSMetaConfigDocumentInstVersionInfo['MinimumCompatibleVersion'])`";`n CompatibleVersionAdditionalProperties= $(Get-CompatibleVersionAddtionaPropertiesStr);`n Author=`"$([system.environment]::UserName)`";`n GenerationDate=`"$(Get-Date)`";`n GenerationHost=`"$([system.environment]::MachineName)`";`n Name=`"$(Get-PSTopConfigurationName)`";`n};"
            }
        }
    }

    # Fix up newlines to be CRLF
    $nodeDoc = $nodeDoc -replace "`n", "`r`n"

    # todo: meta configuration might not be verifiable currently
    $errMsg = Test-MofInstanceText $nodeDoc
    if($errMsg)
    {
        $errorMessage = $LocalizedData.InvalidMOFDefinition -f @($mofNode, $errMsg)
        $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
        Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId InvalidMOFDefinition
        Update-ConfigurationErrorCount
        $nodeOutfile = "$ConfigurationOutputDirectory/$($mofNode).meta.mof.error"
    }

    if($nodeDocCount -gt 0)
    {
        # Write to a file only if no error was generated or we are writing to .mof.error file
        if ($Script:PSConfigurationErrors -eq 0 -or $nodeOutfile.EndsWith('mof.error'))
        {
            $nodeDoc > $nodeOutfile
            Get-ChildItem $nodeOutfile
        }
    }
}

# fixup localConfigmanager to have embedded instance
function Update-LocalConfigManager
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Scope="Function")]
    param(
        [string]
        $localConfigManager,
        [string]
        $resourceManagers,
        [string]
        $reportManagers,
        [string]
        $downloadManagers,
        [string]
        $partialConfigurations
    )

    $curlyPostion = $localConfigManager.LastIndexOf('}')
    if($curlyPostion -gt 0)
    {
        $first = $localConfigManager.Substring(0, $curlyPostion)
        if($resourceManagers)
        {
            $first += " ResourceModuleManagers = {`n" + $resourceManagers + " `n };`n"
        }

        if($reportManagers)
        {
            $first += " ReportManagers = {`n" + $reportManagers + " `n };`n"
        }

        if($downloadManagers)
        {
            $first += " ConfigurationDownloadManagers = {`n" + $downloadManagers + " `n };`n"
        }

        #PartialConfigurations
        if($partialConfigurations)
        {
            $first += " PartialConfigurations = {`n" + $partialConfigurations + " `n };`n"
        }

        $first += "};`n"

        $first
    }
}

function Get-MofInstanceName
{
    param(
        [string]
        $mofInstance
    )
}
#
# A function to write the MOF instance texts of a node to files
#
function Write-NodeMOFFile
{
    param(
        [string]
        $ConfigurationName,

        [string]
        $mofNode,

        [System.Collections.Generic.Dictionary[string,string]]
        $mofNodeHash
    )

    # Set up prefix for both the configuration and metaconfiguration documents.
    $nodeDoc = "/*`n@TargetNode='$mofNode'`n" + "@GeneratedBy=$([system.environment]::UserName)`n@GenerationDate=$(Get-Date)`n@GenerationHost=$([system.environment]::MachineName)`n*/`n"
    $nodeMetaDoc = $nodeDoc
    $nodeConfigurationDocument = $null
    [int]$metaDocCount = 0
    [int]$nodeDocCount = 0

    foreach($mofTypeName in $mofNodeHash.Keys)
    {
        if($mofTypeName -match 'MSFT_DSCMetaConfiguration')
        {
            $tempMetaDoc = $mofNodeHash[$mofTypeName]
            $metaDocCount++
            break
        }
    }

    foreach($mofTypeName in $mofNodeHash.Keys)
    {
        if(($mofTypeName -notmatch 'MSFT_DSCMetaConfiguration'))
        {
            if(($metaDocCount -gt 0) -and ($tempMetaDoc -match [regex]::Escape($mofTypeName)))
            {
                $nodeMetaDoc += $mofNodeHash[$mofTypeName]
            }
            else
            {
                if($mofTypeName -match 'OMI_ConfigurationDocument')
                {
                    $nodeConfigurationDocument = $mofNodeHash[$mofTypeName]
                }
                $nodeDoc += $mofNodeHash[$mofTypeName]
                $nodeDocCount++
            }
        }
    }

    $nodeMetaDoc += $tempMetaDoc

    $nodeOutfile = "$ConfigurationOutputDirectory/$($mofNode).mof"
    if($metaDocCount -gt 0)
    {
        $nodeMetaOutfile = "$ConfigurationOutputDirectory/$($mofNode).meta.mof"
        # this meta config uses v1 schema so will not have v2 properties
        if ($nodeConfigurationDocument)
        {
            Write-Debug -Message " ${ConfigurationName}: Adding OMI_ConfigurationDocument from the current node '$mofNode': $nodeConfigurationDocument"
            $nodeMetaDoc += $nodeConfigurationDocument
        }
        elseif (Get-PSDefaultConfigurationDocument)
        {
            Write-Debug -Message " ${ConfigurationName}: Adding OMI_ConfigurationDocument from $(Get-PSDefaultConfigurationDocument)"
            $nodeMetaDoc += Get-PSDefaultConfigurationDocument
        }
        else
        {
            Write-Debug -Message " ${ConfigurationName}: Adding missing OMI_ConfigurationDocument element to the document"
            if($Script:NodesPasswordEncrypted[$mofNode])
            {
                $nodeMetaDoc += "`ninstance of OMI_ConfigurationDocument`n{`n Version=`"2.0.0`";`n MinimumCompatibleVersion = `"1.0.0`";`n CompatibleVersionAdditionalProperties= $(Get-CompatibleVersionAddtionaPropertiesStr);`n Author=`"$([system.environment]::UserName)`";`n GenerationDate=`"$(Get-Date)`";`n GenerationHost=`"$([system.environment]::MachineName)`";`n ContentType=`"PasswordEncrypted`";`n Name=`"$(Get-PSTopConfigurationName)`";`n};"
            }
            else
            {
                $nodeMetaDoc += "`ninstance of OMI_ConfigurationDocument`n{`n Version=`"2.0.0`";`n MinimumCompatibleVersion = `"1.0.0`";`n CompatibleVersionAdditionalProperties= $(Get-CompatibleVersionAddtionaPropertiesStr);`n Author=`"$([system.environment]::UserName)`";`n GenerationDate=`"$(Get-Date)`";`n GenerationHost=`"$([system.environment]::MachineName)`";`n Name=`"$(Get-PSTopConfigurationName)`";`n};"
            }
        }
    }

    if ($nodeDoc -notmatch 'OMI_ConfigurationDocument')
    {
        if (Get-PSDefaultConfigurationDocument)
        {
            Write-Debug -Message " ${ConfigurationName}: Adding OMI_ConfigurationDocument from $(Get-PSDefaultConfigurationDocument)"
            $nodeDoc += Get-PSDefaultConfigurationDocument
        }
        else
        {
            Write-Debug -Message " ${ConfigurationName}: Adding missing OMI_ConfigurationDocument element to the document"
            if($Script:NodesPasswordEncrypted[$mofNode])
            {
                if($nodeDoc.Contains("PsDscRunAsCredential"))
                {
                    $nodeDoc += "`ninstance of OMI_ConfigurationDocument`n
                    {`n Version=`"2.0.0`";`n
                        MinimumCompatibleVersion = `"2.0.0`";`n
                        CompatibleVersionAdditionalProperties= {`"Omi_BaseResource:ConfigurationName`"};`n
                        Author=`"$([system.environment]::UserName)`";`n
                        GenerationDate=`"$(Get-Date)`";`n
                        GenerationHost=`"$([system.environment]::MachineName)`";`n
                        ContentType=`"PasswordEncrypted`";`n
                        Name=`"$(Get-PSTopConfigurationName)`";`n
                    };"

                }
                else
                {
                    $nodeDoc += "`ninstance of OMI_ConfigurationDocument`n
                    {`n Version=`"2.0.0`";`n
                        MinimumCompatibleVersion = `"1.0.0`";`n
                        CompatibleVersionAdditionalProperties= {`"Omi_BaseResource:ConfigurationName`"};`n
                        Author=`"$([system.environment]::UserName)`";`n
                        GenerationDate=`"$(Get-Date)`";`n
                        GenerationHost=`"$([system.environment]::MachineName)`";`n
                        ContentType=`"PasswordEncrypted`";`n
                        Name=`"$(Get-PSTopConfigurationName)`";`n
                    };"

                }
            }
            else
            {
                if($nodeDoc.Contains("PsDscRunAsCredential"))
                {
                    $nodeDoc += "`ninstance of OMI_ConfigurationDocument`n
                    {`n Version=`"2.0.0`";`n
                        MinimumCompatibleVersion = `"2.0.0`";`n
                        CompatibleVersionAdditionalProperties= {`"Omi_BaseResource:ConfigurationName`"};`n
                        Author=`"$([system.environment]::UserName)`";`n
                        GenerationDate=`"$(Get-Date)`";`n
                        GenerationHost=`"$([system.environment]::MachineName)`";`n
                        Name=`"$(Get-PSTopConfigurationName)`";`n
                    };"

                }
                else
                {
                    $nodeDoc += "`ninstance of OMI_ConfigurationDocument`n
                    {`n Version=`"2.0.0`";`n
                        MinimumCompatibleVersion = `"1.0.0`";`n
                        CompatibleVersionAdditionalProperties= {`"Omi_BaseResource:ConfigurationName`"};`n
                        Author=`"$([system.environment]::UserName)`";`n
                        GenerationDate=`"$(Get-Date)`";`n
                        GenerationHost=`"$([system.environment]::MachineName)`";`n
                        Name=`"$(Get-PSTopConfigurationName)`";`n
                    };"

                }
            }
        }
    }
    # Fix up newlines to be CRLF
    $nodeDoc = $nodeDoc -replace "`n", "`r`n"

    $errMsg = Test-MofInstanceText $nodeDoc
    if($errMsg)
    {
        $errorMessage = $LocalizedData.InvalidMOFDefinition -f @($mofNode, $errMsg)
        $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
        Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId InvalidMOFDefinition
        Update-ConfigurationErrorCount
        $nodeOutfile = "$ConfigurationOutputDirectory/$($mofNode).mof.error"
    }

    if($nodeDocCount -gt 0)
    {
        # Write to a file only if no error was generated or we are writing to .mof.error file
        if ($Script:PSConfigurationErrors -eq 0 -or $nodeOutfile.EndsWith('mof.error'))
        {
            $nodeDoc > $nodeOutfile
            Get-ChildItem $nodeOutfile
        }
    }

    if($nodeMetaDoc -match 'MSFT_DSCMetaConfiguration' -and $Script:PSConfigurationErrors -eq 0)
    {
        $nodeMetaDoc = $nodeMetaDoc -replace "`n", "`r`n"
        $nodeMetaDoc > $nodeMetaOutfile
        Get-ChildItem $nodeMetaOutfile
    }
}

#
# A function to make sure that only valid resources are referenced within a node. It
# operates off of the $Script:NodeResources dictionary. An empty dictionary is not
# considered an error since this function is called at both the node level and the configuration
# level.
# This function also expand the dependsOn to composite resources
#
function ValidateNodeResources
{
    Write-Debug -Message " Validating resource set for node: $(Get-PSCurrentConfigurationNode)"
    $newNodeResources = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,[string[]]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
    if ($Script:NodeResources)
    {
        foreach ($resourceId in $Script:NodeResources.Keys)
        {
            Write-Debug -Message " Checking node $resourceId"
            $newDependsOn = foreach ($requiredResource in $Script:NodeResources[$resourceId])
            {
                # Skip resources that have no DependsOn.
                if ($requiredResource)
                {
                    Write-Debug -Message " > Checking for required node $requiredResource"

                    if (-not $Script:NodeResources.ContainsKey($requiredResource))
                    {
                        Write-Debug -Message " > trying expand for required node $requiredResource"
                        $expandedDependsOn = foreach($rId in $Script:NodeResources.keys)
                        {
                            if($rId.EndsWith($requiredResource, [System.StringComparison]::InvariantCultureIgnoreCase))
                            {
                                $rId
                            }
                        }


                        if(-not $expandedDependsOn)
                        {
                            $errorMessage = $LocalizedData.RequiredResourceNotFound -f @($requiredResource, $resourceId)
                            $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
                            Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId RequiredResourceNotFound
                            Update-ConfigurationErrorCount
                        }
                        else
                        {
                            $expandedDependsOn
                        }
                    }
                    else
                    {
                        $requiredResource
                    }
                }
            }

            if($newDependsOn)
            {
                $newNodeResources[$resourceId] = $newDependsOn
            }
        }

        if($newNodeResources)
        {
            foreach($id in $newNodeResources.keys)
            {
                $Script:NodeResources[$id] = $newNodeResources[$id]
            }
        }
    }
    Write-Debug -Message " Validation complete for node: $(Get-PSCurrentConfigurationNode)"
}

#
# A function to make sure that only valid resources are referenced within the resource without node.
# This function also expand the dependsOn to composite resources
#
function ValidateNoNameNodeResources
{
    Write-Debug -Message ' Validating resource set for resources in the default configuration'
    $newNodeResources = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,[string[]]]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
    if ($Script:NoNameNodesResources)
    {
        foreach ($resourceId in $Script:NoNameNodesResources.Keys)
        {
            Write-Debug -Message " Checking node $resourceId"
            $newDependsOn = foreach ($requiredResource in $Script:NoNameNodesResources[$resourceId])
            {
                # Skip resources that have no DependsOn.
                if ($requiredResource)
                {
                    Write-Debug -Message " > Checking for required node $requiredResource"

                    if (-not $Script:NoNameNodesResources.ContainsKey($requiredResource))
                    {
                        Write-Debug -Message " > trying expand for required node $requiredResource"
                        $expandedDependsOn = foreach($rId in $Script:NoNameNodesResources.keys)
                        {
                            if($rId.EndsWith($requiredResource, [System.StringComparison]::InvariantCultureIgnoreCase))
                            {
                                $rId
                            }
                        }


                        if(-not $expandedDependsOn)
                        {
                            $errorMessage = $LocalizedData.RequiredResourceNotFound -f @($requiredResource, $resourceId)
                            $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
                            Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId RequiredResourceNotFound
                            Update-ConfigurationErrorCount
                        }
                        else
                        {
                            $expandedDependsOn
                        }
                    }
                    else
                    {
                        $requiredResource
                    }
                }
            }

            if($newDependsOn)
            {
                $newNodeResources[$resourceId] = $newDependsOn
            }
        }

        if($newNodeResources)
        {
            foreach($id in $newNodeResources.keys)
            {
                $Script:NoNameNodesResources[$id] = $newNodeResources[$id]
            }
        }
    }
    Write-Debug -Message ' Validation complete for default resources.'
}

#
# A function to make sure that only valid Manager are referenced within a node. It
# operates off of the $Script:NodeManager dictionary.
# An empty dictionary is not
# considered an error since this function is called at both the node level and the configuration
# level.
#
function ValidateNodeManager
{
    Write-Debug -Message " Validating manager set for node: $(Get-PSCurrentConfigurationNode)"
    if ($Script:NodeManager)
    {
        foreach ($resourceId in $Script:NodeManager.Keys)
        {
            Write-Debug -Message " Checking node $resourceId"
            $refManagers = $Script:NodeManager[$resourceId]
            # Skip partial configuration that have no Manager.
            if ($refManagers)
            {
                Write-Debug -Message " > Checking for required manager $refManagers"

                foreach ($refManager in $refManagers)
                {
                    if (-not  $Script:NodeResources.ContainsKey($refManager))
                    {
                        $errorMessage = $LocalizedData.ReferencedManagerNotFound -f @($refManager, $resourceId)
                        $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
                        Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId ReferencedManagerNotFound
                        Update-ConfigurationErrorCount
                    }
                }
            }
        }
    }
    Write-Debug -Message " Validation complete for node: $(Get-PSCurrentConfigurationNode)"
}

#
# A function to make sure that only valid resource source are referenced within a node. It
# operates off of the $Script:NodeResourceSource dictionary.
# An empty dictionary is not
# considered an error since this function is called at both the node level and the configuration
# level.
#
function ValidateNodeResourceSource
{
    Write-Debug -Message " Validating resource source set for node: $(Get-PSCurrentConfigurationNode)"
    if ($Script:NodeResourceSource)
    {
        foreach ($resourceId in $Script:NodeResourceSource.Keys)
        {
            Write-Debug -Message " Checking node $resourceId"
            $refResourceSources = $Script:NodeResourceSource[$resourceId]
            # Skip partial configuration that have no Manager.
            if ($refResourceSources)
            {
                Write-Debug -Message " > Checking for required manager $refManagers"

                foreach ($refResourceSource in $refResourceSources)
                {
                    if (-not  $Script:NodeResources.ContainsKey($refResourceSource))
                    {
                        $errorMessage = $LocalizedData.ReferencedResourceSourceNotFound -f @($refResourceSource, $resourceId)
                        $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
                        Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId ReferencedResourceSourceNotFound
                        Update-ConfigurationErrorCount
                    }
                }
            }
        }
    }
    Write-Debug -Message " Validation complete for node: $(Get-PSCurrentConfigurationNode)"
}

#
# A function to make sure that exclusive resources among partial configurations do not conflict with each other
# it is already of format modulename\resourcName or modulename\* or resourceName
# It also validate they exist
function ValidateNodeExclusiveResources
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "", Target="", Scope="Function")]
    param()

    Write-Debug -Message " Validating exclusive resources for node: $(Get-PSCurrentConfigurationNode)"
    if ($Script:NodeExclusiveResources)
    {
        # map will be of format {moduleName, {Id=ResourceId; ContainsAll = $true/$false; Resources=@('r1','r2'...)}}
        $ModuleBasedExclusiveResourceMap = @{}
        # map for resource that doesn't have module
        $NoModuleExclusiveResourceMap = @{}

        foreach ($resourceId in $Script:NodeExclusiveResources.Keys)
        {
            Write-Debug -Message " Checking node $resourceId"

            # Remove duplicate entries from exclusive Resource array
            $exclusiveResourceList = $Script:NodeExclusiveResources[$resourceId] | Select-Object -Unique

            foreach($refResource in $exclusiveResourceList)
            {
                $resourceSegs = $refResource -split '\\'

                if($resourceSegs.Length -eq 2)
                {
                    if($null -eq $ModuleBasedExclusiveResourceMap[$resourceSegs[0]])
                    {
                        $ModuleBasedExclusiveResourceMap[$resourceSegs[0]] = @{
                            Id = $resourceId
                        }
                        if($resourceSegs[1] -eq '*')
                        {
                            $ModuleBasedExclusiveResourceMap[$resourceSegs[0]]['ContainsAll'] = $true
                        }
                        else
                        {
                            $ModuleBasedExclusiveResourceMap[$resourceSegs[0]]['ContainsAll'] = $false
                            $ModuleBasedExclusiveResourceMap[$resourceSegs[0]]['Resources'] = @($resourceSegs[1])
                        }
                    }
                    else
                    {
                        # 'Module\Resource' in PartialConfiguration1 conflicts with 'Module\*' in PartialConfiguration2
                        # or 'Module\*' in PartialConfiguration1 conflicts with 'Module\Resource' in PartialConfiguration2
                        if(($resourceSegs[1] -eq '*') -or $ModuleBasedExclusiveResourceMap[$resourceSegs[0]]['ContainsAll'])
                        {
                            $errorMessage = $LocalizedData.ConflictInExclusiveResources -f @($ModuleBasedExclusiveResourceMap[$resourceSegs[0]]['Id'], $resourceId)
                            $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
                            Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId ConflictInExclusiveResources
                            Update-ConfigurationErrorCount
                            break
                        }
                        # 'Module\Resource' in PartialConfiguration1 conflicts with 'Module\Resource' in PartialConfiguration2
                        # or 'Module\Resource' in PartialConfiguration1 conflicts with 'Resource' in PartialConfiguration2
                        elseif($ModuleBasedExclusiveResourceMap[$resourceSegs[0]]['Resources'] -icontains $resourceSegs[1] `
                                    -or $null -ne $NoModuleExclusiveResourceMap[$resourceSegs[1]])
                        {
                            $errorMessage = $LocalizedData.ConflictInExclusiveResources -f @($ModuleBasedExclusiveResourceMap[$resourceSegs[0]]['Id'], $resourceId)
                            $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
                            Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId ConflictInExclusiveResources
                            Update-ConfigurationErrorCount
                            break
                        }
                        else
                        {
                            $ModuleBasedExclusiveResourceMap[$resourceSegs[0]]['Resources'] += @($resourceSegs[1])
                        }
                    }
                }
                else # no module name, normally means binary resource
                {
                    if($null -eq $NoModuleExclusiveResourceMap[$refResource])
                    {
                        $resourceFound = $false
                        $ModuleBasedExclusiveResourceMap.GetEnumerator() | ForEach-Object {
                            if($_.Value.Resources -icontains $refResource) {
                                $resourceFound = $true
                                $ConflictingPartialConfigurationId = $_.Value.Id
                            }
                        }

                        # 'Resource' in PartialConfiguration1 conflicts with 'Module\Resource' in PartialConfiguration2
                        if($resourceFound){
                            $errorMessage = $LocalizedData.ConflictInExclusiveResources -f @($ConflictingPartialConfigurationId, $resourceId)
                            $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
                            Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId ConflictInExclusiveResources
                            Update-ConfigurationErrorCount
                            break
                        }

                        $NoModuleExclusiveResourceMap[$refResource] = @{
                            Id = $resourceId
                        }
                    }
                    # 'Resource' in PartialConfiguration1 conflicts with 'Resource' in PartialConfiguration2
                    else
                    {
                        $errorMessage = $LocalizedData.ConflictInExclusiveResources -f @($NoModuleExclusiveResourceMap[$refResource]['Id'], $resourceId)
                        $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
                        Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId ConflictInExclusiveResources
                        Update-ConfigurationErrorCount
                        break
                    }
                }
            }
        }
    }
    Write-Debug -Message " Validation complete for node: $(Get-PSCurrentConfigurationNode)"
}

#
# A function to make sure that only valid resources are referenced within a node. It
# operates off of the $Script:NodeResources dictionary. An empty dictionary is not
# considered an error since this function is called at both the node level and the configuration
# level.
# it uses Tarjan strongly connected component algorithms
#
function ValidateNoCircleInNodeResources
{
    Write-Debug -Message " Validating resource set for node: $(Get-PSCurrentConfigurationNode)"
    [int] $script:CircleIndex = 0
    [System.Collections.Generic.Stack[string]] $script:resourceIdStack =
    New-Object -TypeName 'System.Collections.Generic.Stack[string]'
    [hashtable] $script:resourceIndex = @{}
    [hashtable] $script:resourceLowIndex = @{}
    [int] $script:ComponentDepth = 0
    [int] $script:MaxComponentDepth = 1024

    if ($Script:NodeResources)
    {
        foreach ($resourceId in $Script:NodeResources.Keys)
        {
            if($null -ne ($Script:NodeResources[$resourceId]) -and $Script:NodeResources[$resourceId].Contains($resourceId))
            {
                $errorMessage = $LocalizedData.DependsOnLoopDetected -f "$resourceId->$resourceId"
                $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
                Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId DependsOnLoopDetected
                Update-ConfigurationErrorCount
            }

            if($null -eq $resourceIndex[$resourceId])
            {
                $script:ComponentDepth = 0
                StrongConnect($resourceId)
            }
        }
    }
    Write-Debug -Message " Validation circular reference completed for node: $(Get-PSCurrentConfigurationNode)"
}

function StrongConnect
{
    param ([string]$resourceId)

    $script:resourceIndex[$resourceId] = $script:CircleIndex
    $script:resourceLowIndex[$resourceId] = $script:CircleIndex
    $script:CircleIndex++
    $script:ComponentDepth++
    if($script:ComponentDepth -gt $script:MaxComponentDepth)
    {
        $errorMessage = $LocalizedData.DependsOnLinkTooDeep -f $script:MaxComponentDepth
        $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
        Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId DependsOnLinkTooDeep
        Update-ConfigurationErrorCount
    }

    $script:resourceIdStack.Push($resourceId)

    foreach ($requiredResource in $Script:NodeResources[$resourceId])
    {
        Write-Debug -Message " > Checking for required node $requiredResource"
        #$requiredResource is not visited yet
        if($null -ne ($requiredResource) -and ($null -eq $script:resourceIndex[$requiredResource]))
        {
            StrongConnect($requiredResource)
            $script:resourceLowIndex[$resourceId] = [math]::Min($script:resourceLowIndex[$resourceId], $script:resourceLowIndex[$requiredResource])
        }
        elseif($script:resourceIdStack -Contains $requiredResource)
        {
            $script:resourceLowIndex[$resourceId] = [math]::Min($script:resourceLowIndex[$resourceId], $script:resourceIndex[$requiredResource])
        }
    }

    if($script:resourceIndex[$resourceId] -eq $script:resourceLowIndex[$resourceId])
    {
        $resourceCount = 0
        $circularLinks = ''
        do
        {
            $a = $script:resourceIdStack.Pop()
            $circularLinks += "->$a"
            $resourceCount++
        }
        while($a -ne $resourceId)

        if($resourceCount -gt 1)
        {
            $errorMessage = $LocalizedData.DependsOnLoopDetected -f $circularLinks
            $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
            Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId DependsOnLoopDetected
            Update-ConfigurationErrorCount
        }
    }
}

#
# Returns any validation error messages
#
function Test-MofInstanceText
{
    param (
        [Parameter(Mandatory)]
        $instanceText
    )

    # Ignore empty instances...
    if ( $instanceText)
    {
        try
        {
            [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::ValidateInstanceText($instanceText)
        }
        catch [System.Management.Automation.MethodInvocationException]
        {
            # Return the exception message from the inner most ErrorRecord
            $ErrorRecord = Get-InnerMostErrorRecord $_
            $ErrorRecord.Exception.Message
        }
    }
}

#
# Encrypt a password using CMS
#
function Get-EncryptedPassword
{
    param (
        [Parameter()]
        $Value = $null
    )

    if($Node -and $selectedNodesData)
    {
        if($selectedNodesData -is [array])
        {
            foreach($target in $selectedNodesData)
            {
                # Node name should be exactly the same as one defined in AllNodes
                # -eq does case in sensitive comparison
                if($target['NodeName'] -and $target['NodeName'] -eq $Node)
                {
                    $currentNode = $target
                }
            }
        }
        else
        {
            $currentNode = $selectedNodesData
        }
    }
    # where user need to specify properties for resources not in a node,
    # they can do it through localhost nodeName in $allNodes
    elseif($allnodes -and $allnodes.AllNodes)
    {
        foreach($target in $allnodes.AllNodes)
        {
            if($target['NodeName'] -and $target['NodeName'] -eq 'localhost')
            {
                $currentNode = $target
            }
        }
    }

    if($currentNode)
    {
        # if Certificate is provided, it override PSDscAllowPlainTextPassword : bug 565167
        # CertificateID is currently assumed to be the 'thumbprint' from the certificate
        # Protect-CmsMessage [-To] takes actual cert, path to cert file, path to a directory contains cert, thumbprint or subject name of cert
        # we only support cert file and thumbprint as before now
        $certificateid = $currentNode['CertificateID']

        # If there is no certificateid defined, just return the original value...
        if ( -not $certificateid)
        {
            # CertificateFile is the public key file
            $certificatefile = $currentNode['CertificateFile']

            if ( -not $certificatefile)
            {
                return $Value
            }
            else
            {
                $CmsMessageRecipient = $certificatefile
            }
        }
        else
        {
            $CmsMessageRecipient = $certificateid
        }
    }

    if($CmsMessageRecipient -and $Value -is [string])
    {
        # Encrypt using the public key
        $encMsg = Protect-CmsMessage -To $CmsMessageRecipient -Content $Value

        # Reverse bytes for unmanaged decryption
        #[Array]::Reverse($encbytes)

        #$encMsg = $encMsg -replace '-----BEGIN CMS-----',''
        #$encMsg = $encMsg -replace "`n",''
        #$encMsg = $encMsg -replace '-----END CMS-----',''

        return $encMsg
    }
    else
    {
        # passwords should be some type of string so this is probably an error but pass
        # back the incoming value. Also if there is no key, then we just pass through the
        # password as is.

        $Value
    }
}


#
# Retrieve a public key that can be used for encryption. Matching on thumbprint
#
function Get-PublicKeyFromStore
{
    param(
        [Parameter(Mandatory)]
        [string]
        $certificateid
    )

    $cert = $null

    foreach($certIndex in Get-ChildItem -Path cert:\LocalMachine\My)
    {
        if($certIndex.Thumbprint -match $certificateid)
        {
            $cert = $certIndex
            break
        }
    }

    if(-not $cert)
    {
        $errorMessage = $($LocalizedData.CertificateStoreReadError) -f $certificateid
        ThrowError -ExceptionName 'System.InvalidOperationException' -ExceptionMessage $errorMessage -ExceptionObject $certificateid -ErrorId 'InvalidPathSpecified' -ErrorCategory InvalidOperation
    }
    else
    {
        $cert
    }
}

#
# Retrieve a public key that can be used for encryption. Certificate loaded from
# a certificate file
#
function Get-PublicKeyFromFile
{
    param(
        [Parameter(Mandatory)]
        [string]
        $certificatefile
    )

    try
    {
        $cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2

        if($cert)
        {
            $cert.Import($certificatefile)
            $cert
        }
    }
    catch
    {
        $errorMessage = $($LocalizedData.CertificateFileReadError) -f $certificatefile
        ThrowError -ExceptionName 'System.InvalidOperationException' -ExceptionMessage $errorMessage -ExceptionObject $certificatefile -ErrorId 'InvalidPathSpecified' -ErrorCategory InvalidOperation
    }
}


###########################################################
#
# Checksum generation functions.
#
###########################################################

#-----------------------------------------------------------------------------------------------------
# New-DscChecksum cmdlet is used to create corresponding checksum files for a specified file or folder
#-----------------------------------------------------------------------------------------------------
function New-DscChecksum
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSProvideCommentHelp", "", Scope="Function", Target="*")]
    [CmdletBinding(SupportsShouldProcess = $true, HelpUri = 'http://go.microsoft.com/fwlink/?LinkId=403986')]
    param(
        [Parameter(Mandatory)]
        [Alias('ConfigurationPath')]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Path,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]
        $OutPath = $null,

        [switch]
        $Force
    )

    # Check validity of all configuration paths specified, throw if any of them is invalid
    for ($i = 0 ; $i -lt $Path.Length ; $i++)
    {
        if (!(Test-Path -Path $Path[$i]))
        {
            $errorMessage = $LocalizedData.InvalidConfigPath -f $Path[$i]
            ThrowError -ExceptionName 'System.ArgumentException' -ExceptionMessage $errorMessage -ExceptionObject $Path[$i] -ErrorId 'InvalidConfigurationPath' -ErrorCategory InvalidArgument
        }
    }

    # If an OutPath is specified, handle its creation and error conditions
    if ($OutPath)
    {
        # If and invalid path syntax is specified in $Outpath, throw
        if(([System.IO.Path]::InvalidPathChars | ForEach-Object -Process {
                    $OutPath.Contains($_)
                }
        ).IndexOf($true)[0] -ne -1)
        {
            $errorMessage = $LocalizedData.InvalidOutpath -f $OutPath
            ThrowError -ExceptionName 'System.ArgumentException' -ExceptionMessage $errorMessage -ExceptionObject $OutPath -ErrorId 'InvalidOutPath' -ErrorCategory InvalidArgument
        }

        # If the specified $Outpath conflicts with an existing file, throw
        if(Test-Path -Path $OutPath -PathType Leaf)
        {
            $errorMessage = $LocalizedData.OutpathConflict -f $OutPath
            ThrowError -ExceptionName 'System.ArgumentException' -ExceptionMessage $errorMessage -ExceptionObject $OutPath -ErrorId 'InvalidOutPath' -ErrorCategory InvalidArgument
        }

        # IF THE CONTROL REACHED HERE, $OutPath IS A VALID DIRECTORY PATH WHICH HAS NO CONFLICT WITH AN EXISTING FILE

        # If $OutPath doesn't exist, create it
        if(!(Test-Path -Path $OutPath))
        {
            $null = New-Item -Path $OutPath -ItemType Directory
        }

        $OutPath = (Resolve-Path $OutPath).ProviderPath
    }

    # Retrieve all valid configuration files at the specified $Path
    $allConfigFiles = $Path | ForEach-Object  -Process {
        (Get-ChildItem -Path $_ -Recurse | Where-Object -FilterScript {
                $_.Extension -eq '.mof' -or $_.Extension -eq '.zip'
            }
        )
    }

    # If no valid config file was found, log this and return
    if ($allConfigFiles.Length -eq 0)
    {
        Write-Log -Message $LocalizedData.NoValidConfigFileFound

        return
    }

    # IF THE CONTROL REACHED HERE, VALID CONFIGURATION FILES HAVE BEEN FOUND AND WE NEED TO CALCULATE THEIR HASHES

    foreach ($file in $allConfigFiles)
    {
        $fileOutpath = "$($file.FullName).checksum"
        if ($OutPath)
        {
            $fileOutpath = "$OutPath\$($file.Name).checksum"
        }

        # If the Force parameter was not specified and the hash file already exists for the current file, log this, and skip this file
        if (!$Force -and (Get-Item -Path $fileOutpath -ErrorAction Ignore))
        {
            Write-Log -Message ($LocalizedData.CheckSumFileExists -f $fileOutpath)
            continue
        }

        # Devise appropriate message
        $message = $LocalizedData.CreateChecksumFile -f $fileOutpath
        if (Test-Path -Path $fileOutpath)
        {
            $message = $LocalizedData.OverwriteChecksumFile -f $fileOutpath
        }

        # Finally, if the hash file doesn't exist already or -Force has been specified, then output the corresponding hash file
        if ($pscmdlet.ShouldProcess($message, $null, $null))
        {
            [String]$checksum = (Get-FileHash -Path $file.FullName -Algorithm SHA256).Hash

            WriteFile -Path $fileOutpath -Value $checksum
        }
    }
}
Export-ModuleMember -Function New-DscChecksum


#------------------------------------
# Utility to throw an error/exception
#------------------------------------
function ThrowError
{
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ExceptionName,

        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ExceptionMessage,

        [System.Object]
        $ExceptionObject,

        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $errorId,

        [parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [System.Management.Automation.ErrorCategory]
        $errorCategory
    )

    $exception = New-Object $ExceptionName $ExceptionMessage
    $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception, $errorId, $errorCategory, $ExceptionObject
    throw $ErrorRecord
}

#----------------------------------------
# Utility to write WhatIf or Verbose logs
#----------------------------------------
function Write-Log
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $message
    )

    if ($pscmdlet.ShouldProcess($message, $null, $null))
    {
        Write-Verbose -Message $message
    }
}

# WriteFile is a helper function used to write the content to the file
function WriteFile
{
    param(
        [parameter(Mandatory)]
        [string]
        $Value,

        [parameter(Mandatory)]
        [string]
    $Path)

    try
    {
        [system.io.streamwriter]$stream = New-Object -TypeName system.io.StreamWriter -ArgumentList ($Path, $false)
        try
        {
            [void] $stream.Write($Value)
        }
        finally
        {
            if ($stream)
            {
                $stream.Close()
            }
        }
    }
    catch
    {
        $errorMessage = $LocalizedData.FileReadError -f $Path
        $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
        Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId InvalidPathSpecified
        Update-ConfigurationErrorCount
    }
}

#
# ReadEnvironmentFile imports the contents of a
# file as ConfigurationData
#
function ReadEnvironmentFile
{
    param(
        [parameter(Mandatory)]
        [string]
    $FilePath)

    try
    {
        $resolvedPath = Resolve-Path $FilePath
    }
    catch
    {
        $errorMessage = $LocalizedData.FilePathError -f $FilePath
        $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
        Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId InvalidPathSpecified
        Update-ConfigurationErrorCount
    }

    try
    {
        $content = Get-Content $resolvedPath -Raw
        $sb = [scriptblock]::Create($content)
        $sb.CheckRestrictedLanguage($null, $null, $true)
        $sb.Invoke()
    }
    catch
    {
        $errorMessage = $LocalizedData.EnvironmentContentError -f $FilePath
        $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
        Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId InvalidEnvironmentContentSpecified
        Update-ConfigurationErrorCount
    }
}

function Get-DSCResourceModules
{
    $listPSModuleFolders = $env:PSModulePath.Split([IO.Path]::PathSeparator)
    $dscModuleFolderList = [System.Collections.Generic.HashSet[System.String]]::new()

    foreach ($folder in $listPSModuleFolders)
    {
        if (!(Test-Path $folder))
        {
            continue
        }

        foreach($moduleFolder in Get-ChildItem $folder -Directory)
        {
            $addModule = $false

            $dscFolders = Get-childitem "$($moduleFolder.FullName)\DscResources","$($moduleFolder.FullName)\*\DscResources" -ErrorAction Ignore
            if($null -ne $dscFolders)
            {
                $addModule = $true
            }

            if(-not $addModule)
            {
                foreach($psd1 in Get-ChildItem -Recurse -Filter "$($moduleFolder.Name).psd1" -Path $moduleFolder.fullname -Depth 2)
                {
                    $containsDSCResource = select-string -LiteralPath $psd1 -pattern '^[^#]*\bDscResourcesToExport\b.*'
                    if($null -ne $containsDSCResource)
                    {
                        $addModule = $true
                    }
                }
            }

            if($addModule)
            {
                $dscModuleFolderList.Add($moduleFolder.Name)
            }
        }
    }

    $dscModuleFolderList
}

###########################################################
# Get-DSCResource
###########################################################

#
# Gets DSC resources on the machine. Allows to filter on a particular resource.
# It parses all the resources defined in the schema.mof file and also the composite
# resources defined or imported from PowerShell modules
#
function Get-DscResource
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSProvideCommentHelp", "", Scope="Function", Target="*")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPositionalParameters", "", Scope="Function", Target="*")]
    [CmdletBinding(HelpUri = 'http://go.microsoft.com/fwlink/?LinkId=403985')]
    [OutputType('Microsoft.PowerShell.DesiredStateConfiguration.DscResourceInfo[]')]
    [OutputType('string[]')]
    param (
        [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Name,
        [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [Object]
        $Module,

        [Parameter()]
        [switch]
        $Syntax
    )

    Begin
    {
        $initialized = $false
        $ModuleString = $null
        Write-Progress -Id 1 -Activity $LocalizedData.LoadingDefaultCimKeywords

        $keywordErrors = New-Object -TypeName 'System.Collections.ObjectModel.Collection[System.Exception]'

        # Load the default Inbox providers (keyword) in cache, also allow caching the resources from multiple versions of modules.
        [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::LoadDefaultCimKeywords($keywordErrors, $true)

        foreach($ex in $keywordErrors)
        {
            Write-Error -Exception $ex
            if($ex.InnerException)
            {
                Write-Error -Exception $ex.