Get-ExtendedModuleInfo.ps1
|
# Copyright (c) Matthias Wolf, Mawosoft. using namespace System using namespace System.Collections.Generic using namespace System.Management.Automation using namespace System.Management.Automation.Language <# .SYNOPSIS Gets extended informations about PowerShell script modules. .INPUTS You can pipe module names or simple [PSModuleInfo] objects to this cmdlet. .OUTPUTS [ExtendedModuleInfo] - Extended module info objects. #> function Get-ExtendedModuleInfo { [CmdletBinding(PositionalBinding = $false)] [OutputType('ExtendedModuleInfo')] param( [Parameter(Mandatory, ParameterSetName = 'Name', ValueFromPipeline, Position = 0)] [SupportsWildcards()] [ValidateNotNullOrEmpty()] # Specifies names or name patterns of modules to process. [string[]]$Name, [Parameter(Mandatory, ParameterSetName = 'FullyQualifiedName', ValueFromPipeline, Position = 0)] [ValidateNotNullOrEmpty()] # Specifies module names, full module specifications, or module paths to process. [Microsoft.PowerShell.Commands.ModuleSpecification[]]$FullyQualifiedName, [Parameter(Mandatory, ParameterSetName = 'ModuleInfo', ValueFromPipeline, Position = 0)] [ValidateNotNullOrEmpty()] # Specifies module objects to process. [psmoduleinfo[]]$ModuleInfo, # Recurses and gets infos about nested modules. [switch]$Recurse, # Merges infos about a main module and its nested modules into a single [ExtendedModuleInfo]. [switch]$Merge, # Parses all script files of a module to obtain additional informations. [switch]$Parse ) begin { $reflector = $script:Reflector $resultStack = [Stack[ExtendedModuleInfo]]::new() $modulesSeen = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) $moduleStack = [Stack[psmoduleinfo]]::new() $scriptFiles = [Dictionary[string, HashSet[string]]]::new($script:PathComparer) $emptySet = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) $itemOptionsToIgnore = [ScopedItemOptions]'AllScope, Private' $variableNamesToIgnore = $script:BuiltInPSVariables if ($Merge) { $Recurse = $true } } process { $modules = $null if ($Name) { $modules = Get-Module -Name $Name } if ($FullyQualifiedName) { $modules = Get-Module -FullyQualifiedName $FullyQualifiedName } if ($ModuleInfo) { $modules = $ModuleInfo } foreach ($moduleItem in $modules) { $resultStack.Clear() $modulesSeen.Clear() $moduleStack.Clear() $moduleStack.Push($moduleItem) while ($moduleStack.Count -ne 0) { $module = $moduleStack.Pop() $key = $module.Path if (-not $key) { $key = $module.Name } if (-not $modulesSeen.Add($key)) { continue } if ($Recurse -and $module.NestedModules) { for ($i = $module.NestedModules.Count - 1; $i -ge 0; $i--) { $moduleStack.Push($module.NestedModules[$i]) } } if ($module.ModuleType -ne [ModuleType]::Script) { continue } $result = [ExtendedModuleInfo]::new() $result.Module = $module if ($module.Path -like '*.psm1') { $null = $result.ScriptFiles.Add($module.Path) } if ($null -ne $module.SessionState) { $moduleScope = $reflector.Expose($module.SessionState).Internal.ModuleScope foreach ($kvp in $moduleScope.FunctionTable.GetEnumerator()) { $file = $kvp.Value.ScriptBlock.File if ($file) { $null = $result.ScriptFiles.Add($file) } if ($kvp.Value.Options -band $itemOptionsToIgnore) { continue } $result.Functions[$kvp.Key] = $kvp.Value } foreach ($kvp in $moduleScope.Variables.GetEnumerator()) { if ($kvp.Value.Options -band $itemOptionsToIgnore) { continue } if ($kvp.Value.GetType() -ne [psvariable]) { continue } if ($variableNamesToIgnore.Contains($kvp.Key)) { continue } $result.Variables[$kvp.Key] = $kvp.Value } $typeTable = $moduleScope.TypeTable if ($null -ne $typeTable) { foreach ($kvp in $typeTable.GetEnumerator()) { $type = $kvp.Value $result.Types[$kvp.Key] = $type if ($result.Assemblies.Add($type.Assembly)) { foreach ($attr in $type.Assembly.GetCustomAttributes([DynamicClassImplementationAssemblyAttribute], $false)) { $file = $attr.ScriptFile if ($file) { $null = $result.ScriptFiles.Add($file) } } } } } } if ($Parse) { foreach ($file in $result.ScriptFiles) { $scriptvars = $null if (-not $scriptFiles.TryGetValue($file, [ref]$scriptvars)) { $sbast = $null try { $sbast = [Parser]::ParseFile($file, [ref]$null, [ref]$null) } catch {} if ($null -eq $sbast) { $scriptvars = $emptySet } else { $varasts = $sbast.FindAll({ param($a) $a -is [VariableExpressionAst] -and $a.VariablePath.IsScript }, $true) if (-not $varasts) { $scriptvars = $emptySet } else { $scriptvars = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) foreach ($item in $varasts) { $null = $scriptvars.Add($item.VariablePath.UserPath.Substring(7)) # 'script:' } } } $scriptFiles.Add($file, $scriptvars) } $result.ScriptVariables.UnionWith($scriptvars) } } $resultStack.Push($result) if (-not $Merge) { $resultStack.Pop() } } if ($resultStack.Count -ne 0) { $merged = [ExtendedModuleInfo]::new() $merged.MergedModules = [List[ExtendedModuleInfo]]::new() while ($resultStack.Count -ne 0) { $result = $resultStack.Pop() $merged.Module = $result.Module $merged.MergedModules.Add($result) $merged.Assemblies.UnionWith($result.Assemblies) $merged.ScriptVariables.UnionWith($result.ScriptVariables) foreach ($kvp in $result.Functions.GetEnumerator()) { $f = $kvp.Value if ($null -eq $f.Module) { $reflector.Expose($f).set_Module($result.Module) } $merged.Functions[$kvp.Key] = $f } foreach ($kvp in $result.Variables.GetEnumerator()) { $v = $kvp.Value if ($null -eq $v.Module) { $reflector.Expose($v).SetModule($result.Module) } $merged.Variables[$kvp.Key] = $v } foreach ($kvp in $result.Types.GetEnumerator()) { $merged.Types[$kvp.Key] = $kvp.Value } $merged.ScriptFiles.UnionWith($result.ScriptFiles) } $merged } } } } |