CompositeResource.psm1

<#
    .SYNOPSIS
        Converts a PowerShell Desired State Configuration configuration to a
        composite resources.

    .DESCRIPTION
        Converts a PowerShell Desired State Configuration configuration to a
        composite resources.

        It is possible to convert several configuration to composite resource
        to the same resource module. Use the same ModuleName, ModuleVersion
        and OutputPath for all the configurations that are converted.

    .PARAMETER ConfigurationName
        The configuration name to convert into a composite resource. The
        configuration name must exist in the session.
        If this is used, then the parameter Script cannot be used.

    .PARAMETER Script
        A script block containing the configuration to convert into a composite
        resource.
        If this is used, then the parameter ConfigurationName cannot be used.

    .PARAMETER ResourceName
        The name of the composite resource that will be created. Defaults to
        the same name as the configuration name.

    .PARAMETER ModuleName
        The name of the resource module that will contain the new composite
        resource. Defaults to the same name as the configuration name, suffixed
        with DSC, e.g. ModuleNameDSC.

    .PARAMETER ModuleVersion
        The version number of the resource module being created.

    .PARAMETER Author
        The name of the author of the resource module. This will be added to the
        module manifest. Defaults to $env:USERNAME.

    .PARAMETER Description
        The description of the resource module. This will be added to the
        module manifest.
        Defaults to 'Automatically generated by the Composite Resource module.'

    .PARAMETER OutputPath
        The path to where the new resource module will be created. Defaults to
        the current folder.
        If there are already a resource module with the same name at the output
        path, that module will be updated.

    .OUTPUTS
        No command output returned when successful.

    .NOTES
        Get more information, and how to contribute, at https://github.com/Microsoft/CompositeResource.

    .EXAMPLE
        ConvertTo-CompositeResource -ConfigurationName 'Test' -Author 'Name' -Description 'Text'
#>

function ConvertTo-CompositeResource
{
    [CmdletBinding(DefaultParameterSetName = 'ByConfiguration')]
    param
    (
        [Parameter(Mandatory = $true, ParameterSetName = 'ByConfigurationName')]
        [string]
        $ConfigurationName,

        [Parameter(Mandatory = $true, ParameterSetName = 'ByScript')]
        [string]
        $Script,

        [Parameter()]
        [string]
        $ResourceName = $ConfigurationName,

        [Parameter()]
        [string]
        $ModuleName = "$($ConfigurationName)DSC",

        [Parameter(Mandatory = $true)]
        [version]
        $ModuleVersion,

        [Parameter()]
        [string]
        $Author = $env:USERNAME,

        [Parameter()]
        [string]
        $Description = 'Automatically generated by the Composite Resource module. http://github.com/microsoft/compositeresource',

        [Parameter()]
        [string]
        $OutputPath = '.\'
    )

    switch ($PsCmdlet.ParameterSetName)
    {
        "ByConfigurationName"
        {
            $configuration = Get-Command -Name $ConfigurationName -CommandType 'Configuration' -ErrorAction SilentlyContinue
            if (-not $configuration)
            {
                throw ('Could not find a configuration ''{0}'' loaded in the session.' -f $ConfigurationName)
            }
        }

        "ByScript"
        {
            # Get the configuration definition ast.
            $parseErrors = $null
            $definitionAst = [System.Management.Automation.Language.Parser]::ParseInput($Script, [ref] $null, [ref] $parseErrors)

            if ($parseErrors)
            {
                throw $parseErrors
            }

            $astFilter = {
                $args[0] -is [System.Management.Automation.Language.ConfigurationDefinitionAst]
            }

            $configurationDefinitionAst = $definitionAst.Find($astFilter, $false)

            # Get the script block definition ast, from the result of the configuration definition ast.
            $parseErrors = $null
            $definitionAst = [System.Management.Automation.Language.Parser]::ParseInput($configurationDefinitionAst.Body.Extent.Text, [ref] $null, [ref] $parseErrors)

            if ($parseErrors)
            {
                throw $parseErrors
            }

            $astFilter = {
                $args[0] -is [System.Management.Automation.Language.ScriptBlockAst]
            }

            $configurationContentDefinitionAst = $definitionAst.Find($astFilter, $false)

            # Removes the beginning open brace an ending closing brace of the script block.
            $configurationDefinition = $configurationContentDefinitionAst.Extent.Text
            $configurationDefinition = `
                $configurationDefinition.Remove($configurationContentDefinitionAst.Extent.EndScriptPosition.Offset - 1, 1)
            $configurationDefinition = `
                $configurationDefinition.Remove($configurationContentDefinitionAst.Extent.StartScriptPosition.Offset, 1)

            # Build the correct configuration values.
            $configuration = @{
                Definition =  $configurationDefinition
            }

            $ConfigurationName = $configurationDefinitionAst.InstanceName.Value

            # Set default values if they are not set.
            if (-not $PSBoundParameters.ContainsKey('ResourceName'))
            {
                $ResourceName = $ConfigurationName
            }

            if (-not $PSBoundParameters.ContainsKey('ModuleName'))
            {
                $ModuleName = "$($ConfigurationName)DSC"
            }
        }
    }

    $moduleFolder = Join-Path -Path $OutputPath -ChildPath $ModuleName
    $versionFolder = Join-Path -Path $moduleFolder -ChildPath $ModuleVersion.ToString()
    $dscResourcesFolder = Join-Path -Path $versionFolder -ChildPath 'DSCResources'
    $configurationFolder = Join-Path -Path $dscResourcesFolder -ChildPath $ResourceName

    # Creates the folder structure if any folder does not exist.
    if (-not (Resolve-Path -Path $configurationFolder -ErrorAction 'SilentlyContinue'))
    {
        New-Item -Path $configurationFolder -ItemType Directory -Force -ErrorAction Stop | Out-Null
    }

    $resourcePsm1 = Join-Path -Path $configurationFolder -ChildPath "$ResourceName.schema.psm1"
    $resourcePsd1 = Join-Path -Path $configurationFolder -ChildPath "$ResourceName.psd1"
    $modulePsd1 = Join-Path -Path $versionFolder -ChildPath "$ModuleName.psd1"

    Set-Content -Path $resourcePsm1 -Value @"
Configuration $ResourceName
{
$($configuration.Definition)
}
"@


    $resourceNames = @()

    # If we already got a module manifest, then pick up any existing resource names.
    if (Test-Path -Path $modulePsd1)
    {
        $moduleManifest = Import-PowerShellDataFile -Path $modulePsd1
        $resourceNames = @($moduleManifest.DscResourcesToExport)
    }

    if ($resourceNames -notcontains $ResourceName)
    {
        $resourceNames += $ResourceName
    }

    New-ModuleManifest -Path $modulePsd1 `
        -Guid (New-Guid).Guid `
        -Author $Author `
        -Description $Description `
        -ModuleVersion $ModuleVersion `
        -DscResourcesToExport $resourceNames

    New-ModuleManifest -Path $resourcePsd1 `
        -RootModule "$ResourceName.schema.psm1" `
        -Guid (New-Guid).Guid
}