src/Invoke-CciGetBootstrap.ps1

function Invoke-CciGetBootstrap {
<#
.SYNOPSIS
    Prepares a freshly built machine in one command.
.DESCRIPTION
    Connects to the tenant, installs the module that tenant nominates for a
    machine build, imports it, and runs whatever entry point that module
    declares - so a technician at OOBE types two lines total:
 
        Install-Module cciget -Scope CurrentUser
        Invoke-CciGetBootstrap
 
    Nothing here knows what a customization is. The module to install comes
    from the tenant registry (bootstrapModule) and the final command comes from
    the installed module's own manifest (PrivateData.CciGet.BootstrapCommand),
    so cciget stays tenant-generic and a different tenant can nominate
    something else entirely.
 
    The individual steps remain available and unchanged; this only removes the
    need to remember their order and their arguments.
.PARAMETER Tenant
    Tenant to bootstrap from. Defaults to the configured default feed.
.PARAMETER Scope
    Install scope for the bootstrap module.
.PARAMETER Arguments
    Values for the declared entry point's parameters, e.g. @{ Team = 'platform' }.
    Anything not supplied is resolved interactively.
.PARAMETER Source
    Force the module source. By default an existing connection is reused, and
    otherwise the source is resolved the same way Connect-CciGet resolves it.
.PARAMETER SkipFinalStep
    Install and import, but do not run the module's declared entry point.
.EXAMPLE
    Invoke-CciGetBootstrap
.EXAMPLE
    Invoke-CciGetBootstrap -Arguments @{ Team = 'platform' }
.EXAMPLE
    Invoke-CciGetBootstrap -SkipFinalStep
#>

    [CmdletBinding()]
    param(
        [string]$Tenant,
        [ValidateSet('CurrentUser','AllUsers')]
        [string]$Scope = 'CurrentUser',
        [hashtable]$Arguments = @{},
        [ValidateSet('Auto','Feed','Store')]
        [string]$Source = 'Auto',
        [Alias('SkipPublish')]
        [switch]$SkipFinalStep
    )

    if (-not $Tenant) { $Tenant = (Get-CciGetConfig).defaultFeed }
    $feed = (_Resolve-CciGetFeed -Tenant $Tenant)[0]

    $bootstrapProp = $feed.PSObject.Properties['bootstrapModule']
    $bootstrapModule = if ($bootstrapProp) { $bootstrapProp.Value }
    if (-not $bootstrapModule) {
        throw "cciget: tenant '$($feed.name)' nominates no bootstrap module. Use Connect-CciGet and Install-CciModule directly."
    }

    Write-Host "cciget: preparing this machine from the '$($feed.name)' tenant."
    # Reuse an established connection rather than reconnecting: a second
    # Connect-CciGet re-resolves the source, so a session deliberately pointed
    # at the store would silently switch to the feed wherever an Azure CLI
    # session happens to exist.
    if ($PSBoundParameters.ContainsKey('Source')) {
        Connect-CciGet -Tenant $feed.name -Source $Source | Out-Null
    }
    elseif ($Script:CciGetSource) {
        Write-Host "cciget: using the existing '$Script:CciGetSource' connection."
    }
    else {
        Connect-CciGet -Tenant $feed.name | Out-Null
    }

    Install-CciModule -Name $bootstrapModule -Tenant $feed.name -Scope $Scope -NoGuidance

    $resolved = _Resolve-CciModuleName -Name $bootstrapModule -Feed $feed
    # -Global matters: without it the module loads into cciget's session state
    # and the caller's session sees none of its commands.
    Import-Module $resolved -Global -Force
    $module = Get-Module -Name $resolved | Select-Object -First 1
    Write-Host "cciget: imported $resolved $($module.Version)."

    $command = $null
    $required = @()
    $data = $module.PrivateData
    if ($data -is [hashtable] -and $data.ContainsKey('CciGet')) {
        $cciget = $data['CciGet']
        if ($cciget -is [hashtable]) {
            if ($cciget.ContainsKey('BootstrapCommand'))    { $command  = [string]$cciget['BootstrapCommand'] }
            if ($cciget.ContainsKey('BootstrapParameters')) { $required = @($cciget['BootstrapParameters']) }
        }
    }

    if (-not $command) {
        Write-Host "cciget: $resolved declares no entry point; nothing further to run."
        return
    }
    if ($SkipFinalStep) {
        Write-Host "cciget: skipping '$command' as requested. Run it yourself when ready."
        return
    }
    if (-not (Get-Command $command -ErrorAction SilentlyContinue)) {
        Write-Warning "cciget: $resolved declares '$command' but it is not available. Run it manually once the module is loaded."
        return
    }

    $splat = _Resolve-CciBootstrapArgument -Command $command -Required $required -Supplied $Arguments
    Write-Host ''
    Write-Host "cciget: running $command $(($splat.GetEnumerator() | ForEach-Object { "-$($_.Key) $($_.Value)" }) -join ' ')"
    Write-Host ''
    & $command @splat
}