├── .gitattributes ├── .gitignore ├── LICENSE ├── MrModuleTools ├── MrModuleTools.psd1 ├── MrModuleTools.psm1 ├── private │ └── Get-MrFunctionRequirement.ps1 ├── public │ ├── ConvertTo-MrCsv.ps1 │ ├── ConvertTo-MrScriptBlock.ps1 │ ├── Get-MrAst.ps1 │ ├── Get-MrAstType.ps1 │ ├── Get-MrFunctionsToExport.ps1 │ ├── Get-MrModuleDependency.ps1 │ ├── Get-MrPrivateCommand.ps1 │ ├── Get-MrRequiredModule.ps1 │ ├── Get-MrRequiredPSVersion.ps1 │ ├── Get-MrToken.ps1 │ ├── Get-MrVariableType.ps1 │ └── Test-MrFunctionsToExport.ps1 └── tests │ ├── Get-MrAst.Tests.ps1 │ └── Get-MrPrivateCommand.Tests.ps1 └── Readme.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | .vscode/settings.json 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Mike F. Robbins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /MrModuleTools/MrModuleTools.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'MrModuleTools' 3 | # 4 | # Generated by: Mike F. Robbins 5 | # 6 | # Generated on: 9/5/2018 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'MrModuleTools' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '1.2.0' 16 | 17 | # Supported PSEditions 18 | # CompatiblePSEditions = @() 19 | 20 | # ID used to uniquely identify this module 21 | GUID = '55ccc5b3-b3ab-4336-881d-e8282dd19913' 22 | 23 | # Author of this module 24 | Author = 'Mike F. Robbins' 25 | 26 | # Company or vendor of this module 27 | CompanyName = 'mikefrobbins.com' 28 | 29 | # Copyright statement for this module 30 | Copyright = '(c) 2024 Mike F. Robbins. All rights reserved.' 31 | 32 | # Description of the functionality provided by this module 33 | Description = 'PowerShell Script Module Toolkit' 34 | 35 | # Minimum version of the Windows PowerShell engine required by this module 36 | PowerShellVersion = '4.0' 37 | 38 | # Name of the Windows PowerShell host required by this module 39 | # PowerShellHostName = '' 40 | 41 | # Minimum version of the Windows PowerShell host required by this module 42 | # PowerShellHostVersion = '' 43 | 44 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 45 | # DotNetFrameworkVersion = '' 46 | 47 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 48 | # CLRVersion = '' 49 | 50 | # Processor architecture (None, X86, Amd64) required by this module 51 | # ProcessorArchitecture = '' 52 | 53 | # Modules that must be imported into the global environment prior to importing this module 54 | # RequiredModules = @() 55 | 56 | # Assemblies that must be loaded prior to importing this module 57 | # RequiredAssemblies = @() 58 | 59 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 60 | # ScriptsToProcess = @() 61 | 62 | # Type files (.ps1xml) to be loaded when importing this module 63 | # TypesToProcess = @() 64 | 65 | # Format files (.ps1xml) to be loaded when importing this module 66 | # FormatsToProcess = @() 67 | 68 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 69 | # NestedModules = @() 70 | 71 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 72 | FunctionsToExport = 'ConvertTo-MrCsv', 'ConvertTo-MrScriptBlock', 'Get-MrAst', 'Get-MrAstType', 73 | 'Get-MrFunctionsToExport', 'Get-MrModuleDependency', 'Get-MrPrivateCommand', 74 | 'Get-MrRequiredModule', 'Get-MrRequiredPSVersion', 'Get-MrToken', 75 | 'Get-MrVariableType', 'Test-MrFunctionsToExport' 76 | 77 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 78 | CmdletsToExport = @() 79 | 80 | # Variables to export from this module 81 | VariablesToExport = @() 82 | 83 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 84 | AliasesToExport = @() 85 | 86 | # DSC resources to export from this module 87 | # DscResourcesToExport = @() 88 | 89 | # List of all modules packaged with this module 90 | # ModuleList = @() 91 | 92 | # List of all files packaged with this module 93 | # FileList = @() 94 | 95 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 96 | PrivateData = @{ 97 | 98 | PSData = @{ 99 | 100 | # Tags applied to this module. These help with module discovery in online galleries. 101 | # Tags = @() 102 | 103 | # A URL to the license for this module. 104 | # LicenseUri = '' 105 | 106 | # A URL to the main website for this project. 107 | # ProjectUri = '' 108 | 109 | # A URL to an icon representing this module. 110 | # IconUri = '' 111 | 112 | # ReleaseNotes of this module 113 | # ReleaseNotes = '' 114 | 115 | # Prerelease string of this module 116 | # Prerelease = '' 117 | 118 | # Flag to indicate whether the module requires explicit user acceptance for install/update/save 119 | # RequireLicenseAcceptance = False 120 | 121 | # External dependent modules of this module 122 | # ExternalModuleDependencies = '' 123 | 124 | } # End of PSData hashtable 125 | 126 | } # End of PrivateData hashtable 127 | 128 | # HelpInfo URI of this module 129 | # HelpInfoURI = '' 130 | 131 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 132 | # DefaultCommandPrefix = '' 133 | 134 | } 135 | -------------------------------------------------------------------------------- /MrModuleTools/MrModuleTools.psm1: -------------------------------------------------------------------------------- 1 | #Dot source all functions in all ps1 files located in the module's public and private folders, excluding tests and profiles. 2 | Get-ChildItem -Path $PSScriptRoot\public\*.ps1, $PSScriptRoot\private\*.ps1 -Exclude *.tests.ps1, *profile.ps1 -ErrorAction SilentlyContinue | 3 | ForEach-Object { 4 | . $_.FullName 5 | } -------------------------------------------------------------------------------- /MrModuleTools/private/Get-MrFunctionRequirement.ps1: -------------------------------------------------------------------------------- 1 | function Get-MrFunctionRequirement { 2 | [CmdletBinding(DefaultParameterSetName='File')] 3 | param( 4 | [Parameter(ValueFromPipeline, 5 | ValueFromPipelineByPropertyName, 6 | ValueFromRemainingArguments, 7 | ParameterSetName = 'File', 8 | Position = 0)] 9 | [ValidateNotNullOrEmpty()] 10 | [Alias('FilePath')] 11 | [string[]]$Path = ('.\*.ps1', '.\*.psm1'), 12 | 13 | [Parameter(ValueFromPipelineByPropertyName, 14 | ValueFromRemainingArguments, 15 | ParameterSetName = 'Code', 16 | Position = 0)] 17 | [ValidateNotNull()] 18 | [Alias('ScriptBlock')] 19 | [string[]]$Code 20 | ) 21 | 22 | PROCESS { 23 | if ($PSBoundParameters.Path) { 24 | Write-Verbose 'Path' 25 | $Results = Get-MrAST -Path $Path 26 | } 27 | elseif ($PSBoundParameters.Code) { 28 | Write-Verbose 'Code' 29 | $Results = Get-MrAST -Code $Code 30 | } 31 | else { 32 | Write-Verbose -Message 'Valid input not received.' 33 | } 34 | 35 | $Results | Select-Object -ExpandProperty ScriptRequirements | Sort-Object -Property * -Unique 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /MrModuleTools/public/ConvertTo-MrCsv.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 3.0 2 | function ConvertTo-MrCsv { 3 | [CmdletBinding()] 4 | param ( 5 | [Parameter(Mandatory, 6 | ValueFromPipeline)] 7 | [string[]]$InputObject 8 | ) 9 | 10 | BEGIN { 11 | $Results = @() 12 | } 13 | 14 | PROCESS { 15 | $Results += foreach ($Input in $InputObject) { 16 | "'{0}'" -f $Input 17 | } 18 | } 19 | 20 | END { 21 | Write-Output "$($Results -join ',')" 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /MrModuleTools/public/ConvertTo-MrScriptBlock.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 3.0 2 | function ConvertTo-MrScriptBlock { 3 | [CmdletBinding()] 4 | param ( 5 | [Parameter(ValueFromPipeline, 6 | ValueFromPipelineByPropertyName)] 7 | [ValidateScript({ 8 | If (Test-Path -Path $_) { 9 | $true 10 | } 11 | else { 12 | Throw "'$_' is not valid." 13 | } 14 | })] 15 | [Alias('FilePath')] 16 | [string[]]$Path = ('.\*.ps1', '.\*.psm1') 17 | ) 18 | 19 | PROCESS { 20 | $Files = Get-ChildItem -Path $Path -Exclude *tests.ps1, *profile.ps1 | 21 | Select-Object -ExpandProperty FullName 22 | foreach ($File in $Files) { 23 | $Content = Get-Content -Path $File | Out-String 24 | try { 25 | [scriptblock]::Create($Content) 26 | } 27 | catch { 28 | Write-Warning -Message 'An error occurred' 29 | } 30 | 31 | } 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /MrModuleTools/public/Get-MrAst.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 3.0 2 | function Get-MrAst { 3 | 4 | <# 5 | .SYNOPSIS 6 | Explores the Abstract Syntax Tree (AST). 7 | 8 | .DESCRIPTION 9 | Get-MrAST is an advanced function that provides a mechanism for exploring the Abstract Syntax Tree (AST). 10 | 11 | .PARAMETER Path 12 | Specifies a path to one or more locations. Wildcards are permitted. The default location is the current directory. 13 | 14 | .PARAMETER Code 15 | The code to view the AST for. If Get-Content is being used to obtain the code, use its -Raw parameter otherwise 16 | the formating of the code will be lost. 17 | 18 | .PARAMETER ScriptBlock 19 | An instance of System.Management.Automation.ScriptBlock Microsoft .NET Framework type to view the AST for. 20 | 21 | .PARAMETER AstType 22 | The type of object to view the AST for. If this parameter is ommited, only the top level ScriptBlockAst is returned. 23 | 24 | .EXAMPLE 25 | Get-MrAST -Path 'C:\Scripts' -AstType FunctionDefinition 26 | 27 | .EXAMPLE 28 | Get-MrAST -Code 'function Get-PowerShellProcess {Get-Process -Name PowerShell}' 29 | 30 | .EXAMPLE 31 | Get-MrAST -ScriptBlock ([scriptblock]::Create('function Get-PowerShellProcess {Get-Process -Name PowerShell}')) 32 | 33 | .NOTES 34 | Author: Mike F Robbins 35 | Website: http://mikefrobbins.com 36 | Twitter: @mikefrobbins 37 | #> 38 | 39 | [CmdletBinding(DefaultParameterSetName='Path')] 40 | param( 41 | [Parameter(ValueFromPipeline, 42 | ValueFromPipelineByPropertyName, 43 | ValueFromRemainingArguments, 44 | ParameterSetName = 'Path', 45 | Position = 1)] 46 | [ValidateNotNull()] 47 | [Alias('FilePath')] 48 | [string[]]$Path = ('.\*.ps1', '.\*.psm1'), 49 | 50 | [Parameter(Mandatory, 51 | ValueFromPipelineByPropertyName, 52 | ValueFromRemainingArguments, 53 | ParameterSetName = 'Code')] 54 | [string[]]$Code, 55 | 56 | [Parameter(Mandatory, 57 | ValueFromPipelineByPropertyName, 58 | ValueFromRemainingArguments, 59 | ParameterSetName = 'ScriptBlock')] 60 | [scriptblock[]]$ScriptBlock 61 | ) 62 | 63 | DynamicParam { 64 | $ParameterAttribute = New-Object -TypeName System.Management.Automation.ParameterAttribute 65 | $ParameterAttribute.Position = 0 66 | 67 | $ValidationValues = Get-MrAstType 68 | $ValidateSetAttribute = New-Object -TypeName System.Management.Automation.ValidateSetAttribute($ValidationValues) 69 | 70 | $AttributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute] 71 | $AttributeCollection.Add($ParameterAttribute) 72 | $AttributeCollection.Add($ValidateSetAttribute) 73 | 74 | $ParameterName = 'AstType' 75 | $RuntimeParameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection) 76 | 77 | $RuntimeParameterDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary 78 | $RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter) 79 | $RuntimeParameterDictionary 80 | } 81 | 82 | BEGIN { 83 | $AstType = $PsBoundParameters[$ParameterName] 84 | } 85 | 86 | PROCESS { 87 | switch ($PSCmdlet.ParameterSetName) { 88 | 'Path' { 89 | Write-Verbose -Message 'Path Parameter Set Selected' 90 | Write-Verbose "Path contains $Path" 91 | 92 | $Files = Get-ChildItem -Path $Path -Exclude *tests.ps1, *profile.ps1 | 93 | Select-Object -ExpandProperty FullName 94 | 95 | if (-not ($Files)) { 96 | Write-Warning -Message 'No valid files found.' 97 | Return 98 | } 99 | 100 | $AST = foreach ($File in $Files) { 101 | [System.Management.Automation.Language.Parser]::ParseFile($File, [ref]$null, [ref]$null) 102 | } 103 | 104 | break 105 | } 106 | 'Code' { 107 | Write-Verbose -Message 'Code Parameter Set Selected' 108 | 109 | $AST = foreach ($c in $Code) { 110 | [System.Management.Automation.Language.Parser]::ParseInput($c, [ref]$null, [ref]$null) 111 | } 112 | 113 | break 114 | } 115 | 'ScriptBlock' { 116 | Write-Verbose -Message 'ScriptBlock Parameter Set Selected' 117 | 118 | $AST = $ScriptBlock.Ast 119 | 120 | break 121 | } 122 | default { 123 | Write-Warning -Message 'An unexpected error has occurred' 124 | } 125 | } 126 | 127 | if ($PsBoundParameters.AstType) { 128 | Write-Verbose -Message 'AstType Parameter Entered' 129 | 130 | $AST = $AST.FindAll({$args[0].GetType().Name -like "$($ASTType)Ast"}, $true) 131 | } 132 | 133 | Write-Output $AST 134 | } 135 | 136 | } -------------------------------------------------------------------------------- /MrModuleTools/public/Get-MrAstType.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 3.0 2 | function Get-MrAstType { 3 | [CmdletBinding()] 4 | param () 5 | 6 | ([System.Management.Automation.Language.ArrayExpressionAst].Assembly.GetTypes() | 7 | Where-Object {$_.Name.EndsWith('Ast') -and $_.Name -ne 'Ast'}).Name -replace 'Ast$' | 8 | Sort-Object -Unique 9 | 10 | } -------------------------------------------------------------------------------- /MrModuleTools/public/Get-MrFunctionsToExport.ps1: -------------------------------------------------------------------------------- 1 | function Get-MrFunctionsToExport { 2 | 3 | <# 4 | .SYNOPSIS 5 | Returns a list of functions in the specified directory. 6 | 7 | .DESCRIPTION 8 | Get-MrFunctionsToExport is an advanced function which returns a list of functions 9 | that are each contained in single quotes and each separated by a comma unless the 10 | simple parameter is specified in which case a simple list of the base file names 11 | for the functions is returned. 12 | 13 | .PARAMETER Path 14 | Path to the folder where the functions are located. 15 | 16 | .PARAMETER Exclude 17 | Pattern to exclude. By default profile scripts and Pester tests are excluded. 18 | 19 | .PARAMETER Recurse 20 | Return function names from subdirectories in addition to the specified directory. 21 | 22 | .PARAMETER Simple 23 | Return a simple list instead of a quoted comma separated list. 24 | 25 | .EXAMPLE 26 | Get-MrFunctionsToExport -Path .\MrToolkit 27 | 28 | .EXAMPLE 29 | Get-MrFunctionsToExport -Path .\MrToolkit -Simple 30 | 31 | .INPUTS 32 | None 33 | 34 | .OUTPUTS 35 | String 36 | 37 | .NOTES 38 | Author: Mike F Robbins 39 | Website: http://mikefrobbins.com 40 | Twitter: @mikefrobbins 41 | #> 42 | 43 | [CmdletBinding()] 44 | param ( 45 | [ValidateScript({ 46 | If (Test-Path -Path $_ -PathType Container) { 47 | $True 48 | } 49 | else { 50 | Throw "'$_' is not a valid directory." 51 | } 52 | })] 53 | [string]$Path = (Get-Location), 54 | 55 | [string[]]$Exclude = ('*profile.ps1', '*.tests.ps1'), 56 | 57 | [switch]$Recurse, 58 | 59 | [switch]$Simple 60 | ) 61 | 62 | $Params = @{ 63 | Exclude = $Exclude 64 | } 65 | 66 | if ($PSBoundParameters.Recurse) { 67 | $Params.Recurse = $true 68 | } 69 | 70 | $results = Get-ChildItem -Path "$Path\*.ps1" @Params | 71 | Select-Object -ExpandProperty BaseName 72 | 73 | if ((-not($PSBoundParameters.Simple)) -and $results) { 74 | $results = $results -join "', '" 75 | Write-Output "'$results'" 76 | } 77 | elseif ($results) { 78 | Write-Output $results 79 | } 80 | 81 | } -------------------------------------------------------------------------------- /MrModuleTools/public/Get-MrModuleDependency.ps1: -------------------------------------------------------------------------------- 1 | function Get-MrModuleDependency { 2 | 3 | <# 4 | .SYNOPSIS 5 | Retrieves the dependent modules of a specified PowerShell module(s). 6 | 7 | .DESCRIPTION 8 | The Get-MrModuleDependency function retrieves the dependent modules for the specified 9 | PowerShell module(s). It fetches the module's dependency information from the hidden 10 | PSGetModuleInfo.xml file if available. 11 | 12 | .PARAMETER Name 13 | The name of the module(s) to check for dependencies. Defaults to 'Az'. 14 | 15 | .EXAMPLE 16 | Get-MrModuleDependency -Name Az, AzPreview 17 | 18 | .EXAMPLE 19 | 'Az', 'AzPreview' | Get-MrModuleDependency 20 | 21 | .EXAMPLE 22 | Get-Module -Name Az, AzPreview -ListAvailable | Get-MrModuleDependency 23 | 24 | .NOTES 25 | Author: Mike F. Robbins 26 | Website: https://mikefrobbins.com 27 | Twitter: @mikefrobbins 28 | #> 29 | 30 | [CmdletBinding()] 31 | param ( 32 | [Parameter(ValueFromPipeline, 33 | ValueFromPipelineByPropertyName)] 34 | [ValidateNotNullOrEmpty()] 35 | [Alias('ModuleName')] 36 | [string[]]$Name = 'Az' 37 | ) 38 | 39 | PROCESS { 40 | 41 | foreach ($module in $Name) { 42 | 43 | Write-Verbose -Message "Attempting to locate module: '$module' on the local system." 44 | 45 | $modulePath = Get-Module -Name $module -ListAvailable | 46 | Sort-Object -Property Version -Descending | 47 | Select-Object -First 1 -ExpandProperty ModuleBase 48 | 49 | try { 50 | Write-Verbose -Message "Attempting to read the module's hidden xml file." 51 | 52 | $moduleInfo = Import-Clixml -Path (Join-Path -Path $modulePath -ChildPath PSGetModuleInfo.xml) -ErrorAction Stop 53 | } catch { 54 | Write-Warning -Message "Module: '$module' not found or wasn't installed with Install-Module or Install-PSResource." 55 | } 56 | 57 | foreach ($dependency in $moduleInfo.Dependencies){ 58 | if ($null -ne $dependency.RequiredVersion) { 59 | $moduleVersion = $dependency.RequiredVersion 60 | } elseif ($null -ne $dependency.MinimumVersion) { 61 | $moduleVersion = $dependency.MinimumVersion 62 | } else { 63 | $moduleVersion = $dependency.VersionRange.MinVersion.OriginalVersion 64 | } 65 | 66 | [pscustomobject]@{ 67 | Name = $moduleInfo.Name 68 | Module = $dependency.Name 69 | Version = $moduleVersion 70 | Status = if ($moduleVersion -ge 1) {'GA'} else {'Preview'} 71 | } 72 | } 73 | 74 | } 75 | 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /MrModuleTools/public/Get-MrPrivateCommand.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 3.0 2 | function Get-MrPrivateCommand { 3 | 4 | <# 5 | .SYNOPSIS 6 | Returns a list of private (unexported) commands from the specified module or snap-in. 7 | 8 | .DESCRIPTION 9 | Get-MrPrivateFunction is an advanced function that returns a list of private commands 10 | that are not exported from the specified module or snap-in. 11 | 12 | .PARAMETER Module 13 | Specify the name of a module. Enter the name of a module or snap-in, or a snap-in or module 14 | object. This parameter takes string values, but the value of this parameter can also be a 15 | PSModuleInfo or PSSnapinInfo object, such as the objects that the Get-Module, Get-PSSnapin, 16 | and Import-PSSession cmdlets return. 17 | 18 | .EXAMPLE 19 | Get-MrPrivateCommand -Module Pester 20 | 21 | .NOTES 22 | Author: Mike F Robbins 23 | Website: http://mikefrobbins.com 24 | Twitter: @mikefrobbins 25 | #> 26 | 27 | [CmdletBinding()] 28 | param ( 29 | [Parameter(Mandatory)] 30 | [Alias('PSSnapin')] 31 | [string]$Module 32 | ) 33 | 34 | if (-not((Get-Module -Name $Module -OutVariable ModuleInfo))){ 35 | try { 36 | $ModuleInfo = Import-Module -Name $Module -Force -PassThru -ErrorAction Stop 37 | } 38 | catch { 39 | Write-Warning -Message "$_.Exception.Message" 40 | Break 41 | } 42 | } 43 | 44 | $Global:ModuleName = $Module 45 | 46 | $All = $ModuleInfo.Invoke({Get-Command -Module $ModuleName -All}) 47 | 48 | $Exported = (Get-Module -Name $Module -All).ExportedCommands | 49 | Select-Object -ExpandProperty Values 50 | 51 | if ($All -and $Exported) { 52 | Compare-Object -ReferenceObject $All -DifferenceObject $Exported | 53 | Select-Object -ExpandProperty InputObject | 54 | Add-Member -MemberType NoteProperty -Name Visibility -Value Private -Force -PassThru 55 | } 56 | } -------------------------------------------------------------------------------- /MrModuleTools/public/Get-MrRequiredModule.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 3.0 2 | function Get-MrRequiredModule { 3 | 4 | <# 5 | .SYNOPSIS 6 | Gets a list of the required modules. 7 | 8 | .DESCRIPTION 9 | Get-MrRequiredModule is an advanced function that returns a list of the required module dependencies from one or more 10 | PS1 and/or PSM1 files. 11 | 12 | .PARAMETER Path 13 | Specifies a path to one or more locations. Wildcards are permitted. The default location is the current directory. 14 | 15 | .PARAMETER Code 16 | The code to get the required modules for. If Get-Content is being used to obtain the code, use its -Raw parameter 17 | otherwise the formating of the code will be lost. 18 | 19 | .PARAMETER Detailed 20 | Return a detailed list of all of the modules including built-in modules that are required. This option does not 21 | reply on a Requires statement. 22 | 23 | .EXAMPLE 24 | Get-MrRequiredModule -Path 'C:\Scripts' 25 | 26 | .EXAMPLE 27 | Get-MrRequiredModule -Code 'function Get-PowerShellProcess {Get-Process -Name PowerShell}' -Detailed 28 | 29 | .NOTES 30 | Author: Mike F Robbins 31 | Website: http://mikefrobbins.com 32 | Twitter: @mikefrobbins 33 | #> 34 | 35 | [CmdletBinding(DefaultParameterSetName='File')] 36 | param( 37 | [Parameter(ValueFromPipeline, 38 | ValueFromPipelineByPropertyName, 39 | ValueFromRemainingArguments, 40 | ParameterSetName = 'File', 41 | Position = 0)] 42 | [ValidateNotNullOrEmpty()] 43 | [Alias('FilePath')] 44 | [string[]]$Path = ('.\*.ps1', '.\*.psm1'), 45 | 46 | [Parameter(ValueFromPipelineByPropertyName, 47 | ValueFromRemainingArguments, 48 | ParameterSetName = 'Code', 49 | Position = 0)] 50 | [ValidateNotNull()] 51 | [Alias('ScriptBlock')] 52 | [string[]]$Code, 53 | 54 | [switch]$Detailed 55 | ) 56 | 57 | PROCESS{ 58 | if (-not($PSBoundParameters.Detailed)) { 59 | (Get-MrFunctionRequirement -Path $Path | 60 | Select-Object -ExpandProperty RequiredModules -Unique).Name 61 | } 62 | else { 63 | $PSBoundParameters.Remove('Detailed') | Out-Null 64 | $AllAST = Get-MrAst @PSBoundParameters 65 | 66 | foreach ($AST in $AllAST){ 67 | $FunctionDefinition = $AST.FindAll({$args[0].GetType().Name -like 'FunctionDefinitionAst'}, $true) 68 | $Commands = $AST.FindAll({$args[0].GetType().Name -like 'CommandAst'}, $true) | ForEach-Object {$_.CommandElements[0].Value} | Select-Object -Unique 69 | 70 | foreach ($Command in $Commands){ 71 | [pscustomobject]@{ 72 | Function = $FunctionDefinition.Name 73 | Dependency = $Command 74 | Module = (Get-Command -Name $Command -ErrorAction SilentlyContinue).Source 75 | } 76 | } 77 | 78 | } 79 | 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /MrModuleTools/public/Get-MrRequiredPSVersion.ps1: -------------------------------------------------------------------------------- 1 | function Get-MrRequiredPSVersion { 2 | [CmdletBinding(DefaultParameterSetName='File')] 3 | param( 4 | [Parameter(ValueFromPipeline, 5 | ValueFromPipelineByPropertyName, 6 | ValueFromRemainingArguments, 7 | ParameterSetName = 'File', 8 | Position = 0)] 9 | [ValidateNotNullOrEmpty()] 10 | [Alias('FilePath')] 11 | [string[]]$Path = ('.\*.ps1', '.\*.psm1'), 12 | 13 | [Parameter(ValueFromPipelineByPropertyName, 14 | ValueFromRemainingArguments, 15 | ParameterSetName = 'Code', 16 | Position = 0)] 17 | [ValidateNotNull()] 18 | [Alias('ScriptBlock')] 19 | [string[]]$Code 20 | ) 21 | 22 | PROCESS{ 23 | Get-MrFunctionRequirement -Path $Path | Select-Object -ExpandProperty RequiredPSVersion -Unique 24 | } 25 | } -------------------------------------------------------------------------------- /MrModuleTools/public/Get-MrToken.ps1: -------------------------------------------------------------------------------- 1 | function Get-MrToken { 2 | [CmdletBinding(DefaultParameterSetName='File')] 3 | param ( 4 | [Parameter(ValueFromPipeline, 5 | ValueFromPipelineByPropertyName, 6 | ParameterSetName = 'File', 7 | Position = 0)] 8 | [ValidateNotNullOrEmpty()] 9 | [Alias('FilePath', 'FileName')] 10 | [string[]]$Path = ('.\*.ps1', '.\*.psm1'), 11 | 12 | [Parameter(ValueFromPipelineByPropertyName, 13 | ParameterSetName = 'Code', 14 | Position = 0)] 15 | [ValidateNotNull()] 16 | [Alias('Script', 'ScriptBlock')] 17 | [string[]]$Code, 18 | 19 | [Parameter(Position=1)] 20 | [System.Management.Automation.Language.TokenKind]$Kind, 21 | 22 | [Parameter(Position=2)] 23 | [Alias('TokenFlag')] 24 | [System.Management.Automation.Language.TokenFlags]$Flag 25 | ) 26 | 27 | BEGIN { 28 | $Errors = $null 29 | $Token = $null 30 | $Tokens = $null 31 | } 32 | 33 | PROCESS { 34 | if ($PsBoundParameters.Code) { 35 | $null = [System.Management.Automation.Language.Parser]::ParseInput($Code, [ref]$Tokens, [ref]$Errors) 36 | } 37 | else { 38 | $Files = Get-ChildItem -Path $Path | Select-Object -ExpandProperty FullName 39 | foreach ($File in $Files) { 40 | $null = [System.Management.Automation.Language.Parser]::ParseFile($File, [ref]$Token, [ref]$Errors) 41 | $Tokens += $Token 42 | } 43 | } 44 | 45 | switch ($PsBoundParameters) { 46 | {$_.Keys -contains 'Kind'} {$Tokens = $Tokens | Where-Object {$_.Kind -eq $Kind}} 47 | {$_.Keys -contains 'Flag'} {$Tokens = $Tokens | Where-Object {$_.TokenFlags -eq $Flag}} 48 | } 49 | 50 | Write-Output $Tokens 51 | 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /MrModuleTools/public/Get-MrVariableType.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 4.0 2 | function Get-MrVariableType { 3 | 4 | <# 5 | .SYNOPSIS 6 | List variables and whether they're defined as parameters or in the body of a function. 7 | 8 | .DESCRIPTION 9 | Get-MrVariableType is an advanced function that returns a list of variables defined in a 10 | function and whether they are parameters or user defined within the body of the function. 11 | 12 | .PARAMETER Ast 13 | Provide a ScriptBlockAst object via parameter or pipeline input. Use Get-MrAst to create this 14 | object. 15 | 16 | .EXAMPLE 17 | Get-MrAST -Path 'C:\Scripts' | Get-MrVariableType 18 | 19 | .EXAMPLE 20 | Get-MrVariableType -Ast (Get-MrAST -Path 'C:\Scripts') 21 | 22 | .NOTES 23 | Author: Mike F Robbins 24 | Website: http://mikefrobbins.com 25 | Twitter: @mikefrobbins 26 | #> 27 | 28 | [CmdletBinding()] 29 | param ( 30 | [Parameter(Mandatory, 31 | ValueFromPipeline)] 32 | [System.Management.Automation.Language.ScriptBlockAst]$Ast 33 | ) 34 | 35 | PROCESS { 36 | 37 | $variables = $Ast.FindAll({$args[0].GetType().Name -like 'VariableExpressionAst'}, $true).Where({$_.VariablePath.UserPath -ne '_'}) 38 | 39 | $parameters = $Ast.FindAll({$args[0].GetType().Name -like 'ParameterAst'}, $true) 40 | 41 | $diff = Compare-Object -ReferenceObject $parameters.Name.VariablePath.UserPath -DifferenceObject $variables.VariablePath.UserPath -IncludeEqual 42 | 43 | foreach ($variable in $variables) { 44 | 45 | [pscustomobject]@{ 46 | Name = $variable.VariablePath.UserPath 47 | Type = if ($variable.VariablePath.UserPath -in $diff.Where({$_.SideIndicator -eq '=='}).InputObject) { 48 | 'Parameter' 49 | } else { 50 | 'UserDefined' 51 | } 52 | LineNumber = $variable.Extent.StartLineNumber 53 | Column = $variable.Extent.StartColumnNumber 54 | } 55 | 56 | } 57 | 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /MrModuleTools/public/Test-MrFunctionsToExport.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 3.0 -Modules Pester 2 | function Test-MrFunctionsToExport { 3 | 4 | <# 5 | .SYNOPSIS 6 | Tests that all functions in a module are being exported. 7 | 8 | .DESCRIPTION 9 | Test-MrFunctionsToExport is an advanced function that runs a Pester test against 10 | one or more modules to validate that all functions are being properly exported. 11 | 12 | .PARAMETER ManifestPath 13 | Path to the module manifest (PSD1) file for the modules(s) to test. 14 | 15 | .EXAMPLE 16 | Test-MrFunctionsToExport -ManifestPath .\MyModuleManifest.psd1 17 | 18 | .EXAMPLE 19 | Get-ChildItem -Path .\Modules -Include *.psd1 -Recurse | Test-MrFunctionsToExport 20 | 21 | .INPUTS 22 | String 23 | 24 | .OUTPUTS 25 | None 26 | 27 | .NOTES 28 | Author: Mike F Robbins 29 | Website: http://mikefrobbins.com 30 | Twitter: @mikefrobbins 31 | #> 32 | 33 | [CmdletBinding()] 34 | param ( 35 | [Parameter(ValueFromPipeline)] 36 | [ValidateScript({ 37 | Test-ModuleManifest -Path $_ 38 | })] 39 | [string[]]$ManifestPath 40 | ) 41 | 42 | PROCESS { 43 | foreach ($Manifest in $ManifestPath) { 44 | 45 | $ModuleInfo = Import-Module -Name $Manifest -Force -PassThru 46 | 47 | $PS1FileNames = (Get-MrAST -Path "$($ModuleInfo.ModuleBase)\public" -AstType FunctionDefinition).Name 48 | 49 | $ExportedFunctions = Get-Command -Module $ModuleInfo.Name | 50 | Select-Object -ExpandProperty Name 51 | 52 | Describe "FunctionsToExport for PowerShell module '$($ModuleInfo.Name)'" { 53 | 54 | It 'Exports one function in the module manifest per PS1 file' { 55 | $ModuleInfo.ExportedFunctions.Values.Name.Count | 56 | Should Be $PS1FileNames.Count 57 | } 58 | 59 | It 'Exports functions with names that match the PS1 file base names' { 60 | Compare-Object -ReferenceObject $ModuleInfo.ExportedFunctions.Values.Name -DifferenceObject $PS1FileNames | 61 | Should BeNullOrEmpty 62 | } 63 | 64 | It 'Only exports functions listed in the module manifest' { 65 | $ExportedFunctions.Count | 66 | Should Be $ModuleInfo.ExportedFunctions.Values.Name.Count 67 | } 68 | 69 | It 'Contains the same function names as base file names' { 70 | Compare-Object -ReferenceObject $PS1FileNames -DifferenceObject $ExportedFunctions | 71 | Should BeNullOrEmpty 72 | } 73 | 74 | } 75 | 76 | } 77 | 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /MrModuleTools/tests/Get-MrAst.Tests.ps1: -------------------------------------------------------------------------------- 1 | $here = (Split-Path -Parent $MyInvocation.MyCommand.Path) -replace 'tests', 'public' 2 | $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.' 3 | if (-not (Test-Path -Path "$here\$sut" -PathType Leaf)) { 4 | $here -replace 'public', 'private' 5 | } 6 | . "$here\$sut" 7 | 8 | Import-Module ..\MrModuleBuildTools.psd1 -Force 9 | 10 | InModuleScope MrModuleBuildTools { 11 | $Files = '..\private\Get-MrAstType.ps1', '..\private\Get-MrFunctionRequirement.ps1' 12 | $Directory = '..\private' 13 | $FilesByName = $Files | ForEach-Object {New-Object -TypeName PSObject -Property @{'Path' = $_}} 14 | $DirectoryByName = New-Object -TypeName PSObject -Property @{'Path' = $Directory} 15 | 16 | $Code = Get-Content -Path $Files -Raw 17 | $CodeByName = $Code | ForEach-Object {New-Object -TypeName PSObject -Property @{'Code' = $_}} 18 | 19 | $ScriptBlock = ConvertTo-MrScriptBlock -Path $Files 20 | $ScriptBlockByName = $ScriptBlock | ForEach-Object {New-Object -TypeName PSObject -Property @{'ScriptBlock' = $_}} 21 | 22 | Describe 'the existence of the Get-MrAst private function dependency' { 23 | It 'Tests the Get-MrAstType Private function' { 24 | Get-MrAstType | Should -Not -BeNullOrEmpty 25 | } 26 | } 27 | 28 | Describe 'the Path parameter' { 29 | 30 | Context 'Testing via parameter input' { 31 | 32 | It 'Works with a single file' { 33 | (Get-MrAst -Path $Files[0]).ScriptRequirements.RequiredPSVersion.Major | 34 | Should -BeGreaterThan 2 35 | } 36 | It 'Works with multiple files' { 37 | (Get-MrAst -Path $Files).Count | 38 | Should -BeGreaterThan 1 39 | } 40 | It 'Works with a directory' { 41 | (Get-MrAst -Path $Directory).Count | 42 | Should -BeGreaterThan 1 43 | } 44 | It 'Works with the AstType parameter' { 45 | (Get-MrAst -Path $Files[0] -AstType FunctionDefinition).Extent.Text | 46 | Should -Not -BeNullOrEmpty 47 | } 48 | It 'Does not work with the Code parameter' { 49 | {Get-MrAst -Path $Files -Code $Code} | 50 | Should -Throw 51 | } 52 | It 'Does not work with the ScriptBlock parameter' { 53 | {Get-MrAst -Path $Files -ScriptBlock $ScriptBlock} | 54 | Should -Throw 55 | } 56 | 57 | } 58 | 59 | Context 'Testing via pipeline input by value (by type)' { 60 | 61 | It 'Accepts a single file' { 62 | ($Files[0] | Get-MrAst).ScriptRequirements.RequiredPSVersion.Major | 63 | Should -BeGreaterThan 2 64 | } 65 | It 'Accepts multiple files' { 66 | ($Files | Get-MrAst).Count | 67 | Should -BeGreaterThan 1 68 | } 69 | It 'Accepts a directory' { 70 | ($Directory | Get-MrAst).Count | 71 | Should -BeGreaterThan 1 72 | } 73 | It 'Works with the AstType parameter' { 74 | ($Files[0] | Get-MrAst -AstType FunctionDefinition).Extent.Text | 75 | Should -Not -BeNullOrEmpty 76 | } 77 | It 'Works with the AstType parameter positionally' { 78 | ($Files | Get-MrAst FunctionDefinition).Count | 79 | Should -BeGreaterThan 1 80 | } 81 | 82 | } 83 | 84 | Context 'Testing via pipeline input by property name' { 85 | 86 | It 'Accepts a single file' { 87 | ($FilesByName[0] | Get-MrAst).ScriptRequirements.RequiredPSVersion.Major | 88 | Should -BeGreaterThan 2 89 | } 90 | It 'Accepts multiple files' { 91 | ($FilesByName | Get-MrAst).Count | 92 | Should -BeGreaterThan 1 93 | } 94 | It 'Accepts a directory via pipeline input' { 95 | ($DirectoryByName | Get-MrAst).Count | 96 | Should -BeGreaterThan 1 97 | } 98 | It 'Works with the AstType parameter' { 99 | ($FilesByName | Get-MrAst -AstType FunctionDefinition).Extent.Text | 100 | Should -Not -BeNullOrEmpty 101 | } 102 | 103 | } 104 | 105 | } 106 | 107 | Describe "the Code Parameter" { 108 | 109 | Context 'Testing via parameter input' { 110 | 111 | It 'Works with a single block of code' { 112 | (Get-MrAst -Code $Code[0]).ScriptRequirements.RequiredPSVersion.Major | Should -BeGreaterThan 2 113 | } 114 | It 'Works with multiple blocks of code' { 115 | (Get-MrAst -Code $Code).Count | Should -BeGreaterThan 1 116 | } 117 | It 'Works with the AstType parameter' { 118 | (Get-MrAst -Code $Code[0] -AstType FunctionDefinition).Extent.Text | 119 | Should -Not -BeNullOrEmpty 120 | } 121 | It 'Does not work with the Path parameter' { 122 | {Get-MrAst -Code $Code -Path $Files} | 123 | Should -Throw 124 | } 125 | It 'Does not work with the ScriptBlock parameter' { 126 | {Get-MrAst -Code $Code -ScriptBlock $ScriptBlock} | 127 | Should -Throw 128 | } 129 | } 130 | 131 | Context 'Testing via pipeline input by property name' { 132 | 133 | It 'Works accepts multiple blocks of code' { 134 | ($CodeByName | Get-MrAst).Count | Should -BeGreaterThan 1 135 | } 136 | It 'Works with the AstType parameter' { 137 | ($CodeByName | Get-MrAst -AstType FunctionDefinition).Extent.Text | 138 | Should -Not -BeNullOrEmpty 139 | } 140 | 141 | } 142 | 143 | } 144 | 145 | Describe 'the ScriptBlock Parameter' { 146 | 147 | Context 'Testing via parameter input' { 148 | 149 | It 'Works with a single block of code' { 150 | (Get-MrAst -ScriptBlock $ScriptBlock[0]).ScriptRequirements.RequiredPSVersion.Major | 151 | Should -BeGreaterThan 2 152 | } 153 | It 'Works with multiple blocks of code' { 154 | (Get-MrAst -ScriptBlock $ScriptBlock).Count | 155 | Should -BeGreaterThan 1 156 | } 157 | It 'Works with the AstType parameter' { 158 | (Get-MrAst -ScriptBlock $ScriptBlock -AstType FunctionDefinition).Extent.Text | 159 | Should -Not -BeNullOrEmpty 160 | } 161 | It 'Does not work with the Path parameter' { 162 | {Get-MrAst -ScriptBlock $ScriptBlock -Path $Files} | 163 | Should -Throw 164 | } 165 | It 'Does not work with the ScriptBlock parameter' { 166 | {Get-MrAst -ScriptBlock $ScriptBlock -Code $Code} | 167 | Should -Throw 168 | } 169 | It 'Does not work with input type other than script block' { 170 | {Get-MrAst -ScriptBlock $Code} | 171 | Should -Throw 172 | } 173 | 174 | } 175 | 176 | Context 'Testing via pipeline input by property name' { 177 | 178 | It 'Works accepts multiple script blocks' { 179 | ($ScriptBlockByName | Get-MrAst).Count | 180 | Should -BeGreaterThan 1 181 | } 182 | It 'Works with the AstType parameter' { 183 | ($ScriptBlockByName | Get-MrAst -AstType FunctionDefinition).Extent.Text | 184 | Should -Not -BeNullOrEmpty 185 | } 186 | 187 | } 188 | 189 | } 190 | 191 | } 192 | -------------------------------------------------------------------------------- /MrModuleTools/tests/Get-MrPrivateCommand.Tests.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [string]$Module = 'MrModuleBuildTools' 3 | ) 4 | 5 | $CommandName = $MyInvocation.MyCommand.Name.Replace('.Tests.ps1', '') 6 | Write-Host -Object "Running $PSCommandPath" -ForegroundColor Cyan 7 | 8 | $here = (Split-Path -Parent $MyInvocation.MyCommand.Path) -replace 'Tests', 'Public' 9 | $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.' 10 | . "$here\$sut" 11 | 12 | Describe 'Get-MrPrivateCommand' { 13 | 14 | if (-not(Get-MrPrivateCommand -Module $Module -OutVariable PrivateCommands)){ 15 | Write-Host -Object "Aborting tests! No private commands found for module '$Module'." -ForegroundColor Cyan 16 | Break 17 | } 18 | else { 19 | Write-Host -Object "Testing $($PrivateCommands.Count) private commands for module '$Module'." -ForegroundColor Cyan 20 | } 21 | 22 | $ExportedCommands = Get-Module -Name $Module -All 23 | 24 | Context "Testing module '$Module' with Get-Command" { 25 | $PrivateCommands | 26 | ForEach-Object { 27 | It "Doesn't export the $($_.Name) $($_.CommandType)" { 28 | Get-Command -Name $_.Name -Module $_.Source -ErrorAction SilentlyContinue | 29 | Should -BeNullOrEmpty 30 | } 31 | } 32 | } 33 | 34 | Context "Testing module '$Module' with Get-Module" { 35 | $PrivateCommands | 36 | ForEach-Object { 37 | It "Doesn't export the $($_.Name) $($_.CommandType)" { 38 | $ExportedCommands.ExportedCommands.Values | 39 | Where-Object Name -eq $_.Name | 40 | Should -BeNullOrEmpty 41 | } 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # ModuleTools 2 | 3 | PowerShell Script Module Toolkit 4 | 5 | --------------------------------------------------------------------------------