Public/New-DscResourceManifestFromClass.ps1

function New-DscResourceManifestFromClass {
    <#
        .SYNOPSIS
            Generates a Microsoft Desired State Configuration (DSC) resource manifest from PowerShell class-based DSC resources.
 
        .DESCRIPTION
            The function `New-DscResourceManifestFromClass` generates a Microsoft DSC resource manifest by analyzing PowerShell class definitions
            decorated with the `[DscResource()]` attribute. It uses the Abstract Syntax Tree (AST) to inspect the structure of the classes and
            their members, extracting necessary information to create a manifest that describes the DSC resources.
 
            `New-DscResourceManifestFromClass` accepts a path to a `.ps1` or `.psm1` file containing one or more `[DscResource()]` decorated PowerShell classes.
            It then parses the file to identify classes that are decorated with `[DscResource()]`. For each identified class, the
            function inspects its properties and methods to determine the characteristics of the DSC resource,
            such as which properties are keys, mandatory, or have specific validation attributes.
            It also checks for the presence of methods like Get, Set, Test, Delete, and Export to determine the operations supported by the resource.
 
            When `-GenerateResourceScript` is specified a `resource.ps1` wrapper
            file is also written. This script bridges the class-based DSC resource model
            to the model DSC expects, allowing the manifest to point to it for execution.
 
        .PARAMETER Path
            Path to a `.ps1` or `.psm1` file containing one or more `[DscResource()]` decorated PowerShell classes.
 
        .PARAMETER ResourceTypePrefix
            Optional namespace prefix for the resource type.
            Example: "MyOrg.Windows" → type becomes "MyOrg.Windows/ClassName".
 
            Defaults to "LibreDsc.Tutorial"
 
        .PARAMETER Version
            Semantic version string for all generated resources. Defaults to "0.1.0".
 
        .PARAMETER Description
            Optional description applied to every resource entry. When omitted a sensible default is generated from the class name.
 
        .PARAMETER Executable
            Executable path to embed in the manifest operation entries.
            Only used when `-GenerateResourceScript` is NOT specified (placeholder mode). Defaults to "<executable>".
 
        .PARAMETER AllowNullKeys
            By default, properties decorated with [DscProperty(Key)] are considered required and must have a non-null value.
            When -AllowNullKeys is specified, key properties are allowed to have null values, making them optional in the generated manifest.
 
        .PARAMETER GenerateResourceScript
            When specified, a resource.ps1 wrapper script is generated
            alongside the manifest and the manifest entries point to it.
 
        .PARAMETER OutputDirectory
            Directory where the manifest (and optional script) are written.
            Defaults to the directory of the input file.
 
        .EXAMPLE
            New-DscResourceManifestFromClass -Path .Microsoft.Windows.Settings.psm1 `
                -ResourceTypePrefix 'Microsoft.Windows' `
                -GenerateResourceScript `
                -AllowNullKeys
 
        .NOTES
            Author: LibreDsc
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory, Position = 0)]
        [ValidateScript({ Test-Path $_ -PathType Leaf })]
        [string]
        $Path,

        [Parameter()]
        [string]
        $ResourceTypePrefix = 'LibreDsc.Tutorial',

        [Parameter()]
        [string]
        $Version = '0.1.0',

        [Parameter()]
        [string]
        $Description,

        [Parameter()]
        [string]
        $Executable,

        [Parameter()]
        [switch]
        $GenerateResourceScript,

        [Parameter()]
        [switch]
        $AllowNullKeys,

        [Parameter()]
        [string]
        $OutputDirectory
    )

    $resolvedPath   = (Resolve-Path $Path).Path
    $fileName       = [System.IO.Path]::GetFileNameWithoutExtension($resolvedPath)
    $moduleFileName = [System.IO.Path]::GetFileName($resolvedPath)

    if (-not $OutputDirectory) {
        $OutputDirectory = Split-Path $resolvedPath -Parent
    }
    if (-not (Test-Path $OutputDirectory)) {
        $null = New-Item -Path $OutputDirectory -ItemType Directory -Force
    }

    $resources = @(Get-DscResourceAstInfo -Path $resolvedPath)
    if ($resources.Count -eq 0) {
        throw "No DSC resource classes found in '$Path'."
    }

    Write-Verbose "Found $($resources.Count) DSC resource class(es): $($resources.ClassName -join ', ')"

    $scriptFileName = 'resource.ps1'
    $resourceScriptPath = $null

    if ($GenerateResourceScript) {
        $scriptContent      = New-DscResourceAdapterScript -ResourceInfos $resources -ModuleFileName $moduleFileName
        $resourceScriptPath = Join-Path $OutputDirectory $scriptFileName
        Set-Content -Path $resourceScriptPath -Value $scriptContent -Encoding UTF8 -NoNewline
        Write-Verbose "Generated resource adapter script: $resourceScriptPath"
    }

    $manifestEntries = [System.Collections.Generic.List[object]]::new()
    foreach ($resource in $resources) {
        $entryParams = @{
            ResourceInfo       = $resource
            ResourceTypePrefix = $ResourceTypePrefix
            Version            = $Version
            UseResourceScript  = $GenerateResourceScript
            ScriptFileName     = $scriptFileName
            ModuleFileName     = $moduleFileName
            AllowNullKeys      = $AllowNullKeys
        }
        if ($Description)  { $entryParams['Description'] = $Description }
        if ($Executable)   { $entryParams['Executable']  = $Executable  }

        $manifestEntries.Add((New-DscManifestEntry @entryParams))
    }

    if ($manifestEntries.Count -eq 1) {
        $manifestFileName = "$fileName.dsc.resource.json"
        $manifestContent  = $manifestEntries[0]
    } else {
        $manifestFileName = "$fileName.dsc.manifests.json"
        $manifestContent  = [ordered]@{
            resources = @($manifestEntries)
        }
    }

    $manifestPath = Join-Path $OutputDirectory $manifestFileName
    $json = $manifestContent | ConvertTo-Json -Depth 20
    Set-Content -Path $manifestPath -Value $json -Encoding UTF8 -NoNewline
    Write-Verbose "Generated manifest: $manifestPath"

    [PSCustomObject]@{
        ManifestPath       = $manifestPath
        ResourceScriptPath = $resourceScriptPath
        ResourceCount      = $resources.Count
        ResourceNames      = @($resources.ClassName)
    }
}