Public/Connect-UKGPro.ps1

function Connect-UKGPro {
    <#
    .SYNOPSIS
        Establishes an authenticated session with the UKG Pro HCM API. Supports
        three input flows: explicit parameters, a saved SecretManagement vault,
        or environment variables.

    .DESCRIPTION
        UKG Pro core REST APIs authenticate with THREE pieces sent on every call:
          - Basic auth: a web-service-account username/password, base64 encoded.
          - US-Customer-API-Key: your tenant's Customer API Key.
          - x-api-key: the User API Key generated for the same web service account.

        The base URL (hostname) is tenant-specific and assigned by UKG; find it
        under your Service Endpoint information (see
        https://developer.ukg.com/hcm/docs/web-service-account).

        Three ways to supply these values:

        1. -Explicit (default) — pass everything as parameters.
        2. -FromVault — pull hostname + two API keys from a Microsoft
           SecretManagement vault; prompt (or accept -Credential) for the
           web-service-account username/password. Requires the module to have
           been set up once with Save-UKGProCredential.
        3. -FromEnvironment — read all five values from env vars, for
           fully non-interactive scenarios (CI runners, scheduled tasks,
           containers). Env vars: UKGPRO_HOSTNAME, UKGPRO_USERNAME,
           UKGPRO_PASSWORD, UKGPRO_CUSTOMER_API_KEY, UKGPRO_USER_API_KEY.

        This cmdlet validates and stores all of that in a module-private session
        so the Get-UKGPro* cmdlets can authenticate automatically. Credentials
        are never passed on individual calls.

    .PARAMETER Hostname
        Your tenant's service endpoint host, with or without https://. Examples:
        'servicet.ultipro.com', 'https://service5.ultipro.com'.

    .PARAMETER Credential
        A PSCredential for the UKG Pro web service account (UserName = username,
        Password = password). Mandatory in the Explicit parameter set. Optional
        in the FromVault set — if omitted, the cmdlet calls Get-Credential
        to prompt.

    .PARAMETER CustomerApiKey
        Your tenant's Customer API Key (sent as US-Customer-API-Key).

    .PARAMETER UserApiKey
        The User API Key from the web service account (sent as x-api-key).

    .PARAMETER FromVault
        Pull hostname + two API keys from a SecretManagement vault (populated
        by Save-UKGProCredential). Prompts for the web-service-account
        username/password via Get-Credential unless -Credential is supplied.

    .PARAMETER VaultName
        Optional. Name of a specific SecretManagement vault to read from.
        Omit to use the current default vault.

    .PARAMETER FromEnvironment
        Read all five values from environment variables. Intended for CI /
        scheduled task / container scenarios. Throws with a list of missing
        env vars if any are absent.

    .PARAMETER PassThru
        Return the session object (secrets redacted) instead of nothing.

    .EXAMPLE
        $cred = Get-Credential
        Connect-UKGPro -Hostname 'service5.ultipro.com' `
                       -Credential $cred `
                       -CustomerApiKey 'abc123...' `
                       -UserApiKey 'def456...'

        Explicit flow — original v0.1.0 behavior, still supported.

    .EXAMPLE
        Save-UKGProCredential -Hostname 'service5.ultipro.com' `
                              -CustomerApiKey 'abc...' -UserApiKey 'def...'
        # Then, in every future session:
        Connect-UKGPro -FromVault

        Vault flow — one-time setup with Save-UKGProCredential, then a
        one-line connect that prompts only for the account username/password.

    .EXAMPLE
        $env:UKGPRO_HOSTNAME = 'service5.ultipro.com'
        $env:UKGPRO_USERNAME = 'svc_account'
        $env:UKGPRO_PASSWORD = 'redacted'
        $env:UKGPRO_CUSTOMER_API_KEY = 'abc...'
        $env:UKGPRO_USER_API_KEY = 'def...'
        Connect-UKGPro -FromEnvironment

        Environment flow — for CI, scheduled tasks, containers. Password
        supported here because the surrounding secret store (GitHub Actions,
        Azure Pipelines, etc.) injects it into the runtime.
    #>

    [CmdletBinding(DefaultParameterSetName = 'Explicit')]
    [OutputType([void], [pscustomobject])]
    param (
        [Parameter(Mandatory, ParameterSetName = 'Explicit')]
        [string]$Hostname,

        [Parameter(Mandatory, ParameterSetName = 'Explicit')]
        [Parameter(ParameterSetName = 'FromVault')]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential,

        [Parameter(Mandatory, ParameterSetName = 'Explicit')]
        [string]$CustomerApiKey,

        [Parameter(Mandatory, ParameterSetName = 'Explicit')]
        [string]$UserApiKey,

        [Parameter(Mandatory, ParameterSetName = 'FromVault')]
        [switch]$FromVault,

        [Parameter(ParameterSetName = 'FromVault')]
        [string]$VaultName,

        [Parameter(Mandatory, ParameterSetName = 'FromEnvironment')]
        [switch]$FromEnvironment,

        [switch]$PassThru
    )

    # --- Resolve the five values based on which parameter set was chosen ---

    if ($PSCmdlet.ParameterSetName -eq 'FromVault') {
        Assert-UKGProSecretManagement -VaultName $VaultName

        $extra = @{}
        if ($VaultName) { $extra['Vault'] = $VaultName }

        $Hostname       = Get-Secret -Name 'UKGPro-Hostname'       -AsPlainText @extra
        $CustomerApiKey = Get-Secret -Name 'UKGPro-CustomerApiKey' -AsPlainText @extra
        $UserApiKey     = Get-Secret -Name 'UKGPro-UserApiKey'     -AsPlainText @extra

        if (-not $Credential) {
            $Credential = Get-Credential -Message 'UKG Pro web service account (username + password)'
        }
    }
    elseif ($PSCmdlet.ParameterSetName -eq 'FromEnvironment') {
        $envMap = @{
            'UKGPRO_HOSTNAME'         = $null
            'UKGPRO_USERNAME'         = $null
            'UKGPRO_PASSWORD'         = $null
            'UKGPRO_CUSTOMER_API_KEY' = $null
            'UKGPRO_USER_API_KEY'     = $null
        }
        foreach ($name in @($envMap.Keys)) {
            $envMap[$name] = [Environment]::GetEnvironmentVariable($name)
        }
        $missing = @($envMap.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object Name | Sort-Object)
        if ($missing.Count -gt 0) {
            throw "Connect-UKGPro -FromEnvironment requires these env vars to be set: $($missing -join ', ')"
        }

        $Hostname       = $envMap['UKGPRO_HOSTNAME']
        $CustomerApiKey = $envMap['UKGPRO_CUSTOMER_API_KEY']
        $UserApiKey     = $envMap['UKGPRO_USER_API_KEY']
        $securePassword = ConvertTo-SecureString $envMap['UKGPRO_PASSWORD'] -AsPlainText -Force
        $Credential     = [pscredential]::new($envMap['UKGPRO_USERNAME'], $securePassword)
    }
    # Explicit path: all five values are already parameter-bound.

    # --- Common connect logic (identical across all three flows) ---

    # Normalize the host into a clean https base URL with no trailing slash.
    $h = $Hostname.Trim()
    $h = $h -replace '^https?://', ''
    $h = $h.TrimEnd('/')
    $baseUrl = "https://$h"

    # Pre-compute the Basic token once; store that rather than the raw password.
    # ASCII matches what UKG's own examples use; service-account credentials
    # are ASCII in practice, so this is equivalent to UTF-8 for any real input.
    $user = $Credential.UserName
    $pass = $Credential.GetNetworkCredential().Password
    $pair = "{0}:{1}" -f $user, $pass
    $basicToken = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($pair))

    # Module-private (script scope) so the token doesn't leak into global state.
    $script:UKGProSession = [pscustomobject]@{
        BaseUrl        = $baseUrl
        Hostname       = $h
        Username       = $user
        BasicToken     = $basicToken
        CustomerApiKey = $CustomerApiKey
        UserApiKey     = $UserApiKey
        ConnectedAt    = Get-Date
    }

    Write-Verbose "UKG Pro session established for $baseUrl (user $user)."

    if ($PassThru) {
        # UserApiKey / CustomerApiKey are secrets — deliberately omitted here.
        [pscustomobject]@{
            BaseUrl     = $script:UKGProSession.BaseUrl
            Username    = $script:UKGProSession.Username
            ConnectedAt = $script:UKGProSession.ConnectedAt
        }
    }
}