Public/Export-ModuleToFlatTextAndManifest.ps1
|
<# .SYNOPSIS Export all files and folders in a module to a JSON manifest and a single text file with clearly delimited sections for each file. .DESCRIPTION Export-ModuleToFlatTextAndManifest takes a path to a module root directory, recursively enumerates all files and folders, and generates: - A JSON manifest listing all file and folder paths relative to the module root (using / as the separator so the export can be imported on any platform), with type and parent path information. - A single text file containing the content of every file, with each file's content wrapped in a unique, easily parseable section header and footer. This can be used for archiving, review, or later reconstruction of the module folder structure using a companion import function. Only text files are exported; binary files (files containing NUL characters) are skipped with a warning. .PARAMETER ModulePath The root path of the module to export. .PARAMETER OutputPath The directory where the JSON manifest and text file will be written. Created if it does not exist. .EXAMPLE Export-ModuleToFlatTextAndManifest -ModulePath 'C:\git\tcs.intune\modules\tcs.intune' -OutputPath 'C:\temp\tcs.intune.export' Creates tcs.intune_manifest.json and tcs.intune_flat.txt in C:\temp\tcs.intune.export. .INPUTS None. ModulePath and OutputPath are not accepted from the pipeline. .OUTPUTS None. Writes the manifest and flat text files to OutputPath; use -Verbose to see paths. .LINK Import-ModuleFromFlatTextAndManifest .NOTES Author: TheCodeSaiyan Date: 2025-09-11 Compatible with PowerShell Constrained Language Mode (no [regex] or other restricted .NET types). #> function Export-ModuleToFlatTextAndManifest { [CmdletBinding()] [OutputType([void])] param( [Parameter(Mandatory = $true)] [string]$ModulePath, [Parameter(Mandatory = $true)] [string]$OutputPath ) $TelemetryArgs = @{ ModuleName = $MyInvocation.MyCommand.Module.Name ModuleVersion = [string]$MyInvocation.MyCommand.Module.Version CommandName = $MyInvocation.MyCommand.Name ExecutionID = [guid]::NewGuid().ToString() } Invoke-TelemetryCollection @TelemetryArgs -Stage Start -ClearTimer try { if (-not (Test-Path -Path $ModulePath -PathType Container)) { throw "ModulePath '$ModulePath' does not exist." } # Work with the full path so relative paths and trailing separators give correct relative names $moduleFullPath = (Resolve-Path -Path $ModulePath -ErrorAction Stop).ProviderPath.TrimEnd('\', '/') if (-not (Test-Path -Path $OutputPath)) { $null = New-Item -ItemType Directory -Path $OutputPath -Force -ErrorAction Stop } $items = Get-ChildItem -Path $moduleFullPath -Recurse -Force | ForEach-Object { $relPath = $_.FullName.Substring($moduleFullPath.Length).TrimStart('\', '/') -replace '\\', '/' $parentPath = if ($relPath -match '/') { $relPath -replace '/[^/]*$', '' } else { '' } [PSCustomObject]@{ RelativePath = $relPath ParentPath = $parentPath FullPath = $_.FullName ItemType = if ($_.PSIsContainer) { 'Directory' } else { 'File' } } } | Sort-Object -Property ItemType, RelativePath $moduleRoot = Split-Path -Path $moduleFullPath -Leaf $manifestPath = Join-Path -Path $OutputPath -ChildPath "${moduleRoot}_manifest.json" $textPath = Join-Path -Path $OutputPath -ChildPath "${moduleRoot}_flat.txt" $sections = @() $exportedItems = @() foreach ($item in $items) { if ($item.ItemType -eq 'File') { try { $content = Get-Content -Path $item.FullPath -Raw -Encoding UTF8 -ErrorAction Stop } catch { Write-Warning "Failed to read file: $($item.FullPath). Error: $($_.Exception.Message)" continue } if ($null -eq $content) { $content = '' } if ($content -match "`0") { Write-Warning "Skipping binary file: $($item.FullPath)" continue } # The import removes exactly one line break after the start marker and one before the end marker $sections += "###FILE-START:$($item.RelativePath)###`n$content`n###FILE-END:$($item.RelativePath)###`n" } $exportedItems += $item | Select-Object -Property RelativePath, ParentPath, ItemType, FullPath } $manifestObj = [PSCustomObject]@{ ModuleRoot = $moduleRoot Items = @($exportedItems) } $manifestObj | ConvertTo-Json -Depth 5 | Set-Content -Path $manifestPath -Encoding UTF8 -ErrorAction Stop Set-Content -Path $textPath -Value ($sections -join "`n") -Encoding UTF8 -NoNewline -ErrorAction Stop Write-Verbose "Exported manifest to $manifestPath and flat text to $textPath." Invoke-TelemetryCollection @TelemetryArgs -Stage End } catch { Invoke-TelemetryCollection @TelemetryArgs -Stage End -Failed $true -Exception $_ throw } } |