Functions/GenXdev.Helpers/Invoke-OnEachGenXdevModule.ps1
|
<# .SYNOPSIS Executes a script block on each GenXdev module in the workspace. .DESCRIPTION This function iterates through GenXdev modules in the workspace and executes a provided script block against each module. It can filter modules by name pattern, exclude local modules, include only published modules, or process scripts instead of modules. The function automatically navigates to the correct module directory before executing the script block. .LICENSE Copyright (C) 2026 René Vaessen / GenXdev This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. .PARAMETER Script The script block to execute for each GenXdev module. The module object is passed as an argument to the script block. .PARAMETER ModuleName Filter to apply to module names. Supports wildcards and multiple patterns. Defaults to 'GenXdev*' to include all GenXdev modules. .PARAMETER NoLocal Excludes local development modules from processing. .PARAMETER OnlyPublished Includes only published modules that have LICENSE and README.md files. .PARAMETER FromScripts Process scripts directory instead of module directories. .PARAMETER IncludeScripts Includes the scripts directory in addition to regular modules. .PARAMETER IncludeGenXdevMainModule Includes the main GenXdev module in addition to sub-modules. .EXAMPLE Invoke-OnEachGenXdevModule -Script { Write-Host $args[0].Name } Lists all GenXdev module names. .EXAMPLE foreach-genxdev-module-do { param($ModuleObj, $isScriptsFolder, $isSubModule, $subModuleName) Get-ChildItem } -ModuleName "GenXdev.AI" Uses alias to list contents of the GenXdev.AI module directory. #> function Invoke-OnEachGenXdevModule { [CmdletBinding()] [Alias('foreach-genxdev-module-do')] param( ############################################################################### [Parameter( Mandatory = $true, Position = 0, HelpMessage = ('The script block to execute for each ' + 'GenXdev module') )] [Alias('ScriptBlock')] [scriptblock] $Script, ############################################################################### [Parameter( Mandatory = $false, Position = 1, HelpMessage = 'Filter to apply to module names' )] [ValidateNotNullOrEmpty()] [Alias('Module', 'BaseModuleName', 'SubModuleName')] [ValidatePattern('^(GenXdev|GenXde[v]\*|GenXdev(\.[\w\*\[\]\?]*)+)+$')] [SupportsWildcards()] [string[]] $ModuleName = @('GenXdev*'), ############################################################################### [Parameter( Mandatory = $false, HelpMessage = 'Excludes local development modules from processing' )] [switch] $NoLocal, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = ('Includes only published modules that have LICENSE ' + 'and README.md files') )] [switch] $OnlyPublished, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = 'Process scripts directory instead of module directories' )] [switch] $FromScripts, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = ('Includes the scripts directory in addition to ' + 'regular modules') )] [switch] $IncludeScripts ) begin { # resolve the scripts directory path relative to current module $scriptsPath = [IO.Path]::GetFullPath("$($MyInvocation.MyCommand.Module.ModuleBase)\..\..\..\Scripts") if (-not [IO.Directory]::Exists($scriptsPath)) { mkdir $scriptsPath } # resolve the modules directory path relative to current module $modulesPath = [IO.Path]::GetFullPath("$($MyInvocation.MyCommand.Module.ModuleBase)\..\..") if (-not [IO.Directory]::Exists($modulesPath)) { mkdir $modulesPath } # override module name filter when processing scripts only if ($FromScripts) { $ModuleName = @('GenXdev.Scripts') } $allGenXdevModules = @(Microsoft.PowerShell.Management\Get-ChildItem "$modulesPath\GenXdev*.psd1" -Recurse | Microsoft.PowerShell.Core\Where-Object { $_.FullName -like "*\3.30.2026\GenXdev*" } | Microsoft.PowerShell.Core\ForEach-Object { [IO.Path]::GetFileNameWithoutExtension($_.FullName) }) + @("GenXdev.Scripts") $genXdevModules = $allGenXdevModules if ($ModuleName.Count -eq 1 -and $ModuleName[0] -eq 'GenXdev*') { $genXdevModules = @($allGenXdevModules | Microsoft.PowerShell.Core\ForEach-Object { if ((-not ($IncludeScripts -or $FromScripts)) -and ($_ -like "GenXdev.Scripts")) { return; } $_ }) } } process { } end { # iterate through each specified module name pattern $uniqueModulesNames = @($ModuleName | Microsoft.PowerShell.Core\ForEach-Object { $moduleToFind = $_ foreach ($module in $genXdevModules) { if ($module -like $moduleToFind) { if (($module -like "GenXdev.Scripts") -and ($includeScripts)) { $module continue } if ([IO.File]::Exists(([IO.Path]::GetFullPath("$($MyInvocation.MyCommand.Module.ModuleBase)\..\..\$module\3.30.2026\$module.psd1")))) { $module } } elseif (Microsoft.PowerShell.Management\Test-Path "$($MyInvocation.MyCommand.Module.ModuleBase)\..\..\$module\3.30.2026\Functions\$moduleToFind" -ErrorAction SilentlyContinue) { $module } } } | Microsoft.PowerShell.Utility\Select-Object -Unique | Microsoft.PowerShell.Utility\Sort-Object { $_.Length.ToString().PadLeft(5, '0') + "_" + $_.ToLower() }) foreach ($nextModule in $uniqueModulesNames) { # split module name into parts for pattern matching $partsObj = $nextModule.Split(".") # output current module pattern being processed Microsoft.PowerShell.Utility\Write-Verbose ( "Processing module pattern: $nextModule") # define internal function to process individual modules function processModule { param($ModuleObj, $parts) # construct expected license file path for published modules $licenseFilePath = ( "$($ModuleObj.FullName)\3.30.2026\LICENSE") # construct expected readme file path for published modules $readmeFilePath = ( "$($ModuleObj.FullName)\3.30.2026\README.md") # skip license and readme checks for scripts directory if ($ModuleObj.FullName -eq $scriptsPath) { $licenseFilePath = $null $readmeFilePath = $null } # determine if module should be processed based on all filters $shouldProcess = ( (($ModuleObj.Name -like 'GenXdev') -or ($includeScripts -and ($ModuleObj.Name -like 'GenXdev.Scripts')) -or ((-not $OnlyPublished) -and (-not $NoLocal) -and ($ModuleObj.Name -like 'GenXdev.Local*'))) -and ((-not $OnlyPublished) -or ( [IO.File]::Exists($licenseFilePath) -and [IO.File]::Exists($readmeFilePath)))) # process module if it passes all filters if ($shouldProcess) { # output module name being processed Microsoft.PowerShell.Utility\Write-Verbose ( "Processing module: $($ModuleObj.Name)") # save current location to restore after processing $originalLocation = ( Microsoft.PowerShell.Management\Get-Location).Path try { # handle scripts directory with special logic if ($ModuleObj.Name -like 'GenXdev.Scripts') { # navigate directly to scripts directory Microsoft.PowerShell.Management\Set-Location ` -LiteralPath $scriptsPath } else { # initialize version variable for parsing [version] $version = $null # navigate to module root directory first Microsoft.PowerShell.Management\Set-Location ` -LiteralPath "$($ModuleObj.FullName)" # find the latest version directory for the module $versionDirectory = ( Microsoft.PowerShell.Management\Get-ChildItem ` -LiteralPath '.\' ` -Filter "*.*.*" ` -Directory ` -ErrorAction SilentlyContinue | Microsoft.PowerShell.Core\Where-Object { [Version]::TryParse($_.Name, [ref]$version) } | Microsoft.PowerShell.Utility\Sort-Object { [Version]::new($_.Name) } -Descending | Microsoft.PowerShell.Utility\Select-Object ` -First 1 | Microsoft.PowerShell.Core\ForEach-Object { $_.FullName }) # skip module if no valid version directory found if ($null -eq $versionDirectory) { # output warning for modules without version dirs Microsoft.PowerShell.Utility\Write-Verbose ( "No version directory found for " + "$($ModuleObj.Name)") return } # navigate to the specific version directory Microsoft.PowerShell.Management\Set-Location ` -LiteralPath $versionDirectory # output current working directory for debugging Microsoft.PowerShell.Utility\Write-Verbose ( "Changed to directory: $versionDirectory") } # execute the user script with module context parameters Microsoft.PowerShell.Core\Invoke-Command ` -ScriptBlock $Script ` -ArgumentList @( ($ModuleObj | Microsoft.PowerShell.Utility\ConvertTo-Json | Microsoft.PowerShell.Utility\ConvertFrom-Json), ($ModuleObj.Name -eq "GenXdev.Scripts"), ($parts.Length -gt 2), ($parts -join '.')) ` -NoNewScope } finally { # always restore original working directory Microsoft.PowerShell.Management\Set-Location ` -LiteralPath $originalLocation } } else { # output information about skipped modules Microsoft.PowerShell.Utility\Write-Verbose ( "Skipping module: $($ModuleObj.Name) (filtered out)") } } # handle scripts directory as a special case if ($nextModule -like 'GenXdev.Scripts') { # create mock module object for scripts directory processModule -parts $partsObj -ModuleObj @{ Name = 'GenXdev.Scripts' FullName = $scriptsPath } continue } try { # process the matching module processModule -parts $partsObj -ModuleObj @{ Name = ($partsObj.Length -lt 2 ? $partsObj[0] : ($partsObj[0] + "." + $partsObj[1])) FullName = "$modulesPath\$($partsObj.Length -lt 2 ? $partsObj[0] : ($partsObj[0]+"."+$partsObj[1]))" } } catch { # output any errors encountered during processing Microsoft.PowerShell.Utility\Write-Error ` $_.Exception.Message } } } } ################################################################################ |