Public/Connect-CIEM.ps1

function Connect-CIEM {
    <#
    .SYNOPSIS
        Establishes authentication to all configured cloud providers for CIEM scans.

    .DESCRIPTION
        Reads authentication configuration from the database and connects to each
        enabled cloud provider. This function must be called once before running
        any CIEM scans. The connection is cached for the duration of the PowerShell
        session.

        Provider-specific connector functions are discovered dynamically by naming
        convention: Connect-CIEM<ProviderName> (e.g., Connect-CIEMAzure from the
        Devolutions.CIEM.Azure module).

    .PARAMETER Provider
        Optional. Connect only to specific provider(s). If not specified, connects
        to the default provider defined in the database.

    .PARAMETER AuthenticationProfile
        Optional. A pre-resolved authentication profile object to pass to the
        provider connector (e.g., CIEMAzureAuthenticationProfile). If not
        provided, the connector looks up the active profile automatically.

    .PARAMETER Force
        Force re-authentication even if already connected.

    .OUTPUTS
        [PSCustomObject] Connection summary showing status for each provider.

    .EXAMPLE
        Connect-CIEM
        # Connects to the default provider

    .EXAMPLE
        Connect-CIEM -Provider Azure
        # Connects only to a specific provider

    .EXAMPLE
        Connect-CIEM -Force
        # Forces re-authentication even if already connected

    .NOTES
        This function must be called before running Invoke-CIEMScan or any other
        scan functions.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter()]
        [string[]]$Provider,

        [Parameter()]
        $AuthenticationProfile,

        [Parameter()]
        [switch]$Force
    )

    $ErrorActionPreference = 'Stop'
    $ProgressPreference = 'SilentlyContinue'

    Write-CIEMLog -Message "Connect-CIEM called with Provider=[$($Provider -join ',')] Force=$Force" -Severity INFO -Component 'Connect-CIEM'

    # Initialize auth context storage if not exists
    if (-not $script:AuthContext) {
        $script:AuthContext = @{}
        Write-CIEMLog -Message "Initialized empty AuthContext" -Severity DEBUG -Component 'Connect-CIEM'
    }

    # Determine which providers to connect
    if (-not $Provider) {
        $allProviders = Get-CIEMProvider
        $defaultProvider = ($allProviders | Where-Object Enabled | Select-Object -First 1).Name
        if (-not $defaultProvider) {
            throw "No providers configured. Ensure the database has been initialized with provider rows."
        }
        $Provider = @($defaultProvider)
        Write-CIEMLog -Message "No provider specified, using default: $defaultProvider" -Severity DEBUG -Component 'Connect-CIEM'
    }

    Write-CIEMLog -Message "Providers to connect: $($Provider -join ', ')" -Severity INFO -Component 'Connect-CIEM'

    # Use List for efficient collection (summary returned at end)
    $results = [System.Collections.Generic.List[PSCustomObject]]::new()

    foreach ($p in $Provider) {
        Write-CIEMLog -Message "Processing provider: $p" -Severity INFO -Component 'Connect-CIEM'
        Write-Verbose "Connecting to provider: $p"

        # Skip if already connected and not forcing
        # Cast enum to string for hashtable key lookup (enum keys != string keys)
        $providerKey = $p.ToString()

        if (-not $Force.IsPresent -and $script:AuthContext[$providerKey]) {
            $existing = $script:AuthContext[$providerKey]
            Write-CIEMLog -Message "$p is already connected (AccountId: $($existing.AccountId)). Skipping." -Severity INFO -Component 'Connect-CIEM'
            Write-Verbose "$p is already connected. Use -Force to re-authenticate."
            $results.Add([PSCustomObject]@{
                Provider        = $p
                Status          = 'AlreadyConnected'
                Account         = $existing.AccountId
                TenantId        = $existing.TenantId
                SubscriptionIds = @($existing.SubscriptionIds)
                Message         = 'Already authenticated. Use -Force to re-authenticate.'
            })
            continue
        }

        try {
            # Discover provider-specific connector function dynamically
            $connectorName = "Connect-CIEM$p"
            $connectorCmd = Get-Command -Name $connectorName -ErrorAction SilentlyContinue

            if (-not $connectorCmd) {
                throw "Provider connector function '$connectorName' not found. Ensure the Devolutions.CIEM.$p module is imported."
            }

            Write-CIEMLog -Message "Calling $connectorName..." -Severity INFO -Component 'Connect-CIEM'
            $connectorParams = @{}
            if ($AuthenticationProfile) { $connectorParams.AuthenticationProfile = $AuthenticationProfile }
            $authContext = & $connectorCmd @connectorParams
            $script:AuthContext[$providerKey] = $authContext

            # Build result based on what the auth context provides
            $resultObj = [PSCustomObject]@{
                Provider = $p
                Status   = 'Connected'
                Account  = $authContext.AccountId
                TenantId = if ($authContext.PSObject.Properties['TenantId']) { $authContext.TenantId } else { $null }
                Message  = "Connected as $($authContext.AccountType)"
            }

            # Add provider-specific properties
            if ($authContext.PSObject.Properties['SubscriptionIds']) {
                $resultObj | Add-Member -NotePropertyName 'Subscriptions' -NotePropertyValue @($authContext.SubscriptionIds).Count
                $resultObj | Add-Member -NotePropertyName 'SubscriptionIds' -NotePropertyValue @($authContext.SubscriptionIds)
            }

            Write-CIEMLog -Message "$p connection successful. AccountId: $($authContext.AccountId)" -Severity INFO -Component 'Connect-CIEM'
            $results.Add($resultObj)
        }
        catch {
            $script:AuthContext[$providerKey] = $null
            Write-CIEMLog -Message "Failed to connect to $p : $($_.Exception.Message)" -Severity ERROR -Component 'Connect-CIEM'
            Write-CIEMLog -Message "Stack trace: $($_.ScriptStackTrace)" -Severity DEBUG -Component 'Connect-CIEM'
            $results.Add([PSCustomObject]@{
                Provider = $p
                Status   = 'Failed'
                Account  = $null
                TenantId = $null
                Message  = $_.Exception.Message
            })
            Write-Error "Failed to connect to $p : $_"
        }
    }

    # Display summary using Write-Information for proper pipeline behavior
    Write-Information "`nCIEM Connection Summary:" -InformationAction Continue
    foreach ($r in $results) {
        $statusDisplay = " $($r.Provider): $($r.Status)"
        if ($r.Account) {
            $statusDisplay += " ($($r.Account))"
        }
        Write-Information $statusDisplay -InformationAction Continue
    }

    Write-CIEMLog -Message "Connect-CIEM completed. Results: $(($results | ForEach-Object { "$($_.Provider)=$($_.Status)" }) -join ', ')" -Severity INFO -Component 'Connect-CIEM'

    [PSCustomObject]@{
        Providers = $results
        Timestamp = Get-Date
    }
}