Invoke-PSBuild.psm1
# https://riptutorial.com/powershell/example/27242/exporting-a-variable-from-a-module function Get-Ast { [CmdletBinding( DefaultParameterSetName = "File" )] Param( [Parameter( ParameterSetName = "File", Mandatory, ValueFromPipeline, Position=0 )] [string] $File, [Parameter( ParameterSetName = "Code", Mandatory, ValueFromPipeline, Position=0 )] [string] $Code, [ArgumentCompleter({ param( $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters ) $typeNames = [PSObject].Assembly.GetTypes().Where{$_.Name.EndsWith('Ast')}.Name | Sort-Object $typeNames | Where-Object { $_.LogName -like "$wordToComplete*" } | Foreach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, "ParameterValue", $_) } })] $AstType = '*', [Switch] $NoRecursion ) Begin { $predicate = { param($astObject) $astObject.GetType().Name -like $AstType } } Process { $errors = $null $ast = switch ($PSCmdlet.ParameterSetName) { 'File' { [System.Management.Automation.Language.Parser]::ParseFile($File, [ref]$null, [ref]$errors) } 'Code' { [System.Management.Automation.Language.Parser]::ParseInput($Code, [ref]$null, [ref]$errors) } } if ($errors) { throw [System.InvalidCastException]::new("Submitted text could not be converted to PowerShell because it contains syntax errors: $($errors | Out-String)")} $ast.FindAll($predicate, !$NoRecursion) | Add-Member -MemberType ScriptProperty -Name Type -Value { $this.GetType().Name } -PassThru } } function Get-BuildFunctions { <# .EXAMPLE $c = Get-PSModuleBuildFunctions -Path src -Recurse $c #> [CmdletBinding()] Param( [Parameter( ValueFromPipeline, ValueFromPipelineByPropertyName )] [Alias('FullName')] $Path = (Convert-Path ".\"), [Parameter( Position=2 )] [regex] $Exclude = '\.tests.ps1$', [Switch] $Recurse ) BEGIN { $baseMessage = "[ $($MyInvocation.InvocationName) ]" Write-Verbose "$baseMessage Executing" } PROCESS { if (-not (Test-Path -Path $Path -PathType Container -ErrorAction SilentlyContinue)) { Write-Error "$Path is not a valid Directory" return } $directories = switch ($Recurse) { $true { [System.IO.DirectoryInfo](Convert-Path $Path) Get-ChildItem -Path $Path -Directory -Force -ErrorAction SilentlyContinue -Recurse } $false { [System.IO.DirectoryInfo](Convert-Path $Path) } } $directories | Foreach-Object { $directory = $_ $params = @{ RootDirectory = $Path Path = $directory.FullName } $functionScope = Get-BuildFunctionScope @params Get-ChildItem -Path $directory.FullName -File | Foreach-Object { $file = $_ if ($file.Extension -eq '.ps1') { if ($file.Name -notmatch $Exclude) { $params = @{ File = $file.FullName AstType = 'FunctionDefinitionAst' ErrorAction = 'SilentlyContinue' } Get-Ast @params | Foreach-Object { $ast = $_ [PSCustomObject]@{ Scope = $functionScope Name = $ast.Name AST = $ast } } } } } } | Sort-Object -Property Scope, Name } } function Get-BuildFunctionScope { <# .EXAMPLE $RootDirectory = '/Users/balmerj/Desktop/Github/JerryBalmer/PS-CommonFunctions/src' $Path = '/Users/balmerj/Desktop/Github/JerryBalmer/PS-CommonFunctions/src/psbuild/private' Get-PSScriptFileScopeType -RootDirectory $RootDirectory -Path $Path # Output: private #> [CmdletBinding()] Param( [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position=0 )] [Alias('FullName')] $RootDirectory, [Parameter( Mandatory, Position=1 )] [String] $Path ) BEGIN { $baseMessage = "[ $($MyInvocation.InvocationName) ]" Write-Verbose "$baseMessage Executing" } PROCESS { $split = [System.Collections.ArrayList]@() $reversedSplit = [System.Collections.ArrayList]@() $splitCharacter = if ($IsWindows) { '\' } else { '/' } $processString = $Path -Replace ([regex]::Escape($RootDirectory)),'' $processString.Split($splitCharacter,[System.StringSplitOptions]::RemoveEmptyEntries) | Foreach-Object { $split.Add($_.ToString()) | Out-Null } ($split.Count - 1)..0 | Foreach-Object { $i = $_ $splitItem = $split[$i] Write-Verbose " - Adding Split Item: $splitItem" $reversedSplit.Add($splitItem) | Out-Null } $test = $reversedSplit | Foreach-Object { $i = $_ if ($i -eq 'public') { 'public' } elseif ($i -eq 'private') { 'private' } } $scope = if ($test) { $test | Select-Object -First 1 } else { 'public' } (Get-Culture).TextInfo.ToTitleCase($scope) } } function Split-String { <# https://stackoverflow.com/questions/16435240/how-to-split-string-by-string-in-powershell #> [CmdletBinding()] Param( [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position=0 )] [String] $String, [Parameter( Mandatory, Position=1 )] $Separator, [Parameter( Position=2 )] [Switch] $RemoveEmptyEntries, [Parameter( Position=3 )] [Switch] $Reverse ) BEGIN { $baseMessage = "[ $($MyInvocation.InvocationName) ]" Write-Verbose "$baseMessage Executing" } PROCESS { $_separator = if ($Separator.GetType().FullName -ne 'System.String[]') { [string[]]@($Separator) } else { $Separator } $result = if ($RemoveEmptyEntries) { $String.Split($_separator, [System.StringSplitOptions]::RemoveEmptyEntries) } else { $String.Split($separator) } if ($Reverse) { $array =[System.Collections.ArrayList]@() $result | Foreach-Object { $array.Add($_) | Out-Null } if ($array.Count -gt 0) { ($array.Count - 1)..0 | Foreach-Object { $i = $_ $array[$i] } } } else { $result } } } function Update-Version { [CmdletBinding()] Param( [Parameter( Position=0, ValueFromPipeline )] [version] $Version, [Parameter( Position=1 )] [ValidateSet( 'Major', 'Minor', 'Build' )] [string] $Type = 'Build', [switch] $OutVersion ) Begin { $baseMessage = "[ $($MyInvocation.InvocationName) ]" Write-Verbose "$baseMessage Executing" } Process { $v = $Version $major = $v.Major $minor = $v.Minor $build = $v.Build $version = switch ($Type) { 'Major' { [version]("$($major + 1).0.0") } 'Minor' { [version]("$($major).$($minor + 1).0") } 'Build' { [version]("$($major).$($minor).$($build + 1)") } } } End { if ($PSBoundParameters.ContainsKey('OutVersion')) { $version } else { $version.ToString() } } } function Invoke-EnsureDirectory { [CmdletBinding()] Param( [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position=1 )] [System.IO.DirectoryInfo] $Path, [Switch] $Force ) BEGIN { } PROCESS { if (-not (Test-Path -Path $Path -PathType Container)) { if ($PSBoundParameters.ContainsKey('Force')) { $params = @{ Path = $Path ItemType = 'Directory' Force = $true ErrorAction = 'Stop' } New-Item @params | Select-Object -ExpandProperty FullName } else { Write-Error "Directory does not exist. Use the -Force parameter to create it. $Path" return } } else { (Resolve-Path $Path).Path } } } function Invoke-PSBuild { <# .SYNOPSIS Builds a PowerShell module .DESCRIPTION A PowerShell module that automates the building of PowerShell mdoules. It sets up the standard directories and output directories. .EXAMPLE Invoke-PSBuild -ModuleName 'Test' #> [CmdletBinding(DefaultParameterSetName='Standard')] Param( [Parameter( ParameterSetName='Standard', Mandatory, Position=0 )] $ModuleName, [Parameter( ParameterSetName='Standard', Position=1 )] [String] $Path = ".\", [Parameter( ParameterSetName='Standard', Position=1 )] [String] $SourceDirectoryName = "src", [Parameter( ParameterSetName='Standard', Position=1 )] [System.IO.DirectoryInfo] $OutputDirectory, [Switch] $PassThru, [Switch] $Force ) BEGIN { $baseMessage = "[ $($MyInvocation.InvocationName) ]" Write-Verbose "$baseMessage Executing" } PROCESS { if (-not $OutputDirectory) { $OutputDirectory = Join-Path (Convert-Path $Path) 'bin' } $psm1Name = "$($ModuleName).psm1" $psd1Name = "$($ModuleName).psd1" switch ($PSCmdlet.ParameterSetName) { 'Standard' { #################################################### # Create Directories #################################################### Write-Verbose " - Ensuring the working directory" $params = @{ Path = $Path Force = $PSBoundParameters.ContainsKey('Force') ErrorAction = 'Stop' } $_workingDirectory = Invoke-EnsureDirectory @params ###################################### Write-Verbose " - Ensuring the src directory" $params = @{ Path = Join-Path $_workingDirectory $SourceDirectoryName Force = $Force ErrorAction = 'Stop' } $_sourceDirectory = Invoke-EnsureDirectory @params ###################################### Write-Verbose " - Ensuring the output directory" $params = @{ Path = if ($OutputDirectory) {$OutputDirectory} else {Join-Path $_workingDirectory 'bin'} Force = $PSBoundParameters.ContainsKey('Force') ErrorAction = 'Stop' } $_outputDirectory = Invoke-EnsureDirectory @params ###################################### Write-Verbose " - Ensuring the module output directory" $params = @{ Path = Join-Path $_outputDirectory $ModuleName Force = $PSBoundParameters.ContainsKey('Force') ErrorAction = 'Stop' } $_moduleOutputDirectory = Invoke-EnsureDirectory @params #################################################### # Create Module Files #################################################### Write-Verbose " - Ensuring the $psm1Name file" $psm1Path = Join-Path $_sourceDirectory $psm1Name if (-not (Test-Path -Path $psm1Path -PathType Leaf)) { $params = @{ Path = $psm1Path ItemType = 'File' Force = $true ErrorAction = 'Stop' } New-Item @params | Select-Object -ExpandProperty FullName } ###################################### Write-Verbose " - Ensuring the $psd1Name file" $psd1Path = Join-Path $_sourceDirectory $psd1Name if (-not (Test-Path -Path $psd1Path -PathType Leaf)) { $params = @{ Path = $psd1Path RootModule = $psm1Name ModuleVersion = "0.0.0" } New-ModuleManifest @params } #################################################### # Create Build Object #################################################### $Global:PSBUILD = [PSCustomObject]@{ ModuleName = $ModuleName BuildType = $BuildType CurrentVersion = $null BuildVersion = $null Items = [ordered]@{ Functions = Get-BuildFunctions -Path $_sourceDirectory -Recurse -Verbose:$false } Paths = [ordered]@{ Source = [ordered]@{ WorkingDirectory = $_workingDirectory SourceDirectory = $_sourceDirectory PSM1 = $psm1Path PSD1 = $psd1Path } Destination = [ordered]@{ OutputDirectory = $_outputDirectory ModuleOutputDirectory = $_moduleOutputDirectory PSM1 = Join-Path $_moduleOutputDirectory $psm1Name PSD1 = Join-Path $_moduleOutputDirectory $psd1Name } } } ###################################### Write-Verbose " - Pulling current version" $currentVersion = (Import-PowerShellDataFile -Path $PSBUILD.Paths.Source.PSD1).ModuleVersion $PSBUILD.CurrentVersion = $currentVersion ###################################### Write-Verbose " - Calculating Build Version" $PSBUILD.BuildVersion = $currentVersion | Update-Version -Verbose:$false ################################################ ################################################ # Process the .psm1 file ################################################ ################################################ $params = @{ Path = $PSBUILD.Paths.Source.PSM1 Destination = $PSBUILD.Paths.Destination.PSM1 Force = $true } Copy-Item @params $PSBUILD.Items.Functions | Sort-Object -Property Scope, Name | Foreach-Object { $_function = $_ $_function.AST.Extent.text | Out-File -FilePath $PSBUILD.Paths.Destination.PSM1 -Append "" | Out-File -FilePath $PSBUILD.Paths.Destination.PSM1 -Append } ################################################ # Process the .psd1 file ################################################ $manifestParams = @{} $manifestParams['Path'] = $PSBUILD.Paths.Destination.PSD1 # Copy the manifest $params = @{ Path = $PSBUILD.Paths.Source.PSD1 Destination = $PSBUILD.Paths.Destination.PSD1 Force = $true } Copy-Item @params #------------------------------- # Functions to Export #------------------------------- $functionsToExport = $PSBUILD.Items.Functions ` | Where-Object {$_.Scope -eq 'Public'} ` | Select-Object -ExpandProperty Name if ($functionsToExport) { $manifestParams['FunctionsToExport'] = $functionsToExport } #------------------------------- # Update the Module Manifest #------------------------------- Update-ModuleManifest @manifestParams $mColor = 'Cyan' $bColor = 'Blue' $publicColor = 'Green' $privateColor = 'Red' Write-Host "ModuleName: " -ForegroundColor $mColor -NoNewline Write-Host "$($PSBUILD.ModuleName)" -ForegroundColor $bColor Write-Host "Version: " -ForegroundColor $mColor -NoNewline Write-Host "$($PSBUILD.BuildVersion)" -ForegroundColor $bColor Write-Host "Functions:" -ForegroundColor $mColor Write-Host $PSBUILD.Items.Functions | Where-Object {$_.Scope -eq 'Public'} | Sort-Object -Property Name | Foreach-Object { $functionName = $_.Name Write-Host " Public " -ForegroundColor $publicColor -NoNewline Write-Host $functionName -ForegroundColor $bColor } $PSBUILD.Items.Functions | Where-Object {$_.Scope -eq 'Private'} | Sort-Object -Property Name | Foreach-Object { $functionName = $_.Name Write-Host " Private " -ForegroundColor $privateColor -NoNewline Write-Host $functionName -ForegroundColor $bColor } Write-Host if ($PassThru) { $Global:PSBUILD } } } } } |