functions/utils.ps1

<#PSScriptInfo
.VERSION 1.1.0
.GUID 6d09dd8f-2f19-4e91-9c1a-dfd848e50884
.AUTHOR Black Duck
.COPYRIGHT Copyright 2024 Black Duck Software, Inc. All rights reserved.
.DESCRIPTION Includes utility-related helpers.
#>


function Convert-Base64([string] $file) {

    $path = join-path (get-location) $file
    $contents = [io.file]::ReadAllText($path)
    $contentBytes = [text.encoding]::ascii.getbytes($contents)
    [convert]::ToBase64String($contentBytes)
}

function Get-AppCommandPath([string] $commandName) {

    $command = Get-Command $commandName -Type Application -ErrorAction SilentlyContinue
    if ($null -eq $command) {
        return $null
    }
    $command.Path
}

function Test-IsBlacklisted([string] $text, [string[]] $blacklist) {

    $null -ne ($blacklist | Where-Object {
        $text -ceq $_ -or $text.Contains($_)
    })
}

function Test-IsValidParameterValue([string] $text, [string] $validationExpr, [switch] $permitEmpty) {
    if ($text -eq '' -and $permitEmpty) {
        return $text
    }
    $text -cmatch $validationExpr
}

function Read-HostText([string] $prompt, `
    [int] $minimumLength, [int] $maximumLength, `
    [string[]] $blacklist, `
    [bool] $isSecure, `
    [string] $validationExpr, [string] $validationHelp, `
    [switch] $allowBlankEntry) {

    if ($prompt -eq '') {
        $prompt = ' '
    }

    $instruction = ''
    if ($minimumLength -ne 0) {
        if ($maximumLength -ne 0) {
            $instruction = "(length: $minimumLength-$maximumLength)"
        } else {
            $instruction = "(minimum length: $minimumLength)"
        }
    } else {
        if ($maximumLength -ne 0) {
            $instruction = "(maximum length: $maximumLength)"
        }
    }
    if ($instruction.Length -ne 0) {
        $instruction = " $instruction"
    }

    if ($minimumLength -eq 0) {
        $minimumLength = [int]::MinValue
    }
    if ($maximumLength -eq 0) {
        $maximumLength = [int]::MaxValue
    }

    while ($true) {
        $text = Read-Host -Prompt "$prompt$instruction" -AsSecureString:$isSecure
        if ($isSecure) {
            $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($text)
            $text = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($BSTR)
        }

        if ($text -eq '' -and $allowBlankEntry) {
            return $text
        }

        if (($text -ne '') -and ($text.Length -ge $minimumLength) -and ($text.Length -le $maximumLength)) {
            
            if (Test-IsBlacklisted $text $blacklist) {
                Write-Host "The value cannot contain the following values; please try again.`n$blacklist"
                continue
            }

            if (-not (Test-IsValidParameterValue $text $validationExpr)) {
                if ('' -ne $validationHelp) {
                    Write-Host $validationHelp
                } else {
                    Write-Host "The value you specified is invalid (regex: $validationExpr); please try again."
                }
                continue
            }

            return $text
        }
    }
}

function Read-HostSecureText([string] $prompt, [int] $minimumLength, [int] $maximumLength, [string[]] $blacklist, [switch] $allowBlankEntry) {
    Read-HostText $prompt $minimumLength $maximumLength $blacklist $true -allowBlankEntry:$allowBlankEntry
}

function Invoke-GitClone([string] $url, [string] $branch, [string] $directory) {

    git clone -b $branch $url $directory --config advice.detachedhead=false
    if ($LASTEXITCODE -ne 0) {
        throw "Unable to run git clone with $url and branch $branch, git exited with code $LASTEXITCODE."
    }
}

function Test-IsCore {
    $PSVersionTable.PSEdition -eq 'Core'
}

function Test-MinPsMajorVersion([int] $majorVersion) {
    $PSVersionTable.PSVersion.Major -ge $majorVersion
}

function Test-IsElevated {

    if ($IsWindows) {
        return [bool](([Security.Principal.WindowsIdentity]::GetCurrent()).groups -match "S-1-5-32-544")
    }
    (id -u) -eq 0
}

function Get-IPv4AddressList([string] $hostname) {

    # not using Resolve-DnsName here because it's currently unavailable on ubuntu
    $entry = [net.dns]::gethostentry($hostname)
    $list = $entry.AddressList | Where-Object { -not $_.IsIPv6LinkLocal } | ForEach-Object { $_.IPAddressToString }
    [string]::Join(',', $list)
}

function ConvertTo-YamlMap([string[]] $mapItems) {

    if ($mapItems.Count -eq 0) {
        return '{}'
    }
    
    '{' + '{0}' -f ([string]::Join(',', $mapItems)) + '}'
}

function Test-EmailAddress([string] $emailAddress) {
    try {
        new-object net.mail.mailaddress($emailAddress, 'display name') | out-null
        $true
    } catch {
        $false
    }
}

function ConvertTo-YamlMap([hashtable] $table) {
    ConvertTo-Map $table '' ':' ','
}

function ConvertTo-PsonMap([hashtable] $table) {
    ConvertTo-Map $table '@' '=' ';'
}

function ConvertTo-Map([hashtable] $table, [string] $prefix, [string] $assignment, [string] $separator) {
    
    $sb = new-object text.stringbuilder("$prefix{")
    $initialLength = $sb.Length
    $table.Keys | Sort-Object | ForEach-Object {
        if ($sb.Length -ne $initialLength) {
            $sb.Append($separator) | out-null
        }
        $sb.Append((Format-KeyValueAssignment $_ $table[$_] $assignment)) | out-null
    }
    $sb.Append('}') | out-null
    return $sb.ToString()
}

function ConvertTo-YamlStringArray([string[]] $items) {

    if ($null -eq $items) {
            return '[]'
    }

    '[' + "'{0}'" -f ([string]::Join("','", $items)) + ']'
}

function ConvertTo-YamlIntArray([int[]] $items) {

    if ($null -eq $items) {
        return '[]'
    }

    '[' + "{0}" -f ([string]::Join(",", $items)) + ']'
}

function ConvertTo-PsonStringArray([string[]] $items) {

    if ($null -eq $items) {
            return '@()'
    }

    '@(' + "'{0}'" -f ([string]::Join("','", $items)) + ')'
}

function Format-KeyValueAssignment([string] $key, [string] $value, [string] $assignment) {
    "'{0}'{1}'{2}'" -f ($key.Replace("'", "''")),$assignment,($value.Replace("'", "''"))
}

function Write-ImportantNote([string] $message) {
    Write-Host ('NOTE: {0}' -f $message) -ForegroundColor Black -BackgroundColor White
}

function Write-ErrorMessageAndExit([string] $message) {
    $local:ErrorActionPreference = 'Stop'
    Write-Error $message
}

function Get-SemanticVersionComponents([string] $version) {

    if ($version.ToLower().StartsWith('v')) {
        $version = $version.Substring(1)
    }

    # Regular Expression from https://semver.org/
    $semanticVersionRegex = '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'
    if (-not ($version -match $semanticVersionRegex)) {
        throw "'$version' is not a semantic version"
    }
    $matches
}