Types/PSModuleInfo/FindExtensions.ps1
<# .SYNOPSIS Finds extensions for a module .DESCRIPTION Finds extended commands for a module. .EXAMPLE (Get-Module PipeScript).FindExtensions((Get-Module PipeScript | Split-Path)) #> param() $targetModules = @() $targetPaths = @() $loadedModules = Get-Module foreach ($arg in $args) { if ($arg -is [Management.Automation.PSModuleInfo]) { $targetModules += $arg } elseif ($arg -is [IO.FileInfo] -or $arg -is [IO.DirectoryInfo]) { $targetPaths += $arg } elseif ($arg -is [Management.Automation.PathInfo]) { $targetPaths += "$arg" } elseif ($arg -is [string]) { $argIsModule = foreach ($module in $loadedModules) { if ($module.Name -like $arg) { $module}} if ($argIsModule) { $targetModules += $argIsModule } elseif (Test-Path $arg) { $targetPaths += $arg } } } if (-not $targetModules) { $targetModules = $this} $Splat = @{} if ($targetPaths) { $Splat.FilePath = $targetPaths } foreach ($module in $targetModules) { # Aspect.ModuleExtendedCommand & { <# .SYNOPSIS Returns a module's extended commands .DESCRIPTION Returns the commands or scripts in a module that match the module command pattern. Each returned script will be decorated with the typename(s) that match, so that the extended commands can be augmented by the extended types system. .LINK Aspect.ModuleExtensionPattern .EXAMPLE Aspect.ModuleExtensionCommand -Module PipeScript # Should -BeOfType ([Management.Automation.CommandInfo]) #> [Alias('Aspect.ModuleExtendedCommand')] param( # The name of a module, or a module info object. [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [ValidateScript({ $validTypeList = [System.String],[System.Management.Automation.PSModuleInfo] $thisType = $_.GetType() $IsTypeOk = $(@( foreach ($validType in $validTypeList) { if ($_ -as $validType) { $true;break } })) if (-not $isTypeOk) { throw "Unexpected type '$(@($thisType)[0])'. Must be 'string','psmoduleinfo'." } return $true })] $Module, # A list of commands. # If this is provided, each command that is a valid extension will be returned. [Parameter(ValueFromPipelineByPropertyName)] [Management.Automation.CommandInfo[]] $Commands, # The suffix to apply to each named capture. # Defaults to '_Command' [Parameter(ValueFromPipelineByPropertyName)] [string] $Suffix = '_Command', # The prefix to apply to each named capture. [Parameter(ValueFromPipelineByPropertyName)] [string] $Prefix, # The file path(s). If provided, will look for commands within these paths. [Parameter(ValueFromPipelineByPropertyName)] [Alias('Fullname')] $FilePath, # The PowerShell command type. If this is provided, will only get commands of this type. [Parameter(ValueFromPipelineByPropertyName)] [Management.Automation.CommandTypes] $CommandType, # The base PSTypeName(s). # If provided, any commands that match the pattern will apply these typenames, too. [string[]] $PSTypeName ) process { if ($Module -is [string]) { $Module = Get-Module $Module } $ModuleInfo = $module if (-not $ModuleInfo) { return } $ModuleCommandPattern = # Aspect.ModuleExtensionPattern & { <# .SYNOPSIS Outputs a module's extension pattern .DESCRIPTION Outputs a regular expression that will match any possible pattern. .EXAMPLE Aspect.ModuleCommandPattern -Module PipeScript # Should -BeOfType ([Regex]) #> [Alias('Aspect.ModuleCommandPattern')] param( # The name of a module, or a module info object. [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [ValidateScript({ $validTypeList = [System.String],[System.Management.Automation.PSModuleInfo] $thisType = $_.GetType() $IsTypeOk = $(@( foreach ($validType in $validTypeList) { if ($_ -as $validType) { $true;break } })) if (-not $isTypeOk) { throw "Unexpected type '$(@($thisType)[0])'. Must be 'string','psmoduleinfo'." } return $true })] $Module, # The suffix to apply to each named capture. # Defaults to '_Command' [Parameter(ValueFromPipelineByPropertyName)] [string] $Suffix = '_Command', # The prefix to apply to each named capture. [Parameter(ValueFromPipelineByPropertyName)] [string] $Prefix ) process { if ($Module -is [string]) { $Module = Get-Module $Module } $ModuleInfo = $module #region Search for Module Extension Types if (-not $ModuleInfo) { return } $ModuleExtensionTypes = # Aspect.ModuleExtensionTypes & { <# .SYNOPSIS Outputs a module's extension types .DESCRIPTION Outputs the extension types defined in a module's manifest. .EXAMPLE # Outputs a PSObject with information about extension command types. # The two primary pieces of information are the `.Name` and `.Pattern`. Aspect.ModuleExtensionType -Module PipeScript # Should -BeOfType ([PSObject]) #> [Alias('Aspect.ModuleCommandTypes','Aspect.ModuleCommandType','Aspect.ModuleExtensionTypes')] param( # The name of a module, or a module info object. [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [ValidateScript({ $validTypeList = [System.String],[System.Management.Automation.PSModuleInfo] $thisType = $_.GetType() $IsTypeOk = $(@( foreach ($validType in $validTypeList) { if ($_ -as $validType) { $true;break } })) if (-not $isTypeOk) { throw "Unexpected type '$(@($thisType)[0])'. Must be 'string','psmoduleinfo'." } return $true })] $Module ) begin { $ExtensionCollectionNames = "Extension", "Command", "Cmdlet", "Function", "Alias", "Script", "Application", "File","Configuration" $ExtensionCollectionNames = @($ExtensionCollectionNames -replace '.+$','${0}Type') + @($ExtensionCollectionNames -replace '.+$','${0}Types') } process { #region Resolve Module Info if ($Module -is [string]) { $Module = Get-Module $Module } $ModuleInfo = $module if (-not $ModuleInfo) { return } #endregion Resolve Module Info #region Check Cache and Hopefully Return if (-not $script:ModuleExtensionTypeCache) { $script:ModuleExtensionTypeCache = @{} } if ($script:ModuleExtensionTypeCache[$ModuleInfo]) { return $script:ModuleExtensionTypeCache[$ModuleInfo] } #endregion Check Cache and Hopefully Return #region Find Extension Types $modulePrivateData = $ModuleInfo.PrivateData $SortedExtensionTypes = [Ordered]@{} foreach ($TypeOfExtensionCollection in $ExtensionCollectionNames) { $moduleExtensionTypes = if ($modulePrivateData.$TypeOfExtensionCollection) { $modulePrivateData.$TypeOfExtensionCollection } elseif ($modulePrivateData.PSData.$TypeOfExtensionCollection) { $modulePrivateData.PSData.$TypeOfExtensionCollection } else { $null } if (-not $moduleExtensionTypes) { continue } foreach ($commandType in @($ModuleExtensionTypes.GetEnumerator() | Sort-Object Key)) { if ($commandType.Value -is [Collections.IDictionary]) { if (-not $commandType.Value.Name) { $commandType.Value["Name"] = $commandType.Key } if (-not $commandType.Value.PSTypeName) { $commandType.Value["PSTypeName"] = "$($module.Name).ExtensionCommandType" } $SortedExtensionTypes[$commandType.Name] = $commandType.Value } else { $SortedExtensionTypes[$commandType.Name] = [Ordered]@{ PSTypeName = "$($module.Name).ExtensionCommandType" Name = $commandType.Key Pattern = $commandType.Value } } if ($TypeOfExtensionCollection -notmatch '(?>Extension|Command|Cmdlet)') { $SortedExtensionTypes[$commandType.Name].CommandType = $TypeOfExtensionCollection -replace 'Type(?:s)?$' } elseif ($TypeOfExtensionCollection -match 'Cmdlet') { $SortedExtensionTypes[$commandType.Name].CommandType = "(?>Alias|Function|Filter|Cmdlet)" } } } $SortedExtensionTypes.PSTypeName="$($Module.Name).ExtensionCommandTypes" $script:ModuleExtensionTypeCache[$ModuleInfo] = [PSCustomObject]$SortedExtensionTypes $script:ModuleExtensionTypeCache[$ModuleInfo] #endregion Find Extension Types } } -Module $moduleInfo if (-not $ModuleExtensionTypes) { return } # With some clever understanding of Regular expressions, we can make match any/all of our potential command types. # Essentially: Regular Expressions can look ahead (matching without changing the position), and be optional. # So we can say "any/all" by making a series of optional lookaheads. # We'll go thru each pattern in order $combinedRegex = @(foreach ($categoryExtensionTypeInfo in @($ModuleExtensionTypes.psobject.properties)) { $categoryPattern = $categoryExtensionTypeInfo.Value.Pattern # ( and skip anyone that does not have a pattern) if (-not $categoryPattern) { continue } '(?=' + # Start a lookahead '.{0,}' + # match any or no characters # followed by the command pattern "(?<$Prefix$($categoryExtensionTypeInfo.Name -replace '\p{P}', '_')$Suffix>$categoryPattern)" + ')?' # made optional }) -join [Environment]::NewLine # Now that we've combined the whole thing, make it a Regex and output it. [Regex]::new("$combinedRegex", 'IgnoreCase,IgnorePatternWhitespace','00:00:01') } } $ModuleInfo -Prefix $prefix -Suffix $Suffix $ModuleCommandTypes = # Aspect.ModuleExtensionType & { <# .SYNOPSIS Outputs a module's extension types .DESCRIPTION Outputs the extension types defined in a module's manifest. .EXAMPLE # Outputs a PSObject with information about extension command types. # The two primary pieces of information are the `.Name` and `.Pattern`. Aspect.ModuleExtensionType -Module PipeScript # Should -BeOfType ([PSObject]) #> [Alias('Aspect.ModuleCommandTypes','Aspect.ModuleCommandType','Aspect.ModuleExtensionTypes')] param( # The name of a module, or a module info object. [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [ValidateScript({ $validTypeList = [System.String],[System.Management.Automation.PSModuleInfo] $thisType = $_.GetType() $IsTypeOk = $(@( foreach ($validType in $validTypeList) { if ($_ -as $validType) { $true;break } })) if (-not $isTypeOk) { throw "Unexpected type '$(@($thisType)[0])'. Must be 'string','psmoduleinfo'." } return $true })] $Module ) begin { $ExtensionCollectionNames = "Extension", "Command", "Cmdlet", "Function", "Alias", "Script", "Application", "File","Configuration" $ExtensionCollectionNames = @($ExtensionCollectionNames -replace '.+$','${0}Type') + @($ExtensionCollectionNames -replace '.+$','${0}Types') } process { #region Resolve Module Info if ($Module -is [string]) { $Module = Get-Module $Module } $ModuleInfo = $module if (-not $ModuleInfo) { return } #endregion Resolve Module Info #region Check Cache and Hopefully Return if (-not $script:ModuleExtensionTypeCache) { $script:ModuleExtensionTypeCache = @{} } if ($script:ModuleExtensionTypeCache[$ModuleInfo]) { return $script:ModuleExtensionTypeCache[$ModuleInfo] } #endregion Check Cache and Hopefully Return #region Find Extension Types $modulePrivateData = $ModuleInfo.PrivateData $SortedExtensionTypes = [Ordered]@{} foreach ($TypeOfExtensionCollection in $ExtensionCollectionNames) { $moduleExtensionTypes = if ($modulePrivateData.$TypeOfExtensionCollection) { $modulePrivateData.$TypeOfExtensionCollection } elseif ($modulePrivateData.PSData.$TypeOfExtensionCollection) { $modulePrivateData.PSData.$TypeOfExtensionCollection } else { $null } if (-not $moduleExtensionTypes) { continue } foreach ($commandType in @($ModuleExtensionTypes.GetEnumerator() | Sort-Object Key)) { if ($commandType.Value -is [Collections.IDictionary]) { if (-not $commandType.Value.Name) { $commandType.Value["Name"] = $commandType.Key } if (-not $commandType.Value.PSTypeName) { $commandType.Value["PSTypeName"] = "$($module.Name).ExtensionCommandType" } $SortedExtensionTypes[$commandType.Name] = $commandType.Value } else { $SortedExtensionTypes[$commandType.Name] = [Ordered]@{ PSTypeName = "$($module.Name).ExtensionCommandType" Name = $commandType.Key Pattern = $commandType.Value } } if ($TypeOfExtensionCollection -notmatch '(?>Extension|Command|Cmdlet)') { $SortedExtensionTypes[$commandType.Name].CommandType = $TypeOfExtensionCollection -replace 'Type(?:s)?$' } elseif ($TypeOfExtensionCollection -match 'Cmdlet') { $SortedExtensionTypes[$commandType.Name].CommandType = "(?>Alias|Function|Filter|Cmdlet)" } } } $SortedExtensionTypes.PSTypeName="$($Module.Name).ExtensionCommandTypes" $script:ModuleExtensionTypeCache[$ModuleInfo] = [PSCustomObject]$SortedExtensionTypes $script:ModuleExtensionTypeCache[$ModuleInfo] #endregion Find Extension Types } } $ModuleInfo $commands = @( if ($PSBoundParameters['Commands']) { $commands } elseif ($PSBoundParameters['FilePath']) { if (-not $commandType) { $commandType = 'Application,ExternalScript' } $shouldRecurse = $($PSBoundParameters['FilePath'] -notmatch '^\.\\') -as [bool] foreach ($file in Get-ChildItem -File -Path $PSBoundParameters['FilePath'] -Recurse:$shouldRecurse ) { $ExecutionContext.SessionState.InvokeCommand.GetCommand($file.FullName, $commandType) } } else { if (-not $CommandType) { $commandType = 'Function,Alias,Filter,Cmdlet' } $ExecutionContext.SessionState.InvokeCommand.GetCommands('*', $commandType, $true) }) :nextCommand foreach ($cmd in $commands) { $matched = $ModuleCommandPattern.Match("$cmd") if (-not $matched.Success) { continue } $NamedGroupMatch = $false :nextCommandType foreach ($group in $matched.Groups) { if (-not $group.Success) { continue } if ($null -ne ($group.Name -as [int])) { continue } $CommandTypeName = $group.Name.Replace('_','.') $ThisCommandsType = $ModuleCommandTypes.($group.Name -replace "^$prefix" -replace "$suffix$") if ($ThisCommandsType) { $ThisTypeFilter = @($ThisCommandsType.CommandType,$ThisCommandsType.CommandTypes -ne $null)[0] if ($ThisTypeFilter -and ($cmd.CommandType -notmatch $ThisTypeFilter)) { continue nextCommandType } $ThisExcludeFilter = @($ThisCommandsType.ExcludeCommandType,$ThisCommandsType.ExcludeCommandTypes -ne $null)[0] if ($ThisExcludeFilter -and ($cmd.CommandType -match $ThisExcludeFilter)) { continue nextCommandType } } $NamedGroupMatch = $true if ($PSTypeName) { foreach ($psuedoType in $PSTypeName) { if ($cmd.pstypenames -notcontains $psuedoType) { $cmd.pstypenames.insert(0, $psuedoType) } } } if ($cmd.pstypenames -notcontains $CommandTypeName) { $cmd.pstypenames.insert(0, $CommandTypeName) } } if ($NamedGroupMatch) { $cmd } } } } -Module $module @Splat } |