CimKeywordImplementationFunction.ps1
|
param ( [Parameter(Mandatory)] $KeywordData, [Parameter(Mandatory)] $Name, [Parameter(Mandatory)] [Hashtable] $Value, [Parameter(Mandatory)] $SourceMetadata ) $complexResourceQualifier = Get-ComplexResourceQualifier -IncludeCurrent # # Utility function used to validate that the DependsOn arguments are well-formed. # The function also adds them to the define nodes resource collection. # in the case of resources generated inside a script resource, this routine # will also fix up the DependsOn references to '[Type]Instance::[OuterType]::OuterInstance # function Test-DependsOn { # make sure the references are well-formed $updatedDependsOn = foreach ($DependsOnVar in $value['DependsOn']) { # match [ResourceType]ResourceName. ResourceName should starts with [a-z_0-9] followed by [a-z_0-9\p{Zs}\.\\-]* if ($DependsOnVar -notmatch '^\[[a-z]\w*\][a-z_0-9][a-z_0-9\p{Zs}\.\\-]*$') { Update-ConfigurationErrorCount Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::GetBadlyFormedRequiredResourceIdErrorRecord($DependsOnVar, $resourceId)) } # Fix up DependsOn for nested names if ($MyTypeName -and $typeName -ne $MyTypeName -and $InstanceName) { "$DependsOnVar::$complexResourceQualifier" } else { $DependsOnVar } } $value['DependsOn']= $updatedDependsOn if($null -ne $DependsOn) { # Combine DependsOn with dependson from outer composite resource # which is set as local variable $DependsOn at the composite resource context $value['DependsOn']= @($value['DependsOn']) + $DependsOn } # Save the resource id in a per-node dictionary to do cross validation at the end Set-NodeResources $resourceId @( $value['DependsOn']) # Remove depends on because it need to be fixed up for composite resources # We do it in ValidateNodeResource and Update-Depends on in configuration/Node function $value.Remove('DependsOn') } # A copy of the value object with correctly-cased property names $canonicalizedValue = @{} $typeName = $keywordData.ResourceName # CIM type $keywordName = $keywordData.Keyword # user-friendly alias that is used in scripts $keyValues = '' $debugPrefix = " ${TypeName}:" # set up a debug prefix string that makes it easier to track what's happening. Write-Debug "${debugPrefix} RESOURCE PROCESSING STARTED [KeywordName='$keywordName'] Function='$($myinvocation.Invocationname)']" # Check whether it's an old style metaconfig $OldMetaConfig = $false if ((-not $IsMetaConfig) -and ($keywordName -ieq 'LocalConfigurationManager')) { $OldMetaConfig = $true } # Check to see if it's a resource keyword. If so add the meta-properties to the canonical property collection. $resourceId = $null # todo: need to include configuration managers and partial configuration if (($keywordData.Properties.Keys -contains 'DependsOn') -or (($KeywordData.ImplementingModule -ieq 'PSDesiredStateConfigurationEngine') -and ($KeywordData.NameMode -eq [System.Management.Automation.Language.DynamicKeywordNameMode]::NameRequired))) { $resourceId = "[$keywordName]$name" if ($MyTypeName -and $keywordName -ne $MyTypeName -and $InstanceName) { $resourceId += "::$complexResourceQualifier" } Write-Debug "${debugPrefix} ResourceID = $resourceId" # copy the meta-properties $canonicalizedValue['ResourceID'] = $resourceId $canonicalizedValue['SourceInfo'] = $SourceMetadata if(-not $IsMetaConfig) { $canonicalizedValue['ModuleName'] = $keywordData.ImplementingModule $canonicalizedValue['ModuleVersion'] = $keywordData.ImplementingModuleVersion -as [string] } # see if there is already a resource with this ID. if (Test-NodeResources $resourceId) { Update-ConfigurationErrorCount Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::DuplicateResourceIdInNodeStatementErrorRecord($resourceId, (Get-PSCurrentConfigurationNode))) } else { # If there are prerequisite resources, validate that the references are well-formed strings # This routine also adds the resource to the global node resources table. Test-DependsOn # Check if PsDscRunCredential is being specified as Arguments to Configuration if($null -ne $PsDscRunAsCredential) { # Check if resource is also trying to set the value for RunAsCred # In that case we will generate error during compilation, this is merge error if($null -ne $value['PsDscRunAsCredential']) { Update-ConfigurationErrorCount Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::PsDscRunAsCredentialMergeErrorForCompositeResources($resourceId)) } # Set the Value of RunAsCred to that of outer configuration else { $value['PsDscRunAsCredential'] = $PsDscRunAsCredential } } # Save the resource id in a per-node dictionary to do cross validation at the end if($keywordData.ImplementingModule -ieq "PSDesiredStateConfigurationEngine") { #$keywordName is PartialConfiguration if($keywordName -eq 'PartialConfiguration') { # RefreshMode is 'Pull' and .ConfigurationSource is empty if($value['RefreshMode'] -eq 'Pull' -and -not $value['ConfigurationSource']) { Update-ConfigurationErrorCount Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::GetPullModeNeedConfigurationSource($resourceId)) } # Verify that RefreshMode is not Disabled for Partial configuration if($value['RefreshMode'] -eq 'Disabled') { Update-ConfigurationErrorCount Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::DisabledRefreshModeNotValidForPartialConfig($resourceId)) } if($null -ne $value['ConfigurationSource']) { Set-NodeManager $resourceId $value['ConfigurationSource'] } if($null -ne $value['ResourceModuleSource']) { Set-NodeResourceSource $resourceId $value['ResourceModuleSource'] } } if($null -ne $value['ExclusiveResources']) { # make sure the references are well-formed foreach ($ExclusiveResource in $value['ExclusiveResources']) { if (($ExclusiveResource -notmatch '^[a-z][a-z_0-9]*\\[a-z][a-z_0-9]*$') -and ($ExclusiveResource -notmatch '^[a-z][a-z_0-9]*$') -and ($ExclusiveResource -notmatch '^[a-z][a-z_0-9]*\\\*$')) { Update-ConfigurationErrorCount Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::GetBadlyFormedExclusiveResourceIdErrorRecord($ExclusiveResource, $resourceId)) } } # Save the resource id in a per-node dictionary to do cross validation at the end # Validate resource exist # Also update the resource reference from module\friendlyname to module\name $value['ExclusiveResources'] = @(Set-NodeExclusiveResources $resourceId @( $value['ExclusiveResources'] )) } } } } else { Write-Debug "${debugPrefix} TYPE IS NOT AS DSC RESOURCE" } # Copy the user-supplied values into a new collection with canonicalized property names foreach ($key in $keywordData.Properties.Keys) { Write-Debug "${debugPrefix} Processing property '$key' [" if ($value.Contains($key)) { if ($OldMetaConfig -and (-not ($V1MetaConfigPropertyList -contains $key))) { Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::InvalidLocalConfigurationManagerPropertyErrorRecord($key, ($V1MetaConfigPropertyList -join ', '))) Update-ConfigurationErrorCount } # see if there is a list of allowed values for this property (similar to an enum) $allowedValues = $keywordData.Properties[$key].Values # If there is and user-provided value is not in that list, write an error. if ($allowedValues) { if(($null -eq $value[$key]) -and ($allowedValues -notcontains $value[$key])) { Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::InvalidValueForPropertyErrorRecord($key, "$($value[$key])", $keywordData.Keyword, ($allowedValues -join ', '))) Update-ConfigurationErrorCount } else { $notAllowedValue=$null foreach($v in $value[$key]) { if($allowedValues -notcontains $v) { $notAllowedValue +=$v.ToString() + ', ' } } if($notAllowedValue) { $notAllowedValue = $notAllowedValue.Substring(0, $notAllowedValue.Length -2) Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::UnsupportedValueForPropertyErrorRecord($key, $notAllowedValue, $keywordData.Keyword, ($allowedValues -join ', '))) Update-ConfigurationErrorCount } } } # see if a value range is defined for this property $allowedRange = $keywordData.Properties[$key].Range if($allowedRange) { $castedValue = $value[$key] -as [int] if((($castedValue -is [int]) -and (($castedValue -lt $keywordData.Properties[$key].Range.Item1) -or ($castedValue -gt $keywordData.Properties[$key].Range.Item2))) -or ($null -eq $castedValue)) { Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::ValueNotInRangeErrorRecord($key, $keywordName, $value[$key], $keywordData.Properties[$key].Range.Item1, $keywordData.Properties[$key].Range.Item2)) Update-ConfigurationErrorCount } } Write-Debug "${debugPrefix} Canonicalized property '$key' = '$($value[$key])'" if ($keywordData.Properties[$key].IsKey) { if($null -eq $value[$key]) { $keyValues += "::__NULL__" } else { $keyValues += "::" + $value[$key] } } # see if ValueMap is also defined for this property (actual values) $allowedValueMap = $keywordData.Properties[$key].ValueMap #if it is and the ValueMap contains the user-provided value as a key, use the actual value if ($allowedValueMap -and $allowedValueMap.ContainsKey($value[$key])) { $canonicalizedValue[$key] = $allowedValueMap[$value[$key]] } else { $canonicalizedValue[$key] = $value[$key] } } elseif ($keywordData.Properties[$key].Mandatory) { # If the property was mandatory but the user didn't provide a value, write and error. Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::MissingValueForMandatoryPropertyErrorRecord($keywordData.Keyword, $keywordData.Properties[$key].TypeConstraint, $Key)) Update-ConfigurationErrorCount } Write-Debug "${debugPrefix} Processing completed '$key' ]" } if($keyValues) { $keyValues = $keyValues.Substring(2) # Remove the leading '::' Add-NodeKeys $keyValues $keywordName Test-ConflictingResources $keywordName $canonicalizedValue $keywordData } # update OMI_ConfigurationDocument if($IsMetaConfig) { if($keywordData.ResourceName -eq 'OMI_ConfigurationDocument') { if($(Get-PSMetaConfigurationProcessed)) { $PSMetaConfigDocumentInstVersionInfo = Get-PSMetaConfigDocumentInstVersionInfo $canonicalizedValue['MinimumCompatibleVersion']=$PSMetaConfigDocumentInstVersionInfo['MinimumCompatibleVersion'] } else { Set-PSMetaConfigDocInsProcessedBeforeMeta $canonicalizedValue['MinimumCompatibleVersion']='1.0.0' } } if(($keywordData.ResourceName -eq 'MSFT_WebDownloadManager') ` -or ($keywordData.ResourceName -eq 'MSFT_FileDownloadManager') ` -or ($keywordData.ResourceName -eq 'MSFT_WebResourceManager') ` -or ($keywordData.ResourceName -eq 'MSFT_FileResourceManager') ` -or ($keywordData.ResourceName -eq 'MSFT_WebReportManager') ` -or ($keywordData.ResourceName -eq 'MSFT_SignatureValidation') ` -or ($keywordData.ResourceName -eq 'MSFT_PartialConfiguration')) { Set-PSMetaConfigVersionInfoV2 } } elseif($keywordData.ResourceName -eq 'OMI_ConfigurationDocument') { $canonicalizedValue['MinimumCompatibleVersion']='1.0.0' $canonicalizedValue['CompatibleVersionAdditionalProperties']=@('Omi_BaseResource:ConfigurationName') } if(($keywordData.ResourceName -eq 'MSFT_DSCMetaConfiguration') -or ($keywordData.ResourceName -eq 'MSFT_DSCMetaConfigurationV2')) { if($canonicalizedValue['DebugMode'] -and @($canonicalizedValue['DebugMode']).Length -gt 1) { # we only allow one value for debug mode now. Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::DebugModeShouldHaveOneValue()) Update-ConfigurationErrorCount } } # Generate the MOF text for this resource instance. # when generate mof text for OMI_ConfigurationDocument we handle below two cases: # 1. we will add versioning related property based on meta configuration instance already process # 2. we update the existing OMI_ConfigurationDocument instance if it already exists when process meta configuration instance $aliasId = ConvertTo-MOFInstance $keywordName $canonicalizedValue # If a OMI_ConfigurationDocument is executed outside of a node statement, it becomes the default # for all nodes that don't have an explicit OMI_ConfigurationDocument declaration if ($keywordData.ResourceName -eq 'OMI_ConfigurationDocument' -and -not (Get-PSCurrentConfigurationNode)) { $data = Get-MoFInstanceText $aliasId Write-Debug "${debugPrefix} DEFINING DEFAULT CONFIGURATION DOCUMENT: $data" Set-PSDefaultConfigurationDocument $data } Write-Debug "${debugPrefix} MOF alias for this resource is '$aliasId'" # always return the aliasId so the generated file will be well-formed if not valid $aliasId Write-Debug "${debugPrefix} RESOURCE PROCESSING COMPLETED. TOTAL ERROR COUNT: $(Get-ConfigurationErrorCount)" |