Split-Config.ps1
function Split-Config { <# .Synopsis Splits a DSC configuration .Description Splits a single DSC configuration into multiple DSC configurations .Link Join-Config .Example { configuration InstallWebServer { node localhost { WindowsFeature IIS { Ensure = “Present” Name = “Web-Server” } Package UrlRewrite { #Install URL Rewrite module for IIS Ensure = "Present" Name = "IIS URL Rewrite Module 2" Path = "http://download.microsoft.com/download/6/7/D/67D80164-7DD0-48AF-86E3-DE7A182D6815/rewrite_2.0_rtw_x64.msi" Arguments = "/quiet" ProductId = "EB675D0A-2C95-405B-BEE8-B42A65D23E11" } } } } | Split-Config #> [OutputType([ScriptBlock])] param( # The configuration [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [ScriptBlock] $Configuration, # If -ExcludeNode is provided, any DSC resources within an excluded node will be ignored [string[]] $ExcludeNode, # If -ExcludeResource is provided, any DSC resource section with a given resource name will be ignored [string[]] $ExcludeResource, # If -ExcludeSettinngName is provided, any DSC resource section with a given setting name will be ignored [string[]] $ExcludeSettingName ) begin { $allConfiguraitons = New-Object Collections.ArrayList } process { $null = $allConfiguraitons.Add(@{} + $PSBoundParameters) } end { foreach ($params in $allConfiguraitons) { $Configuration =$params["Configuration"] $ExcludeNode = $params["ExcludeNode"] $ExcludeResource = $params["ExcludeResource"] $ExcludeSettingName = $params["ExcludeSettingName"] $allModulesImported = New-Object Collections.ArrayList $ExplicitResourceNames = New-Object Collections.ArrayList #region Parse Config and Extract Parts $text = "$Configuration" $tokens = [Management.Automation.PSParser]::Tokenize("$configuration", [ref]$null) $paramBlocks = @{} for ($i =0; $i -lt $tokens.count; $i++) { Write-Verbose "$i" if ('keyword', 'command' -contains $tokens[$i].type -and $tokens[$i].Content -eq 'configuration') { $ConfigurationName = $tokens[$i+1].Content while ($tokens[$i].type -ne 'GroupStart' -and $tokens[$i].type -ne '{') { $i++ } $braceCount = 0 $j = $i $GroupStart = $tokens[$i] do { if ($tokens[$j].type -eq 'GroupStart' -and $tokens[$j].Content -eq '{') { $braceCount++ } if ($tokens[$j].type -eq 'GroupEnd' -and $tokens[$j].Content -eq '}') { $braceCount-- } $j++ } while ($braceCount -and ($j -lt $tokens.Count)) continue } if ($tokens[$i].type -eq 'keyword' -and $tokens[$i].Content -eq 'param') { $paramEnd = $i + 1 while ($tokens[$paramEnd].Type -ne 'GroupStart' -and $tokens[$paramEnd].Content -ne '(') { $paramEnd++ } $paramStart = $paramEnd $lastParamStart = $paramEnd $braceCount = 0 do { if ($tokens[$paramEnd].type -eq 'GroupStart' -and $tokens[$paramEnd].Content -eq '(') { $braceCount++ } if ($tokens[$paramEnd].type -eq 'GroupEnd' -and $tokens[$paramEnd].Content -eq ')') { $braceCount-- } if ($tokens[$paramEnd].Content -eq ',' -and $braceCount -eq 1 ) { $theParam = $tokens[$lastParamStart + 1] $paramBody = $text.Substring($theParam.Start, $tokens[$paramEnd - 1].Start + $tokens[$paramEnd - 1].Length - $theParam.Start) $lastVariable = @([Regex]::Matches($paramBody, "\`$([\w-]{1,})"))[-1].Captures[0].ToString() $paramBlocks[$lastVariable] = $paramBody.Trim("(),") $lastParamStart = $paramEnd + 1 } $paramEnd++ } while ($braceCount -and ($paramEnd -lt $tokens.Count)) if ($lastParamStart -ne $paramStart) { $theParam = $tokens[$lastParamStart + 1] $paramBody = $text.Substring($theParam.Start, $tokens[$paramEnd - 1].Start + $tokens[$paramEnd - 1].Length - $theParam.Start) $lastVariable = @([Regex]::Matches($paramBody, "\`$([\w-]{1,})"))[-1].Captures[0].ToString() $paramBlocks[$lastVariable] = $paramBody.Trim("(),") } $i = $paramEnd continue } if ($tokens[$i].type -eq 'command' -and $tokens[$i].Content -eq 'Import-DSCResource') { $j = $i + 1 $afterModuleNameParameter = $false $afterNameParameter = $false while ($tokens[$j].Type -ne 'Newline' -and $tokens[$j].Type -ne 'StatementSeparator') { if ($tokens[$j].Type -eq 'CommandParameter') { $afterModuleNameParameter = $tokens[$j].Content -eq '-ModuleName' -or $tokens[$j].Content -eq '-Module' $afterNameParameter = $tokens[$j].Content -eq '-Name' -or $tokens[$j].Content -eq 'ResourceName' } if ($tokens[$j].Type -eq 'CommandArgument' -or $tokens[$j].Type -eq 'string') { if ($afterModuleNameParameter) { $null = $allModulesImported.Add($tokens[$j].Content) } elseif ($afterNameParameter) { $null = $ExplicitResourceNames.Add($tokens[$j].Content) } } $j++ } $i = $j - 1 continue } if ('keyword', 'command' -contains $tokens[$i].type -and $tokens[$i].Content -eq 'node') { $InNode = $tokens[$i + 1].Content while ($tokens[$i].type -ne 'GroupStart' -and $tokens[$i].Content -ne '{') { $i++ } $GroupStart = $null continue } if ('keyword', 'command' -contains $tokens[$i].type -and ($tokens[$i].Content -ne 'node' -and $tokens[$i].Content -ne 'param')) { $DscResourceInUse = $tokens[$i].Content $DscResourceSettingName = $tokens[$i+1].Content while ($tokens[$i].type -ne 'GroupStart' -and $tokens[$i].Content -ne '{') { $i++ } $GroupStart = $tokens[$i] $braceCount = 0 do { if ($tokens[$i].type -eq 'GroupStart' -and $tokens[$i].Content -eq '{') { $braceCount++ } if ($tokens[$i].type -eq 'GroupEnd' -and $tokens[$i].Content -eq '}') { $braceCount-- } $i++ } while ($braceCount) $ConfigurationSettingsBlock = $text.Substring($GroupStart.Start, ($tokens[$i].Start + $tokens[$i].Length) - $GroupStart.Start) $ResourceNames += $DscResourceInUse $SettingNames += $DscResourceSettingName $dscImports = New-Object Text.StringBuilder if ($allModulesImported) { $null = $dscImports.AppendLine(" " * 4 + "Import-DSCResource -Module '$(($allModulesImported | Select-Object -Unique | Sort-Object) -join "','")'") } foreach ($resourceName in $ExplicitResourceNames | Select-Object -Unique | Sort-Object) { if ($DscResourceInUse -eq $resourceName) { $null = $dscImports.AppendLine(" " * 4 + "Import-DSCResource -Name '$resourceName'") } } $variablesUsed = [Regex]::Matches($ConfigurationSettingsBlock, "\`$([\w-]{1,})") $paramBody = @(foreach ($variableName in $variablesUsed) { if (-not $variableName) { continue } $paramBlocks[$variableName.Captures[0].ToString()] }) -join (',' + [Environment]::NewLine + (" " * 4)) if ($InNode -and $ExcludeNode -contains $InNode) { continue } if ($ExcludeResource -contains $DscResourceInUse) { continue } if ($ExcludeSettingName -contains $DscResourceSettingName) { continue } [ScriptBlock]::Create(" configuration ${ConfigurationName}_${DscResourceSettingName} { $(if ($paramBody) {" param($paramBody)"}) $dscImports $(if ($inNode) { "node $InNode {" }) $DscResourceInUse $DscResourceSettingName $configurationSettingsBlock $(if ($inNode) { "}" }) } ") continue } } #endregion Parse Config and Extract Parts } } } |