functions/powershell/module/Update-Psm1FromSource.ps1
function Update-Psm1FromSource { <# .SYNOPSIS Generates a .psm1 module file from function source files and optionally updates the module manifest. .DESCRIPTION This function scans the `functions` folder of a PowerShell module, filters `.ps1` files using an optional `exclude-files.json`, and assembles a `.psm1` file by dot-sourcing each included function. It also loads the associated compiled DLL (from the `lib` folder), and optionally updates the module manifest with the exported function names. To identify functions to export, a temporary module is created and imported to detect all defined functions before writing the final `.psm1`. .PARAMETER ModuleRoot The root directory of the module. This path must contain the module folder, a `functions` subfolder, and optionally the `exclude-files.json`. .PARAMETER UpdateManifest If specified, the associated module manifest (`.psd1`) will be updated with the list of exported functions. .OUTPUTS No direct output. The function writes the `.psm1` file and optionally updates the `.psd1` manifest file. .EXAMPLE Update-Psm1FromSource -ModuleRoot "C:\Modules\MyModule" -UpdateManifest Generates `MyModule.psm1` from function files and updates `MyModule.psd1` with exported function names. .NOTES • Uses a temporary copy of the module to detect exported functions via Import-Module. • Supports exclusion of specific files or folders via `exclude-files.json`. • Ensures the associated DLL (`lib\<ModuleName>.dll`) is correctly loaded in the module. #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$ModuleRoot, [string]$Psm1HeaderBlock, [string]$Psm1VariablesBlock, [switch]$UpdateManifest ) $ModuleRoot = (Resolve-Path -Path $ModuleRoot).Path $moduleName = Split-Path -Path $ModuleRoot -Leaf $psm1Path = Join-Path $ModuleRoot "$moduleName.psm1" $manifestPath = Join-Path $ModuleRoot "$moduleName.psd1" $functionsFolder = Join-Path $ModuleRoot 'functions' # $dllRelativePath = "lib\$moduleName.dll" $ExcludeListFile = Join-Path $ModuleRoot 'exclude-files.json' # Erstelle temporäres Verzeichnis $tempRoot = (New-TempDirectory -Name $moduleName).FullName $tempPsm1Path = Join-Path $tempRoot "$moduleName.psm1" # Header mit literalem Pfad (kein $PSScriptRoot im Temp-Modul!) # $header = @" # #NotRequires -RunAsAdministrator # "@ # # Load the compiled C# DLL # Import-Module '$tempRoot\$dllRelativePath' # $dllRelativePath = "lib\$moduleName.dll" # $tempFilePath = Join-Path -Path $tempRoot -ChildPath $dllRelativePath # $tempFileDir = Split-Path -Path $tempFilePath # New-Directory -Path $tempFileDir | Out-Null # Write-Verbose "Copy $(Split-Path -Leaf $dllRelativePath) to $tempFilePath" # Copy-Item -Path $(Join-Path -Path $ModuleRoot -ChildPath $dllRelativePath) -Destination $tempFilePath # Dot-source Zeilen sammeln $dotSourceLines = @() $finalDotSourceLines = @() $exportAliases = @() $functionFiles = Get-ChildItem -Path $functionsFolder -Recurse -Filter '*.ps1' | Sort-Object FullName $functionFiles = Get-FilteredFiles -Files $functionFiles -ExcludeListFile $ExcludeListFile -RootPath $ModuleRoot foreach ($file in $functionFiles) { $relativePath = $file.FullName.Substring($ModuleRoot.Length).TrimStart('\') $dotSourceLines += ". `"$tempRoot\$relativePath`"" $finalDotSourceLines += (". `$PSScriptRoot\$relativePath` ").TrimEnd() $tempFilePath = Join-Path -Path $tempRoot -ChildPath $relativePath $tempFileDir = Split-Path -Path $tempFilePath New-Directory -Path $tempFileDir | Out-Null Write-Verbose "Copy $($file.Name) to $tempFilePath" Copy-Item -Path $file.FullName -Destination $tempFilePath Write-Verbose "Search for Set-Alias definitions" Select-String -Path $file.FullName -Pattern 'Set-Alias\s+-Name\s+(\S+)' | ForEach-Object { $aliasMatches = [regex]::Match($_.Line, 'Set-Alias\s+-Name\s+(\S+)') if ($aliasMatches.Success) { $exportAliases += $aliasMatches.Groups[1].Value } } } # Erzeuge temporäre psm1-Datei zum Importieren $tempContent = @() if (-not ([string]::IsNullOrWhiteSpace($Psm1HeaderBlock))) { $tempContent += $Psm1HeaderBlock $tempContent += "" } $tempContent += $dotSourceLines if (-not ([string]::IsNullOrWhiteSpace($Psm1VariablesBlock))) { $tempContent += "" $tempContent += $Psm1VariablesBlock } Set-Content -Path $tempPsm1Path -Value $tempContent -Encoding UTF8 # Temporär importieren, um Funktionen zu erkennen Write-Verbose "Import module $tempPsm1Path" Import-Module $tempPsm1Path -Force $module = Get-Module -Name $moduleName $exportFunctions = $module.ExportedFunctions.Values.Name Write-Host "Functions loaded in temp module (Name: $moduleName, loaded from Path: $tempPsm1Path):" $exportFunctions $exportCmdlets = $module.ExportedCmdlets.Values.Name Write-Host "Cmdlets loaded in temp module (Name: $moduleName, loaded from Path: $tempPsm1Path):" $exportCmdlets Write-Host "Aliases loaded in temp module (Name: $moduleName, loaded from Path: $tempPsm1Path):" $exportAliases $confirmation = Read-Host "Geladene Funktionsliste, Cmdlets und Aliase korrekt und fortfahren? (y/n)" if ($confirmation -ne 'y') { throw '.psm1-Update abgebrochen.' } Remove-Item -Path $tempRoot -Recurse -Force # Export-Zeilen schreiben $exportLines = @() # Functions if ($exportFunctions.Count -gt 0) { $grouped = $exportFunctions | Sort-Object | Group-Object { ($_ -split '-')[0] } foreach ($group in $grouped) { $exportLines += ("Export-ModuleMember -Function " + ($group.Group -join ', ')).TrimEnd() } } #Aliases if ($exportAliases.Count -gt 0) { $grouped = $exportAliases | Sort-Object | Group-Object { ($_ -split '-')[0] } foreach ($group in $grouped) { $exportLines += ("Export-ModuleMember -Alias " + ($group.Group -join ', ')).TrimEnd() } } # Schreibe neue .psm1-Datei # $finalHeader = @" # #NotRequires -RunAsAdministrator # "@ # # Load the compiled C# DLL # Write-Verbose "Import `'`$PSScriptRoot\$dllRelativePath`'" # Import-Module "`$PSScriptRoot\$dllRelativePath" $psm1Content = @() if (-not ([string]::IsNullOrWhiteSpace($Psm1HeaderBlock))) { $psm1Content += $Psm1HeaderBlock $psm1Content += "" } $psm1Content += $finalDotSourceLines $psm1Content += "" $psm1Content += $exportLines if (-not ([string]::IsNullOrWhiteSpace($Psm1VariablesBlock))) { $psm1Content += "" $psm1Content += $Psm1VariablesBlock } Set-Content -Path $psm1Path -Value $psm1Content -Encoding UTF8 Write-Host "✅ Updated $psm1Path with $($dotSourceLines.Count) dot-sourced files and $($exportFunctions.Count) exported functions." if ($UpdateManifest) { Update-ModuleManifest -Path $manifestPath -FunctionsToExport $exportFunctions -CmdletsToExport $exportCmdlets -AliasesToExport $exportAliases Write-Host "✅ Updated $manifestPath with $($exportFunctions.Count) functions." } } |