Join-Config.ps1

function Join-Config
{
    <#
    .Synopsis
        Joins several DSC configurations
    .Description
        Joins several DSC configurations into a single DSC configuration
    .Link
        Split-Config
    .Example
{
configuration InstallWebServer_IIS {
 
    node localhost {
        WindowsFeature IIS {
            Ensure = “Present”
            Name = “Web-Server”
        }
        
    }
}
}, {
 
configuration InstallWebServer_UrlRewrite {
 
    node localhost {
        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"
        }
        
    }
}
} | Join-Config -Name InstallIISAndUrlRewrite
    #>

    [OutputType([ScriptBlock])]
    param(
    # The configuration
    [Parameter(Mandatory=$true,ValueFromPipeline=$true, Position=0)]
    [ScriptBlock[]]
    $Configuration,

    # The name of the new configuration. If this is not provided, the name of the first configuration will be used
    [string]
    $Name
    )

    begin {
        $allModulesImported = New-Object Collections.ArrayList
        $ExplicitResourceNames = New-Object Collections.ArrayList
        $allVariablesUsed = New-Object Collections.ArrayList
        $allConfigs = New-Object Collections.ArrayList
    }
    
    process {
        $allConfigs.AddRange($Configuration)
    }

    end {
        #region Parse All Configurations
        $paramBlocks = @{}

        $allConfigParts = 
            foreach ($config in $allConfigs) {
                $text = "$config"
                $tokens = [Management.Automation.PSParser]::Tokenize("$config", [ref]$null) 
                
                for ($i =0; $i -lt $tokens.count; $i++) {
                    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("<>,")
                        }
                    
                        continue                    
                    }
                    if ($tokens[$i].type -eq 'keyword' -and $tokens[$i].Content -eq 'configuration') {
                        
                        $ConfigurationName  = $tokens[$i+1].Content
                        if (-not $Name) {
                            $Name = $ConfigurationName
                        }
                        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 'command' -and $tokens[$i].Content -eq 'Import-DSCResource') {
                        $null = $null
                        $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 ($tokens[$i].type -eq 'keyword' -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 ($tokens[$i].type -eq 'keyword' -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)
                        $variablesUsed = @(foreach ($_ in [Regex]::Matches($ConfigurationSettingsBlock, "\`$([\w-]{1,})")) { 
                            $_.Captures[0].ToString()
                        })
                        $allVariablesUsed.AddRange($variablesUsed)

                        $ResourceNames += $DscResourceInUse
                        $SettingNames += $DscResourceSettingName

                
" $DscResourceInUse $DscResourceSettingName $configurationSettingsBlock
    "
 | Add-Member NoteProperty Node $InNode -PassThru |
      Add-Member NoteProperty ResourceName "$DscResourceInUse" -PassThru |
      Add-Member NoteProperty SettingName $DscResourceSettingName -PassThru |
      Add-Member NoteProperty VariablesUsed $DscResourceSettingName -PassThru
                
                

                
                
                
                        continue
                    }            
                }
            }
            #endregion Parse All Configurations
            


            $configBuilder =  New-Object Text.StringBuilder
            $null = $configBuilder.AppendLine("configuration $Name {")
if ($allVariablesUsed) {
    $paramBody = 
        @(foreach ($variableName in $allVariablesUsed) {
            if (-not $variableName) { continue } 
            $paramBlocks[$variableName]
        }) -join (',' + [Environment]::NewLine + (" " * 4))
    $null = $configBuilder.AppendLine(" " * 4 + "param($($paramBody.Trim("()").Trim()))")
}
if ($allModulesImported) {
    $null = $configBuilder.AppendLine(" " * 4 + "Import-DSCResource -Module '$(($allModulesImported | Select-Object -Unique | Sort-Object) -join "','")'")
}
foreach ($resourceName in $ExplicitResourceNames) {
    $null = $configBuilder.AppendLine(" " * 4 + "Import-DSCResource -Name '$resourceName'")
}

foreach ($partsByNode in $allConfigParts | Group-Object Node) {
    $indentSize = if ($partsByNode.Name) {
        8
        $null = $configBuilder.AppendLine(" " * 4 + "node $($partsByNode.Name) {")    
    } else {
        4
    }
    
    foreach ($part in $partsByNode.Group) { 
        $null = $configBuilder.AppendLine(" " * $indentSize + $part.Trim())
    }
    if ($indentSize -eq 8) {
        $null = $configBuilder.AppendLine(" " * 4 + "}")
    }
}
$null = $configBuilder.AppendLine("}")
[ScriptBlock]::Create("$configBuilder")
    }
}