PSModule.FX.psm1
[Cmdletbinding()] param() $scriptName = $MyInvocation.MyCommand.Name Write-Verbose "[$scriptName] Importing subcomponents" #region - Data import Write-Verbose "[$scriptName] - [data] - Processing folder" $dataFolder = (Join-Path $PSScriptRoot 'data') Write-Verbose "[$scriptName] - [data] - [$dataFolder]" Get-ChildItem -Path "$dataFolder" -Recurse -Force -Include '*.psd1' -ErrorAction SilentlyContinue | ForEach-Object { Write-Verbose "[$scriptName] - [data] - [$($_.Name)] - Importing" New-Variable -Name $_.BaseName -Value (Import-PowerShellDataFile -Path $_.FullName) -Force Write-Verbose "[$scriptName] - [data] - [$($_.Name)] - Done" } Write-Verbose "[$scriptName] - [data] - Done" #endregion - Data import #region - From /private Write-Verbose "[$scriptName] - [/private] - Processing folder" #region - From /private/Build Write-Verbose "[$scriptName] - [/private/Build] - Processing folder" #region - From /private/Build/Add-ContentFromItem.ps1 Write-Verbose "[$scriptName] - [/private/Build/Add-ContentFromItem.ps1] - Importing" function Add-ContentFromItem { param( [string] $Path, [string] $RootModuleFilePath, [string] $RootPath ) $relativeFolderPath = $Path.Replace($RootPath, '').TrimStart($pathSeparator) Add-Content -Path $RootModuleFilePath -Value @" #region - From $relativeFolderPath Write-Verbose "[`$scriptName] - [$relativeFolderPath] - Processing folder" "@ $subFolders = $Path | Get-ChildItem -Directory -Force | Sort-Object -Property Name foreach ($subFolder in $subFolders) { Add-ContentFromItem -Path $subFolder.FullName -RootModuleFilePath $RootModuleFilePath -RootPath $RootPath } $files = $Path | Get-ChildItem -File -Force -Filter '*.ps1' | Sort-Object -Property FullName foreach ($file in $files) { $relativeFilePath = $file.FullName.Replace($RootPath, '').TrimStart($pathSeparator) Add-Content -Path $RootModuleFilePath -Value @" #region - From $relativeFilePath Write-Verbose "[`$scriptName] - [$relativeFilePath] - Importing" "@ Get-Content -Path $file.FullName | Add-Content -Path $RootModuleFilePath Add-Content -Path $RootModuleFilePath -Value @" Write-Verbose "[`$scriptName] - [$relativeFilePath] - Done" #endregion - From $relativeFilePath "@ } Add-Content -Path $RootModuleFilePath -Value @" Write-Verbose "[`$scriptName] - [$relativeFolderPath] - Done" #endregion - From $relativeFolderPath "@ } Write-Verbose "[$scriptName] - [/private/Build/Add-ContentFromItem.ps1] - Done" #endregion - From /private/Build/Add-ContentFromItem.ps1 #region - From /private/Build/Build-PSModuleBase.ps1 Write-Verbose "[$scriptName] - [/private/Build/Build-PSModuleBase.ps1] - Importing" function Build-PSModuleBase { [CmdletBinding()] param( # Path to the folder where the module source code is located. [Parameter(Mandatory)] [string] $SourceFolderPath, # Path to the folder where the built modules are outputted. [Parameter(Mandatory)] [string] $OutputFolderPath ) $moduleName = Split-Path -Path $SourceFolderPath -Leaf Write-Output "::group::[$moduleName] - Build base" $deletePaths = @( 'init', 'private', 'public', "$moduleName.psd1", "$moduleName.psm1" ) Write-Verbose "Copying files from [$SourceFolderPath] to [$OutputFolderPath]" Copy-Item -Path "$SourceFolderPath" -Destination $OutputFolderPath -Recurse -Force -Verbose Write-Verbose "Deleting files from [$OutputFolderPath] that are not needed" Get-ChildItem -Path $OutputFolderPath -Recurse -Force | Where-Object { $_.Name -in $deletePaths } | Remove-Item -Force -Recurse -Verbose Write-Output '::endgroup::' Write-Output "::group::[$moduleName] - Build base - Result" (Get-ChildItem -Path $OutputFolderPath -Recurse -Force).FullName | Sort-Object Write-Output '::endgroup::' } Write-Verbose "[$scriptName] - [/private/Build/Build-PSModuleBase.ps1] - Done" #endregion - From /private/Build/Build-PSModuleBase.ps1 #region - From /private/Build/Build-PSModuleDocumentation.ps1 Write-Verbose "[$scriptName] - [/private/Build/Build-PSModuleDocumentation.ps1] - Importing" function Build-PSModuleDocumentation { [CmdletBinding()] param( # Path to the folder where the module source code is located. [Parameter(Mandatory)] [string] $SourceFolderPath, # Path to the folder where the built modules are outputted. [Parameter(Mandatory)] [string] $OutputFolderPath ) $moduleName = Split-Path -Path $SourceFolderPath -Leaf Write-Output "::group::[$moduleName] - Build documentation" $manifestFile = Get-PSModuleManifest -SourceFolderPath $SourceFolderPath -As FileInfo -Verbose:$false Resolve-PSModuleDependencies -ManifestFilePath $manifestFile Write-Verbose "[$moduleName] - Importing module" Import-Module $moduleName Write-Verbose "[$moduleName] - List loaded modules" $availableModules = Get-Module -ListAvailable -Refresh -Verbose:$false $availableModules | Select-Object Name, Version, Path | Sort-Object Name | Format-Table -AutoSize if ($moduleName -notin $availableModules.Name) { throw "[$moduleName] - Module not found" } New-MarkdownHelp -Module $moduleName -OutputFolder $OutputFolderPath -Force -Verbose Write-Output '::endgroup::' Write-Output "::group::[$moduleName] - Build documentation - Result" Get-ChildItem -Path $OutputFolderPath -Recurse -Force -Include '*.md' | ForEach-Object { Write-Output "::debug::[$moduleName] - [$_] - [$(Get-FileHash -Path $_.FullName -Algorithm SHA256)]" } Write-Output '::endgroup::' } Write-Verbose "[$scriptName] - [/private/Build/Build-PSModuleDocumentation.ps1] - Done" #endregion - From /private/Build/Build-PSModuleDocumentation.ps1 #region - From /private/Build/Build-PSModuleManifest.ps1 Write-Verbose "[$scriptName] - [/private/Build/Build-PSModuleManifest.ps1] - Importing" function Build-PSModuleManifest { [CmdletBinding()] param( # Path to the folder where the module source code is located. [Parameter(Mandatory)] [string] $SourceFolderPath, # Path to the folder where the built modules are outputted. [Parameter(Mandatory)] [string] $OutputFolderPath ) $moduleName = Split-Path -Path $SourceFolderPath -Leaf Write-Output "::group::[$moduleName] - Build manifest file" Write-Verbose "[$moduleName] - Finding manifest file" $manifestFile = Get-PSModuleManifest -SourceFolderPath $SourceFolderPath -As FileInfo $manifestFileName = $manifestFile.Name $manifest = Get-PSModuleManifest -SourceFolderPath $SourceFolderPath -As Hashtable $manifest.RootModule = Get-PSModuleRootModule -SourceFolderPath $SourceFolderPath $manifest.Author = $manifest.Keys -contains 'Author' ? -not [string]::IsNullOrEmpty($manifest.Author) ? $manifest.Author : $env:GITHUB_REPOSITORY_OWNER : $env:GITHUB_REPOSITORY_OWNER Write-Verbose "[$moduleName] - [Author] - [$($manifest.Author)]" $manifest.CompanyName = $manifest.Keys -contains 'CompanyName' ? -not [string]::IsNullOrEmpty($manifest.CompanyName) ? $manifest.CompanyName : $env:GITHUB_REPOSITORY_OWNER : $env:GITHUB_REPOSITORY_OWNER Write-Verbose "[$moduleName] - [CompanyName] - [$($manifest.CompanyName)]" $year = Get-Date -Format 'yyyy' $copyRightOwner = $manifest.CompanyName -eq $manifest.Author ? $manifest.Author : "$($manifest.Author) | $($manifest.CompanyName)" $copyRight = "(c) $year $copyRightOwner. All rights reserved." $manifest.CopyRight = $manifest.Keys -contains 'CopyRight' ? -not [string]::IsNullOrEmpty($manifest.CopyRight) ? $manifest.CopyRight : $copyRight : $copyRight Write-Verbose "[$moduleName] - [CopyRight] - [$($manifest.CopyRight)]" $manifest.Description = $manifest.Keys -contains 'Description' ? -not [string]::IsNullOrEmpty($manifest.Description) ? $manifest.Description : 'Unknown' : 'Unknown' Write-Verbose "[$moduleName] - [Description] - [$($manifest.Description)]" $manifest.PowerShellHostName = $manifest.Keys -contains 'PowerShellHostName' ? -not [string]::IsNullOrEmpty($manifest.PowerShellHostName) ? $manifest.PowerShellHostName : $null : $null Write-Verbose "[$moduleName] - [PowerShellHostName] - [$($manifest.PowerShellHostName)]" $manifest.PowerShellHostVersion = $manifest.Keys -contains 'PowerShellHostVersion' ? -not [string]::IsNullOrEmpty($manifest.PowerShellHostVersion) ? $manifest.PowerShellHostVersion : $null : $null Write-Verbose "[$moduleName] - [PowerShellHostVersion] - [$($manifest.PowerShellHostVersion)]" $manifest.DotNetFrameworkVersion = $manifest.Keys -contains 'DotNetFrameworkVersion' ? -not [string]::IsNullOrEmpty($manifest.DotNetFrameworkVersion) ? $manifest.DotNetFrameworkVersion : $null : $null Write-Verbose "[$moduleName] - [DotNetFrameworkVersion] - [$($manifest.DotNetFrameworkVersion)]" $manifest.ClrVersion = $manifest.Keys -contains 'ClrVersion' ? -not [string]::IsNullOrEmpty($manifest.ClrVersion) ? $manifest.ClrVersion : $null : $null Write-Verbose "[$moduleName] - [ClrVersion] - [$($manifest.ClrVersion)]" $manifest.ProcessorArchitecture = $manifest.Keys -contains 'ProcessorArchitecture' ? -not [string]::IsNullOrEmpty($manifest.ProcessorArchitecture) ? $manifest.ProcessorArchitecture : 'None' : 'None' Write-Verbose "[$moduleName] - [ProcessorArchitecture] - [$($manifest.ProcessorArchitecture)]" #Get the path separator for the current OS $pathSeparator = [System.IO.Path]::DirectorySeparatorChar Write-Verbose "[$moduleName] - [FileList]" $files = $SourceFolderPath | Get-ChildItem -File -ErrorAction SilentlyContinue | Where-Object -Property Name -NotLike '*.ps1' $files += $SourceFolderPath | Get-ChildItem -Directory | Get-ChildItem -Recurse -File -ErrorAction SilentlyContinue $files = $files | Select-Object -ExpandProperty FullName | ForEach-Object { $_.Replace($SourceFolderPath, '').TrimStart($pathSeparator) } $fileList = $files | Where-Object { $_ -notLike 'public*' -and $_ -notLike 'private*' -and $_ -notLike 'classes*' } $manifest.FileList = $fileList.count -eq 0 ? @() : @($fileList) $manifest.FileList | ForEach-Object { Write-Verbose "[$moduleName] - [FileList] - [$_]" } Write-Verbose "[$moduleName] - [RequiredAssemblies]" $requiredAssembliesFolderPath = Join-Path $SourceFolderPath 'assemblies' $requiredAssemblies = Get-ChildItem -Path $RequiredAssembliesFolderPath -Recurse -File -ErrorAction SilentlyContinue -Filter '*.dll' | Select-Object -ExpandProperty FullName | ForEach-Object { $_.Replace($SourceFolderPath, '').TrimStart($pathSeparator) } $manifest.RequiredAssemblies = $requiredAssemblies.count -eq 0 ? @() : @($requiredAssemblies) $manifest.RequiredAssemblies | ForEach-Object { Write-Verbose "[$moduleName] - [RequiredAssemblies] - [$_]" } Write-Verbose "[$moduleName] - [NestedModules]" $nestedModulesFolderPath = Join-Path $SourceFolderPath 'modules' $nestedModules = Get-ChildItem -Path $nestedModulesFolderPath -Recurse -File -ErrorAction SilentlyContinue -Include '*.psm1', '*.ps1' | Select-Object -ExpandProperty FullName | ForEach-Object { $_.Replace($SourceFolderPath, '').TrimStart($pathSeparator) } $manifest.NestedModules = $nestedModules.count -eq 0 ? @() : @($nestedModules) $manifest.NestedModules | ForEach-Object { Write-Verbose "[$moduleName] - [NestedModules] - [$_]" } Write-Verbose "[$moduleName] - [ScriptsToProcess]" $allScriptsToProcess = @('scripts', 'classes') | ForEach-Object { Write-Verbose "[$moduleName] - [ScriptsToProcess] - Processing [$_]" $scriptsFolderPath = Join-Path $SourceFolderPath $_ $scriptsToProcess = Get-ChildItem -Path $scriptsFolderPath -Recurse -File -ErrorAction SilentlyContinue -Include '*.ps1' | Select-Object -ExpandProperty FullName | ForEach-Object { $_.Replace($SourceFolderPath, '').TrimStart($pathSeparator) } $scriptsToProcess } $manifest.ScriptsToProcess = $allScriptsToProcess.count -eq 0 ? @() : @($allScriptsToProcess) $manifest.ScriptsToProcess | ForEach-Object { Write-Verbose "[$moduleName] - [ScriptsToProcess] - [$_]" } Write-Verbose "[$moduleName] - [TypesToProcess]" $typesToProcess = Get-ChildItem -Path $SourceFolderPath -Recurse -File -ErrorAction SilentlyContinue -Include '*.Types.ps1xml' | Select-Object -ExpandProperty FullName | ForEach-Object { $_.Replace($SourceFolderPath, '').TrimStart($pathSeparator) } $manifest.TypesToProcess = $typesToProcess.count -eq 0 ? @() : @($typesToProcess) $manifest.TypesToProcess | ForEach-Object { Write-Verbose "[$moduleName] - [TypesToProcess] - [$_]" } Write-Verbose "[$moduleName] - [FormatsToProcess]" $formatsToProcess = Get-ChildItem -Path $SourceFolderPath -Recurse -File -ErrorAction SilentlyContinue -Include '*.Format.ps1xml' | Select-Object -ExpandProperty FullName | ForEach-Object { $_.Replace($SourceFolderPath, '').TrimStart($pathSeparator) } $manifest.FormatsToProcess = $formatsToProcess.count -eq 0 ? @() : @($formatsToProcess) $manifest.FormatsToProcess | ForEach-Object { Write-Verbose "[$moduleName] - [FormatsToProcess] - [$_]" } Write-Verbose "[$moduleName] - [DscResourcesToExport]" $dscResourcesToExportFolderPath = Join-Path $SourceFolderPath 'dscResources' $dscResourcesToExport = Get-ChildItem -Path $dscResourcesToExportFolderPath -Recurse -File -ErrorAction SilentlyContinue -Include '*.psm1' | Select-Object -ExpandProperty FullName | ForEach-Object { $_.Replace($SourceFolderPath, '').TrimStart($pathSeparator) } $manifest.DscResourcesToExport = $dscResourcesToExport.count -eq 0 ? @() : @($dscResourcesToExport) $manifest.DscResourcesToExport | ForEach-Object { Write-Verbose "[$moduleName] - [DscResourcesToExport] - [$_]" } $manifest.FunctionsToExport = Get-PSModuleFunctionsToExport -SourceFolderPath $SourceFolderPath $manifest.CmdletsToExport = Get-PSModuleCmdletsToExport -SourceFolderPath $SourceFolderPath $manifest.AliasesToExport = Get-PSModuleAliasesToExport -SourceFolderPath $SourceFolderPath $manifest.VariablesToExport = Get-PSModuleVariablesToExport -SourceFolderPath $SourceFolderPath Write-Verbose "[$moduleName] - [ModuleList]" $moduleList = Get-ChildItem -Path $SourceFolderPath -Recurse -File -ErrorAction SilentlyContinue -Include '*.psm1' -Exclude "$moduleName.psm1" | Select-Object -ExpandProperty FullName | ForEach-Object { $_.Replace($SourceFolderPath, '').TrimStart($pathSeparator) } $manifest.ModuleList = $moduleList.count -eq 0 ? @() : @($moduleList) $manifest.ModuleList | ForEach-Object { Write-Verbose "[$moduleName] - [ModuleList] - [$_]" } Write-Verbose "[$moduleName] - Gather dependencies from files" $capturedModules = @() $capturedVersions = @() $capturedPSEdition = @() $files = $SourceFolderPath | Get-ChildItem -Recurse -File -ErrorAction SilentlyContinue Write-Verbose "[$moduleName] - Processing [$($files.Count)] files" foreach ($file in $files) { $relativePath = $file.FullName.Replace($SourceFolderPath, '').TrimStart($pathSeparator) Write-Verbose "[$moduleName] - [$relativePath]" if ($file.extension -in '.psm1', '.ps1') { $fileContent = Get-Content -Path $file switch -Regex ($fileContent) { # RequiredModules -> REQUIRES -Modules <Module-Name> | <Hashtable>, @() if not provided '^#Requires -Modules (.+)$' { # Add captured module name to array $capturedMatches = $matches[1].Split(',').trim() $capturedMatches | ForEach-Object { Write-Verbose "[$moduleName] - [$relativePath] - [REQUIRED -Modules] - [$_]" $hashtable = '\@\s*\{[^\}]*\}' if ($_ -match $hashtable) { $modules = ConvertTo-Hashtable -InputString $_ Write-Verbose "[$moduleName] - [$relativePath] - [REQUIRED -Modules] - [$_] - Hashtable" $modules.Keys | ForEach-Object { Write-Verbose "$($modules[$_])]" } $capturedModules += $modules } else { Write-Verbose "[$moduleName] - [$relativePath] - [REQUIRED -Modules] - [$_] - String" $capturedModules += $_ } } } # PowerShellVersion -> REQUIRES -Version <N>[.<n>], $null if not provided '^#Requires -Version (.+)$' { Write-Verbose "[$moduleName] - [$relativePath] - [REQUIRED -Version] - [$($matches[1])]" # Add captured module name to array $capturedVersions += $matches[1] } #CompatiblePSEditions -> REQUIRES -PSEdition <PSEdition-Name>, $null if not provided '^#Requires -PSEdition (.+)$' { Write-Verbose "[$moduleName] - [$relativePath] - [REQUIRED -PSEdition] - [$($matches[1])]" # Add captured module name to array $capturedPSEdition += $matches[1] } } } } Write-Verbose "[$moduleName] - [RequiredModules]" $capturedModules = $capturedModules $manifest.RequiredModules = $capturedModules $manifest.RequiredModules | ForEach-Object { Write-Verbose "[$moduleName] - [RequiredModules] - [$_]" } Write-Verbose "[$moduleName] - [RequiredModulesUnique]" $manifest.RequiredModules = $manifest.RequiredModules | Sort-Object -Unique $manifest.RequiredModules | ForEach-Object { Write-Verbose "[$moduleName] - [RequiredModulesUnique] - [$_]" } Write-Verbose "[$moduleName] - [PowerShellVersion]" $capturedVersions = $capturedVersions | Sort-Object -Unique -Descending $manifest.PowerShellVersion = $capturedVersions.count -eq 0 ? [version]'7.0' : [version]($capturedVersions | Select-Object -First 1) Write-Verbose "[$moduleName] - [PowerShellVersion] - [$($manifest.PowerShellVersion)]" Write-Verbose "[$moduleName] - [CompatiblePSEditions]" $capturedPSEdition = $capturedPSEdition | Sort-Object -Unique if ($capturedPSEdition.count -eq 2) { throw 'The module is requires both Desktop and Core editions.' } $manifest.CompatiblePSEditions = $capturedPSEdition.count -eq 0 ? @('Core', 'Desktop') : @($capturedPSEdition) $manifest.CompatiblePSEditions | ForEach-Object { Write-Verbose "[$moduleName] - [CompatiblePSEditions] - [$_]" } Write-Verbose "[$moduleName] - [PrivateData]" $privateData = $manifest.Keys -contains 'PrivateData' ? $null -ne $manifest.PrivateData ? $manifest.PrivateData : @{} : @{} if ($manifest.Keys -contains 'PrivateData') { $manifest.Remove('PrivateData') } Write-Verbose "[$moduleName] - [HelpInfoURI]" $manifest.HelpInfoURI = $privateData.Keys -contains 'HelpInfoURI' ? $null -ne $privateData.HelpInfoURI ? $privateData.HelpInfoURI : '' : '' Write-Verbose "[$moduleName] - [HelpInfoURI] - [$($manifest.HelpInfoURI)]" if ([string]::IsNullOrEmpty($manifest.HelpInfoURI)) { $manifest.Remove('HelpInfoURI') } Write-Verbose "[$moduleName] - [DefaultCommandPrefix]" $manifest.DefaultCommandPrefix = $privateData.Keys -contains 'DefaultCommandPrefix' ? $null -ne $privateData.DefaultCommandPrefix ? $privateData.DefaultCommandPrefix : '' : '' Write-Verbose "[$moduleName] - [DefaultCommandPrefix] - [$($manifest.DefaultCommandPrefix)]" $PSData = $privateData.Keys -contains 'PSData' ? $null -ne $privateData.PSData ? $privateData.PSData : @{} : @{} Write-Verbose "[$moduleName] - [Tags]" $manifest.Tags = $PSData.Keys -contains 'Tags' ? $null -ne $PSData.Tags ? $PSData.Tags : @() : @() # Add tags for compatability mode. https://docs.microsoft.com/en-us/powershell/scripting/developer/module/how-to-write-a-powershell-module-manifest?view=powershell-7.1#compatibility-tags if ($manifest.CompatiblePSEditions -contains 'Desktop') { if ($manifest.Tags -notcontains 'PSEdition_Desktop') { $manifest.Tags += 'PSEdition_Desktop' } } if ($manifest.CompatiblePSEditions -contains 'Core') { if ($manifest.Tags -notcontains 'PSEdition_Core') { $manifest.Tags += 'PSEdition_Core' } } $manifest.Tags | ForEach-Object { Write-Verbose "[$moduleName] - [Tags] - [$_]" } if ($PSData.Tags -contains 'PSEdition_Core' -and $manifest.PowerShellVersion -lt '6.0') { throw "[$moduleName] - [Tags] - Cannot be PSEdition = 'Core' and PowerShellVersion < 6.0" } Write-Verbose "[$moduleName] - [LicenseUri]" $manifest.LicenseUri = $PSData.Keys -contains 'LicenseUri' ? $null -ne $PSData.LicenseUri ? $PSData.LicenseUri : '' : '' Write-Verbose "[$moduleName] - [LicenseUri] - [$($manifest.LicenseUri)]" if ([string]::IsNullOrEmpty($manifest.LicenseUri)) { $manifest.Remove('LicenseUri') } Write-Verbose "[$moduleName] - [ProjectUri]" $manifest.ProjectUri = $PSData.Keys -contains 'ProjectUri' ? $null -ne $PSData.ProjectUri ? $PSData.ProjectUri : '' : '' Write-Verbose "[$moduleName] - [ProjectUri] - [$($manifest.ProjectUri)]" if ([string]::IsNullOrEmpty($manifest.ProjectUri)) { $manifest.Remove('ProjectUri') } Write-Verbose "[$moduleName] - [IconUri]" $manifest.IconUri = $PSData.Keys -contains 'IconUri' ? $null -ne $PSData.IconUri ? $PSData.IconUri : '' : '' Write-Verbose "[$moduleName] - [IconUri] - [$($manifest.IconUri)]" if ([string]::IsNullOrEmpty($manifest.IconUri)) { $manifest.Remove('IconUri') } Write-Verbose "[$moduleName] - [ReleaseNotes]" $manifest.ReleaseNotes = $PSData.Keys -contains 'ReleaseNotes' ? $null -ne $PSData.ReleaseNotes ? $PSData.ReleaseNotes : '' : '' Write-Verbose "[$moduleName] - [ReleaseNotes] - [$($manifest.ReleaseNotes)]" if ([string]::IsNullOrEmpty($manifest.ReleaseNotes)) { $manifest.Remove('ReleaseNotes') } Write-Verbose "[$moduleName] - [PreRelease]" $manifest.PreRelease = $PSData.Keys -contains 'PreRelease' ? $null -ne $PSData.PreRelease ? $PSData.PreRelease : '' : '' Write-Verbose "[$moduleName] - [PreRelease] - [$($manifest.PreRelease)]" if ([string]::IsNullOrEmpty($manifest.PreRelease)) { $manifest.Remove('PreRelease') } Write-Verbose "[$moduleName] - [RequireLicenseAcceptance]" $manifest.RequireLicenseAcceptance = $PSData.Keys -contains 'RequireLicenseAcceptance' ? $null -ne $PSData.RequireLicenseAcceptance ? $PSData.RequireLicenseAcceptance : $false : $false Write-Verbose "[$moduleName] - [RequireLicenseAcceptance] - [$($manifest.RequireLicenseAcceptance)]" if ($manifest.RequireLicenseAcceptance -eq $false) { $manifest.Remove('RequireLicenseAcceptance') } Write-Verbose "[$moduleName] - [ExternalModuleDependencies]" $manifest.ExternalModuleDependencies = $PSData.Keys -contains 'ExternalModuleDependencies' ? $null -ne $PSData.ExternalModuleDependencies ? $PSData.ExternalModuleDependencies : @() : @() if (($manifest.ExternalModuleDependencies).count -eq 0) { $manifest.Remove('ExternalModuleDependencies') } else { $manifest.ExternalModuleDependencies | ForEach-Object { Write-Verbose "[$moduleName] - [ExternalModuleDependencies] - [$_]" } } <# PSEdition_Desktop: Packages that are compatible with Windows PowerShell PSEdition_Core: Packages that are compatible with PowerShell 6 and higher Windows: Packages that are compatible with the Windows Operating System Linux: Packages that are compatible with Linux Operating Systems MacOS: Packages that are compatible with the Mac Operating System https://learn.microsoft.com/en-us/powershell/gallery/concepts/package-manifest-affecting-ui?view=powershellget-2.x#tag-details #> Write-Verbose 'Creating new manifest file in outputs folder' $outputManifestPath = (Join-Path -Path $OutputFolderPath $moduleName $manifestFileName) Write-Verbose "OutputManifestPath - [$outputManifestPath]" New-ModuleManifest -Path $outputManifestPath @manifest Write-Verbose 'Invoke-Formatter on manifest file' $manifestContent = Get-Content -Path $outputManifestPath -Raw Invoke-Formatter -ScriptDefinition $manifestContent -Verbose | Out-File -FilePath $outputManifestPath -Encoding utf8 -Force Write-Output "::group::[$moduleName] - Build manifest file - Result" Show-FileContent -Path $outputManifestPath Write-Output '::endgroup::' } Write-Verbose "[$scriptName] - [/private/Build/Build-PSModuleManifest.ps1] - Done" #endregion - From /private/Build/Build-PSModuleManifest.ps1 #region - From /private/Build/Build-PSModuleRootModule.ps1 Write-Verbose "[$scriptName] - [/private/Build/Build-PSModuleRootModule.ps1] - Importing" function Build-PSModuleRootModule { [CmdletBinding()] param( # Path to the folder where the module source code is located. [Parameter(Mandatory)] [string] $SourceFolderPath, # Path to the folder where the built modules are outputted. [Parameter(Mandatory)] [string] $OutputFolderPath ) $moduleName = Split-Path -Path $SourceFolderPath -Leaf Write-Output "::group::[$moduleName] - Build root module" # RE-create the moduleName.psm1 file # concat all the files, and add Export-ModuleMembers at the end with modules. $moduleOutputfolder = Join-Path -Path $OutputFolderPath -ChildPath $moduleName $rootModuleFile = New-Item -Path $moduleOutputfolder -Name "$moduleName.psm1" -Force # Add content to the root module file in the following order: # 1. Load data files from Data folder # 2. Init # 3. Private # 4. Public # 5 *.ps1 on module root # 6. Export-ModuleMember Add-Content -Path $rootModuleFile.FullName -Value @' [Cmdletbinding()] param() $scriptName = $MyInvocation.MyCommand.Name Write-Verbose "[$scriptName] Importing subcomponents" #region - Data import Write-Verbose "[$scriptName] - [data] - Processing folder" $dataFolder = (Join-Path $PSScriptRoot 'data') Write-Verbose "[$scriptName] - [data] - [$dataFolder]" Get-ChildItem -Path "$dataFolder" -Recurse -Force -Include '*.psd1' -ErrorAction SilentlyContinue | ForEach-Object { Write-Verbose "[$scriptName] - [data] - [$($_.Name)] - Importing" New-Variable -Name $_.BaseName -Value (Import-PowerShellDataFile -Path $_.FullName) -Force Write-Verbose "[$scriptName] - [data] - [$($_.Name)] - Done" } Write-Verbose "[$scriptName] - [data] - Done" #endregion - Data import '@ $folderProcessingOrder = @( 'init', 'private', 'public' ) $subFolders = Get-ChildItem -Path $SourceFolderPath -Directory -Force | Where-Object -Property Name -In $folderProcessingOrder foreach ($subFolder in $subFolders) { Add-ContentFromItem -Path $subFolder.FullName -RootModuleFilePath $rootModuleFile.FullName -RootPath $SourceFolderPath } $files = $SourceFolderPath | Get-ChildItem -File -Force -Filter '*.ps1' foreach ($file in $files) { $relativePath = $file.FullName.Replace($SourceFolderPath, '').TrimStart($pathSeparator) Add-Content -Path $rootModuleFile.FullName -Value @" #region - From $relativePath Write-Verbose "[`$scriptName] - [$relativePath] - Importing" "@ Get-Content -Path $file.FullName | Add-Content -Path $rootModuleFile.FullName Add-Content -Path $rootModuleFile.FullName -Value @" Write-Verbose "[`$scriptName] - [$relativePath] - Done" #endregion - From $relativePath "@ $file | Remove-Item -Force } $functionsToExport = Get-PSModuleFunctionsToExport -SourceFolderPath $SourceFolderPath $functionsToExport = $($functionsToExport -join "','") $cmdletsToExport = Get-PSModuleCmdletsToExport -SourceFolderPath $SourceFolderPath $cmdletsToExport = $($cmdletsToExport -join "','") $variablesToExport = Get-PSModuleVariablesToExport -SourceFolderPath $SourceFolderPath $variablesToExport = $($variablesToExport -join "','") $aliasesToExport = Get-PSModuleAliasesToExport -SourceFolderPath $SourceFolderPath $aliasesToExport = $($aliasesToExport -join "','") Add-Content -Path $rootModuleFile -Value "Export-ModuleMember -Function '$functionsToExport' -Cmdlet '$cmdletsToExport' -Variable '$variablesToExport' -Alias '$aliasesToExport'" Write-Output '::endgroup::' Write-Output "::group::[$moduleName] - Build root module - Result" Show-FileContent -Path $rootModuleFile Write-Output '::endgroup::' } Write-Verbose "[$scriptName] - [/private/Build/Build-PSModuleRootModule.ps1] - Done" #endregion - From /private/Build/Build-PSModuleRootModule.ps1 #region - From /private/Build/Get-PSModuleAliasesToExport.ps1 Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleAliasesToExport.ps1] - Importing" function Get-PSModuleAliasesToExport { [CmdletBinding()] param( # Path to the folder where the module source code is located. [Parameter(Mandatory)] [string] $SourceFolderPath ) $moduleName = Split-Path -Path $SourceFolderPath -Leaf $manifestPropertyName = 'AliasesToExport' $manifest = Get-PSModuleManifest -SourceFolderPath $SourceFolderPath -Verbose:$false Write-Verbose "[$moduleName] - [$manifestPropertyName]" $aliasesToExport = ($manifest.AliasesToExport).count -eq 0 ? '' : @($manifest.AliasesToExport) $aliasesToExport | ForEach-Object { Write-Verbose "[$moduleName] - [$manifestPropertyName] - [$_]" } $aliasesToExport } Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleAliasesToExport.ps1] - Done" #endregion - From /private/Build/Get-PSModuleAliasesToExport.ps1 #region - From /private/Build/Get-PSModuleCmdletsToExport.ps1 Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleCmdletsToExport.ps1] - Importing" function Get-PSModuleCmdletsToExport { [CmdletBinding()] param( # Path to the folder where the module source code is located. [Parameter(Mandatory)] [string] $SourceFolderPath ) $moduleName = Split-Path -Path $SourceFolderPath -Leaf $manifestPropertyName = 'CmdletsToExport' $manifest = Get-PSModuleManifest -SourceFolderPath $SourceFolderPath -Verbose:$false Write-Verbose "[$moduleName] - [$manifestPropertyName]" $cmdletsToExport = ($manifest.CmdletsToExport).count -eq 0 ? '' : @($manifest.CmdletsToExport) $cmdletsToExport | ForEach-Object { Write-Verbose "[$moduleName] - [$manifestPropertyName] - [$_]" } $cmdletsToExport } Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleCmdletsToExport.ps1] - Done" #endregion - From /private/Build/Get-PSModuleCmdletsToExport.ps1 #region - From /private/Build/Get-PSModuleFolders.ps1 Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleFolders.ps1] - Importing" function Get-PSModuleFolders { <# .SYNOPSIS Get all folders where the content of the folder is a module file or manifest file. .DESCRIPTION Get all folders where the content of the folder is a module file or manifest file. Search is recursive. .EXAMPLE Get-PSModuleFolders -Path 'src' Get all folders where the content of the folder is a module file or manifest file. #> [CmdletBinding()] param( # Path to the folder where the modules are located. [Parameter()] [ValidateNotNullOrEmpty()] [string] $Path = 'src' ) $moduleFolders = Get-ChildItem -Path $Path -Directory -Recurse -ErrorAction SilentlyContinue | Where-Object { Get-ChildItem -Path $_.FullName -File -ErrorAction SilentlyContinue | Where-Object { $_.Name -match '.*\.psm1|.*\.psd1' } } return $moduleFolders } Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleFolders.ps1] - Done" #endregion - From /private/Build/Get-PSModuleFolders.ps1 #region - From /private/Build/Get-PSModuleFunctionsToExport.ps1 Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleFunctionsToExport.ps1] - Importing" function Get-PSModuleFunctionsToExport { [CmdletBinding()] param( # Path to the folder where the module source code is located. [Parameter(Mandatory)] [string] $SourceFolderPath ) $moduleName = Split-Path -Path $SourceFolderPath -Leaf $manifestPropertyName = 'FunctionsToExport' Write-Verbose "[$moduleName] - [$manifestPropertyName]" Write-Verbose "[$moduleName] - [$manifestPropertyName] - Checking path for functions and filters" $publicFolderPath = Join-Path $SourceFolderPath 'public' Write-Verbose "[$moduleName] - [$manifestPropertyName] - [$publicFolderPath]" $functionsToExport = Get-ChildItem -Path $publicFolderPath -Recurse -File -ErrorAction SilentlyContinue -Include '*.ps1' | ForEach-Object { $fileContent = Get-Content -Path $_.FullName -Raw $containsFunction = ($fileContent -match 'function ') -or ($fileContent -match 'filter ') Write-Verbose "[$moduleName] - [$manifestPropertyName] - [$($_.BaseName)] - [$containsFunction]" $containsFunction ? $_.BaseName : $null } $functionsToExport = $functionsToExport.count -eq 0 ? @() : @($functionsToExport) $functionsToExport } Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleFunctionsToExport.ps1] - Done" #endregion - From /private/Build/Get-PSModuleFunctionsToExport.ps1 #region - From /private/Build/Get-PSModuleManifest.ps1 Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleManifest.ps1] - Importing" function Get-PSModuleManifest { <# .SYNOPSIS Get the module manifest. .DESCRIPTION Get the module manifest as a hashtable. .EXAMPLE Get-PSModuleManifest -SourceFolderPath 'src/PSModule.FX' #> [OutputType([string], [System.IO.FileInfo], [System.Collections.Hashtable])] [CmdletBinding()] param( # Path to the folder where the module source code is located. [Parameter(Mandatory)] [string] $SourceFolderPath, # The format of the output [Parameter()] [ValidateSet('FileName', 'FilePath', 'FileInfo', 'Content', 'Hashtable')] [string] $As = 'Hashtable' ) $moduleName = Split-Path -Path $SourceFolderPath -Leaf $manifestPropertyName = 'ManifestFile' Write-Verbose "[$moduleName] - [$manifestPropertyName]" $manifestFileName = "$moduleName.psd1" Write-Verbose "[$moduleName] - [$manifestPropertyName] - [$manifestFileName]" Write-Verbose "[$moduleName] - [$manifestPropertyName] - Checking path for manifest file" $manifestFilePath = Join-Path -Path $SourceFolderPath $manifestFileName if (-not (Test-Path -Path $manifestFilePath)) { Write-Warning "[$moduleName] - [$manifestPropertyName] - 🟥 No manifest file found" return $null } Write-Verbose "[$moduleName] - [$manifestPropertyName] - 🟩 Found manifest file" switch ($As) { 'FileName' { return $manifestFileName } 'FilePath' { return $manifestFilePath } 'FileInfo' { return Get-Item -Path $manifestFilePath } 'Content' { return Get-Content -Path $manifestFilePath } 'Hashtable' { return Import-PowerShellDataFile -Path $manifestFilePath } } } Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleManifest.ps1] - Done" #endregion - From /private/Build/Get-PSModuleManifest.ps1 #region - From /private/Build/Get-PSModuleRootModule.ps1 Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleRootModule.ps1] - Importing" function Get-PSModuleRootModule { [CmdletBinding()] param( # Path to the folder where the module source code is located. [Parameter(Mandatory)] [string] $SourceFolderPath ) $moduleName = Split-Path -Path $SourceFolderPath -Leaf $manifestPropertyName = 'RootModule' Write-Verbose "[$moduleName] - [$manifestPropertyName] - Find root module" $manifest = Get-PSModuleManifest -SourceFolderPath $SourceFolderPath -Verbose:$false $rootModule = $(Get-ChildItem -Path $SourceFolderPath -File | Where-Object { $_.BaseName -like $_.Directory.BaseName -and ($_.Extension -in '.psm1', '.ps1', '.dll', '.cdxml', '.xaml') } | Select-Object -First 1 -ExpandProperty Name ) if (-not $rootModule) { Write-Verbose "[$moduleName] - [$manifestPropertyName] - No RootModule found" } Write-Verbose "[$moduleName] - [$manifestPropertyName] - [$RootModule]" $moduleType = switch -Regex ($RootModule) { '\.(ps1|psm1)$' { 'Script' } '\.dll$' { 'Binary' } '\.cdxml$' { 'CIM' } '\.xaml$' { 'Workflow' } default { 'Manifest' } } Write-Verbose "[$moduleName] - [$manifestPropertyName] - [$moduleType]" $supportedModuleTypes = @('Script', 'Manifest') if ($moduleType -notin $supportedModuleTypes) { Write-Warning "[$moduleName] - [$manifestPropertyName] - [$moduleType] - Module type not supported" } $rootModule = [string]::IsNullOrEmpty($manifest.RootModule) ? $rootModule : @($manifest.RootModule) $rootModule } Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleRootModule.ps1] - Done" #endregion - From /private/Build/Get-PSModuleRootModule.ps1 #region - From /private/Build/Get-PSModuleVariablesToExport.ps1 Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleVariablesToExport.ps1] - Importing" function Get-PSModuleVariablesToExport { [CmdletBinding()] param( # Path to the folder where the module source code is located. [Parameter(Mandatory)] [string] $SourceFolderPath ) $moduleName = Split-Path -Path $SourceFolderPath -Leaf $manifestPropertyName = 'VariablesToExport' $manifest = Get-PSModuleManifest -SourceFolderPath $SourceFolderPath -Verbose:$false Write-Verbose "[$moduleName] - [$manifestPropertyName]" $variablesToExport = ($manifest.VariablesToExport).count -eq 0 ? @() : @($manifest.VariablesToExport) $variablesToExport | ForEach-Object { Write-Verbose "[$moduleName] - [$manifestPropertyName] - [$_]" } $variablesToExport } Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleVariablesToExport.ps1] - Done" #endregion - From /private/Build/Get-PSModuleVariablesToExport.ps1 #region - From /private/Build/Resolve-PSModuleDependencies.ps1 Write-Verbose "[$scriptName] - [/private/Build/Resolve-PSModuleDependencies.ps1] - Importing" function Resolve-PSModuleDependencies { <# .SYNOPSIS Resolve dependencies for a module based on the manifest file. .DESCRIPTION Resolve dependencies for a module based on the manifest file, following PSModuleInfo structure .EXAMPLE Resolve-PSModuleDependencies -Path 'C:\MyModule\MyModule.psd1' Installs all modules defined in the manifest file, following PSModuleInfo structure. .NOTES Should later be adapted to support both pre-reqs, and dependencies. Should later be adapted to take 4 parameters sets: specific version ("requiredVersion" | "GUID"), latest version ModuleVersion, and latest version within a range MinimumVersion - MaximumVersion. #> [CmdletBinding()] param( # The path to the manifest file. [Parameter(Mandatory)] [string] $ManifestFilePath ) Write-Verbose "[$moduleName] - Resolving dependencies" $moduleName = $ManifestFilePath | Get-Item | Select-Object -ExpandProperty BaseName $manifest = Import-PowerShellDataFile -Path $ManifestFilePath Write-Verbose "[$moduleName] - Reading [$ManifestFilePath]" Write-Verbose "[$moduleName] - Found [$($manifest.RequiredModules.Count)] modules to install" foreach ($requiredModule in $manifest.RequiredModules) { $installParams = @{} if ($requiredModule -is [string]) { $installParams.Name = $requiredModule } else { $installParams.Name = $requiredModule.ModuleName $installParams.MinimumVersion = $requiredModule.ModuleVersion $installParams.RequiredVersion = $requiredModule.RequiredVersion $installParams.MaximumVersion = $requiredModule.MaximumVersion } $installParams.Verbose = $false $installParams.Force = $true Write-Verbose "[$moduleName] - [$($installParams.Name)] - Installing module" $VerbosePreferenceOriginal = $VerbosePreference $VerbosePreference = 'SilentlyContinue' Install-Module @installParams $VerbosePreference = $VerbosePreferenceOriginal Write-Verbose "[$moduleName] - [$($installParams.Name)] - Importing module" $VerbosePreferenceOriginal = $VerbosePreference $VerbosePreference = 'SilentlyContinue' Import-Module @installParams $VerbosePreference = $VerbosePreferenceOriginal Write-Verbose "[$moduleName] - [$($installParams.Name)] - Done" } Write-Verbose "[$moduleName] - Resolving dependencies - Done" } Write-Verbose "[$scriptName] - [/private/Build/Resolve-PSModuleDependencies.ps1] - Done" #endregion - From /private/Build/Resolve-PSModuleDependencies.ps1 Write-Verbose "[$scriptName] - [/private/Build] - Done" #endregion - From /private/Build #region - From /private/Publish Write-Verbose "[$scriptName] - [/private/Publish] - Processing folder" #region - From /private/Publish/Get-ModulesToPublish.ps1 Write-Verbose "[$scriptName] - [/private/Publish/Get-ModulesToPublish.ps1] - Importing" #region Helper functions function Get-ModifiedFileList { <# .SYNOPSIS Get modified files between previous and current commit depending on if you are running on main/master or a custom branch. .EXAMPLE Get-ModifiedFileList Directory: C:\Repo\Azure\ResourceModules\utilities\pipelines\resourcePublish Mode LastWriteTime Length Name ---- ------------- ------ ---- la--- 08.12.2021 15:50 7133 Script.ps1 Get modified files between previous and current commit depending on if you are running on main/master or a custom branch. #> $CurrentBranch = Get-GitBranchName if (($CurrentBranch -eq 'main') -or ($CurrentBranch -eq 'master')) { Write-Verbose 'Gathering modified files from the pull request' -Verbose $Diff = git diff --name-only --diff-filter=AM HEAD^ HEAD } else { Write-Verbose 'Gathering modified files between current branch and main' -Verbose $Diff = git diff --name-only --diff-filter=AM origin/main if ($Diff.count -eq 0) { Write-Verbose 'Gathering modified files between current branch and master' -Verbose $Diff = git diff --name-only --diff-filter=AM origin/master } } $ModifiedFiles = $Diff | Get-Item -Force return $ModifiedFiles } function Get-GitBranchName { <# .SYNOPSIS Get the name of the current checked out branch. .DESCRIPTION Get the name of the current checked out branch. If git cannot find it, best effort based on environment variables is used. .EXAMPLE Get-CurrentBranch feature-branch-1 Get the name of the current checked out branch. #> [CmdletBinding()] param () # Get branch name from Git $BranchName = git branch --show-current # If git could not get name, try GitHub variable if ([string]::IsNullOrEmpty($BranchName) -and (Test-Path env:GITHUB_REF_NAME)) { $BranchName = $env:GITHUB_REF_NAME } # If git could not get name, try Azure DevOps variable if ([string]::IsNullOrEmpty($BranchName) -and (Test-Path env:BUILD_SOURCEBRANCHNAME)) { $BranchName = $env:BUILD_SOURCEBRANCHNAME } return $BranchName } function Find-TemplateFile { <# .SYNOPSIS Find the closest main.bicep/json file to the current directory/file. .DESCRIPTION This function will search the current directory and all parent directories for a main.bicep/json file. .PARAMETER Path Mandatory. Path to the folder/file that should be searched .EXAMPLE Find-TemplateFile -Path "C:\Repos\Azure\ResourceModules\modules\storage\storage-account\table-service\table\.bicep\nested_roleAssignments.bicep" Directory: C:\Repos\Azure\ResourceModules\modules\storage\storage-account\table-service\table Mode LastWriteTime Length Name ---- ------------- ------ ---- la--- 05.12.2021 22:45 1230 main.bicep Gets the closest main.bicep/json file to the current directory. #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string] $Path ) $FolderPath = Split-Path $Path -Parent $FolderName = Split-Path $Path -Leaf if ($FolderName -eq 'modules') { return $null } #Prioritizing the bicep file $TemplateFilePath = Join-Path $FolderPath 'main.bicep' if (-not (Test-Path $TemplateFilePath)) { $TemplateFilePath = Join-Path $FolderPath 'main.json' } if (-not (Test-Path $TemplateFilePath)) { return Find-TemplateFile -Path $FolderPath } return $TemplateFilePath | Get-Item } function Get-TemplateFileToPublish { <# .SYNOPSIS Find the closest main.bicep/json file to the changed files in the module folder structure. .DESCRIPTION Find the closest main.bicep/json file to the changed files in the module folder structure. .PARAMETER ModuleFolderPath Mandatory. Path to the main/parent module folder. .EXAMPLE Get-TemplateFileToPublish -ModuleFolderPath "C:\Repos\Azure\ResourceModules\modules\storage\storage-account\" C:\Repos\Azure\ResourceModules\modules\storage\storage-account\table-service\table\main.bicep Gets the closest main.bicep/json file to the changed files in the module folder structure. Assuming there is a changed file in 'storage\storage-account\table-service\table' the function would return the main.bicep file in the same folder. #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string] $ModuleFolderPath ) $ModuleFolderRelPath = $ModuleFolderPath.Split('/modules/')[-1] $ModifiedFiles = Get-ModifiedFileList -Verbose Write-Verbose "Looking for modified files under: [$ModuleFolderRelPath]" -Verbose $ModifiedModuleFiles = $ModifiedFiles | Where-Object { $_.FullName -like "*$ModuleFolderPath*" } $TemplateFilesToPublish = $ModifiedModuleFiles | ForEach-Object { Find-TemplateFile -Path $_.FullName -Verbose } | Sort-Object -Property FullName -Unique -Descending if ($TemplateFilesToPublish.Count -eq 0) { Write-Verbose 'No template file found in the modified module.' -Verbose } Write-Verbose ('Modified modules found: [{0}]' -f $TemplateFilesToPublish.count) -Verbose $TemplateFilesToPublish | ForEach-Object { $RelPath = ($_.FullName).Split('/modules/')[-1] $RelPath = $RelPath.Split('/main.')[0] Write-Verbose " - [$RelPath]" -Verbose } return $TemplateFilesToPublish } function Get-ParentModuleTemplateFile { <# .SYNOPSIS Gets the parent main.bicep/json file(s) to the changed files in the module folder structure. .DESCRIPTION Gets the parent main.bicep/json file(s) to the changed files in the module folder structure. .PARAMETER TemplateFilePath Mandatory. Path to a main.bicep/json file. .PARAMETER Recurse Optional. If true, the function will recurse up the folder structure to find the closest main.bicep/json file. .EXAMPLE Get-ParentModuleTemplateFile -TemplateFilePath 'C:\Repos\Azure\ResourceModules\modules\storage\storage-account\table-service\table\main.bicep' -Recurse Directory: C:\Repos\Azure\ResourceModules\modules\storage\storage-account\table-service Mode LastWriteTime Length Name ---- ------------- ------ ---- la--- 05.12.2021 22:45 1427 main.bicep Directory: C:\Repos\Azure\ResourceModules\modules\storage\storage-account Mode LastWriteTime Length Name ---- ------------- ------ ---- la--- 02.12.2021 13:19 10768 main.bicep Gets the parent main.bicep/json file(s) to the changed files in the module folder structure. #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string] $TemplateFilePath, [Parameter(Mandatory = $false)] [switch] $Recurse ) $ModuleFolderPath = Split-Path $TemplateFilePath -Parent $ParentFolderPath = Split-Path $ModuleFolderPath -Parent #Prioritizing the bicep file $ParentTemplateFilePath = Join-Path $ParentFolderPath 'main.bicep' if (-not (Test-Path $TemplateFilePath)) { $ParentTemplateFilePath = Join-Path $ParentFolderPath 'main.json' } if (-not (Test-Path -Path $ParentTemplateFilePath)) { return } $ParentTemplateFilesToPublish = [System.Collections.ArrayList]@() $ParentTemplateFilesToPublish += $ParentTemplateFilePath | Get-Item if ($Recurse) { $ParentTemplateFilesToPublish += Get-ParentModuleTemplateFile $ParentTemplateFilePath -Recurse } return $ParentTemplateFilesToPublish } function Get-GitDistance { <# .SYNOPSIS Get the number of commits following the specified commit. .PARAMETER Commit Optional. A specified git reference to get commit counts on. .EXAMPLE Get-GitDistance -Commit origin/main. 620 There are currently 620 commits on origin/main. When run as a push on main, this will be the current commit number on the main branch. #> [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string] $Commit = 'HEAD' ) return [int](git rev-list --count $Commit) + 1 } function Get-ModuleVersionFromFile { <# .SYNOPSIS Gets the version from the version file from the corresponding main.bicep/json file. .DESCRIPTION Gets the version file from the corresponding main.bicep/json file. The file needs to be in the same folder as the template file itself. .PARAMETER TemplateFilePath Mandatory. Path to a main.bicep/json file. .EXAMPLE Get-ModuleVersionFromFile -TemplateFilePath 'C:\Repos\Azure\ResourceModules\modules\storage\storage-account\table-service\table\main.bicep' 0.3 Get the version file from the specified main.bicep file. #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string] $TemplateFilePath ) $ModuleFolder = Split-Path -Path $TemplateFilePath -Parent $VersionFilePath = Join-Path $ModuleFolder 'version.json' if (-not (Test-Path -Path $VersionFilePath)) { throw "No version file found at: [$VersionFilePath]" } $VersionFileContent = Get-Content $VersionFilePath | ConvertFrom-Json return $VersionFileContent.version } function Get-NewModuleVersion { <# .SYNOPSIS Generates a new version for the specified module. .DESCRIPTION Generates a new version for the specified module, based on version.json file and git commit count. Major and minor version numbers are gathered from the version.json file. Patch version number is calculated based on the git commit count on the branch. .PARAMETER TemplateFilePath Mandatory. Path to a main.bicep/json file. .EXAMPLE Get-NewModuleVersion -TemplateFilePath 'C:\Repos\Azure\ResourceModules\modules\storage\storage-account\table-service\table\main.bicep' 0.3.630 Generates a new version for the table module. #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string] $TemplateFilePath ) $Version = Get-ModuleVersionFromFile -TemplateFilePath $TemplateFilePath $Patch = Get-GitDistance $NewVersion = "$Version.$Patch" $BranchName = Get-GitBranchName -Verbose if ($BranchName -ne 'main' -and $BranchName -ne 'master') { $NewVersion = "$NewVersion-prerelease".ToLower() } return $NewVersion } #endregion <# .SYNOPSIS Generates a hashtable with template file paths to publish with a new version. .DESCRIPTION Generates a hashtable with template file paths to publish with a new version. .PARAMETER TemplateFilePath Mandatory. Path to a main.bicep/json file. .PARAMETER PublishLatest Optional. Publish an absolute latest version. Note: This version may include breaking changes and is not recommended for production environments .EXAMPLE Get-ModulesToPublish -TemplateFilePath 'C:\Repos\Azure\ResourceModules\modules\storage\storage-account\main.bicep' Name Value ---- ----- TemplateFilePath C:\Repos\Azure\ResourceModules\modules\storage\storage-account\file-service\share\main.bicep Version 0.3.848-prerelease TemplateFilePath C:\Repos\Azure\ResourceModules\modules\storage\storage-account\file-service\main.bicep Version 0.3.848-prerelease TemplateFilePath C:\Repos\Azure\ResourceModules\modules\storage\storage-account\main.bicep Version 0.3.848-prerelease Generates a hashtable with template file paths to publish and their new versions. #># function Get-ModulesToPublish { [CmdletBinding()] param ( [Parameter(Mandatory)] [string] $TemplateFilePath, [Parameter(Mandatory = $false)] [bool] $PublishLatest = $true ) $ModuleFolderPath = Split-Path $TemplateFilePath -Parent $TemplateFilesToPublish = Get-TemplateFileToPublish -ModuleFolderPath $ModuleFolderPath | Sort-Object FullName -Descending $modulesToPublish = [System.Collections.ArrayList]@() foreach ($TemplateFileToPublish in $TemplateFilesToPublish) { $ModuleVersion = Get-NewModuleVersion -TemplateFilePath $TemplateFileToPublish.FullName -Verbose $modulesToPublish += @{ Version = $ModuleVersion TemplateFilePath = $TemplateFileToPublish.FullName } if ($ModuleVersion -notmatch 'prerelease') { # Latest Major,Minor $modulesToPublish += @{ Version = ($ModuleVersion.Split('.')[0..1] -join '.') TemplateFilePath = $TemplateFileToPublish.FullName } # Latest Major $modulesToPublish += @{ Version = ($ModuleVersion.Split('.')[0]) TemplateFilePath = $TemplateFileToPublish.FullName } if ($PublishLatest) { # Absolute latest $modulesToPublish += @{ Version = 'latest' TemplateFilePath = $TemplateFileToPublish.FullName } } } $ParentTemplateFilesToPublish = Get-ParentModuleTemplateFile -TemplateFilePath $TemplateFileToPublish.FullName -Recurse foreach ($ParentTemplateFileToPublish in $ParentTemplateFilesToPublish) { $ParentModuleVersion = Get-NewModuleVersion -TemplateFilePath $ParentTemplateFileToPublish.FullName $modulesToPublish += @{ Version = $ParentModuleVersion TemplateFilePath = $ParentTemplateFileToPublish.FullName } if ($ModuleVersion -notmatch 'prerelease') { # Latest Major,Minor $modulesToPublish += @{ Version = ($ParentModuleVersion.Split('.')[0..1] -join '.') TemplateFilePath = $ParentTemplateFileToPublish.FullName } # Latest Major $modulesToPublish += @{ Version = ($ParentModuleVersion.Split('.')[0]) TemplateFilePath = $ParentTemplateFileToPublish.FullName } if ($PublishLatest) { # Absolute latest $modulesToPublish += @{ Version = 'latest' TemplateFilePath = $ParentTemplateFileToPublish.FullName } } } } } $modulesToPublish = $modulesToPublish | Sort-Object TemplateFilePath, Version -Descending -Unique if ($modulesToPublish.count -gt 0) { Write-Verbose 'Publish the following modules:'-Verbose $modulesToPublish | ForEach-Object { $RelPath = ($_.TemplateFilePath).Split('/modules/')[-1] $RelPath = $RelPath.Split('/main.')[0] Write-Verbose (' - [{0}] [{1}] ' -f $RelPath, $_.Version) -Verbose } } else { Write-Verbose 'No modules with changes found to publish.'-Verbose } return $modulesToPublish } Write-Verbose "[$scriptName] - [/private/Publish/Get-ModulesToPublish.ps1] - Done" #endregion - From /private/Publish/Get-ModulesToPublish.ps1 Write-Verbose "[$scriptName] - [/private/Publish] - Done" #endregion - From /private/Publish #region - From /private/Test Write-Verbose "[$scriptName] - [/private/Test] - Processing folder" #region - From /private/Test/Invoke-PSCustomTests.ps1 Write-Verbose "[$scriptName] - [/private/Test/Invoke-PSCustomTests.ps1] - Importing" function Invoke-PSCustomTests { [CmdletBinding()] param( # Path to the folder where the built modules are outputted. [Parameter(Mandatory)] [string] $ModuleFolder, # Path to the folder where the built modules are outputted. [Parameter(Mandatory)] [string] $TestFolderPath ) $containerParams = @{ Path = $TestFolderPath Data = @{ Path = $ModuleFolder } } Write-Verbose 'ContainerParams:' Write-Verbose "$($containerParams | ConvertTo-Json -Depth 5)" $pesterParams = @{ Configuration = @{ Run = @{ Container = New-PesterContainer @containerParams PassThru = $false } TestResult = @{ TestSuiteName = 'PSSA' OutputPath = '.\outputs\CustomTest.Results.xml' OutputFormat = 'NUnitXml' Enabled = $true } Output = @{ Verbosity = 'Detailed' StackTraceVerbosity = 'None' } } ErrorAction = 'Stop' } Write-Verbose 'PesterParams:' Write-Verbose "$($pesterParams | ConvertTo-Json -Depth 5)" Invoke-Pester @pesterParams } Write-Verbose "[$scriptName] - [/private/Test/Invoke-PSCustomTests.ps1] - Done" #endregion - From /private/Test/Invoke-PSCustomTests.ps1 #region - From /private/Test/Invoke-PSScriptAnalyserTest.ps1 Write-Verbose "[$scriptName] - [/private/Test/Invoke-PSScriptAnalyserTest.ps1] - Importing" function Invoke-PSSATest { [CmdLetBinding()] param( # Path to the folder where the built modules are outputted. [Parameter(Mandatory)] [string] $ModuleFolder ) $modules = Get-Module -ListAvailable $PSSAModule = $modules | Where-Object Name -EQ PSScriptAnalyzer | Sort-Object Version -Descending | Select-Object -First 1 $pesterModule = $modules | Where-Object Name -EQ Pester | Sort-Object Version -Descending | Select-Object -First 1 Write-Verbose 'Testing with:' -Verbose Write-Verbose " PowerShell $($PSVersionTable.PSVersion.ToString())" -Verbose Write-Verbose " Pester $($pesterModule.version)" -Verbose Write-Verbose " PSScriptAnalyzer $($PSSAModule.version)" -Verbose $containerParams = @{ Path = (Join-Path -Path $PSScriptRoot -ChildPath 'tests' 'PSScriptAnalyser' 'PSScriptAnalyser.Tests.ps1') Data = @{ Path = $ModuleFolder } } Write-Verbose 'ContainerParams:' Write-Verbose "$($containerParams | ConvertTo-Json -Depth 5)" $pesterParams = @{ Configuration = @{ Run = @{ Container = New-PesterContainer @containerParams PassThru = $false } TestResult = @{ TestSuiteName = 'PSSA' OutputPath = '.\outputs\PSSA.Results.xml' OutputFormat = 'NUnitXml' Enabled = $true } Output = @{ Verbosity = 'Detailed' StackTraceVerbosity = 'None' } } ErrorAction = 'Stop' } Write-Verbose 'PesterParams:' Write-Verbose "$($pesterParams | ConvertTo-Json -Depth 5)" Invoke-Pester @pesterParams } Write-Verbose "[$scriptName] - [/private/Test/Invoke-PSScriptAnalyserTest.ps1] - Done" #endregion - From /private/Test/Invoke-PSScriptAnalyserTest.ps1 Write-Verbose "[$scriptName] - [/private/Test] - Done" #endregion - From /private/Test #region - From /private/Utilities Write-Verbose "[$scriptName] - [/private/Utilities] - Processing folder" #region - From /private/Utilities/Add-PSModulePath.ps1 Write-Verbose "[$scriptName] - [/private/Utilities/Add-PSModulePath.ps1] - Importing" function Add-PSModulePath { [CmdletBinding()] param( # Path to the folder where the module source code is located. [Parameter(Mandatory)] [string] $Path ) if ($IsWindows) { $PSModulePathSeparator = ';' } else { $PSModulePathSeparator = ':' } $env:PSModulePath += "$PSModulePathSeparator$Path" Write-Verbose "PSModulePath:" $env:PSModulePath.Split($PSModulePathSeparator) | ForEach-Object { Write-Verbose " - [$_]" } } Write-Verbose "[$scriptName] - [/private/Utilities/Add-PSModulePath.ps1] - Done" #endregion - From /private/Utilities/Add-PSModulePath.ps1 #region - From /private/Utilities/ConvertTo-Hashtable.ps1 Write-Verbose "[$scriptName] - [/private/Utilities/ConvertTo-Hashtable.ps1] - Importing" function ConvertTo-Hashtable { <# .SYNOPSIS Converts a string to a hashtable. .DESCRIPTION Converts a string to a hashtable. .EXAMPLE ConvertTo-Hashtable -InputString "@{Key1 = 'Value1'; Key2 = 'Value2'}" Key Value --- ----- Key1 Value1 Key2 Value2 Converts the string to a hashtable. #> param ( # The string to convert to a hashtable. [Parameter(Mandatory = $true)] [string]$InputString ) $outputHashtable = @{} # Match pairs of key = value $regexPattern = "\s*(\w+)\s*=\s*\'([^\']+)\'" $regMatches = [regex]::Matches($InputString, $regexPattern) foreach ($match in $regMatches) { $key = $match.Groups[1].value $value = $match.Groups[2].value $outputHashtable[$key] = $value } return $outputHashtable } $InputString = "@{ ModuleName = 'AzureRM.Netcore'; MaximumVersion = '0.12.0' }" Write-Verbose "[$scriptName] - [/private/Utilities/ConvertTo-Hashtable.ps1] - Done" #endregion - From /private/Utilities/ConvertTo-Hashtable.ps1 #region - From /private/Utilities/Show-FileContent.ps1 Write-Verbose "[$scriptName] - [/private/Utilities/Show-FileContent.ps1] - Importing" function Show-FileContent { <# .SYNOPSIS Prints the content of a file with line numbers in front of each line. .DESCRIPTION Prints the content of a file with line numbers in front of each line. .EXAMPLE $Path = 'C:\Repos\GitHub\PSModule\Framework\PSModule.FX\src\PSModule.FX\private\Utilities\Show-FileContent.ps1' Show-FileContent -Path $Path Shows the content of the file with line numbers in front of each line. #> [CmdletBinding()] param ( # The path to the file to show the content of. [Parameter(Mandatory)] [string]$Path ) $content = Get-Content -Path $Path $lineNumber = 1 # Foreach line print the line number in front of the line with [ ] around it. The linenumber should dynamically adjust to the number of digits with the length of the file. foreach ($line in $content) { $lineNumberFormatted = $lineNumber.ToString().PadLeft($content.Count.ToString().Length) '[{0}] {1}' -f $lineNumberFormatted, $line $lineNumber++ } } Write-Verbose "[$scriptName] - [/private/Utilities/Show-FileContent.ps1] - Done" #endregion - From /private/Utilities/Show-FileContent.ps1 Write-Verbose "[$scriptName] - [/private/Utilities] - Done" #endregion - From /private/Utilities #region - From /private/Invoke-PSModuleBuild.ps1 Write-Verbose "[$scriptName] - [/private/Invoke-PSModuleBuild.ps1] - Importing" function Invoke-PSModuleBuild { #DECISION: The manifest file = name of the folder. #DECISION: The basis of the module manifest comes from the defined manifest file. #DECISION: Values that are not defined in the module manifest file are generated from reading the module files. #DECISION: If no RootModule is defined in the manifest file, we assume a .psm1 file with the same name as the module is on root. #DECISION: Currently only Script and Manifest modules are supported. #DECISION: The output folder = .\outputs on the root of the repo. #DECISION: The module that is build is stored under the output folder in a folder with the same name as the module. #DECISION: A new module manifest file is created every time to get a new GUID, so that the specific version of the module can be imported. param( # Path to the folder where the module source code is located. [Parameter(Mandatory)] [string] $ModuleFolderPath, # Path to the folder where the built modules are outputted. [Parameter(Mandatory)] [string] $ModulesOutputFolder, # Path to the folder where the built module documentation is outputted. [Parameter(Mandatory)] [string] $DocsOutputFolder ) $moduleName = Split-Path -Path $ModuleFolderPath -Leaf Write-Output "::group::[$moduleName]" Write-Verbose "ModuleFolderPath - [$ModuleFolderPath]" $moduleSourceFolder = Get-Item -Path $ModuleFolderPath Build-PSModuleBase -SourceFolderPath $moduleSourceFolder -OutputFolderPath $modulesOutputFolder Build-PSModuleRootModule -SourceFolderPath $moduleSourceFolder -OutputFolderPath $modulesOutputFolder Build-PSModuleManifest -SourceFolderPath $moduleSourceFolder -OutputFolderPath $modulesOutputFolder $moduleOutputFolder = Join-Path $modulesOutputFolder $moduleName $docOutputFolder = Join-Path $docsOutputFolder $moduleName Build-PSModuleDocumentation -SourceFolderPath $moduleOutputFolder -OutputFolderPath $docOutputFolder Write-Verbose "[$moduleName] - Done" } Write-Verbose "[$scriptName] - [/private/Invoke-PSModuleBuild.ps1] - Done" #endregion - From /private/Invoke-PSModuleBuild.ps1 #region - From /private/Invoke-PSModuleTest.ps1 Write-Verbose "[$scriptName] - [/private/Invoke-PSModuleTest.ps1] - Importing" function Invoke-PSModuleTest { param( # Path to the folder where the built modules are outputted. [Parameter(Mandatory)] [string] $ModuleFolderPath ) $moduleName = Split-Path -Path $ModuleFolderPath -Leaf Write-Output "::group::[$moduleName]" Write-Verbose "ModuleFolderPath - [$ModuleFolderPath]" Write-Output "::group::[$moduleName] - Invoke-ScriptAnalyzer" $moduleFolder = Get-Item -Path $ModuleFolderPath Invoke-PSSATest -ModuleFolder $moduleFolder -Verbose:$false Write-Output "::endgroup::" Write-Verbose "[$moduleName] - Done" } # <# # Run tests from ".\tests\$moduleName.Tests.ps1" # # Import the module using Import-Module $moduleManifestFilePath, # # Do not not just add the outputted module file to the PATH of the runner (current context is enough) $env:PATH += ";.\outputs\$moduleName" as the import-module will actually test that the module is importable. # #> # <# # Run tests from ".\tests\$moduleName.Tests.ps1" # #> # <# # Test-ModuleManifest -Path $Path # #> Write-Verbose "[$scriptName] - [/private/Invoke-PSModuleTest.ps1] - Done" #endregion - From /private/Invoke-PSModuleTest.ps1 Write-Verbose "[$scriptName] - [/private] - Done" #endregion - From /private #region - From /public Write-Verbose "[$scriptName] - [/public] - Processing folder" #region - From /public/Build-PSModule.ps1 Write-Verbose "[$scriptName] - [/public/Build-PSModule.ps1] - Importing" #Requires -Modules platyPS, PowerShellGet, PackageManagement function Build-PSModule { <# .SYNOPSIS Builds a PowerShell module .DESCRIPTION Builds a PowerShell module .EXAMPLE Build-PSModule -Path 'src' -OutputPath 'outputs' Builds all modules in the 'src' folder and stores them in the 'outputs' folder .EXAMPLE Build-PSModule -Path 'src' -Name 'PSModule.FX' -OutputPath 'outputs' Builds the 'PSModule.FX' module in the 'src' folder and stores it in the 'outputs' folder. .NOTES #DECISION: Modules are default located under the '.\src' folder which is the root of the repo. #DECISION: Module name = the name of the folder under src. Inherited decision from PowerShell team. #DECISION: The module manifest file = name of the folder. #> [CmdletBinding(SupportsShouldProcess)] param( # Path to the folder where the modules are located. [Parameter()] [string] $Path = 'src', # Name of the module to process. [Parameter()] [string] $Name = '*', # Path to the folder where the built modules are outputted. [Parameter()] [string] $OutputPath = 'outputs' ) Write-Output "::group::Starting..." $modulesOutputFolderPath = Join-Path -Path $OutputPath 'modules' Write-Verbose "Creating module output folder [$modulesOutputFolderPath]" $modulesOutputFolder = New-Item -Path $modulesOutputFolderPath -ItemType Directory -Force Add-PSModulePath -Path $modulesOutputFolder $docsOutputFolderPath = Join-Path -Path $OutputPath 'docs' Write-Verbose "Creating docs output folder [$docsOutputFolderPath]" $docsOutputFolder = New-Item -Path $docsOutputFolderPath -ItemType Directory -Force $moduleFolders = Get-PSModuleFolders -Path $Path | Where-Object { $_.Name -like $Name } Write-Verbose "Found $($moduleFolders.Count) module(s)" $moduleFolders | ForEach-Object { Write-Verbose "[$($_.Name)]" } foreach ($moduleFolder in $moduleFolders) { Invoke-PSModuleBuild -ModuleFolderPath $moduleFolder.FullName -ModulesOutputFolder $modulesOutputFolder -DocsOutputFolder $docsOutputFolder } } Write-Verbose "[$scriptName] - [/public/Build-PSModule.ps1] - Done" #endregion - From /public/Build-PSModule.ps1 #region - From /public/Publish-PSModule.ps1 Write-Verbose "[$scriptName] - [/public/Publish-PSModule.ps1] - Importing" #Requires -Modules platyPS function Publish-PSModule { <# .SYNOPSIS Publishes a module to the PowerShell Gallery and GitHub Pages. .DESCRIPTION Publishes a module to the PowerShell Gallery and GitHub Pages. .EXAMPLE Publish-PSModule -Name 'PSModule.FX' -APIKey $env:PSGALLERY_API_KEY #> [Alias('Release-Module')] [CmdletBinding(SupportsShouldProcess)] param( # Name of the module to process. [Parameter()] [string] $Name = '*', # The API key for the destination repository. [Parameter(Mandatory)] [string] $APIKey ) $task = New-Object System.Collections.Generic.List[string] #region Publish-Module $task.Add('Release-Module') Write-Output "::group::[$($task -join '] - [')] - Starting..." Import-Module PackageManagement, PowerShellGet -Verbose:$false -ErrorAction Stop ######################## # Gather some basic info ######################## $outputPath = Get-Item -Path .\outputs\modules | Select-Object -ExpandProperty FullName $env:PSModulePath += ":$SRCPath" $env:PSModulePath -Split ':' $modulesOutputFolderPath = Join-Path -Path 'outputs' 'modules' Write-Verbose "Getting module output folder [$modulesOutputFolderPath]" $modulesOutputFolder = Get-Item -Path $modulesOutputFolderPath Add-PSModulePath -Path $modulesOutputFolder $moduleFolders = Get-ChildItem -Path $modulesOutputFolder -Directory | Where-Object { $_.Name -like $Name } foreach ($module in $moduleFolders) { $moduleName = $module.Name $manifestFilePath = "$module\$moduleName.psd1" $task.Add($moduleName) Write-Output "::group::[$($task -join '] - [')] - Starting..." #region Generate-Version $task.Add('Generate-Version') Write-Output "::group::[$($task -join '] - [')]" Write-Verbose "[$($task -join '] - [')] - Generate version" [Version]$newVersion = '0.0.0' try { $onlineVersion = [Version](Find-Module $moduleName -Verbose:$false).Version } catch { $onlineVersion = $newVersion Write-Warning "Could not find module online. Using [$($onlineVersion.ToString())]" } Write-Warning "Online: [$($onlineVersion.ToString())]" $manifestVersion = [Version](Test-ModuleManifest $manifestFilePath -Verbose:$false).Version Write-Warning "Manifest: [$($manifestVersion.ToString())]" Write-Verbose "branch is: [$env:GITHUB_REF_NAME]" if ($manifestVersion.Major -gt $onlineVersion.Major) { $newVersionMajor = $manifestVersion.Major $newVersionMinor = 0 $newVersionBuild = 0 } else { $newVersionMajor = $onlineVersion.Major if ($manifestVersion.Minor -gt $onlineVersion.Minor) { $newVersionMinor = $manifestVersion.Minor $newVersionBuild = 0 } else { $newVersionMinor = $onlineVersion.Minor $newVersionBuild = $onlineVersion.Build + 1 } } [Version]$newVersion = [version]::new($newVersionMajor, $newVersionMinor, $newVersionBuild) Write-Warning "newVersion: [$($newVersion.ToString())]" Write-Verbose "[$($task -join '] - [')] - Create draft release with version" gh release create $newVersion --title $newVersion --generate-notes --draft --target $env:GITHUB_REF_NAME if ($env:GITHUB_REF_NAME -ne 'main') { Write-Verbose "[$($task -join '] - [')] - Not on main, but on [$env:GITHUB_REF_NAME]" Write-Verbose "[$($task -join '] - [')] - Generate pre-release version" $prerelease = $env:GITHUB_REF_NAME -replace '[^a-zA-Z0-9]', '' Write-Verbose "[$($task -join '] - [')] - Prerelease is: [$prerelease]" if ($newVersion -ge [version]'1.0.0') { Write-Verbose "[$($task -join '] - [')] - Version is greater than 1.0.0 -> Update-ModuleManifest with prerelease [$prerelease]" Update-ModuleManifest -Path $manifestFilePath -Prerelease $prerelease -ErrorAction Continue gh release edit $newVersion -tag "$newVersion-$prerelease" --prerelease } } Write-Verbose "[$($task -join '] - [')] - Bump module version -> module metadata: Update-ModuleMetadata" Update-ModuleManifest -Path $manifestFilePath -ModuleVersion $newVersion -ErrorAction Continue Write-Output "::group::[$($task -join '] - [')] - Done" $task.RemoveAt($task.Count - 1) #endregion Generate-Version #region Publish-Docs $task.Add('Publish-Docs') Write-Output "::group::[$($task -join '] - [')]" Write-Output "::group::[$($task -join '] - [')] - Do something" Write-Verbose "[$($task -join '] - [')] - Publish docs to GitHub Pages" Write-Verbose "[$($task -join '] - [')] - Update docs path: Update-ModuleMetadata" # What about updateable help? https://learn.microsoft.com/en-us/powershell/scripting/developer/help/supporting-updatable-help?view=powershell-7.3 Write-Output "::group::[$($task -join '] - [')] - Done" $task.RemoveAt($task.Count - 1) #endregion Publish-Docs #region Publish-ToPSGallery $task.Add('Publish-ToPSGallery') Write-Output "::group::[$($task -join '] - [')]" Write-Output "::group::[$($task -join '] - [')] - Do something" Write-Verbose "[$($task -join '] - [')] - Publish module to PowerShell Gallery using [$APIKey]" Publish-Module -Path "$module" -NuGetApiKey $APIKey Write-Verbose "[$($task -join '] - [')] - Publish GitHub release for [$newVersion]" gh release edit $newVersion --draft=false Write-Verbose "[$($task -join '] - [')] - Doing something" Write-Output "::group::[$($task -join '] - [')] - Done" $task.RemoveAt($task.Count - 1) #endregion Publish-ToPSGallery } $task.RemoveAt($task.Count - 1) Write-Output "::group::[$($task -join '] - [')] - Stopping..." Write-Output '::endgroup::' #endregion Publish-Module } Write-Verbose "[$scriptName] - [/public/Publish-PSModule.ps1] - Done" #endregion - From /public/Publish-PSModule.ps1 #region - From /public/Test-PSModule.ps1 Write-Verbose "[$scriptName] - [/public/Test-PSModule.ps1] - Importing" #Requires -Modules Pester, PSScriptAnalyzer function Test-PSModule { <# .SYNOPSIS Test a module. .DESCRIPTION Test a module using Pester and PSScriptAnalyzer. Runs both custom tests defined in a module's tests folder and the default tests. .EXAMPLE Test-PSModule -Name 'PSModule.FX' -Path 'outputs' #> [CmdletBinding(SupportsShouldProcess)] param( # Name of the module to process. [Parameter()] [string] $Name = '*', # Path to the folder where the built modules are outputted. [Parameter()] [string] $Path ) Write-Output '::group::Starting...' $moduleFolders = Get-PSModuleFolders -Path $Path | Where-Object { $_.Name -like $Name } Write-Verbose "Found $($moduleFolders.Count) module(s)" $moduleFolders | ForEach-Object { Write-Verbose "[$($_.Name)]" } foreach ($moduleFolder in $moduleFolders) { Invoke-PSModuleTest -ModuleFolderPath $moduleFolder.FullName } } Write-Verbose "[$scriptName] - [/public/Test-PSModule.ps1] - Done" #endregion - From /public/Test-PSModule.ps1 Write-Verbose "[$scriptName] - [/public] - Done" #endregion - From /public Export-ModuleMember -Function 'Build-PSModule','Publish-PSModule','Test-PSModule' -Cmdlet '' -Variable '' -Alias '' |