Public/Export-ModuleCommandsForLLM.ps1

function Export-ModuleCommandsForLLM {
    <#
    .SYNOPSIS
        Exports Write-Verbose "Enumerating commands in module..."
        $commands = @(Get-Command -Module $ModuleName | Sort-Object Name)
 
        # Add validation after getting commands
        if (-not $commands) {
            throw "No commands found in module '$ModuleName'"
        }
 
        # Validate command count before processing
        if ($commands.Count -eq 0) {
            Write-Warning "Module '$ModuleName' contains no exportable commands"
            return $null
        }
 
        Write-Verbose "Found $($commands.Count) commands. Gathering detailed information using parallel processing..." -Verboseule commands information in LLM-friendly format.
    .PARAMETER ModuleName
        Name of the PowerShell module to analyze.
    .PARAMETER OutputDirectory
        Directory path where the output file will be saved.
    .PARAMETER FileName
        Name of the output file (without extension).
    .PARAMETER Format
        Output format: JSON, Markdown, or XML. Default is JSON.
    .EXAMPLE
        Export-ModuleCommandsForLLM -ModuleName "VCF.PowerCLI" -OutputDirectory "C:\Exports" -FileName "VCF-Commands"
    #>

    param(
        [Parameter(Mandatory = $true)]
        [string]$ModuleName,

        [Parameter(Mandatory = $true)]
        [ValidateScript({
            if(-not (Test-Path (Split-Path $_ -Parent))) {
                throw "Parent directory does not exist"
            }
            return $true
        })]
        [string]$OutputDirectory,

        [Parameter(Mandatory = $true)]
        [string]$FileName,

        [Parameter(Mandatory = $false)]
        [ValidateSet("JSON", "Markdown", "XML")]
        [string]$Format = "JSON",

        [Parameter(Mandatory = $false)]
        [int]$MaxConcurrentJobs = 8,

        [Parameter(Mandatory = $false)]
        [switch]$UseStreaming,

        [Parameter(Mandatory = $false)]
        [switch]$SkipProgressBar
    )

    try {
        # Add execution time tracking at the beginning
        $executionTime = [System.Diagnostics.Stopwatch]::StartNew()

        # Add input validation
        if ([string]::IsNullOrWhiteSpace($ModuleName)) {
            throw "ModuleName cannot be null or empty"
        }

        if ([string]::IsNullOrWhiteSpace($FileName)) {
            throw "FileName cannot be null or empty"
        }

        Write-Verbose "Analyzing module: $ModuleName" -Verbose

        # Check if module exists and is imported
        $module = Get-Module -Name $ModuleName -ErrorAction SilentlyContinue
        if (-not $module) {
            # Try to import the module
            Import-Module $ModuleName -ErrorAction Stop
            $module = Get-Module -Name $ModuleName
        }

        if (-not $module) {
            throw "Module '$ModuleName' not found or could not be imported"
        }

        # Create output directory if it doesn't exist
        if (-not (Test-Path $OutputDirectory)) {
            New-Item -Path $OutputDirectory -ItemType Directory -Force | Out-Null
        }

        # Get all commands from the module
        Write-Verbose "Enumerating commands in module..." -Verbose
        $commands = Get-Command -Module $ModuleName | Sort-Object Name

        # Add validation after getting commands
        if (-not $commands) {
            throw "No commands found in module '$ModuleName'"
        }

        # Validate command count before processing
        if ($commands.Count -eq 0) {
            Write-Warning "Module '$ModuleName' contains no exportable commands"
            return $null
        }

        Write-Verbose "Found $($commands.Count) commands. Gathering detailed information using parallel processing..." -Verbose

        # Define script block for parallel processing
        $processCommandScript = {
            param($CommandName, $ModuleName)

            try {
                # Import the module in this job context
                Import-Module $ModuleName -ErrorAction SilentlyContinue

                # Get command object
                $command = Get-Command -Name $CommandName -ErrorAction SilentlyContinue
                if (-not $command) {
                    throw "Command not found: $CommandName"
                }

                # Get help information
                $help = Get-Help $CommandName -Full -ErrorAction SilentlyContinue

                # Build command info object - Include Version property from the start
                $commandInfo = [PSCustomObject]@{
                    Name = $command.Name
                    CommandType = $command.CommandType.ToString()
                    Module = $command.ModuleName
                    Version = "Unknown"  # Add Version property here
                    Synopsis = if ($help.Synopsis) { $help.Synopsis.Trim() } else { "No synopsis available" }
                    Description = if ($help.Description) { ($help.Description | ForEach-Object { $_.Text }) -join "`n" } else { "No description available" }
                    Syntax = if ($help.Syntax) { ($help.Syntax.SyntaxItem | ForEach-Object { $_.ToString() }) } else { @() }
                    Parameters = @()
                    Examples = @()
                    Notes = if ($help.AlertSet) { ($help.AlertSet | ForEach-Object { $_.Alert.Text }) -join "`n" } else { "" }
                    RelatedLinks = if ($help.RelatedLinks) { ($help.RelatedLinks.NavigationLink | ForEach-Object { $_.Uri }) } else { @() }
                    OutputType = if ($command.OutputType) { $command.OutputType.Name } else { @() }
                    ProcessingError = $null
                }

                # Process parameters
                if ($help.Parameters -and $help.Parameters.Parameter) {
                    foreach ($param in $help.Parameters.Parameter) {
                        $paramInfo = [PSCustomObject]@{
                            Name = $param.Name
                            Type = $param.Type.Name
                            Required = $param.Required -eq $true
                            Position = $param.Position
                            DefaultValue = $param.DefaultValue
                            AcceptsPipelineInput = $param.PipelineInput -ne "false"
                            Description = if ($param.Description) { ($param.Description | ForEach-Object { $_.Text }) -join " " } else { "" }
                        }
                        $commandInfo.Parameters += $paramInfo
                    }
                }

                # Process examples
                if ($help.Examples -and $help.Examples.Example) {
                    foreach ($example in $help.Examples.Example) {
                        $exampleInfo = [PSCustomObject]@{
                            Title = if ($example.Title) { $example.Title.Trim() } else { "Example" }
                            Code = if ($example.Code) { $example.Code.Trim() } else { "" }
                            Remarks = if ($example.Remarks) { ($example.Remarks | ForEach-Object { $_.Text }) -join "`n" } else { "" }
                        }
                        $commandInfo.Examples += $exampleInfo
                    }
                }

                return $commandInfo
            }
            catch {
                # Return error info for failed commands
                return [PSCustomObject]@{
                    Name = $CommandName
                    CommandType = "Unknown"
                    Module = $ModuleName
                    Version = "Unknown"  # Version property already included
                    Synopsis = "Error retrieving help information"
                    Description = "Error retrieving help information"
                    Syntax = @()
                    Parameters = @()
                    Examples = @()
                    Notes = ""
                    RelatedLinks = @()
                    OutputType = @()
                    ProcessingError = $_.Exception.Message
                }
            }
        }

        # Process commands in parallel batches
        $commandDetails = @()
        $totalCommands = $commands.Count
        $processedCount = 0

        # Split commands into batches for parallel processing
        $batchSize = [Math]::Min($MaxConcurrentJobs, $totalCommands)
        $batches = for ($i = 0; $i -lt $totalCommands; $i += $batchSize) {
            $endIndex = [Math]::Min($i + $batchSize - 1, $totalCommands - 1)
            $commands[$i..$endIndex]
        }

        foreach ($batch in $batches) {
            if (-not $SkipProgressBar) {
                Write-Progress -Activity "Processing Commands" -Status "Processing batch of $(@($batch).Count) commands" -PercentComplete (($processedCount / $totalCommands) * 100)
            }

            # Start jobs for this batch
            $jobs = @()
            foreach ($command in $batch) {
                $job = Start-Job -ScriptBlock $processCommandScript -ArgumentList $command.Name, $ModuleName
                $jobs += $job
            }

            # Wait for all jobs in this batch to complete with timeout
            $timeout = 120 # 2 minutes per batch
            $null = Wait-Job -Job $jobs -Timeout $timeout

            # Collect results from completed jobs with better error recovery
            foreach ($job in $jobs) {
                try {
                    if ($job.State -eq "Completed") {
                        $result = Receive-Job -Job $job -ErrorAction SilentlyContinue
                        if ($result) {
                            # Ensure Version property exists before setting
                            if ($result.PSObject.Properties.Name -contains 'Version') {
                                $result.Version = $module.Version.ToString()
                            }
                            $commandDetails += $result
                        }
                    } elseif ($job.State -eq "Failed") {
                        $errorInfo = Receive-Job -Job $job -ErrorAction SilentlyContinue
                        Write-Warning "Job failed for command processing: $($errorInfo)"
                        # Add placeholder for failed job
                        $commandDetails += [PSCustomObject]@{
                            Name = "Unknown"
                            CommandType = "Unknown"
                            Module = $ModuleName
                            Version = $module.Version.ToString()
                            Synopsis = "Job processing failed or timed out"
                            Description = "Job processing failed or timed out"
                            Syntax = @()
                            Parameters = @()
                            Examples = @()
                            Notes = ""
                            RelatedLinks = @()
                            OutputType = @()
                            ProcessingError = "Job timeout or failure"
                        }
                    } else {
                        # Job is still running or in an unknown state
                        Write-Warning "Job for command processing timed out or failed: $($job.Name)"
                        # Add placeholder for failed job
                        $commandDetails += [PSCustomObject]@{
                            Name = "Unknown"
                            CommandType = "Unknown"
                            Module = $ModuleName
                            Version = $module.Version.ToString()
                            Synopsis = "Job processing failed or timed out"
                            Description = "Job processing failed or timed out"
                            Syntax = @()
                            Parameters = @()
                            Examples = @()
                            Notes = ""
                            RelatedLinks = @()
                            OutputType = @()
                            ProcessingError = "Job timeout or failure"
                        }
                    }
                } catch {
                    Write-Warning "Error receiving job results: $($_.Exception.Message)"
                } finally {
                    Remove-Job -Job $job -Force -ErrorAction SilentlyContinue
                }
            }

            $processedCount += @($batch).Count
        }

        if (-not $SkipProgressBar) {
            Write-Progress -Activity "Processing Commands" -Completed
        }

        # Sort results by name for consistency
        $commandDetails = $commandDetails | Sort-Object Name

        # Report any processing errors
        $errorCount = @($commandDetails | Where-Object ProcessingError).Count
        if ($errorCount -gt 0) {
            Write-Warning "Processing errors occurred for $errorCount commands"
        }

        Write-Verbose "Parallel processing completed. Successfully processed $($commandDetails.Count - $errorCount) of $($commandDetails.Count) commands." -Verbose

        # Create module summary
        $moduleSummary = [PSCustomObject]@{
            ModuleName = $ModuleName
            ModuleVersion = $module.Version.ToString()
            ExportedCommands = $commands.Count
            CommandTypes = ($commands | Group-Object CommandType | ForEach-Object { "$($_.Name): $($_.Count)" })
            GeneratedOn = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
            GeneratedBy = $env:USERNAME
            Commands = $commandDetails
        }

        # Export based on format
        $fileExtension = switch ($Format) {
            "JSON" { "json" }
            "Markdown" { "md" }
            "XML" { "xml" }
        }

        $outputPath = Join-Path $OutputDirectory "$FileName.$fileExtension"

        switch ($Format) {
            "JSON" {
                if ($UseStreaming) {
                    # Stream JSON output for large datasets
                    $writer = [System.IO.StreamWriter]::new($outputPath)
                    try {
                        $writer.WriteLine("{")
                        $writer.WriteLine("`"ModuleName`": `"$($ModuleName)`",")
                        $writer.WriteLine("`"ModuleVersion`": `"$($module.Version)`",")
                        $writer.WriteLine("`"ExportedCommands`": $($commands.Count),")
                        $writer.WriteLine("`"GeneratedOn`": `"$($moduleSummary.GeneratedOn)`",")
                        $writer.WriteLine("`"GeneratedBy`": `"$($moduleSummary.GeneratedBy)`",")
                        $writer.WriteLine("`"CommandTypes`": {")

                        $commandTypeGroups = $commands | Group-Object CommandType
                        $firstGroup = $true
                        foreach ($group in $commandTypeGroups) {
                            if (-not $firstGroup) { $writer.Write(",") }
                            $writer.Write("`"$($group.Name)`": $($group.Count)")
                            $firstGroup = $false
                        }

                        $writer.WriteLine("},")
                        $writer.WriteLine("`"Commands`": [")

                        $first = $true
                        foreach ($cmd in $commandDetails) {
                            if (-not $first) { $writer.Write(",") }
                            $writer.WriteLine(($cmd | ConvertTo-Json -Depth 10))
                            $first = $false
                        }

                        $writer.WriteLine("]}")
                    } finally {
                        $writer.Dispose()
                    }
                } else {
                    # Use proper formatting for all datasets
                    $moduleSummary | ConvertTo-Json -Depth 10 | Set-Content -Path $outputPath -Encoding UTF8
                }
            }
            "Markdown" {
                Export-ToMarkdown -ModuleData $moduleSummary -OutputPath $outputPath
            }
            "XML" {
                $moduleSummary | Export-Clixml -Path $outputPath
            }
        }

        # Validate output path was created
        if (-not (Test-Path $outputPath)) {
            throw "Failed to create output file at: $outputPath"
        }

        # Stop execution timer
        $executionTime.Stop()

        # Add file size information
        $fileInfo = Get-Item $outputPath
        Write-Verbose "Output file size: $([Math]::Round($fileInfo.Length / 1MB, 2)) MB" -Verbose

        Write-Verbose "Module analysis complete!" -Verbose
        Write-Verbose "Commands processed: $($commandDetails.Count)" -Verbose
        Write-Verbose "Export completed in $([Math]::Round($executionTime.Elapsed.TotalSeconds, 2)) seconds" -Verbose
        Write-Verbose "Output saved to: $outputPath" -Verbose

        # Return only the path
        return $outputPath
    }
    catch {
        Write-Error "Failed to export module commands: $($_.Exception.Message)"
        Write-Verbose "Stack Trace: $($_.ScriptStackTrace)" -Verbose:$VerbosePreference
        throw
    }
}