Public/Build-Module.ps1

function Build-Module {
  # .SYNOPSIS
  # Module buildScript
  # .DESCRIPTION
  # A custom Psake buildScript for any module that was created by PsCraft.
  # .EXAMPLE
  # Running Build-Module will only "Compile & Import" the module; That's it, no tests.
  # To run tests Use:
  # Build-Module Test
  # This Will build the module, Import it and run tests using the ./Test-Module.ps1 script.
  # .EXAMPLE
  # Build-Module deploy
  # Will build the module, test it and deploy it to PsGallery
  # .LINK
  # https://github.com/alainQtec/PsCraft/blob/main/public/Build-Module.ps1
  [cmdletbinding(DefaultParameterSetName = 'task')]
  param(
    [parameter(Mandatory = $false, Position = 0, ParameterSetName = 'task')]
    [ValidateScript({
        $task_seq = [string[]]$_; $IsValid = $true
        $Tasks = @('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 = 'Test',

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

    [Parameter(Mandatory = $false, ParameterSetName = 'task')]
    [string[]]$RequiredModules = @(),

    [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
    #Requires -Psedition Core
    $script:build_requirements = $($RequiredModules + @(
        "PackageManagement", "PSScriptAnalyzer",
        "cliHelper.env", "cliHelper.core",
        "PsCraft", "Pester", "psake")
    ) | Select-Object -Unique # ie: defaults /really essential.
    $script:PSake_ScriptBlock = [scriptblock]::Create({
        Properties {
          # variables that will be available to all tasks in the build script
          $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')
          $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}_$(Get-Date -UFormat %Y%m%d-%H%M%S).xml"
          $outputModVerDir = [IO.path]::Combine([Environment]::GetEnvironmentVariable($env:RUN_ID + 'BuildOutput'), $ProjectName, $BuildNumber)
          $PathSeperator = [IO.Path]::PathSeparator
          $DirSeperator = [IO.Path]::DirectorySeparatorChar
          $buildrequirements = <build_requirements>
          # To prevent "variable not used" warnings:
          $null = @($taskList, $Cmdlet, $tests, $getelapsed, $TestFile, $ProjectRoot, $outputDir, $outputModDir, $outputModVerDir, $lines, $DirSeperator, $PathSeperator, $buildrequirements)
        }

        Task default -Depends Test

        Task Compile -Depends Clean {
          $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
          $buildrequirements.ForEach({ Import-Module $_ -Verbose:$false -ea Stop }); $Host.ui.WriteLine();
          #Make sure everything is updated to the latest version:
          $target = "https://www.powershellgallery.com"; $Isconnected = $(try { [System.Net.NetworkInformation.PingReply]$PingReply = [System.Net.NetworkInformation.Ping]::new().Send($target); $PingReply.Status -eq [System.Net.NetworkInformation.IPStatus]::Success } catch [System.Net.Sockets.SocketException], [System.Net.NetworkInformation.PingException] { Write-Verbose "Ping $target : $($_.Exception.InnerException.Message)"; $false });
          if ($Isconnected) { $buildrequirements | PsCraft\Resolve-Module -Update -Verbose:$false }
          Write-EnvironmentSummary "Initialize [$ProjectName] build environment"
          Set-Location $ProjectRoot; Write-Verbose "Module Build version: $BuildNumber"
          $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 -ea Ignore)) {
            Unregister-PSRepository -Name PSGallery -Verbose:$false -ea 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 -ea Ignore) -and ([bool](Get-Variable -Name IsWindows -ea 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
              }
            }
          }

          Write-Verbose "Create module Output directory"
          New-Item -Path $outputModVerDir -ItemType Directory -Force -ea 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 -ea 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 -ea 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 Clean {
          $Host.UI.WriteLine();
          Write-Heading "CleanUp: Module '$ProjectName' env variables and previous build Output"
          if (Test-Path -Path $outputDir -PathType Container -ea Ignore) {
            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 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) {
            $_err_r = [System.Management.Automation.ErrorRecord]::new([System.IO.FileNotFoundException]::New($test_Script.FullName), 'CouldNotFindTestScript', 'ObjectNotFound', $test_Script.FullName)
            $(Get-Variable psake -Scope global -ValueOnly).error_message = $_err_r
            $Cmdlet.ThrowTerminatingError($_err_r)
          }
          Import-Module Pester -Verbose:$false -Force -ea Stop
          $origModulePath = $Env:PSModulePath
          Push-Location $ProjectRoot
          if ($Env:PSModulePath.split($pathSeperator) -notcontains $outputDir) {
            $Env:PSModulePath = ($outputDir + $pathSeperator + $origModulePath)
          }
          Remove-Module $ProjectName -ea 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) {
            $(Get-Variable psake -Scope global -ValueOnly).error_message = [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-Env -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-Env -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/$ProjectName/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 = $(try { git rev-parse --verify HEAD } catch { Write-Warning $_; $null });
              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-Env -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   = $(try { [string][uri]::new((git config --get remote.origin.url)).Segments[1].Replace('/', '') } catch { Write-Warning $_; $null })
                  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"
          }
        }
      }
    )
    $psd1 = [IO.Path]::Combine($Path, "$([IO.DirectoryInfo]::new($Path).BaseName).psd1")
    if ([IO.File]::Exists($psd1)) {
      $data = [PsObject]([scriptblock]::Create("$([IO.File]::ReadAllText($psd1))").Invoke() | Select-Object *)
      $build_requirements = $data.RequiredModules + $build_requirements | Select-Object -Unique
    }
    function Test-NetworkConnectivity {
      #.SYNOPSIS
      # Pretty straight-forward fxn to test if a device is offline or not.
      #.DESCRIPTION
      # Attempts to connect to the target multiple times, checking for successful connection status
      #.EXAMPLE
      # Test-NetworkConnectivity fast.com
      [CmdletBinding()][OutputType([bool])]
      param (
        # The hostname or IP address to test connectivity to
        [Parameter(Mandatory = $false, Position = 0)]
        [ValidateNotNullOrWhiteSpace()]
        [string]$Target = "www.github.com",

        [Parameter(Mandatory = $false, Position = 1)]
        [int]$MaxAttempts = 5,

        [Parameter(Mandatory = $false, Position = 2)]
        [int]$TimeoutSeconds = 1
      )
      # Define successful status values
      $a = 1; $ic = $false; $SS = [System.Net.NetworkInformation.IPStatus[]]@( "Success", "TtlExpired" )
      while ($a -le $MaxAttempts -and !$ic) {
        try {
          Write-Verbose "Test connection - Attempt [$a/$MaxAttempts]"
          $cr = Test-Connection -TargetName $Target -Count 1 -TimeoutSeconds $TimeoutSeconds -ea Ignore
          $ic = $SS.Contains($cr.Status)
          $cr | Out-String | Write-Host -f (@{ 1 = "Green"; 0 = "Red" }[[int]$ic]);
        } catch {
          Write-Verbose "Exception occurred on attempt $a : $($_.Exception.Message)"
        }
        if ($a -lt $MaxAttempts) { Start-Sleep -Milliseconds 600 }; $a++
      }
      return $ic
    }
  }
  Process {
    #region packagefeed
    # .DESCRIPTION
    # This will fix any crazy errors you might have when installing modules:
    $PackageProviders = Get-PackageProvider -ListAvailable -ea Ignore -Verbose:$false
      ("NuGet", "PowerShellGet") | ForEach-Object {
      if (!$PackageProviders.Name.Contains($_)) { Install-PackageProvider -Name $_ -Force }
      Get-PackageProvider -Name $_ -ForceBootstrap -Verbose:$false
    }
    if ($null -eq (Get-PSRepository -Name PSGallery -ea Ignore -Verbose:$false)) {
      Unregister-PSRepository -Name PSGallery -Verbose:$false -ea Ignore
      Register-PSRepository -Default -InstallationPolicy Trusted
    }
    if ((Get-PSRepository -Name PSGallery).InstallationPolicy -ne 'Trusted') {
      Set-PSRepository -Name PSGallery -InstallationPolicy Trusted -Verbose:$false
    }
    #endregion packagefeed
    #region buildrequirements
    Write-Host "Resolve build requirements: [$($build_requirements -join ', ')]" -f Green
    $IsGithubRun = ![string]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable('GITHUB_WORKFLOW'))
    $IsConnected = $IsGithubRun ? $true : $(Test-NetworkConnectivity); $InstalledModules = $(if (!$IsConnected) { (Get-Module -Verbose:$false) + (Get-InstalledModule -Verbose:$false) | Select-Object -Unique -ExpandProperty Name } else { @() })
    $L = (($build_requirements | Select-Object @{l = 'L'; e = { $_.Length } }).L | Sort-Object -Descending)[0]
    foreach ($name in $build_requirements) {
      try {
        if ($IsConnected) {
          Install-Module -Name $name -Verbose:$false -ea Stop;
          Write-Host " [+] Installed module $name" -f Green
        } elseif ($InstalledModules -contains $name) {
          Write-Host " [+] Module $name$(' '* $($L - $name.Length))was already installed" -f Green
        } else {
          throw [System.Management.Automation.ItemNotFoundException]::new("Module $name is not installed.")
        }
      } catch {
        $PSCmdlet.ThrowTerminatingError($_)
      }
    }
    $psds = (Get-Module -Name $build_requirements -ListAvailable -Verbose:$false).Path | Sort-Object -Unique { Split-Path $_ -Leaf }
    $psds | Import-Module -Verbose:$false -ea Stop
    #endregion buildrequirements
    try {
      $Psake_BuildFile = New-Item $([IO.Path]::GetTempFileName().Replace('.tmp', '.ps1'))
      Set-Content -Path $Psake_BuildFile -Value $script:PSake_ScriptBlock.ToString().Replace('<build_requirements>', [string]('@("' + ($build_requirements -join '", "') + '")')) | Out-Null
      if ($Help.IsPresent) {
        Write-Heading "Getting help"; Get-PSakeScriptTasks -BuildFile $Psake_BuildFile.FullName | Sort-Object -Property Name | Format-Table -Property Name, Description, Alias, DependsOn;
        exit 0
      };
      [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);
      Set-BuildVariables $Path $env:RUN_ID
      Write-Heading "Invoking psake with task: [ $($Task -join ', ') ]"
      if ($Task -contains 'TestOnly') {
        Set-Variable -Name ExcludeTag -Scope global -Value @('Module')
      } else {
        Set-Variable -Name ExcludeTag -Scope global -Value $null
      }
      $psakeParams = @{
        nologo    = $true
        buildFile = $Psake_BuildFile.FullName
        taskList  = $Task
      }
      Invoke-psake @psakeParams @verbose
    } catch {
      $psake.error_message = $_
      $PSCmdlet.ThrowTerminatingError($_)
    } finally {
      $psake.build_success = $null -eq $psake.error_message
      $LocalPSRepo = [IO.Path]::Combine([environment]::GetEnvironmentVariable("HOME"), 'LocalPSRepo'); $Host.UI.WriteLine()
      Remove-Item $Psake_BuildFile -ea Ignore -Verbose:$false | Out-Null
      if ($psake.build_success) {
        Write-Heading "Create a Local repository"
        if (!(Get-Variable -Name IsWindows -ea Ignore) -or $(Get-Variable IsWindows -ValueOnly)) {
          $LocalPSRepo = [IO.Path]::Combine([environment]::GetEnvironmentVariable("UserProfile"), 'LocalPSRepo')
        }; if (!(Test-Path -Path $LocalPSRepo -PathType Container -ea Ignore)) { New-Directory -Path $LocalPSRepo | Out-Null }
        Register-PSRepository LocalPSRepo -SourceLocation $LocalPSRepo -PublishLocation $LocalPSRepo -InstallationPolicy Trusted -Verbose:$false -ea Ignore;
        Register-PackageSource -Name LocalPsRepo -Location $LocalPSRepo -Trusted -ProviderName Bootstrap -ea Ignore
        Write-Verbose "Verify that the new repository was created successfully"
        if ($null -ne (Get-PSRepository LocalPSRepo -Verbose:$false -ea Ignore)) {
          $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 -ea 'SilentlyContinue'
          }
          Write-Heading "Publish to Local PsRepository"
          $RequiredModules = Read-ModuleData -File ([IO.Path]::Combine($ModulePath, "$([Environment]::GetEnvironmentVariable($env:RUN_ID + 'ProjectName')).psd1")) -Property 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 -ea Ignore
          }
          Publish-Module -Path $ModulePath -Repository LocalPSRepo
          Install-Module $ModuleName -Repository LocalPSRepo
          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
          }
        } else {
          $PSCmdlet.ThrowTerminatingError([System.Management.Automation.ErrorRecord]::new([System.Exception]::New('Failed to create LocalPsRepo', [System.IO.DirectoryNotFoundException]::New($LocalPSRepo)), 'LocalPsRepo_NOT_FOUND', 'ObjectNotFound', $LocalPSRepo))
        }
      }
      Write-EnvironmentSummary "Build $($psake.build_success ? "complete" : "Failed")"
      if (![bool][int]$env:IsAC -or $Task -contains 'Clean') {
        Write-Heading "CleanUp: Remove '$ModuleName' env variables and clean LocalPSRepo"
        if (![string]::IsNullOrWhiteSpace($env:RUN_ID)) {
          $OldEnvNames = [Environment]::GetEnvironmentVariables().Keys | Where-Object { $_ -like "$env:RUN_ID*" }
          if ($OldEnvNames.Count -gt 0) {
            foreach ($Name in $OldEnvNames) {
              Write-BuildLog "Remove env variable $Name"
              [Environment]::SetEnvironmentVariable($Name, $null)
            }
          } else {
            Write-BuildLog "No env variables to remove; Move on ...`n"
          }
        } else {
          Write-Warning "Invalid RUN_ID! can't remove env variables.`n"
        }
        if ($ModuleName) { Uninstall-Module $ModuleName -MinimumVersion $BuildNumber -ea Ignore }
        if ([IO.Directory]::Exists($LocalPSRepo)) {
          if ($null -ne (Get-PSRepository -Name 'LocalPSRepo' -ea Ignore -Verbose:$false)) {
            Invoke-Command -ScriptBlock ([ScriptBlock]::Create("Unregister-PSRepository -Name 'LocalPSRepo' -Verbose:`$false -ea Ignore"))
          }; Remove-Item $LocalPSRepo -Verbose:$false -Force -Recurse -ea Ignore
        }
        [Environment]::SetEnvironmentVariable('RUN_ID', $null)
      }
    }
  }
  end {
    exit ([int](!$psake.build_success))
  }
}