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 } } |