Setup/Save-M365ConnectionProfile.ps1

# ------------------------------------------------------------------
# Shared helper: read/write .m365assess.json config
# ------------------------------------------------------------------
function Get-ProfileConfigPath {
    [CmdletBinding()]
    [OutputType([string])]
    param()
    $root = if ($PSCommandPath) { Split-Path -Parent (Split-Path -Parent $PSCommandPath) } else { $PSScriptRoot }
    Join-Path -Path $root -ChildPath '.m365assess.json'
}

function Read-ProfileConfig {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param([string]$ConfigPath)
    $config = @{}
    if (Test-Path -Path $ConfigPath) {
        try {
            $config = Get-Content -Path $ConfigPath -Raw | ConvertFrom-Json -AsHashtable
        }
        catch {
            Write-Warning "Could not read config: $_"
        }
    }
    if (-not $config.ContainsKey('profiles')) { $config['profiles'] = @{} }
    return $config
}

function Write-ProfileConfig {
    [CmdletBinding()]
    param([hashtable]$Config, [string]$ConfigPath)
    $Config | ConvertTo-Json -Depth 5 | Set-Content -Path $ConfigPath -Encoding UTF8
}

function Build-ProfileEntry {
    <#
    .SYNOPSIS
        Builds a profile hashtable from parameters.
    #>

    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [string]$TenantId,
        [string]$AuthMethod,
        [string]$M365Environment,
        [string]$ClientId,
        [string]$CertificateThumbprint,
        [string]$UserPrincipalName,
        [string]$AppName
    )
    $entry = @{
        tenantId    = $TenantId
        authMethod  = $AuthMethod
        environment = $M365Environment
        saved       = (Get-Date -Format 'yyyy-MM-dd')
        lastUsed    = $null
    }
    if ($ClientId) { $entry['clientId'] = $ClientId }
    if ($CertificateThumbprint) { $entry['thumbprint'] = $CertificateThumbprint }
    if ($UserPrincipalName) { $entry['upn'] = $UserPrincipalName }
    if ($AppName) { $entry['appName'] = $AppName }
    return $entry
}

# Shared parameter set for profile creation/update
$script:ProfileParams = @(
    'ProfileName', 'TenantId', 'AuthMethod', 'ClientId',
    'CertificateThumbprint', 'UserPrincipalName', 'M365Environment', 'AppName'
)

# ------------------------------------------------------------------
# New-M365ConnectionProfile -- create (fail if exists)
# ------------------------------------------------------------------
function New-M365ConnectionProfile {
    <#
    .SYNOPSIS
        Creates a new named connection profile.
    .DESCRIPTION
        Creates a connection profile in .m365assess.json. Fails if a profile
        with the same name already exists -- use Set-M365ConnectionProfile to
        update an existing profile.
    .PARAMETER ProfileName
        A friendly name for this connection profile (e.g., 'Production', 'DevTenant').
    .PARAMETER TenantId
        Tenant ID or domain (e.g., 'contoso.onmicrosoft.com').
    .PARAMETER AuthMethod
        Authentication method: Interactive, DeviceCode, Certificate, ManagedIdentity.
    .PARAMETER ClientId
        Application (client) ID for app-only authentication.
    .PARAMETER CertificateThumbprint
        Certificate thumbprint for app-only authentication.
    .PARAMETER UserPrincipalName
        Optional UPN for EXO/Purview interactive auth.
    .PARAMETER M365Environment
        Cloud environment: commercial, gcc, gcchigh, dod.
    .PARAMETER AppName
        Optional friendly name for the app registration.
    .EXAMPLE
        New-M365ConnectionProfile -ProfileName 'Production' -TenantId 'contoso.onmicrosoft.com' -AuthMethod Interactive
    .EXAMPLE
        New-M365ConnectionProfile -ProfileName 'CertAuth' -TenantId 'contoso.onmicrosoft.com' -ClientId '...' -CertificateThumbprint 'ABC123' -AuthMethod Certificate
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$ProfileName,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$TenantId,

        [Parameter()]
        [ValidateSet('Interactive', 'DeviceCode', 'Certificate', 'ManagedIdentity')]
        [string]$AuthMethod = 'Interactive',

        [Parameter()]
        [string]$ClientId,

        [Parameter()]
        [string]$CertificateThumbprint,

        [Parameter()]
        [string]$UserPrincipalName,

        [Parameter()]
        [ValidateSet('commercial', 'gcc', 'gcchigh', 'dod')]
        [string]$M365Environment = 'commercial',

        [Parameter()]
        [string]$AppName
    )

    if ($AuthMethod -eq 'Certificate' -and (-not $ClientId -or -not $CertificateThumbprint)) {
        Write-Error "Certificate auth requires both -ClientId and -CertificateThumbprint."
        return
    }

    $configPath = Get-ProfileConfigPath
    $config = Read-ProfileConfig -ConfigPath $configPath

    $existingKey = $config['profiles'].Keys | Where-Object { $_ -eq $ProfileName } | Select-Object -First 1
    if ($existingKey) {
        Write-Error "Profile '$existingKey' already exists. Use Set-M365ConnectionProfile to update it, or Remove-M365ConnectionProfile to delete it first."
        return
    }

    $entry = Build-ProfileEntry -TenantId $TenantId -AuthMethod $AuthMethod -M365Environment $M365Environment -ClientId $ClientId -CertificateThumbprint $CertificateThumbprint -UserPrincipalName $UserPrincipalName -AppName $AppName
    $config['profiles'][$ProfileName] = $entry
    Write-ProfileConfig -Config $config -ConfigPath $configPath
    Write-Host " Created connection profile '$ProfileName' for $TenantId" -ForegroundColor Green
}

# ------------------------------------------------------------------
# Set-M365ConnectionProfile -- create or update (upsert)
# ------------------------------------------------------------------
function Set-M365ConnectionProfile {
    <#
    .SYNOPSIS
        Creates or updates a named connection profile.
    .DESCRIPTION
        Upserts a connection profile in .m365assess.json. Creates the profile
        if it does not exist, or overwrites it if it does.
    .PARAMETER ProfileName
        A friendly name for this connection profile.
    .PARAMETER TenantId
        Tenant ID or domain (e.g., 'contoso.onmicrosoft.com').
    .PARAMETER AuthMethod
        Authentication method: Interactive, DeviceCode, Certificate, ManagedIdentity.
    .PARAMETER ClientId
        Application (client) ID for app-only authentication.
    .PARAMETER CertificateThumbprint
        Certificate thumbprint for app-only authentication.
    .PARAMETER UserPrincipalName
        Optional UPN for EXO/Purview interactive auth.
    .PARAMETER M365Environment
        Cloud environment: commercial, gcc, gcchigh, dod.
    .PARAMETER AppName
        Optional friendly name for the app registration.
    .EXAMPLE
        Set-M365ConnectionProfile -ProfileName 'Production' -TenantId 'contoso.onmicrosoft.com' -AuthMethod Certificate -ClientId '...' -CertificateThumbprint 'ABC123'
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$ProfileName,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$TenantId,

        [Parameter()]
        [ValidateSet('Interactive', 'DeviceCode', 'Certificate', 'ManagedIdentity')]
        [string]$AuthMethod = 'Interactive',

        [Parameter()]
        [string]$ClientId,

        [Parameter()]
        [string]$CertificateThumbprint,

        [Parameter()]
        [string]$UserPrincipalName,

        [Parameter()]
        [ValidateSet('commercial', 'gcc', 'gcchigh', 'dod')]
        [string]$M365Environment = 'commercial',

        [Parameter()]
        [string]$AppName
    )

    if ($AuthMethod -eq 'Certificate' -and (-not $ClientId -or -not $CertificateThumbprint)) {
        Write-Error "Certificate auth requires both -ClientId and -CertificateThumbprint."
        return
    }

    $configPath = Get-ProfileConfigPath
    $config = Read-ProfileConfig -ConfigPath $configPath
    $verb = if ($config['profiles'].ContainsKey($ProfileName)) { 'Updated' } else { 'Created' }

    $entry = Build-ProfileEntry -TenantId $TenantId -AuthMethod $AuthMethod -M365Environment $M365Environment -ClientId $ClientId -CertificateThumbprint $CertificateThumbprint -UserPrincipalName $UserPrincipalName -AppName $AppName
    $config['profiles'][$ProfileName] = $entry
    Write-ProfileConfig -Config $config -ConfigPath $configPath
    Write-Host " $verb connection profile '$ProfileName' for $TenantId" -ForegroundColor Green
}

# ------------------------------------------------------------------
# Remove-M365ConnectionProfile -- delete by name or all
# ------------------------------------------------------------------
function Remove-M365ConnectionProfile {
    <#
    .SYNOPSIS
        Removes a saved connection profile.
    .DESCRIPTION
        Deletes a named profile from .m365assess.json. Use -All to remove
        all saved profiles and reset the config file.
    .PARAMETER ProfileName
        Name of the profile to remove.
    .PARAMETER All
        Remove all saved profiles.
    .EXAMPLE
        Remove-M365ConnectionProfile -ProfileName 'OldTenant'
    .EXAMPLE
        Remove-M365ConnectionProfile -All
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ParameterSetName = 'ByName')]
        [ValidateNotNullOrEmpty()]
        [string]$ProfileName,

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

    $configPath = Get-ProfileConfigPath
    $config = Read-ProfileConfig -ConfigPath $configPath

    if ($All) {
        $count = $config['profiles'].Count
        $config['profiles'] = @{}
        Write-ProfileConfig -Config $config -ConfigPath $configPath
        Write-Host " Removed all $count connection profile(s)." -ForegroundColor Yellow
        return
    }

    # Case-insensitive lookup
    $matchKey = $config['profiles'].Keys | Where-Object { $_ -eq $ProfileName } | Select-Object -First 1
    if (-not $matchKey) {
        Write-Error "Profile '$ProfileName' not found. Use Get-M365ConnectionProfile to list available profiles."
        return
    }

    $config['profiles'].Remove($matchKey)
    Write-ProfileConfig -Config $config -ConfigPath $configPath
    Write-Host " Removed connection profile '$ProfileName'." -ForegroundColor Yellow
}

# Backward compatibility: alias Save- to Set-
Set-Alias -Name Save-M365ConnectionProfile -Value Set-M365ConnectionProfile -Scope Global