public/Split-ModuleHelp.ps1
function Split-ModuleHelp { <# .SYNOPSIS Splits module help content into multiple files for use with ChatGPT Custom GPTs. .DESCRIPTION The Split-ModuleHelp function retrieves clean help content for specified modules using Get-CleanHelp and combines it with files from an optional path. It then splits the content into a specified number of files (up to a maximum of 20, which is the limit for ChatGPT Custom GPTs) and exports them to an output directory. The function can process multiple modules and returns the list of output files and module files for each module. If the number of commands in the module plus the count of additional files is less than or equal to the specified FileCount, it outputs one file per command. .PARAMETER Module The name(s) of the module(s) to retrieve help content from. Accepts an array of strings or PSCustomObjects with a Name or ModuleName property. .PARAMETER IncludePath An optional path to include additional files for splitting. .PARAMETER OutputPath The directory where the split files will be saved. If not specified, it defaults to the current directory plus the module name. .PARAMETER FileCount The desired number of output files. Defaults to 20, with a maximum of 20. .PARAMETER Filter A script block to filter the help content before splitting. .PARAMETER MaxFileSizeKB Maximum size in KB for each split file to ensure manageability. .EXAMPLE PS C:\> Split-ModuleHelp -Module dbatools, PSOpenAI -IncludePath C:\AdditionalFiles -OutputPath C:\SplitFiles -FileCount 18 Splits clean help content from the dbatools and PSOpenAI modules, along with files from "C:\AdditionalFiles", into 18 files each in the "C:\SplitFiles\dbatools" and "C:\SplitFiles\PSOpenAI" directories. .EXAMPLE PS C:\> Split-ModuleHelp -Module @{ModuleName = 'PSOpenAI'}, @{Name = 'dbatools'} Splits clean help content from the PSOpenAI and dbatools modules into 20 files (default) in the current directory plus the respective module names. #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string[]]$Module, [string]$IncludePath, [string]$OutputPath, [ValidateRange(1, 20)] [int]$FileCount = 20, [scriptblock]$Filter, [int]$MaxFileSizeKB ) $PSDefaultParameterValues['Get-CleanHelp:NoProgress'] = $true foreach ($moduleName in $Module) { if ($moduleName -is [PSCustomObject]) { if ($moduleName.Name) { $moduleName = $moduleName.Name } elseif ($moduleName.ModuleName) { $moduleName = $moduleName.ModuleName } } try { # Import the module Import-Module $moduleName -ErrorAction Stop -Verbose:$false } catch { Write-Error "Failed to import module $moduleName - $PSItem" continue } # Get the commands in the module $commands = Get-Command -Module $moduleName -Type Cmdlet, Function # Collect additional files if IncludePath is specified $additionalFiles = @() if ($IncludePath -and (Test-Path -Path $IncludePath)) { try { $additionalFiles = Get-ChildItem -Path $IncludePath -File } catch { Write-Warning "Failed to retrieve files from path $IncludePath - $PSItem" } } # Calculate the total count of commands and additional files $totalCount = $commands.Count + $additionalFiles.Count # Create the output directory if not specified if (-not $OutputPath) { $OutputPath = Join-Path -Path (Get-Location) -ChildPath $moduleName } $null = New-Item -ItemType Directory -Path $OutputPath -Force # Replace placeholders in the instructions file $moduleVersion = (Get-Module $moduleName | Select-Object -First 1).Version.ToString() $instructionsFile = Join-Path -Path $script:ModuleRoot -ChildPath instructions.md $outfile = Join-Path -Path $OutputPath -ChildPath "instructions.txt" if (Test-Path -Path $instructionsFile) { $instructions = Get-Content -Path $instructionsFile -Raw $instructions = $instructions -replace '--MODULENAME--', $moduleName $instructions = $instructions -replace '--MODULEVERSION--', $moduleVersion $instructions = $instructions -replace '--TODAYSDATE--', (Get-Date -Format "dd MMM yyyy") $instructions | Out-File -FilePath $outfile -Encoding UTF8 -Force Get-ChildItem $outfile } # Initialize progress $progressActivity = "Splitting Help Content for $moduleName" $progressParams = @{ Activity = $progressActivity Status = "Processing file 0 of $FileCount" PercentComplete = 0 } Write-Progress @progressParams $previousContent = "" if ($totalCount -le $FileCount -or $moduleName -eq 'PSHelp.Copilot') { # Output one file per command if the total count is less than or equal to FileCount foreach ($command in $commands) { $outputFile = Join-Path -Path $OutputPath -ChildPath "$($command.Name).txt" try { $helpContent = Get-CleanHelp -Command $command -As String if (-not $helpContent) { Write-Verbose "No help content found for command: $($command.Name)" continue } if ($Filter) { $helpContent = $helpContent | Where-Object $Filter } if ($helpContent -eq $previousContent) { continue } $previousContent = $helpContent $helpContent | Out-File -FilePath $outputFile -Encoding UTF8 -Force Get-ChildItem $outputFile Write-Verbose "Processed command: $($command.Name)" } catch { Write-Warning "Failed to retrieve help content for command $($command.Name) in module $moduleName - $PSItem" } # Update progress $progressParams.PercentComplete = (($commands.IndexOf($command) + 1) / $totalCount) * 100 $progressParams.Status = "Processing file $($commands.IndexOf($command) + 1) of $totalCount" Write-Progress @progressParams } # Output additional files foreach ($file in $additionalFiles) { $outputFile = Join-Path -Path $OutputPath -ChildPath $file.Name try { $content = Get-Content -Path $file.FullName -Raw if ($content -eq $previousContent) { continue } $previousContent = $content $content | Out-File -FilePath $outputFile -Encoding UTF8 -Force Get-ChildItem $outputFile Write-Verbose "Processed additional file: $($file.Name)" } catch { Write-Warning "Failed to retrieve content from file $($file.FullName) - $PSItem" } # Update progress $progressParams.PercentComplete = (($commands.Count + $additionalFiles.IndexOf($file) + 1) / $totalCount) * 100 $progressParams.Status = "Processing file $($commands.Count + $additionalFiles.IndexOf($file) + 1) of $totalCount" Write-Progress @progressParams } } else { # Calculate the number of items per file $itemsPerFile = [math]::Ceiling($totalCount / $FileCount) # Process commands and additional files in batches for ($i = 0; $i -lt $FileCount; $i++) { $startIndex = $i * $itemsPerFile $endIndex = [math]::Min((($i + 1) * $itemsPerFile) - 1, $totalCount - 1) $outputFile = Join-Path -Path $OutputPath -ChildPath "$moduleName-$($i + 1).txt" # Get clean help content for the batch of commands $helpContent = $commands[$startIndex..$endIndex] | ForEach-Object { try { Get-CleanHelp -Command $PSItem -As String } catch { Write-Warning "Failed to retrieve help content for command $($PSItem.Name) in module $moduleName - $PSItem" return } } if ($Filter) { $helpContent = $helpContent | Where-Object $Filter } # Get content from the batch of additional files $additionalContent = $additionalFiles[$startIndex..$endIndex] | ForEach-Object { try { Get-Content -Path $PSItem.FullName -Raw } catch { Write-Warning "Failed to retrieve content from file $($PSItem.FullName) - $PSItem" return } } # Combine help content and additional file content $combinedContent = @($helpContent) + @($additionalContent) -join "`n" # Check if combined content is same as previous if ($combinedContent -eq $previousContent) { continue } $previousContent = $combinedContent # Check file size if MaxFileSizeKB is specified if ($MaxFileSizeKB) { while ([System.Text.Encoding]::UTF8.GetByteCount($combinedContent) / 1KB -gt $MaxFileSizeKB) { $combinedContent = $combinedContent.Substring(0, $combinedContent.LastIndexOf("`n")) } } # Output the file to the pipeline $combinedContent | Out-File -FilePath $outputFile -Encoding UTF8 -Force Get-ChildItem $outputFile $batchCommands = $commands[$startIndex..$endIndex] $batchAdditionalFiles = $additionalFiles[$startIndex..$endIndex] $batchCount = $batchCommands.Count + $batchAdditionalFiles.Count $batchPercentage = [math]::Round(($batchCount / $totalCount) * 100) Write-Verbose "Processed batch $($i + 1) of $FileCount - Commands: $($batchCommands.Count), Additional Files: $($batchAdditionalFiles.Count), Total: $batchCount ($batchPercentage%)" # Update progress $progressParams.PercentComplete = (($i + 1) / $FileCount) * 100 $progressParams.Status = "Processing file $($i + 1) of $FileCount" Write-Progress @progressParams } } # Complete progress $progressParams.Completed = $true Write-Progress @progressParams } } |