Public/Build-Module.ps1

function Build-Module {
  # .SYNOPSIS
  # Module buildScript
  # .DESCRIPTION
  # A custom Psake buildScript for any module that was created by PsCraft.
  # .LINK
  # https://github.com/alainQtec/PsCraft/blob/main/public/Build-Module.ps1
  # .EXAMPLE
  # Running Build-Module will only "Init, Compile & Import" the module; That's it, no tests.
  # To run tests Use:
  # Build-Module -Task Test
  # This Will build the module, Import it and run tests using the ./Test-Module.ps1 script.
  # .EXAMPLE
  # Build-Module -t deploy
  # Will build the module, test it and deploy it to PsGallery
  [cmdletbinding(DefaultParameterSetName = 'task')]
  param(
    [parameter(Position = 0, ParameterSetName = 'task')]
    [ValidateScript({
        $task_seq = [string[]]$_; $IsValid = $true
        $Tasks = @('Init', 'Clean', 'Compile', 'Test', 'Deploy')
        foreach ($name in $task_seq) {
          $IsValid = $IsValid -and ($name -in $Tasks)
        }
        if ($IsValid) {
          return $true
        } else {
          throw [System.ArgumentException]::new('Task', "ValidSet: $($Tasks -join ', ').")
        }
      }
    )][ValidateNotNullOrEmpty()][Alias('t')]
    [string[]]$Task = @('Init', 'Clean', 'Compile'),

    # Module buildRoot
    [Parameter(Mandatory = $false, ParameterSetName = 'task')]
    [ValidateScript({
        if (Test-Path -Path $_ -PathType Container -ErrorAction Ignore) {
          return $true
        } else {
          throw [System.ArgumentException]::new('Path', "Path: $_ is not a valid directory.")
        }
      })][Alias('p')]
    [string]$Path = (Get-Item -Path "." -Verbose:$false).FullName,

    [Parameter(Mandatory = $false, ParameterSetName = 'task')]
    [Alias('u')][ValidateNotNullOrWhiteSpace()]
    [string]$gitUser,

    [parameter(ParameterSetName = 'task')]
    [Alias('i')]
    [switch]$Import,

    [parameter(ParameterSetName = 'help')]
    [Alias('h', '-help')]
    [switch]$Help
  )

  Begin {
    #Requires -RunAsAdministrator
    if ($null -ne ${env:=::}) { $PSCmdlet.ThrowTerminatingError('Please Run this command as administrator') }
    #region Variables
    [Environment]::SetEnvironmentVariable('IsAC', $(if (![string]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable('GITHUB_WORKFLOW'))) { '1' } else { '0' }), [System.EnvironmentVariableTarget]::Process)
    [Environment]::SetEnvironmentVariable('IsCI', $(if (![string]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable('TF_BUILD'))) { '1' }else { '0' }), [System.EnvironmentVariableTarget]::Process)
    [Environment]::SetEnvironmentVariable('RUN_ID', $(if ([bool][int]$env:IsAC -or $env:CI -eq "true") { [Environment]::GetEnvironmentVariable('GITHUB_RUN_ID') }else { [Guid]::NewGuid().Guid.substring(0, 21).replace('-', [string]::Join('', (0..9 | Get-Random -Count 1))) + '_' }), [System.EnvironmentVariableTarget]::Process);
    $LocalPSRepo = [IO.Path]::Combine([environment]::GetEnvironmentVariable("HOME"), 'LocalPSRepo')
    Set-BuildVariables -Path $Path -Prefix $env:RUN_ID
    $script:Psake_BuildFile = New-Item $([IO.Path]::GetTempFileName().Replace('.tmp', '.ps1'))
    $script:PSake_ScriptBlock = [scriptblock]::Create({
        Write-Heading "Installing Pscraft module Requirements..."
        if (!(Get-Module PsCraft -ListAvailable -ErrorAction Ignore)) { Install-Module PsCraft -Verbose:$false };
        (Get-InstalledModule PsCraft -ErrorAction Ignore).InstalledLocation | Split-Path | Import-Module -Verbose:$false
        Import-PackageProvider -Name NuGet -Force
        $RequiredModules = @(
          "PackageManagement"
          "PSScriptAnalyzer",
          "cliHelper.core",
          "PowerShellGet",
          "cliHelper.env",
          "Pester",
          "psake"
        )
        foreach ($Name in $RequiredModules) {
          $Host.UI.WriteLine();
          Resolve-Module -Name $Name -UpdateModule -Verbose:$false
        }
        $Host.UI.WriteLine()
        Write-BuildLog "Module Requirements Successfully resolved."
        Properties {
          # PSake makes variables declared in here available in other scriptblocks
          $taskList = $Task
          $Cmdlet = $PSCmdlet
          $ProjectName = [Environment]::GetEnvironmentVariable($env:RUN_ID + 'ProjectName')
          $BuildNumber = [Environment]::GetEnvironmentVariable($env:RUN_ID + 'BuildNumber')
          $ProjectRoot = [Environment]::GetEnvironmentVariable($env:RUN_ID + 'ProjectPath')
          $outputDir = [Environment]::GetEnvironmentVariable($env:RUN_ID + 'BuildOutput')
          $Timestamp = Get-Date -UFormat "%Y%m%d-%H%M%S"
          $PSVersion = $PSVersionTable.PSVersion.ToString()
          $outputModDir = [IO.path]::Combine([Environment]::GetEnvironmentVariable($env:RUN_ID + 'BuildOutput'), $ProjectName)
          $tests = [IO.Path]::Combine($projectRoot, "Tests")
          $lines = ('-' * 70);
          $TestFile = "TestResults_PS$PSVersion`_$TimeStamp.xml"
          $outputModVerDir = [IO.path]::Combine([Environment]::GetEnvironmentVariable($env:RUN_ID + 'BuildOutput'), $ProjectName, $BuildNumber)
          $PathSeperator = [IO.Path]::PathSeparator
          $DirSeperator = [IO.Path]::DirectorySeparatorChar
          # To prevent "variable not used" warnings:
          $null = @($taskList, $Cmdlet, $tests, $getelapsed, $TestFile, $ProjectRoot, $outputDir, $outputModDir, $outputModVerDir, $lines, $DirSeperator, $PathSeperator)
        }
        #Task Default -Depends Init,Test and Compile. Deploy Has to be done Manually
        Task default -Depends Test

        Task Init {
          Set-Location $ProjectRoot
          Write-EnvironmentSummary "Build started"
          Write-Verbose "Module Build version: $BuildNumber"
          $security_protocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::SystemDefault
          if ([Net.SecurityProtocolType].GetMember("Tls12").Count -gt 0) { $security_protocol = $security_protocol -bor [Net.SecurityProtocolType]::Tls12 }
          [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]$security_protocol
          $Host.ui.WriteLine()
          Invoke-CommandWithLog { $script:DefaultParameterValues = @{
              '*-Module:Verbose'           = $false
              'Import-Module:ErrorAction'  = 'Stop'
              'Import-Module:Force'        = $true
              'Import-Module:Verbose'      = $false
              'Install-Module:ErrorAction' = 'Stop'
              'Install-Module:Scope'       = 'CurrentUser'
              'Install-Module:Verbose'     = $false
            }
          }
          Write-Heading "Prepare package feeds"
          $Host.ui.WriteLine()
          if ($null -eq (Get-PSRepository -Name PSGallery -ErrorAction Ignore)) {
            Unregister-PSRepository -Name PSGallery -Verbose:$false -ErrorAction Ignore
            Register-PSRepository -Default -InstallationPolicy Trusted
          }
          if ((Get-PSRepository -Name PSGallery).InstallationPolicy -ne 'Trusted') {
            Invoke-CommandWithLog { Set-PSRepository -Name PSGallery -InstallationPolicy Trusted -Verbose:$false }
          }
          # if ((Get-Command dotnet -ErrorAction Ignore) -and ([bool](Get-Variable -Name IsWindows -ErrorAction Ignore) -and !$(Get-Variable IsWindows -ValueOnly))) {
          # dotnet dev-certs https --trust
          # }
          Invoke-CommandWithLog { Get-PackageProvider -Name Nuget -ForceBootstrap -Verbose:$false }
          if (!(Get-PackageProvider -Name Nuget)) {
            Invoke-CommandWithLog { Install-PackageProvider -Name NuGet -Force | Out-Null }
          }
          $build_sys = [Environment]::GetEnvironmentVariable($env:RUN_ID + 'BuildSystem');
          $lastCommit = git log -1 --pretty=%B
          Write-BuildLog "Current build system is $build_sys"
          Write-Heading "Finalizing build Prerequisites and Resolving dependencies ..."

          if ($build_sys -eq 'VSTS' -or ($env:CI -eq "true" -and $env:GITHUB_RUN_ID)) {
            if ($Task -contains 'Deploy') {
              $MSG = "Task is 'Deploy' and conditions for deployment are:`n" +
              " + GitHub API key is not null : $(![string]::IsNullOrWhiteSpace($env:GitHubPAT))`n" +
              " + Current branch is main : $(($env:GITHUB_REF -replace "refs/heads/") -eq 'main')`n" +
              " + Source is not a pull request : $($env:GITHUB_EVENT_NAME -ne "pull_request") [$env:GITHUB_EVENT_NAME]`n" +
              " + Commit message matches '!deploy' : $($lastCommit -match "!deploy") [$lastCommit]`n" +
              " + Is Current PS version < 5 ? : $($PSVersionTable.PSVersion.Major -lt 5) [$($PSVersionTable.PSVersion.ToString())]`n" +
              " + NuGet API key is not null : $(![string]::IsNullOrWhiteSpace($env:NUGETAPIKEY))`n"
              if ($PSVersionTable.PSVersion.Major -lt 5 -or [string]::IsNullOrWhiteSpace($env:NUGETAPIKEY) -or [string]::IsNullOrWhiteSpace($env:GitHubPAT) ) {
                $MSG = $MSG.Replace('and conditions for deployment are:', 'but conditions are not correct for deployment.')
                $MSG | Write-Host -f Yellow
                if (($([Environment]::GetEnvironmentVariable($env:RUN_ID + 'CommitMessage')) -match '!deploy' -and $([Environment]::GetEnvironmentVariable($env:RUN_ID + 'BranchName')) -eq "main") -or $script:ForceDeploy -eq $true) {
                  Write-Warning "Force Deploying detected"
                } else {
                  "Skipping Psake for this job!" | Write-Host -f Yellow
                  exit 0
                }
              } else {
                $MSG | Write-Host -f Green
              }
            }
          }
        } -Description 'Initialize build environment'

        Task -Name clean -Depends Init {
          $Host.UI.WriteLine()
          Write-Host " Removed any installed versions of [$ProjectName]" -F Green
          $modules = Get-Module -Name $ProjectName -ListAvailable -ErrorAction Ignore
          if ($modules) { $modules | Remove-Module -Verbose:$false -Force -ErrorAction Ignore | Out-Null }
          if (Test-Path -Path $outputDir -PathType Container -ErrorAction Ignore) {
            Write-Verbose "Cleaning Previous build Output ..."
            Get-ChildItem -Path $outputDir -Recurse -Force | Remove-Item -Force -Recurse -Verbose:$false | Out-Null
          }
          Write-Host " Removed previous Output directory [$outputDir]" -F Green
        } -Description 'Cleans module output directory'

        Task Compile -Depends Clean {
          Write-Verbose "Create module Output directory"
          New-Item -Path $outputModVerDir -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null
          $ModuleManifest = [IO.FileInfo]::New([Environment]::GetEnvironmentVariable($env:RUN_ID + 'PSModuleManifest'))
          Write-BuildLog "Add Module files ...`nRef: https://aka.ms/nuget/authoring-best-practices"
          try {
            $d = [Environment]::GetEnvironmentVariable($env:RUN_ID + 'PSModulePath')
            @(
              "en-US"
              "Private"
              "Public"
              "LICENSE"
              "README.md"
              "$($ModuleManifest.Name)"
              "$ProjectName.psm1"
            ).ForEach({
                $p = [IO.Path]::Combine($([Environment]::GetEnvironmentVariable($env:RUN_ID + 'BuildScriptPath')), $_)
                if (Test-Path -Path $p -ErrorAction Ignore) {
                  Copy-Item -Recurse -Path $p -Destination $d
                }
              }
            )
          } catch {
            $Cmdlet.ThrowTerminatingError([System.Management.Automation.ErrorRecord]::new($_.Exception, $_.FullyQualifiedErrorId, $_.CategoryInfo, $_.TargetObject))
          }
          if (!$ModuleManifest.Exists) {
            $Cmdlet.ThrowTerminatingError([System.Management.Automation.ErrorRecord]::new([IO.FileNotFoundException]::New('Could Not Create Module Manifest!'), 'CouldNotCreateModuleManifest', 'ObjectNotFound', $ModuleManifest))
          }
          $functionsToExport = @(); $publicFunctionsPath = [IO.Path]::Combine([Environment]::GetEnvironmentVariable($env:RUN_ID + 'ProjectPath'), "Public")
          if (Test-Path $publicFunctionsPath -PathType Container -ErrorAction SilentlyContinue) {
            Get-ChildItem -Path $publicFunctionsPath -Filter "*.ps1" -Recurse -File | ForEach-Object {
              $functionsToExport += $_.BaseName
            }
          }
          $manifestContent = Get-Content -Path $ModuleManifest -Raw
          $publicFunctionNames = Get-ChildItem -Path $publicFunctionsPath -Filter "*.ps1" | Select-Object -ExpandProperty BaseName

          Write-Verbose -Message "Editing $($ModuleManifest.Name) ..."
          # Using .Replace() is Better than Update-ModuleManifest as this does not destroy the Indentation in the Psd1 file.
          $manifestContent = $manifestContent.Replace(
            "'<FunctionsToExport>'", $(if ((Test-Path -Path $publicFunctionsPath) -and $publicFunctionNames.count -gt 0) { "'$($publicFunctionNames -join "',`n '")'" }else { $null })
          ).Replace(
            "<ModuleVersion>", $BuildNumber
          ).Replace(
            "<ReleaseNotes>", $([Environment]::GetEnvironmentVariable($env:RUN_ID + 'ReleaseNotes'))
          ).Replace(
            "<Year>", ([Datetime]::Now.Year)
          )
          $manifestContent | Set-Content -Path $ModuleManifest
          if ((Get-ChildItem $outputModVerDir | Where-Object { $_.Name -eq "$($([Environment]::GetEnvironmentVariable($env:RUN_ID + 'ProjectName'))).psd1" }).BaseName -cne $([Environment]::GetEnvironmentVariable($env:RUN_ID + 'ProjectName'))) {
            " Renaming manifest to correct casing"
            Rename-Item (Join-Path $outputModVerDir "$($([Environment]::GetEnvironmentVariable($env:RUN_ID + 'ProjectName'))).psd1") -NewName "$($([Environment]::GetEnvironmentVariable($env:RUN_ID + 'ProjectName'))).psd1" -Force
          }
          $Host.UI.WriteLine()
          " Created compiled module at [$outputModDir]"
          " Output version directory contents"
          Get-ChildItem $outputModVerDir | Format-Table -AutoSize
        } -Description 'Compiles module from source'

        Task Test -Depends Compile {
          Write-Heading "Executing Script: ./Test-Module.ps1"
          $test_Script = [IO.FileInfo]::New([IO.Path]::Combine($ProjectRoot, 'Test-Module.ps1'))
          if (!$test_Script.Exists) { $Cmdlet.ThrowTerminatingError([System.Management.Automation.ErrorRecord]::new([System.IO.FileNotFoundException]::New($test_Script.FullName), 'CouldNotFindTestScript', 'ObjectNotFound', $test_Script.FullName)) }
          Import-Module Pester -Verbose:$false -Force -ErrorAction Stop
          $origModulePath = $Env:PSModulePath
          Push-Location $ProjectRoot
          if ($Env:PSModulePath.split($pathSeperator) -notcontains $outputDir ) {
            $Env:PSModulePath = ($outputDir + $pathSeperator + $origModulePath)
          }
          Remove-Module $ProjectName -ErrorAction SilentlyContinue -Verbose:$false
          Import-Module $outputModDir -Force -Verbose:$false
          $Host.UI.WriteLine();
          $TestResults = & $test_Script
          Write-Host ' Pester invocation complete!' -ForegroundColor Green
          $TestResults | Format-List
          if ($TestResults.FailedCount -gt 0) {
            $Cmdlet.WriteError([System.Management.Automation.ErrorRecord]::new([Exception]::new("One or more Pester tests failed!"), "PesterTestsFailed", 'OperationStopped', @{}))
          }
          Pop-Location
          $Env:PSModulePath = $origModulePath
        } -Description 'Run Pester tests against compiled module'

        Task Deploy -Depends Test -Description 'Release new github version and Publish module to PSGallery' {
          if ([Environment]::GetEnvironmentVariable($env:RUN_ID + 'BuildSystem') -eq 'VSTS' -or ($env:CI -eq "true" -and $env:GITHUB_RUN_ID)) {
            # Load the module, read the exported functions, update the psd1 FunctionsToExport
            $commParsed = [Environment]::GetEnvironmentVariable($env:RUN_ID + 'CommitMessage') | Select-String -Pattern '\sv\d+\.\d+\.\d+\s'
            if ($commParsed) {
              $commitVer = $commParsed.Matches.Value.Trim().Replace('v', '')
            }
            $current_build_version = $CurrentVersion = (Get-Module $([Environment]::GetEnvironmentVariable($env:RUN_ID + 'ProjectName'))).Version
            $Latest_Module_Verion = Get-LatestModuleVersion -Name ([Environment]::GetEnvironmentVariable($env:RUN_ID + 'ProjectName')) -Source PsGallery
            "Module Current version on the PSGallery: $Latest_Module_Verion"
            $galVerSplit = "$Latest_Module_Verion".Split('.')
            $nextGalVer = [System.Version](($galVerSplit[0..($galVerSplit.Count - 2)] -join '.') + '.' + ([int]$galVerSplit[-1] + 1))
            # Bump MODULE Version
            $versionToDeploy = switch ($true) {
              $($commitVer -and ([System.Version]$commitVer -lt $nextGalVer)) {
                Write-Host -f Yellow "Version in commit message is $commitVer, which is less than the next Gallery version and would result in an error. Possible duplicate deployment build, skipping module bump and negating deployment"
                Set-EnvironmentVariable -Name ($env:RUN_ID + 'CommitMessage') -Value $([Environment]::GetEnvironmentVariable($env:RUN_ID + 'CommitMessage')).Replace('!deploy', '')
                $null
                break
              }
              $($commitVer -and ([System.Version]$commitVer -gt $nextGalVer)) {
                Write-Host -f Green "Module Bumped version: $commitVer [from commit message]"
                [System.Version]$commitVer
                break
              }
              $($CurrentVersion -ge $nextGalVer) {
                Write-Host -f Green "Module Bumped version: $CurrentVersion [from manifest]"
                $CurrentVersion
                break
              }
              $(([Environment]::GetEnvironmentVariable($env:RUN_ID + 'CommitMessage')) -match '!hotfix') {
                Write-Host -f Green "Module Bumped version: $nextGalVer [commit message match '!hotfix']"
                $nextGalVer
                break
              }
              $(([Environment]::GetEnvironmentVariable($env:RUN_ID + 'CommitMessage')) -match '!minor') {
                $minorVers = [System.Version]("{0}.{1}.{2}" -f $nextGalVer.Major, ([int]$nextGalVer.Minor + 1), 0)
                Write-Host -f Green "Module Bumped version: $minorVers [commit message match '!minor']"
                $minorVers
                break
              }
              $(([Environment]::GetEnvironmentVariable($env:RUN_ID + 'CommitMessage')) -match '!major') {
                $majorVers = [System.Version]("{0}.{1}.{2}" -f ([int]$nextGalVer.Major + 1), 0, 0)
                Write-Host -f Green "Module Bumped version: $majorVers [commit message match '!major']"
                $majorVers
                break
              }
              Default {
                Write-Host -f Green "Module Bumped version: $nextGalVer [PSGallery next version]"
                $nextGalVer
              }
            }
            if (!$versionToDeploy) {
              Write-Host -f Yellow "No module version matched! Negating deployment to prevent errors"
              Set-EnvironmentVariable -Name ($env:RUN_ID + 'CommitMessage') -Value $([Environment]::GetEnvironmentVariable($env:RUN_ID + 'CommitMessage')).Replace('!deploy', '')
            }
            try {
              [ValidateNotNullOrWhiteSpace()][string]$versionToDeploy = $versionToDeploy.ToString()
              $manifest = Import-PowerShellDataFile -Path $([Environment]::GetEnvironmentVariable($env:RUN_ID + 'PSModuleManifest'))
              $latest_Github_release = Invoke-WebRequest "https://api.github.com/repos/alainQtec/PsCraft/releases/latest" | ConvertFrom-Json
              $latest_Github_release = [PSCustomObject]@{
                name = $latest_Github_release.name
                ver  = [version]::new($latest_Github_release.tag_name.substring(1))
                url  = $latest_Github_release.html_url
              }
              $Is_Lower_PsGallery_Version = [version]$current_build_version -le $Latest_Module_Verion
              $should_Publish_ToPsGallery = ![string]::IsNullOrWhiteSpace($env:NUGETAPIKEY) -and !$Is_Lower_PsGallery_Version
              $Is_Lower_GitHub_Version = [version]$current_build_version -le $latest_Github_release.ver
              $should_Publish_GitHubRelease = ![string]::IsNullOrWhiteSpace($env:GitHubPAT) -and ($env:CI -eq "true" -and $env:GITHUB_RUN_ID) -and !$Is_Lower_GitHub_Version
              if ($should_Publish_ToPsGallery) {
                $manifestPath = Join-Path $outputModVerDir "$($([Environment]::GetEnvironmentVariable($env:RUN_ID + 'ProjectName'))).psd1"
                if (-not $manifest) {
                  $manifest = Import-PowerShellDataFile -Path $manifestPath
                }
                if ($manifest.ModuleVersion.ToString() -eq $versionToDeploy.ToString()) {
                  " Manifest is already the expected version. Skipping manifest version update"
                } else {
                  " Updating module version on manifest to [$versionToDeploy]"
                  Update-Metadata -Path $manifestPath -PropertyName ModuleVersion -Value $versionToDeploy -Verbose
                }
                Write-Host " Publishing version [$versionToDeploy] to PSGallery..." -ForegroundColor Green
                Publish-Module -Path $outputModVerDir -NuGetApiKey $env:NUGETAPIKEY -Repository PSGallery -Verbose
                Write-Host " Published to PsGallery successful!" -ForegroundColor Green
              } else {
                if ($Is_Lower_PsGallery_Version) { Write-Warning "SKIPPED Publishing. Module version $Latest_Module_Verion already exists on PsGallery!" }
                Write-Verbose " SKIPPED Publish of version [$versionToDeploy] to PSGallery"
              }
              $commitId = git rev-parse --verify HEAD;
              if ($should_Publish_GitHubRelease) {
                $ReleaseNotes = [Environment]::GetEnvironmentVariable($env:RUN_ID + 'ReleaseNotes')
                [ValidateNotNullOrWhiteSpace()][string]$ReleaseNotes = $ReleaseNotes
                " Creating Release ZIP..."
                $ZipTmpPath = [System.IO.Path]::Combine($ProjectRoot, "$($([Environment]::GetEnvironmentVariable($env:RUN_ID + 'ProjectName'))).zip")
                if ([IO.File]::Exists($ZipTmpPath)) { Remove-Item $ZipTmpPath -Force }
                Add-Type -Assembly System.IO.Compression.FileSystem
                [System.IO.Compression.ZipFile]::CreateFromDirectory($outputModDir, $ZipTmpPath)
                Write-Heading " Publishing Release v$versionToDeploy @ commit Id [$($commitId)] to GitHub..."
                $ReleaseNotes += (git log -1 --pretty=%B | Select-Object -Skip 2) -join "`n"
                $ReleaseNotes = $ReleaseNotes.Replace('<versionToDeploy>', $versionToDeploy)
                Set-EnvironmentVariable -Name ('{0}{1}' -f $env:RUN_ID, 'ReleaseNotes') -Value $ReleaseNotes
                $gitHubParams = @{
                  VersionNumber    = $versionToDeploy
                  CommitId         = $commitId
                  ReleaseNotes     = [Environment]::GetEnvironmentVariable($env:RUN_ID + 'ReleaseNotes')
                  ArtifactPath     = $ZipTmpPath
                  GitHubUsername   = 'alainQtec'
                  GitHubRepository = [Environment]::GetEnvironmentVariable($env:RUN_ID + 'ProjectName')
                  GitHubApiKey     = $env:GitHubPAT
                  Draft            = $false
                }
                Publish-GitHubRelease @gitHubParams
                Write-Heading " Github release created successful!"
              } else {
                if ($Is_Lower_GitHub_Version) { Write-Warning "SKIPPED Releasing. Module version $current_build_version already exists on Github!" }
                Write-Verbose " SKIPPED GitHub Release v$($versionToDeploy) @ commit Id [$($commitId)] to GitHub"
              }
            } catch {
              $_ | Format-List * -Force
              $Cmdlet.WriteError([System.Management.Automation.ErrorRecord]::new($_.Exception, $_.FullyQualifiedErrorId, $_.CategoryInfo, $_.TargetObject))
            }
          } else {
            Write-Host -f Magenta "UNKNOWN Build system"
          }
        }
      }
    )
    $script:Clean_EnvBuildvariables = [scriptblock]::Create({
        Param (
          [Parameter(Position = 0)]
          [ValidatePattern('\w*')]
          [ValidateNotNullOrEmpty()]
          [string]$build_Id
        )
        Write-Heading "CleanUp: Remove $ModuleName, env variables, and delete LocalPSRepo"
        if (![string]::IsNullOrWhiteSpace($build_Id)) {
          $OldEnvNames = [Environment]::GetEnvironmentVariables().Keys | Where-Object { $_ -like "$build_Id*" }
          if ($OldEnvNames.Count -gt 0) {
            foreach ($Name in $OldEnvNames) {
              Write-BuildLog "Remove env variable $Name"
              [Environment]::SetEnvironmentVariable($Name, $null)
            }
          } else {
            Write-BuildLog "No old Env variables to remove; Move on ...`n"
          }
        } else {
          Write-Warning "Invalid RUN_ID! Skipped 'Remove env variables' ...`n"
        }
      }
    )
    #endregion Variables
  }
  Process {
    if ($Help) {
      Write-Heading "Getting help"
      Write-BuildLog -c '"psake" | Resolve-Module @Mod_Res -Verbose'
      Resolve-Module -Name 'psake' -Verbose:$false
      Get-PSakeScriptTasks -BuildFile $Psake_BuildFile.FullName | Sort-Object -Property Name | Format-Table -Property Name, Description, Alias, DependsOn
      exit 0
    };
    try {
      $null = Set-Content -Path $script:Psake_BuildFile -Value $script:PSake_ScriptBlock
      Write-Heading "Invoking psake with task list: [ $($Task -join ', ') ]"
      $psakeParams = @{
        nologo    = $true
        buildFile = $script:Psake_BuildFile.FullName
        taskList  = $Task
      }
      if ($Task -contains 'TestOnly') {
        Set-Variable -Name ExcludeTag -Scope global -Value @('Module')
      } else {
        Set-Variable -Name ExcludeTag -Scope global -Value $null
      }
      Invoke-psake @psakeParams @verbose
    } catch {
      $PSCmdlet.ThrowTerminatingError($_)
    } finally {
      $Host.UI.WriteLine()
      Remove-Item $Psake_BuildFile -Verbose | Out-Null
      if ($psake.build_success) {
        Write-Heading "Create a Local repository"
        if (!(Get-Variable -Name IsWindows -ErrorAction Ignore) -or $(Get-Variable IsWindows -ValueOnly)) {
          $LocalPSRepo = [IO.Path]::Combine([environment]::GetEnvironmentVariable("UserProfile"), 'LocalPSRepo')
        }; if (!(Test-Path -Path $LocalPSRepo -PathType Container -ErrorAction Ignore)) { New-Directory -Path $LocalPSRepo | Out-Null }
        Register-PSRepository LocalPSRepo -SourceLocation $LocalPSRepo -PublishLocation $LocalPSRepo -InstallationPolicy Trusted -Verbose:$false -ErrorAction Ignore;
        Register-PackageSource -Name LocalPsRepo -Location $LocalPSRepo -Trusted -ProviderName Bootstrap -ErrorAction Ignore
        Write-Verbose "Verify that the new repository was created successfully"
        if ($null -eq (Get-PSRepository LocalPSRepo -Verbose:$false -ErrorAction Ignore)) {
          $PSCmdlet.ThrowTerminatingError([System.Exception]::New('Failed to create LocalPsRepo', [System.IO.DirectoryNotFoundException]::New($LocalPSRepo)))
        }
        $ModuleName = [Environment]::GetEnvironmentVariable($env:RUN_ID + 'ProjectName')
        $BuildNumber = [Environment]::GetEnvironmentVariable($env:RUN_ID + 'BuildNumber')
        $ModulePath = [IO.Path]::Combine($([Environment]::GetEnvironmentVariable($env:RUN_ID + 'BuildOutput')), $([Environment]::GetEnvironmentVariable($env:RUN_ID + 'ProjectName')), $BuildNumber)
        # Publish To LocalRepo
        $ModulePackage = [IO.Path]::Combine($LocalPSRepo, "${ModuleName}.${BuildNumber}.nupkg")
        if ([IO.File]::Exists($ModulePackage)) {
          Remove-Item -Path $ModulePackage -ErrorAction 'SilentlyContinue'
        }
        Write-Heading "Publish to Local PsRepository"
        $RequiredModules = Get-ModuleManifest ([IO.Path]::Combine($ModulePath, "$([Environment]::GetEnvironmentVariable($env:RUN_ID + 'ProjectName')).psd1")) RequiredModules -Verbose:$false
        foreach ($Module in $RequiredModules) {
          $mdPath = (Get-Module $Module -ListAvailable -Verbose:$false)[0].Path | Split-Path
          Write-Verbose "Publish RequiredModule $Module ..."
          Publish-Module -Path $mdPath -Repository LocalPSRepo -Verbose:$false -ErrorAction Ignore
        }
        Publish-Module -Path $ModulePath -Repository LocalPSRepo
        # Install Module
        Install-Module $ModuleName -Repository LocalPSRepo
        # Import Module
        if ($Import.IsPresent -and $(Get-Variable psake -Scope global -ValueOnly).build_success) {
          Write-Heading "Import $ModuleName to local scope"
          # Import-Module $([IO.Path]::Combine([Environment]::GetEnvironmentVariable($env:RUN_ID + 'BuildOutput'), $ModuleName))
          Import-Module $ModuleName -Verbose:$false
        }
      }
      Write-EnvironmentSummary "Build finished"
      if (![bool][int]$env:IsAC -or $Task -contains 'Clean') {
        Invoke-Command $Clean_EnvBuildvariables -ArgumentList $env:RUN_ID
        if ($ModuleName) { Uninstall-Module $ModuleName -MinimumVersion $BuildNumber -ErrorAction Ignore }
        if ([IO.Directory]::Exists($LocalPSRepo)) {
          if ($null -ne (Get-PSRepository -Name 'LocalPSRepo' -ErrorAction Ignore -Verbose:$false)) {
            Invoke-Command -ScriptBlock ([ScriptBlock]::Create("Unregister-PSRepository -Name 'LocalPSRepo' -Verbose:`$false -ErrorAction Ignore"))
          }; Remove-Item $LocalPSRepo -Verbose:$false -Force -Recurse -ErrorAction Ignore
        }
        [Environment]::SetEnvironmentVariable('RUN_ID', $null)
      }
    }
  }
  end {
    exit ([int](!$psake.build_success))
  }
}