PSModuleBuild.psm1
Function Invoke-PSModuleBuild { <# .SYNOPSIS Easily build PowerShell modules for a set of functions contained in individual PS1 files .DESCRIPTION Put a collection of your favorite functions into their own PS1 files create a PowerShell module. The module will be named after the folder name they're placed under. Key folders can be used to specify different file types. Unless the path name contains one of the below key names, all functions will be exported by the module and available to the user. Include.txt - Sometimes you want some code to set the environment for your module, or to do some task. While not necessary, if you want the code to appear at the top of the module you can use Include.txt to accomplish this. Do not place any functions in this file because script will not process it, it just puts it into the module file. *Private* - if Private is in the path name, all functions found in this path will be not be exported and will not be available to the user. However, they will be available as internal functions to the module. *Exclude* - any files found with Exclude in the path name will not be included in the module at all. *Tests* - any files found with Tests in the path name will not be included in the module at all (put your Pester tests here). Manifest file for the module will also be created with the correct PowerShell version requirement (assuming you specified this with the "#requires -Version" code in your functions). Manifest file can also be edited to suit your requirements. .PARAMETER Path The path where you module folders and PS1 files containing your functions is located. .PARAMETER ModuleName What you want to call your module. By default the module will be named after the folder you point to in Path. .PARAMETER BuildVersion Update the ModuleVersion field in the module manifest .PARAMETER Passthru Will produce an object with information about the newly created module .INPUTS None .OUTPUTS [PSCustomObject] .EXAMPLE Invoke-PSModuleBuild -Path c:\Test-Module Module will be named Test-Module (.psm1 and .psd1) and will include all functions in that path. .EXAMPLE Invoke-PSModuleBuild -Path c:\Test-Module -ModuleName Make-GreatStuff -Passthru Module will be named Make-GreatStuff. Returned object will be: Name : Make-GreatStuff Path : c:\Test-Module ManifestPath : c:\Test-Module\Test-Module.psd1 ModulePath : c:\Test-Module\Test-Module.psm1 PublicFunctions : {Test1, Test2} PrivateFunctions: {Test3} .NOTES Author: Martin Pugh Twitter: @thesurlyadm1n Spiceworks: Martin9700 Blog: www.thesurlyadmin.com Changelog: 1.0 Initial Release 1.0.9 Moved from RegEx to AST for function parsing 1.0.10 Updated comment based help. Added Passthru parameter 1.0.11 Updated comment based help. Exclude psake.ps1, build.ps1 and .psdeploy. from function import. Added BuildVersion 1.0.12 Removed BuildVersion. Added dynamic parameters from New-ModuleManifest. 1.0.13 Removed a debugging line. 1.0.14 Rename to Invoke-PSModuleBuild and create module named PSModuleBuild. Added ReleaseNotes support (New and Update-ModuleManifest treat ReleaseNotes differently) .LINK https://github.com/martin9700/PSModuleBuild #> [CmdletBinding()] Param ( [ValidateScript({ Test-Path $_ })] [string]$Path, [string]$TargetPath, [string]$ModuleName, [switch]$Passthru, [string[]]$ReleaseNotes ) DynamicParam { # Create the dictionary that this scriptblock will return: $DynParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $CommonParams = [System.Management.Automation.PSCmdlet]::CommonParameters $CommonParams += [System.Management.Automation.PSCmdlet]::OptionalCommonParameters # Get dynamic params that real Cmdlet would have: $Parameters = Get-Command -Name New-ModuleManifest | Select-Object -ExpandProperty Parameters ForEach ($Parameter in $Parameters.GetEnumerator()) { If ($CommonParams -notcontains $Parameter.Key) { $DynamicParameter = New-Object System.Management.Automation.RuntimeDefinedParameter ( $Parameter.Key, $Parameter.Value.ParameterType, $Parameter.Value.Attributes ) #Added in check to not add Name or NotificationEmail parameters because they are defined in static parameters If (-not $DynParamDictionary.ContainsKey($Parameter.Key) -and $Parameter.Key -notmatch "Path|Passthru|ReleaseNotes") { $DynParamDictionary.Add($Parameter.Key, $DynamicParameter) } } } # Return the dynamic parameters $DynParamDictionary } PROCESS { Write-Verbose "$(Get-Date): Invoke-PSModuleBuild started" If (-not $Path) { $Path = $PSScriptRoot } If ($TargetPath) { If (-not (Test-Path $TargetPath)) { New-Item -Path $TargetPath -ItemType Directory } } Else { $TargetPath = $Path } If (-not $ModuleName) { $ModuleName = Get-ItemProperty -Path $Path | Select -ExpandProperty BaseName } $Module = New-Object -TypeName System.Collections.ArrayList $FunctionNames = New-Object -TypeName System.Collections.ArrayList $FunctionPredicate = { ($args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst]) } $HighVersion = [version]"2.0" Write-Verbose "$(Get-Date): Searching for ps1 files and include.txt for module" #Retrieve Include.txt file(s) $Files = Get-ChildItem $Path\Include.txt -Recurse | Sort FullName ForEach ($File in $Files) { $Raw = Get-Content $File $null = $Module.Add($Raw) } #Retrieve ps1 files $Files = Get-ChildItem $Path\*.ps1 -File -Recurse | Where FullName -NotMatch "Exclude|Tests|psake\.ps1|^build\.ps1|\.psdeploy\." | Sort FullName ForEach ($File in $Files) { $Raw = Get-Content $File -Raw $Private = $false If ($File.DirectoryName -like "*Private*") { $Private = $true } $null = $Module.Add($Raw) #Parse out the function names #Thanks Zachary Loeber $ParseError = $null $Tokens = $null $AST = [System.Management.Automation.Language.Parser]::ParseInput($Raw, [ref]$Tokens, [ref]$ParseError) If ($ParseError) { Write-Error "Unable to parse $($File.FullName) because ""$ParseError""" -ErrorAction Stop } ForEach ($Name in ($AST.FindAll($FunctionPredicate, $true) | Select -ExpandProperty Name)) { If ($FunctionNames.Name -contains $Name) { Write-Error "Your module has duplicate function names: $Name. Duplicate found in $($File.FullName)" -ErrorAction Stop } Else { $null = $FunctionNames.Add([PSCustomObject]@{ Name = $Name Private = $Private }) } } If ($AST.ScriptRequirements.RequiredPSVersion -gt $HighVersion) { $HighVersion = $AST.ScriptRequirements.RequiredPSVersion } } #Create the manifest file Write-Verbose "$(Get-Date): Creating/Updating module manifest and module file" $Manifest = @{} $ManifestPath = Join-Path $TargetPath -ChildPath "$ModuleName.psd1" ForEach ($Key in ($PSBoundParameters.GetEnumerator() | Where { $_.Key -NotMatch "Path|Passthru|ModuleName" -and $CommonParams -notcontains $_.Key })) { $Manifest.Add($Key.Key,$Key.Value) } If (Test-Path $ManifestPath) { $OldManifest = Invoke-Expression -Command (Get-Content $ManifestPath -Raw) If ([version]$OldManifest.PowerShellVersion -gt $HighVersion) { $HighVersion = [version]$OldManifest.PowerShellVersion } If ($OldManifest.PrivateData.PSData.ReleaseNotes) { $Manifest.ReleaseNotes = $ReleaseNotes + $OldManifest.PrivateData.PSData.ReleaseNotes } If ($Manifest.PowerShellVersion -gt $HighVersion) { $HighVersion = $Manifest.PowerShellVersion } $Manifest.Path = $ManifestPath $Manifest.PowerShellVersion = $HighVersion $Manifest.FunctionsToExport = $FunctionNames | Where Private -eq $false | Select -ExpandProperty Name If ($BuildVersion) { $Manifest.Add("ModuleVersion",$BuildVersion) } Update-ModuleManifest @Manifest } Else { $Manifest.RootModule = $ModuleName $Manifest.Path = $ManifestPath $Manifest.PowerShellVersion = "$($HighVersion.Major).$($HighVersion.Minor)" $Manifest.FunctionsToExport = $FunctionNames | Where Private -eq $false | Select -ExpandProperty Name If ($BuildVersion) { $Manifest.Add("ModuleVersion",$BuildVersion) } If ($ReleaseNotes) { $Manifest.ReleaseNotes = $ReleaseNotes | Out-String } New-ModuleManifest @Manifest } #Save the Module file $ModulePath = Join-Path -Path $TargetPath -ChildPath "$ModuleName.psm1" $Module | Out-File $ModulePath -Encoding ascii #Passthru If ($Passthru) { [PSCustomObject]@{ Name = $ModuleName SourcePath = $Path TargetPath = $TargetPath ManifestPath = $ManifestPath ModulePath = $ModulePath RequiredVersion = $Manifest.PowerShellVersion PublicFunctions = @($FunctionNames | Where Private -eq $false | Select -ExpandProperty Name) PrivateFunctions = @($FunctionNames | Where Private -eq $true | Select -ExpandProperty Name) ReleaseNotes = $ReleaseNotes } } Write-Verbose "Module created at: $Path as $ModuleName" -Verbose Write-Verbose "$(Get-Date): Invoke-PSModuleBuild completed." } } |