ps-utilities.psm1

# This file contains random Powershell utilities required by some modules
# Nothing is exported from here

# Confirmation helper function
function Get-Confirmation
{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]$Title,
        [Parameter(Mandatory=$true,Position=1)]
        [string]$Message,
        [Parameter(Mandatory=$true,Position=2)]
        [string]$YesDescription,
        [Parameter(Mandatory=$true,Position=3)]
        [string]$NoDescription
    )

    $Yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", $YesDescription
    $No = New-Object System.Management.Automation.Host.ChoiceDescription "&No", $NoDescription
    $Options = [System.Management.Automation.Host.ChoiceDescription[]]($Yes, $No)
    $Result = $host.ui.PromptForChoice($Title, $Message, $Options, 0)
    switch ($result)
    {
        0 {$true}
        1 {$false}
    }
}
# Add web client type with controllable timeout
function Add-ExWebClientExType
{
    [CmdletBinding()]
    Param(
    )

    if (-not ([System.Management.Automation.PSTypeName]"Ex.WebClientEx").Type)
    {
        Add-Type -WarningAction SilentlyContinue -TypeDefinition @"
using System;
using System.Net;
 
namespace Ex
{
    public class WebClientEx : WebClient
    {
        int _timeoutSeconds;
 
        public WebClientEx(int timeoutSeconds)
        {
            _timeoutSeconds = timeoutSeconds;
        }
        protected override WebRequest GetWebRequest(Uri uri)
        {
            var webRequest = base.GetWebRequest(uri);
            webRequest.Timeout = (int)TimeSpan.FromSeconds(_timeoutSeconds).TotalMilliseconds;
            return webRequest;
        }
    }
}
"@

    }
}
# Test whether a string is an IP address
function Test-IpAddress
{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true)]
        [string]$IpAddress
    )

    [bool]($IpAddress -as [IPAddress])
}

# Certificate helper function
function Get-CertificateFileContents
{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true, Position=0)]
        [string]$CertificateFile
    )

    if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" }
    if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") }

    try
    {
        $local:CertificateFullPath = (Resolve-Path $CertificateFile).ToString()
        if ((Get-Item $local:CertificateFullPath).Length -gt 100kb)
        {
            throw "'$CertificateFile' appears to be too large to be a certificate"
        }
    }
    catch
    {
        throw "'$CertificateFile' does not exist"
    }
    $local:CertificateContents = [string](Get-Content $local:CertificateFullPath)
    if (-not ($CertificateContents.StartsWith("-----BEGIN CERTIFICATE-----")))
    {
        Write-Host "Converting to Base64..."
        $local:CertificateContents = [System.IO.File]::ReadAllBytes($local:CertificateFullPath)
        $local:CertificateContents = [System.Convert]::ToBase64String($local:CertificateContents)
    }

    $local:CertificateContents
}
# Helper function for finding tools to generate certificates
function Get-Tool
{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true, Position=0)]
        [string[]]$Paths,
        [Parameter(Mandatory=$true, Position=1)]
        [string]$Tool
    )

    if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" }
    if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") }

    foreach ($local:SearchPath in $Paths)
    {
        Write-Host "Searching $($local:SearchPath) for $Tool"
        $local:ToolPath = (Get-ChildItem -Recurse -EA SilentlyContinue $local:SearchPath | Where-Object { $_.Name -eq $Tool })
        if ($local:ToolPath.Length -gt 0)
        {
            $local:ToolPath[-1].Fullname
            return
        }
    }
    throw "Unable to find $Tool"
}
# Helper function for getting a client certificate from either the system store or from a PFX file
function Use-CertificateFile
{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]$CertificateFile,
        [Parameter(Mandatory=$false,Position=1)]
        [SecureString]$Password
    )

    if (-not $Password)
    {
        Get-PfxCertificate -FilePath $CertificateFile
    }
    else
    {
        $local:X509KeyStorageFlag = 0
        $local:Cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
        $local:Cert.Import([string]($CertificateFile), [SecureString]($Password), $local:X509KeyStorageFlag)
        $local:Cert
    }
}
# Helper function for identifying a particular asset account
function Resolve-SgDevOpsAvailableAccount
{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$false, Position=0)]
        [object]$Asset,
        [Parameter(Mandatory=$true, Position=1)]
        [object]$Account,
        [Parameter(Mandatory=$false)]
        [string]$Domain
    )

    if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" }
    if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") }

    # Coalesce an object down to an ID
    # This will allow passing a Safeguard asset or asset account to this method
    if ($Asset.Id -as [int])
    {
        $Asset = $Asset.Id
    }
    if ($Account.Id -as [int])
    {
        $Account = $Account.Id
    }

    if (-not ($Account -as [int]))
    {
        # Account is a string
        if (-not ($Asset -as [int]))
        {
            # Asset is a string, search for Account name and Asset name
            $local:AssetAccount = (Invoke-SgDevOpsMethod GET "Safeguard/AvailableAccounts" `
                -Parameters @{ filter = "(SystemName ieq '$Asset') and (Name ieq '$Account')" })
            if (-not $local:AssetAccount)
            {
                # not found, search for Account name and Asset network address
                $local:AssetAccount = (Invoke-SgDevOpsMethod GET "Safeguard/AvailableAccounts" `
                    -Parameters @{ filter = "(SystemNetworkAddress ieq '$Asset') and (Name ieq '$Account')" })
            }
        }
        else
        {
            # Asset is an ID, search for Account name
            $local:AssetAccount = (Invoke-SgDevOpsMethod GET "Safeguard/AvailableAccounts" `
                -Parameters @{ filter = "(SystemId eq $Asset) and (Name ieq '$Account')" })
        }
    }
    else
    {
        # Account is an ID, simply get the ID
        $local:AssetAccount = (Invoke-SgDevOpsMethod GET "Safeguard/AvailableAccounts" -Parameters @{ filter = "Id eq $Account" })
    }

    if (-not $local:AssetAccount)
    {
        throw "Unable to find asset account matching asset='$Asset' and account='$Account'"
    }
    if ($local:AssetAccount.Count -ne 1)
    {
        throw "Found $($local:AssetAccount.Count) asset accounts matching asset='$Asset' and account='$Account'"
    }
    $local:AssetAccount
}

function Resolve-SgDevOpsRegisteredAccount
{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$false, Position=0)]
        [object]$Asset,
        [Parameter(Mandatory=$true, Position=1)]
        [object]$Account,
        [Parameter(Mandatory=$false)]
        [string]$Domain
    )

    if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" }
    if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") }

    if ($Account -as [int])
    {
        $local:AccountId = $Account
    }
    else
    {
        $local:AvailableAccount = (Resolve-SgDevOpsAvailableAccount -Asset $Asset -Account $Account -Domain $Domain)
        $local:AccountId = $local:AvailableAccount.Id
    }

    ## look up the registered account for the available account
    $local:RegisteredAccount = (Invoke-SgDevOpsMethod GET "Safeguard/A2ARegistration/RetrievableAccounts/$($local:AccountId)")

    if (-not $local:RegisteredAccount)
    {
        throw "Unable to find registered asset account matching asset='$Asset' and account='$Account'"
    }
    $local:RegisteredAccount
}