FastHost.ps1
|
# Fast compilation host: compiles a configuration script without triggering the # engine's parse-time resource import by stripping Import-DscResource statements # and registering keywords from a persistent schema cache instead. $script:FastHostAdapterText = @' $__fhKeywordName = $MyInvocation.InvocationName if ($__fhKeywordName.Contains('\')) { $__fhKeywordName = $__fhKeywordName.Substring($__fhKeywordName.LastIndexOf('\') + 1) } $__fhKeyword = Get-FastHostKeyword -Name $__fhKeywordName if ($null -eq $__fhKeyword) { throw "The DSC resource '$__fhKeywordName' is not present in the schema cache." } if ($args.Count -lt 1) { throw "Resource '$__fhKeywordName': expected a { } property block as the last argument." } $__fhBody = $args[$args.Count - 1] $__fhName = '' if ($__fhKeyword.NameMode -eq [System.Management.Automation.Language.DynamicKeywordNameMode]::NameRequired) { if ($args.Count -lt 2 -or $args[0] -isnot [System.String]) { throw "Resource '$__fhKeywordName': expected '$__fhKeywordName <instance name> { ... }'." } $__fhName = $args[0] } elseif ($args.Count -ge 2 -and $args[0] -is [System.String]) { $__fhName = $args[0] } if ($__fhBody -isnot [hashtable]) { throw "Resource '$__fhKeywordName': the property block reached the adapter as $($__fhBody.GetType().Name) instead of a hashtable. ConvertTo-FastHostCompileText did not rewrite it." } $__fhValue = $__fhBody $__fhFile = $MyInvocation.ScriptName if ([System.String]::IsNullOrEmpty($__fhFile)) { # The body runs from a scriptblock built out of the stripped text, so it carries no # file. Use the path the compile was started from, to keep SourceInfo identical to # what the standard path emits. $__fhFile = Get-FastHostScriptPath } $__fhSource = "$__fhFile::$($MyInvocation.ScriptLineNumber)::$($MyInvocation.OffsetInLine)::$__fhKeywordName" & (Get-CimKeywordImplementationFunction) -KeywordData $__fhKeyword -Name $__fhName -Value $__fhValue -SourceMetadata $__fhSource '@ function Get-FastHostScriptPath { [OutputType([System.String])] param() $script:FastHostScriptPath } # The cache stays as text lines until a compile asks for a keyword. function Get-FastHostKeyword { param ( [Parameter(Mandatory)] [System.String] $Name ) if ($script:FastHostKeywords) { $keyword = $script:FastHostKeywords[$Name] if ($null -ne $keyword) { return $keyword } } if ($script:FastHostKeywordSource) { foreach ($cache in $script:FastHostKeywordSource) { $keyword = Get-DscSchemaCacheKeyword -Cache $cache -Name $Name if ($null -ne $keyword) { $script:FastHostKeywords[$keyword.Keyword] = $keyword return $keyword } } } $null } function Get-FastHostKeywordName { [OutputType([System.Collections.Generic.HashSet[System.String]])] param() $names = New-Object -TypeName 'System.Collections.Generic.HashSet[System.String]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase) if ($script:FastHostKeywords) { foreach ($key in $script:FastHostKeywords.Keys) { $null = $names.Add($key) } } if ($script:FastHostKeywordSource) { foreach ($cache in $script:FastHostKeywordSource) { foreach ($name in (Get-DscSchemaCacheKeywordName -Cache $cache)) { $null = $names.Add($name) } } } , $names } function Get-FastHostBodyScriptBlock { [OutputType([ScriptBlock])] param ( [Parameter(Mandatory)] [ScriptBlock] $Body ) $text = $Body.Ast.Extent.Text $cached = $script:FastHostBodyCache[$text] if ($null -eq $cached) { $inner = $text.Substring(1, $text.Length - 2) $cached = [ScriptBlock]::Create('@{' + $inner + '}') $script:FastHostBodyCache[$text] = $cached } $cached } function Register-DscSchemaCache { param ( [Parameter(Mandatory)] $Cache ) if ($null -eq $script:FastHostKeywords) { $script:FastHostKeywords = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,System.Management.Automation.Language.DynamicKeyword]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase) } if ($null -eq $script:FastHostAdapters) { $script:FastHostAdapters = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,scriptblock]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase) } if ($null -eq $script:FastHostKeywordSource) { $script:FastHostKeywordSource = New-Object -TypeName 'System.Collections.Generic.List[System.Object]' } $script:FastHostKeywordSource.Add($Cache) $adapter = [ScriptBlock]::Create($script:FastHostAdapterText) $moduleName = [System.String]$Cache.module.name foreach ($name in (Get-DscSchemaCacheKeywordName -Cache $Cache)) { $script:FastHostAdapters[$name] = $adapter if ($moduleName) { $script:FastHostAdapters["$moduleName\$name"] = $adapter } } } # Returns the keyword a next-line brace would belong to, or $null. There are two possible ways # for such a keyword to appear: # # KeywordName [InstanceName] - a resource statement # PropertyName = KeywordName - a nested CIM instance inside a resource body # # The second shape is not an assignment. It is inside a configuration body, and 'PropertyName' is a # bare word, so the parser produces one CommandAst whose elements are the property name, # a literal '=' and the keyword. function Get-FastHostMergeCandidateKeyword { [OutputType([System.Object])] param ( [Parameter(Mandatory)] [System.Management.Automation.Language.StatementAst] $Statement ) $pipeline = $Statement -as [System.Management.Automation.Language.PipelineAst] if ($null -eq $pipeline) { $assignment = $Statement -as [System.Management.Automation.Language.AssignmentStatementAst] if ($null -eq $assignment) { return $null } $pipeline = $assignment.Right -as [System.Management.Automation.Language.PipelineAst] } if ($null -eq $pipeline -or $pipeline.PipelineElements.Count -ne 1) { return $null } $command = $pipeline.PipelineElements[0] -as [System.Management.Automation.Language.CommandAst] if ($null -eq $command) { return $null } $elements = $command.CommandElements if ($elements.Count -le 2) { return [PSCustomObject]@{ Name = $command.GetCommandName() EndOffset = $command.Extent.EndOffset } } # 'PropertyName = KeywordName [InstanceName]' if ($elements.Count -le 4 -and $elements[1].Extent.Text -eq '=') { $keywordElement = $elements[2] -as [System.Management.Automation.Language.StringConstantExpressionAst] if ($null -eq $keywordElement) { return $null } return [PSCustomObject]@{ Name = $keywordElement.Value EndOffset = $command.Extent.EndOffset } } return $null } function Get-FastHostBodyKeywordName { [OutputType([System.String])] param ( [Parameter(Mandatory)] [System.Management.Automation.Language.CommandAst] $Command ) $elements = $Command.CommandElements if ($elements.Count -ge 3 -and $elements[1].Extent.Text -eq '=') { $keyword = $elements[2] -as [System.Management.Automation.Language.StringConstantExpressionAst] if ($null -eq $keyword) { return $null } return $keyword.Value } return $Command.GetCommandName() } # A parsed configuration statement imports its resource modules. Masking the word keeps the # parser away from that, and the same-length replacement keeps every extent offset valid for # the original text. function Get-FastHostMaskedAst { [OutputType([System.Management.Automation.Language.ScriptBlockAst])] param ( [Parameter(Mandatory)] [System.String] $Text ) $masked = [regex]::Replace($Text, '(?i)\bConfiguration\b', 'C0nfiguration') $tokens = $null $parseErrors = $null [System.Management.Automation.Language.Parser]::ParseInput($masked, [ref]$tokens, [ref]$parseErrors) } function Invoke-FastHostTextEdit { [OutputType([System.String])] param ( [Parameter(Mandatory)] [AllowEmptyString()] [System.String] $Text, [Parameter(Mandatory)] [AllowEmptyCollection()] $Edit ) if ($Edit.Count -eq 0) { return $Text } $ordered = @($Edit | Sort-Object -Property Start, Length) $builder = New-Object -TypeName System.Text.StringBuilder -ArgumentList ($Text.Length + $ordered.Count * 2) $position = 0 foreach ($item in $ordered) { if ($item.Start -lt $position) { throw "Overlapping text edits at offset $($item.Start)." } $null = $builder.Append($Text.Substring($position, $item.Start - $position)) $null = $builder.Append($item.Text) $position = $item.Start + $item.Length } $null = $builder.Append($Text.Substring($position)) $builder.ToString() } function ConvertTo-FastHostCompileText { [OutputType([System.String])] param ( [Parameter(Mandatory)] [System.String] $Text, [System.Management.Automation.Language.ScriptBlockAst] $Ast, [AllowEmptyCollection()] $ImportStatement = @(), [AllowEmptyCollection()] $KeywordNames = @(), [switch] $Merge, [switch] $Convert ) if ($null -eq $Ast) { $Ast = Get-FastHostMaskedAst -Text $Text } $names = $KeywordNames -as [System.Collections.Generic.HashSet[System.String]] if ($null -eq $names) { $names = New-Object -TypeName 'System.Collections.Generic.HashSet[System.String]' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase) foreach ($name in @($KeywordNames)) { $null = $names.Add([System.String]$name) } } $edits = New-Object -TypeName 'System.Collections.Generic.List[System.Object]' foreach ($statement in @($ImportStatement)) { $edits.Add([PSCustomObject]@{ Start = $statement.Extent.StartOffset; Length = $statement.Extent.EndOffset - $statement.Extent.StartOffset; Text = '' }) } if ($names.Count -eq 0 -or -not ($Merge -or $Convert)) { return Invoke-FastHostTextEdit -Text $Text -Edit $edits } $bodyStarts = New-Object -TypeName 'System.Collections.Generic.HashSet[int]' # With the keyword unknown to the parser, a next-line brace parses as a separate scriptblock # statement and the engine rejects the resource as undefined. if ($Merge) { $blocks = $Ast.FindAll({ $args[0] -is [System.Management.Automation.Language.StatementBlockAst] -or $args[0] -is [System.Management.Automation.Language.NamedBlockAst] }, $true) foreach ($block in $blocks) { $statements = $block.Statements for ($i = 0; $i -lt $statements.Count - 1; $i++) { $candidate = Get-FastHostMergeCandidateKeyword -Statement $statements[$i] if ($null -eq $candidate -or -not $candidate.Name -or -not $names.Contains($candidate.Name)) { continue } $second = $statements[$i + 1] if ($second -isnot [System.Management.Automation.Language.PipelineAst] -or $second.PipelineElements.Count -ne 1) { continue } $expression = $second.PipelineElements[0] -as [System.Management.Automation.Language.CommandExpressionAst] if ($null -eq $expression -or ($expression.Expression -isnot [System.Management.Automation.Language.ScriptBlockExpressionAst] -and $expression.Expression -isnot [System.Management.Automation.Language.HashtableAst])) { continue } $gapStart = $candidate.EndOffset $gapLength = $second.Extent.StartOffset - $gapStart if ($gapLength -gt 0) { $edits.Add([PSCustomObject]@{ Start = $gapStart; Length = $gapLength; Text = ' ' }) } if ($Convert -and $expression.Expression -is [System.Management.Automation.Language.ScriptBlockExpressionAst]) { $offset = $second.Extent.StartOffset if ($bodyStarts.Add($offset)) { $prefix = if ($gapLength -gt 0 -or ($offset -gt 0 -and [char]::IsWhiteSpace($Text[$offset - 1]))) { '@' } else { ' @' } $edits.Add([PSCustomObject]@{ Start = $offset; Length = 0; Text = $prefix }) } } } } } if ($Convert) { $commands = $Ast.FindAll({ $args[0] -is [System.Management.Automation.Language.CommandAst] }, $true) foreach ($command in $commands) { $elements = $command.CommandElements $body = $elements[$elements.Count - 1] -as [System.Management.Automation.Language.ScriptBlockExpressionAst] if ($null -eq $body) { continue } $name = Get-FastHostBodyKeywordName -Command $command if (-not $name -or -not $names.Contains($name)) { continue } $offset = $body.Extent.StartOffset if ($bodyStarts.Add($offset)) { # 'MSFT_Type @{' parses, 'MSFT_Type@{' does not # Keep a separator when the source had none $prefix = if ($offset -gt 0 -and -not [char]::IsWhiteSpace($Text[$offset - 1])) { ' @' } else { '@' } $edits.Add([PSCustomObject]@{ Start = $offset; Length = 0; Text = $prefix }) } } } Invoke-FastHostTextEdit -Text $Text -Edit $edits } function Merge-FastHostResourceStatements { [OutputType([System.String])] param ( [Parameter(Mandatory)] [System.String] $Text, [Parameter(Mandatory)] $KeywordNames ) ConvertTo-FastHostCompileText -Text $Text -KeywordNames $KeywordNames -Merge } function Convert-FastHostBodyToHashtable { [OutputType([System.String])] param ( [Parameter(Mandatory)] [System.String] $Text, [Parameter(Mandatory)] $KeywordNames ) ConvertTo-FastHostCompileText -Text $Text -KeywordNames $KeywordNames -Convert } function Get-StrippedConfigurationText { param ( [Parameter(Mandatory)] [System.String] $Text ) $ast = Get-FastHostMaskedAst -Text $Text $configurationNames = @() $importStatements = @() $commands = $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.CommandAst] }, $true) foreach ($command in $commands) { $commandName = $command.GetCommandName() if ($commandName -eq 'C0nfiguration') { foreach ($element in $command.CommandElements) { if ($element -is [System.Management.Automation.Language.StringConstantExpressionAst] -and $element.Value -ne 'C0nfiguration') { $configurationNames += $element.Value break } } } elseif ($commandName -eq 'Import-DscResource') { $importStatements += $command } } $moduleSpecs = @() foreach ($statement in $importStatements) { $names = @() $moduleNames = @() $moduleVersion = $null $currentParameter = 'Name' $positionalIndex = 0 $supported = $true for ($i = 1; $i -lt $statement.CommandElements.Count; $i++) { $element = $statement.CommandElements[$i] if ($element -is [System.Management.Automation.Language.CommandParameterAst]) { $currentParameter = $element.ParameterName continue } $values = @() if ($element -is [System.Management.Automation.Language.StringConstantExpressionAst]) { $values = @($element.Value) } elseif ($element -is [System.Management.Automation.Language.ConstantExpressionAst]) { $values = @($element.Extent.Text) } elseif ($element -is [System.Management.Automation.Language.ArrayLiteralAst]) { foreach ($arrayElement in $element.Elements) { if ($arrayElement -is [System.Management.Automation.Language.StringConstantExpressionAst]) { $values += $arrayElement.Value } else { $supported = $false } } } else { $supported = $false } if (-not $supported) { break } $effectiveParameter = $currentParameter if ($currentParameter -eq 'Name' -and $element -eq $statement.CommandElements[$i] -and -not ($statement.CommandElements[$i - 1] -is [System.Management.Automation.Language.CommandParameterAst])) { $effectiveParameter = switch ($positionalIndex) { 0 { 'Name' } 1 { 'ModuleName' } 2 { 'ModuleVersion' } default { $null } } $positionalIndex++ } switch ($effectiveParameter) { 'Name' { $names += $values } 'ModuleName' { $moduleNames += $values } 'ModuleVersion' { $moduleVersion = [version]$values[0] } default { $supported = $false } } $currentParameter = 'Name' } if (-not $supported) { return [PSCustomObject]@{ Supported = $false Reason = "Unsupported Import-DscResource form at line $($statement.Extent.StartLineNumber): only constant -Name/-ModuleName/-ModuleVersion values are supported." Text = $null ModuleSpecs = @() ConfigurationNames = @() Ast = $null ImportStatements = @() } } if ($moduleNames.Count -eq 0 -and $names.Count -gt 0) { return [PSCustomObject]@{ Supported = $false Reason = "Import-DscResource at line $($statement.Extent.StartLineNumber) uses -Name without -ModuleName, which requires scanning all installed modules." Text = $null ModuleSpecs = @() ConfigurationNames = @() Ast = $null ImportStatements = @() } } foreach ($moduleName in $moduleNames) { $moduleSpecs += [PSCustomObject]@{ ModuleName = $moduleName ModuleVersion = $moduleVersion Resources = if ($names.Count -gt 0) { $names } else { @('*') } } } } [PSCustomObject]@{ Supported = $true Reason = $null Text = ConvertTo-FastHostCompileText -Text $Text -Ast $ast -ImportStatement $importStatements ModuleSpecs = $moduleSpecs ConfigurationNames = $configurationNames Ast = $ast ImportStatements = $importStatements } } # Get-Module -ListAvailable analyzes every nested module of a manifest and costs seconds for # a large resource module. A manifest scan is enough for the cache lookup. function Resolve-FastHostModule { [OutputType([System.Object])] param ( [Parameter(Mandatory)] [System.String] $ModuleName, [version] $ModuleVersion ) $candidates = New-Object -TypeName 'System.Collections.Generic.List[System.Object]' foreach ($entry in ($env:PSModulePath -split [System.IO.Path]::PathSeparator)) { if ([System.String]::IsNullOrWhiteSpace($entry)) { continue } $moduleFolder = Join-Path -Path $entry -ChildPath $ModuleName if (-not [System.IO.Directory]::Exists($moduleFolder)) { continue } $flat = Join-Path -Path $moduleFolder -ChildPath "$ModuleName.psd1" if ([System.IO.File]::Exists($flat)) { $candidates.Add([PSCustomObject]@{ Path = $flat; FolderVersion = $null }) } foreach ($versionFolder in [System.IO.Directory]::EnumerateDirectories($moduleFolder)) { $manifest = Join-Path -Path $versionFolder -ChildPath "$ModuleName.psd1" if (-not [System.IO.File]::Exists($manifest)) { continue } $folderVersion = $null $null = [version]::TryParse((Split-Path $versionFolder -Leaf), [ref]$folderVersion) $candidates.Add([PSCustomObject]@{ Path = $manifest; FolderVersion = $folderVersion }) } } $best = $null foreach ($candidate in $candidates) { if ($ModuleVersion -and $candidate.FolderVersion -and $candidate.FolderVersion -ne $ModuleVersion) { continue } try { $data = Import-PowerShellDataFile -Path $candidate.Path -ErrorAction Stop } catch { continue } $version = $null if (-not [version]::TryParse([System.String]$data.ModuleVersion, [ref]$version)) { continue } if ($ModuleVersion -and $version -ne $ModuleVersion) { continue } if ($null -eq $best -or $version -gt $best.Version) { $best = [PSCustomObject]@{ Name = $ModuleName Version = $version ModuleBase = Split-Path $candidate.Path -Parent Path = $candidate.Path } } } $best } function Get-DscFastCompileTiming { [OutputType([System.Collections.Specialized.OrderedDictionary])] param() if ($null -eq $script:FastHostTiming) { return $null } $copy = [ordered]@{} foreach ($entry in $script:FastHostTiming.GetEnumerator()) { $copy[$entry.Key] = $entry.Value } $copy } Export-ModuleMember -Function Get-DscFastCompileTiming function Invoke-DscFastCompile { [CmdletBinding(DefaultParameterSetName = 'Path')] param ( [Parameter(Mandatory, ParameterSetName = 'Path', Position = 0)] [System.String] $Path, [Parameter(Mandatory, ParameterSetName = 'Text')] [System.String] $ScriptText, [System.String] $ConfigurationName, [System.Collections.Hashtable] $Parameters, [System.Object] $ConfigurationData, [System.String] $OutputPath, [System.String[]] $SchemaCachePath, [switch] $Force, [switch] $ValidateMof, [switch] $NoFallback ) if ($PSCmdlet.ParameterSetName -eq 'Path') { $Path = (Resolve-Path -Path $Path).ProviderPath $ScriptText = [System.IO.File]::ReadAllText($Path) } $timing = [ordered]@{} $script:FastHostTiming = $timing $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() $total = [System.Diagnostics.Stopwatch]::StartNew() Assert-DscConfigurationShim $fallbackReason = $null $stripResult = Get-StrippedConfigurationText -Text $ScriptText if (-not $stripResult.Supported) { $fallbackReason = $stripResult.Reason } $timing['parse'] = $stopwatch.ElapsedMilliseconds $stopwatch.Restart() $resolvedModules = @() if (-not $fallbackReason) { foreach ($spec in $stripResult.ModuleSpecs) { $specKey = "$($spec.ModuleName)|$($spec.ModuleVersion)" $module = $script:FastHostResolvedModules[$specKey] if ($null -eq $module) { $module = Resolve-FastHostModule -ModuleName $spec.ModuleName -ModuleVersion $spec.ModuleVersion if ($null -ne $module) { $script:FastHostResolvedModules[$specKey] = $module } } if (-not $module) { $fallbackReason = "Module '$($spec.ModuleName)' $(if ($spec.ModuleVersion) { "version $($spec.ModuleVersion) " })was not found." break } $dscResourcesPath = Join-Path $module.ModuleBase 'DscResources' if (Test-Path $dscResourcesPath) { $compositeFiles = @([System.IO.Directory]::EnumerateFiles($dscResourcesPath, '*.schema.psm1', [System.IO.SearchOption]::AllDirectories)) if ($compositeFiles.Count -gt 0) { $fallbackReason = "Module '$($module.Name)' contains composite resources, which the fast host does not support yet." break } } $resolvedModules += $module } } $timing['resolve'] = $stopwatch.ElapsedMilliseconds $stopwatch.Restart() if ($fallbackReason) { if ($NoFallback) { throw "Fast compilation is not possible: $fallbackReason" } Write-Warning -Message "Falling back to standard compilation: $fallbackReason" return Invoke-DscFastCompileBody -Text $ScriptText -ConfigurationName $ConfigurationName -Parameters $Parameters -ConfigurationData $ConfigurationData -OutputPath $OutputPath -ScriptPath $Path -ConfigurationNames $stripResult.ConfigurationNames } foreach ($module in $resolvedModules) { $registered = $script:FastHostRegisteredModules[$module.Name] if ($registered -and $registered -eq $module.Version.ToString() -and -not $Force) { continue } $cache = $null if (-not $Force) { $cache = Get-DscSchemaCache -Module $module -SchemaCachePath $SchemaCachePath } if (-not $cache) { $cache = New-DscSchemaCacheForModule -Module $module } if (-not $cache) { if ($NoFallback) { throw "No schema cache could be obtained for module '$($module.Name)' $($module.Version)." } Write-Warning -Message "Falling back to standard compilation: no usable schema cache for module '$($module.Name)' $($module.Version)." return Invoke-DscFastCompileBody -Text $ScriptText -ConfigurationName $ConfigurationName -Parameters $Parameters -ConfigurationData $ConfigurationData -OutputPath $OutputPath -ScriptPath $Path -ConfigurationNames $stripResult.ConfigurationNames } Register-DscSchemaCache -Cache $cache $script:FastHostRegisteredModules[$module.Name] = $module.Version.ToString() } $timing['cache'] = $stopwatch.ElapsedMilliseconds $stopwatch.Restart() $keywordNames = Get-FastHostKeywordName $compileText = ConvertTo-FastHostCompileText -Text $ScriptText -Ast $stripResult.Ast -ImportStatement $stripResult.ImportStatements -KeywordNames $keywordNames -Merge -Convert $timing['rewrite'] = $stopwatch.ElapsedMilliseconds $stopwatch.Restart() $script:FastHostActive = $true $script:FastHostValidateMof = [bool]$ValidateMof $Global:PSDscFastCompileActive = $true try { Invoke-DscFastCompileBody -Text $compileText -ConfigurationName $ConfigurationName -Parameters $Parameters -ConfigurationData $ConfigurationData -OutputPath $OutputPath -ScriptPath $Path -ConfigurationNames $stripResult.ConfigurationNames } finally { $script:FastHostActive = $false $script:FastHostValidateMof = $false $Global:PSDscFastCompileActive = $false [System.Management.Automation.Language.DynamicKeyword]::Reset() $timing['compile'] = $stopwatch.ElapsedMilliseconds $timing['total'] = $total.ElapsedMilliseconds } } Export-ModuleMember -Function Invoke-DscFastCompile function Invoke-DscFastCompileBody { param ( [Parameter(Mandatory)] [System.String] $Text, [System.String] $ConfigurationName, [System.Collections.Hashtable] $Parameters, [System.Object] $ConfigurationData, [System.String] $OutputPath, [System.String] $ScriptPath, [System.String[]] $ConfigurationNames ) $scriptBlock = [ScriptBlock]::Create($Text) $script:FastHostScriptPath = $ScriptPath # Parsing the configuration statement makes the engine load the inbox module by # file path, which takes the qualified Configuration name over. Assert-DscConfigurationShim $pushed = $false if ($ScriptPath) { Push-Location -Path (Split-Path $ScriptPath -Parent) $pushed = $true } try { $selfInvocationOutput = @(. $scriptBlock) $mofFiles = @($selfInvocationOutput | Where-Object { $_ -is [System.IO.FileInfo] }) if ($mofFiles.Count -gt 0) { return $mofFiles } if (-not $ConfigurationName) { if ($ConfigurationNames.Count -eq 1) { $ConfigurationName = $ConfigurationNames[0] } else { throw "The script defines $($ConfigurationNames.Count) configurations ($($ConfigurationNames -join ', ')); specify -ConfigurationName." } } $invokeArguments = @{} if ($Parameters) { $invokeArguments = $Parameters.Clone() } if ($null -ne $ConfigurationData) { $invokeArguments['ConfigurationData'] = $ConfigurationData } if ($OutputPath) { $invokeArguments['OutputPath'] = $OutputPath } & $ConfigurationName @invokeArguments } finally { if ($pushed) { Pop-Location } } } |