DSCParser.psm1

# DSCParser.CSharp PowerShell Module
# Loads the C# assembly using Assembly Load Context for isolation

$Script:ModuleRoot = $PSScriptRoot

# Check if running in PowerShell Core or Windows PowerShell
$Script:IsPowerShellCore = $PSVersionTable.PSEdition -eq 'Core'
$Script:AssemblyPath = Join-Path $Script:ModuleRoot "bin\DSCParser.CSharp.dll"
$Script:AssemblyLoaded = $false

# Function to load the C# assembly using Assembly Load Context
function Initialize-DscParserAssembly
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param()

    if ($Script:AssemblyLoaded)
    {
        Write-Verbose "DSCParser.CSharp assembly is already initialized."
        return $true
    }

    try
    {
        # Check if assembly file exists
        if (-not (Test-Path -Path $Script:AssemblyPath))
        {
            throw "DSCParser.CSharp assembly not found at: $Script:AssemblyPath. Please build the C# project first."
        }

        Add-Type -Path $Script:AssemblyPath -ErrorAction Stop

        Write-Verbose -Message "Successfully loaded DSCParser.CSharp assembly (PowerShell $($PSVersionTable.PSEdition))"
        return $true
    }
    catch
    {
        Write-Error "Failed to load DSCParser.CSharp assembly: $_"
        return $false
    }
}

# Initialize the assembly on module import
$Script:AssemblyLoaded = Initialize-DscParserAssembly

<#
.SYNOPSIS
    Converts a DSC configuration file or content to DSC objects.
 
.DESCRIPTION
    This function parses a DSC configuration file or string content and converts it
    into an array of hashtables representing each DSC resource instance.
    Uses the C# implementation with Assembly Load Context isolation.
 
.PARAMETER Path
    The path to the DSC configuration file to parse.
 
.PARAMETER Content
    The DSC configuration content as a string.
 
.PARAMETER IncludeComments
    Include comment metadata in the parsed output.
 
.PARAMETER Schema
    Optional schema definition for parsing.
 
.PARAMETER IncludeCIMInstanceInfo
    Include CIM instance information in the output. Default is $true.
 
.PARAMETER DscResources
    An array of DscResourceInfo objects to assist in parsing.
 
.EXAMPLE
    ConvertTo-DSCObject -Path "C:\DSCConfigs\MyConfig.ps1"
 
.EXAMPLE
    $content = Get-Content "MyConfig.ps1" -Raw
    ConvertTo-DSCObject -Content $content -IncludeComments $true
#>

function ConvertTo-DSCObject
{
    [CmdletBinding(DefaultParameterSetName = 'Path')]
    [OutputType([Array])]
    param
    (
        [Parameter(Mandatory = $true, ParameterSetName = 'Path')]
        [ValidateScript({
            if (-not ($_ | Test-Path)) {
                throw "File or folder does not exist"
            }
            if (-not ($_ | Test-Path -PathType Leaf)) {
                throw "The Path argument must be a file. Folder paths are not allowed."
            }
            return $true
        })]
        [System.String]
        $Path,

        [Parameter(Mandatory = $true, ParameterSetName = 'Content')]
        [System.String]
        $Content,

        [Parameter(ParameterSetName = 'Path')]
        [Parameter(ParameterSetName = 'Content')]
        [System.Boolean]
        $IncludeComments = $false,

        [Parameter(ParameterSetName = 'Path')]
        [Parameter(ParameterSetName = 'Content')]
        [System.String]
        $Schema,

        [Parameter(ParameterSetName = 'Path')]
        [Parameter(ParameterSetName = 'Content')]
        [System.Boolean]
        $IncludeCIMInstanceInfo = $true,

        [Parameter(ParameterSetName = 'Path')]
        [Parameter(ParameterSetName = 'Content')]
        [System.Object[]]
        $DscResourceInfo
    )

    if (-not $Script:AssemblyLoaded)
    {
        throw "DSCParser.CSharp assembly is not loaded. Module initialization failed."
    }

    try
    {
        if ($null -eq $Script:DscResourceCache -and -not $PSBoundParameters.ContainsKey('DscResourceInfo'))
        {
            $Script:DscResourceCache = Get-DscResourceV2
        }
        elseif ($PSBoundParameters.ContainsKey('DscResourceInfo'))
        {
            $Script:DscResourceCache = $DscResourceInfo
        }

        $options = [DSCParser.CSharp.DscParseOptions]::new()

        # Set options
        $options.IncludeComments = $IncludeComments
        $options.IncludeCIMInstanceInfo = $IncludeCIMInstanceInfo
        if (-not [string]::IsNullOrEmpty($Schema))
        {
            $options.Schema = $Schema
        }

        # Call ConvertToDscObject
        if ($PSCmdlet.ParameterSetName -eq 'Path')
        {
            $result = [DSCParser.CSharp.DscParser]::ConvertToDscObject($Path, $null, $options, $Script:DscResourceCache)
        }
        else
        {
            $result = [DSCParser.CSharp.DscParser]::ConvertToDscObject($null, $Content, $options, $Script:DscResourceCache)
        }

        # Convert result to array of hashtables
        $output = @()
        foreach ($item in $result)
        {
            $hashtable = $item.ToHashtable()
            $output += $hashtable
        }

        return $output
    }
    catch
    {
        Write-Error "Error parsing DSC configuration: $_"
        throw
    }
}

<#
.SYNOPSIS
    Converts DSC objects back to DSC configuration text.
 
.DESCRIPTION
    This function takes an array of hashtables representing DSC resources
    and converts them back into DSC configuration text format.
    Uses the C# implementation with Assembly Load Context isolation.
 
.PARAMETER DSCResources
    An array of hashtables representing DSC resource instances.
 
.PARAMETER ChildLevel
    The indentation level for nested resources. Default is 0.
 
.EXAMPLE
    $resources = ConvertTo-DSCObject -Path "MyConfig.ps1"
    $dscText = ConvertFrom-DSCObject -DSCResources $resources
#>

function ConvertFrom-DSCObject
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [System.Collections.Hashtable[]]
        $DSCResources,

        [Parameter(Mandatory = $false)]
        [System.Int32]
        $ChildLevel = 0
    )

    process
    {
        if (-not $Script:AssemblyLoaded)
        {
            throw "DSCParser.CSharp assembly is not loaded. Module initialization failed."
        }

        try
        {
            $result = [DSCParser.CSharp.DscParser]::ConvertFromDscObject($DSCResources, $ChildLevel)
            return $result
        }
        catch
        {
            Write-Error "Error converting DSC objects: $_"
            throw
        }
    }
}