pf-module.ps1
$ErrorActionPreference = 'stop' function Import-Module_Ensure ($name) { if (Get-Module -name $name ) { return } if (-not (Get-Module -name $name -ListAvailable )) { Set-PSRepository -name PsGallery -InstallationPolicy Trusted Install-Module -Name $name -Repository PsGallery } Import-Module $name } function Initialize-File_InNotPresent($path) { if (-not (Test-Path -Path $path)) { Set-Content -Path $path -Value "" } } function Get-ScriptFolder { $scriptPath = Get-PSCallStack | Where-Object ScriptName | Select-Object -first 1 $scriptPath = Split-Path $scriptPath.ScriptName -Parent return $scriptPath } function Get-ScriptList([switch]$NoExcludeThisScript, $path) { $fileExtension = "ps1" if (-not $path) { $path = Get-ScriptFolder } $result = Get-ChildItem -Path $path -Recurse -Filter "*.$fileExtension" | Where-Object Extension -eq ".$fileExtension" | Sort-Object FullName if (-not $NoExcludeThisScript) { $result = $result | Where-Object FullName -NE $MyInvocation.ScriptName } return $result } function Remove-Alias { Param( [Parameter(ValueFromPipeline=$true)]$name, [Switch]$NoResolvedCommand ) process { If ($name -and ( Test-Path Alias:$name)) {Remove-Item Alias:$name} } end { if ($NoResolvedCommand) { $toRemove = Get-Alias | Where-Object { -not $_.ResolvedCommand } $toRemove.name | Where-Object {$_} | Where-Object { Test-Path Alias:$_ } | ForEach-Object { Remove-Item Alias:$_ -Verbose } } } } function Remove-Alias:::Example { Remove-Alias -NoResolvedCommand } function Get-Functions { param( [Parameter(ValueFromPipeline=$true)]$path ) process { $path = $path.FullName ?? $path . $path return Get-Command | Where-Object ScriptBlock | Where-Object { $_.ScriptBlock.File -eq $path } } } function Get-ContentToInclude($functionName) { if ($functionName) { $header = get-command DefineModuleHeader $body = $header.ScriptBlock.ToString() $result = "#Include-Start $functionName`n$body`n#Include-end $functionName`n" } return $result } function Update-ModulePsm1($path) { $folder = Split-Path $path -Parent $files = Get-ScriptList -path $folder -NoExcludeThisScript $fullnameList = $files.fullname | ForEach-Object { ([string]$_).Substring($folder.Length + 1) } function DefineModuleHeader { $ErrorActionPreference = 'stop' $script:scriptFolder = Split-Path ( Get-PSCallStack )[0].ScriptName -Parent } $content = $fullnameList | ForEach-Object { "`n" + '. $scriptFolder\' + $_ } $DefineModuleHeader = Get-ContentToInclude -functionName DefineModuleHeader $content = $DefineModuleHeader + $content Set-Content -Value $content -Path $path } function Update-ModulePsm1:::Example { Update-ModulePsm1 -path C:\code\PowerFrame\pf-azFuncDeploy\pf-azFuncDeploy.psm1 } function Import-Module_EnsureManifest ($Path, $FunctionsToExport,$Revision) { function NewModuleManifest_Default { $moduleName = Split-Path $Path -Leaf New-ModuleManifest -Path $Path -ModuleVersion "1.0.0.1" -Description $moduleName } if (-not (Test-Path $Path) ) { NewModuleManifest_Default } $psd = Import-PowerShellDataFile -Path $path -ErrorAction SilentlyContinue if (-not $psd) { # psd1 likely to be invalid so recreate one NewModuleManifest_Default } $description = if ($psd.Description) { $psd.Description } else { "$moduleName" } $ModuleVersion = if ($psd.ModuleVersion) { [Version]$psd.ModuleVersion } else { "$moduleName" } $Revision = if ($Revision) { $Revision } else { [Math]::Max($ModuleVersion.Revision, 1) } $ModuleVersion = [Version]::new([Math]::Max(1,$ModuleVersion.Major), [Math]::Max(0,$ModuleVersion.Minor), [Math]::Max(0,$ModuleVersion.Build), $Revision) $functionsToExport = if ( $functionsToExport ) { $functionsToExport } else { $psd.functionsToExport } Update-ModuleManifest -Path $Path -FunctionsToExport $functionsToExport ` -Description $description -ModuleVersion $ModuleVersion -RootModule "$moduleName.psm1" # Remove comments in order to avoid commit changes because of header containg dates Remove-PowershellComments -path $Path } function Import-Module_EnsureManifest:::Example { Import-Module_EnsureManifest -Path C:\code\PowerFrame\pf-git\pf-git.psd1 -Revision 20 } function Remove-PowershellComments($path) { $content = Get-Content -Path $path -Raw $result = Remove-PowershellCommentsFromString -content $content Set-Content -Path $path -Value $result } function Remove-PowershellCommentsFromString($content) { $result = $content -replace '\s*#(?!\brequires\b).*', '' $result = $result.Trim() return $result } function Remove-PowershellCommentsFromString:::Test($content) { Remove-PowershellCommentsFromString -content "#comment" | assert -eq "" Remove-PowershellCommentsFromString -content "#requires" | assert -eq "#requires" Remove-PowershellCommentsFromString -content " #comment abc" | assert -eq "" Remove-PowershellCommentsFromString -content "abc" | assert -eq " abc" } function Initialize-Module_Manifest_EnsureFiles { param( [Parameter(ValueFromPipeline=$true)]$module, $Revision ) begin { $approvedVerbs = Get-Verb | ForEach-Object { $_.Verb } # $approvedVerbs += "Ensure" $approvedVerbsRegEx = [string]::Join( '|', ( $approvedVerbs | ForEach-Object { "$_-" } ) ) $approvedVerbsRegEx += "|Assert" } process { $modulePath = $module.DirectoryName ?? $module $moduleName = Split-Path $modulePath -Leaf $fullPathNoExtension = "$modulePath\$moduleName" function Update-ModuleManifest_FunctionsToExport { $scriptList = Get-ScriptList -NoExcludeThisScript -path $modulePath $functionList = $scriptList | Get-Functions $functionsToExport = $functionList | Where-Object name -NotLike '*:*' | Where-Object name -Match $approvedVerbsRegEx | Sort-Object name Import-Module_EnsureManifest -Path "$fullPathNoExtension.psd1" ` -FunctionsToExport $functionsToExport.Name -Revision $Revision } Initialize-File_InNotPresent -path "$fullPathNoExtension.Format.ps1xml" Update-ModulePsm1 -path "$fullPathNoExtension.psm1" Update-ModuleManifest_FunctionsToExport } } function Add-Module_PSModulePath ([string]$ModulesFolder = 'PSModules', [switch]$notRequired ) { $p = split-path -parent ($current = ( Get-PSCallStack )[0].ScriptName ) while ( $p -and -not ( Test-Path ( "$p\$ModulesFolder" ) ) ) { $p = Split-Path $p -Parent } if (-not $p -and -not $notRequired) { throw "Modules folder '$ModulesFolder' cannot be found starting from '$current'" } $p = Resolve-Path "$p\$ModulesFolder" if (-not $env:PSModulePath.Contains("$p;")) { $env:PSModulePath = "$p;$env:PSModulePath" } } function Get-Modulefolders ($path) { $folders = if ($path) { Get-ChildItem -Directory -path $path } else { Get-ChildItem -Directory } $folders | Where-Object name -NotLike '.*' } function Initialize-Module_Manifest_EnsureFiles:::Example { $folderModules = Get-Modulefolders -path C:\code\PowerFrame $folderModules.FullName | Initialize-Module_Manifest_EnsureFiles -Revision 3 } function Sync-ModuleManifests { $commitCount = Get-GitCommitCount Get-Modules_Local | Get-Path | Get-Git_Changed -folder | Initialize-Module_Manifest_EnsureFiles -Revision $commitCount } function Publish-ModuleEx { param( [Parameter(ValueFromPipeline=$true)]$modulePath, [string]$NuGetApiKey, [string[]]$repositoryList = @('LocalRepo') ) process { $moduleName = Split-Path $modulePath -Leaf $psd = Import-PowerShellDataFile -Path "$modulePath\$moduleName.psd1" foreach ($repository in $repositoryList) { $module = Find-Module -Name $moduleName -RequiredVersion $psd.ModuleVersion ` -ErrorAction SilentlyContinue -Repository $repository if (-not $module) { $additionalParams = @{} if ($NuGetApiKey) { $additionalParams.Add("NuGetApiKey",$NuGetApiKey) } Publish-Module @additionalParams -Path $modulePath -Repository $repository -Force } } $repository = $repositoryList[0] Uninstall-Module -Name $moduleName -ErrorAction SilentlyContinue # Install-Module -Name $moduleName -Scope CurrentUser -Repository $repository ` # -Force -AllowClobber -RequiredVersion $psd.ModuleVersion } } function Publish-ModuleEx:::Example { $folderModules = Get-Modulefolders -path C:\code\PowerFrame $folderModules.FullName | Initialize-Module_Manifest_EnsureFiles $folderModules.FullName | Publish-ModuleEx } function Get-ScriptDependencies { # based on https://mikefrobbins.com/2019/02/21/powershell-tokenizer-more-accurate-than-ast-in-certain-scenarios/ # see also https://mikefrobbins.com/2019/05/17/using-the-ast-to-find-module-dependencies-in-powershell-functions-and-scripts/ param ( [Parameter(ValueFromPipeline=$true)] $file, [Switch]$IgnoreMissingCommand ) begin { $getCommandErrorAction = ($IgnoreMissingCommand) ? 'SilentlyContinue' : 'Stop' } process { $file = $file.fullname ?? $file $Token = $null $ignoredOutput = [System.Management.Automation.Language.Parser]::ParseFile($File, [ref]$Token, [ref]$null) Write-Verbose $ignoredOutput $commandTokenList = $Token | Where-Object {$_.TokenFlags -eq 'CommandName'} $commandNameList = $commandTokenList.Value | Select-Object -Unique | Sort-Object $commandList = $commandNameList | ForEach-Object { Get-Command -name $_ -ErrorAction $getCommandErrorAction } $commandList.Module | Select-Object -Unique } } function Get-ScriptDependencies:::Examples{ $moduleList = Get-ChildItem -filter pf-git.ps1 -Recurse | Get-ScriptDependencies -IgnoreMissingCommand Write-Host ( $moduleList | Format-Table | Out-String ) } function Get-ScriptFunctions { param ( [Parameter(ValueFromPipeline=$true)] $file ) process { $file = $file.fullname ?? $file $Token = $null $ignoredOutput = [System.Management.Automation.Language.Parser]::ParseFile($File, [ref]$Token, [ref]$null) Write-Verbose $ignoredOutput $funcBegin = $false $Token | ForEach-Object { if ($funcBegin) { $_.Text } $funcBegin = ($_.Kind -like "function") } } } function Get-ScriptFunctions:::Example { Get-ChildItem pf-* -Recurse -Filter *.ps1 | Select-Object -First 1 | Get-ScriptFunctions } function Get-ScriptFunctions_WithInvalidChars { Get-ChildItem pf-* -Recurse -Filter *.ps1 | Get-ScriptFunctions | Where-Object { $_ -match '\w+-\w+-' } } function Get-Modules_Local { $folders = Get-ChildItem -file -Recurse -Filter *.* -include @('*.psd1', '*.psm1', "*.ps1") | Where-Object { $_.FullName -notlike "*pending*" } | Where-Object { $_.BaseName -eq $_.Directory.BaseName } | Select-Object DirectoryName -Unique $folders.DirectoryName | ForEach-Object { [PSCustomObject]@{ Name = (Split-Path $_ -LeafBase) DirectoryName = $_ } } } function Uninstall-Module_WhenDuplicated { [CmdletBinding()] param( [Parameter(ValueFromPipeline=$true)] $Module, [switch]$PassThru ) process { $mm = Get-module $Module.Name -ListAvailable if ( $mm -and -not $mm.Path.StartsWith($Module.DirectoryName)) { Uninstall-Module $Module.Name -Force -Verbose } if ($PassThru) { return $Module } } } function Import-Module_Reload { [CmdletBinding()] param( [Parameter(ValueFromPipeline=$true)] $Module ) process { if ($Module) { if (get-module $Module.Name ) { remove-module $Module.Name } $modulePath = Join-Path -Path $Module.DirectoryName -ChildPath ($Module.Name + ".psm1") if (Test-Path $modulePath) { Import-Module $Module.DirectoryName -Global } } } } function Import-PSSnapin ($name) { if (-not (Get-PSSnapin | Where-Object Name -eq $name)) { if ( Get-PSSnapin -Registered | Where-Object Name -eq $name ) { Add-PsSnapin $name } else { Write-Warning "SNAPIN not available : '$name'" } } } function Import-PSSnapin:::Example { Import-PSSnapin Microsoft.SharePoint.PowerShell } |