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
        }
    }
}