Public/Get-emsEnvironmentFromLocalFile.ps1

<#PSScriptInfo
 
.VERSION 1.0.0
 
.GUID 666c04e1-2bab-4491-b18c-baa964f58bda
 
.AUTHOR enthus Managed Services GmbH
 
.COMPANYNAME enthus Managed Services GmbH
 
#>


#Requires -Version 5.1

<#
    .SYNOPSIS
    Loads the content of an environment file. Asks the user interactively for the desired environment.
 
    .DESCRIPTION
    This cmdlet loads the content of an environment file by prompting the user to select a tenant name and stage name. It searches for environment files matching the pattern '.env.{tenant name}.{stage name}.json' in the current directory and provides interactive selection for available options.
 
    The function validates file existence, provides user-friendly prompts with default values and returns the parsed JSON content. It supports the following stage name types: loc (local), stg (staging), prd (production) and tst (test).
 
    .NOTES
    PSVersion: 5.1.x or 7.2.x
 
    ENVIRONMENT:
    [ ] Azure Automation
    [ ] Azure Function
    [x] Local
    [ ] Nerdio
    [ ] PowerShell Universal
    [ ] Server
    [ ] ...
 
    REQUIRED CONTEXT:
    [ ] Application && Delegated
    [ ] Application
    [ ] Delegated
    [x] User
 
    REQUIRED PERMISSIONS:
    - None
 
    .PARAMETER TenantName
    Reference parameter that will be updated with the selected tenant name. This parameter is mandatory. The value is used as a default option if already set.
 
    .PARAMETER StageName
    Reference parameter that will be updated with the selected stage name. This parameter is mandatory. Supported values: loc, stg, prd, tst. The value is used as a default option if already set.
 
    .INPUTS
    None. This cmdlet does not accept pipeline input.
 
    .OUTPUTS
    System.Object. Returns the parsed JSON content of the selected environment file.
 
    .EXAMPLE
    $environmentTenantName = $null
    $environmentStageName = $null
    $environment = Get-emsEnvironmentFromLocalFile -TenantName ([ref]$environmentTenantName) -StageName ([ref]$environmentStageName)
 
    Prompts the user to select an environment file and returns its content while updating the tenant and stage name variables.
 
    .EXAMPLE
    $environmentTenantName = "contoso"
    $environmentStageName = "prd"
    $environment = Get-emsEnvironmentFromLocalFile -TenantName ([ref]$environmentTenantName) -StageName ([ref]$environmentStageName)
 
    Uses existing values as defaults and prompts for confirmation or allows changing the selection.
 
    .EXAMPLE
    $environmentTenantName = $null
    $environmentStageName = $null
    $environment = Get-emsEnvironmentFromLocalFile -TenantName ([ref]$environmentTenantName) -StageName ([ref]$environmentStageName) -Path "C:\Config"
 
    Searches for environment files in the specified directory instead of the current directory.
#>

function Get-emsEnvironmentFromLocalFile {
    [CmdletBinding()]
    [OutputType([System.Object])]
    param (
        [Parameter(Mandatory = $true)]
        [ref]$TenantName,

        [Parameter(Mandatory = $true)]
        [ref]$StageName,

        [Parameter()]
        [string]$Path = "./"
    )
    $verbosePrefix = "Get-emsEnvironmentFromLocalFile | "

    #region Validation
    if (!(Get-emsIsInteractiveHost)) {
        throw "This function requires an interactive PowerShell host."
    }
    #endregion Validation

    $validStageNames = @('loc', 'stg', 'prd', 'tst')
    $stageNamePattern = "($($validStageNames -join '|'))"

    try {
        #region File discovery
        $environmentFilesRegex = "^\.env\.(?<tenantName>[\w-]+)\.(?<stageName>$stageNamePattern)\.json$"
        $environmentFilesNamingExample = ".env.tenantName.stageName.json"

        Write-Verbose "$($verbosePrefix)Search for environment files in path '$Path' ..."

        if (!(Test-Path -Path $Path)) {
            throw [System.IO.DirectoryNotFoundException]::new("The specified path '$Path' does not exist.")
        }

        $files = Get-ChildItem -Path $Path -File -ErrorAction Stop

        Write-Verbose "$($verbosePrefix)Filter files by environment naming schema ('$environmentFilesRegex') ..."
        $environmentFiles = $files | Where-Object { $_.Name -match $environmentFilesRegex } | ForEach-Object {
            $match = [regex]::Match($_.Name, $environmentFilesRegex)
            [PSCustomObject]@{
                File = $_
                TenantName = $match.Groups['tenantName'].Value
                StageName = $match.Groups['stageName'].Value
                FullName = $_.FullName
            }
        }

        if ($environmentFiles.Count -eq 0) {
            throw [System.IO.FileNotFoundException]::new("No environment files found with naming schema: '$environmentFilesNamingExample'.")
        }

        Write-Verbose "$($verbosePrefix)>> $($environmentFiles.Count) environment file(s) found"
        #endregion File discovery

        #region Tenant name selection
        $availableTenantNames = $environmentFiles.TenantName | Sort-Object -Unique
        Write-Verbose "$($verbosePrefix)Available tenant names: $($availableTenantNames -join ', ')"

        $selectedTenantName = Read-emsPrompt -Title "Environment file selection" -Question "Please enter the tenant name you want to use:" -Options $availableTenantNames -DefaultOption $TenantName.Value

        # Reset stage name if tenant name has changed
        if ($TenantName.Value -and ($TenantName.Value -ne $selectedTenantName)) {
            Write-Verbose "$($verbosePrefix)Tenant name changed from '$($TenantName.Value)' to '$selectedTenantName'; reset stage name to null ..."
            $StageName.Value = $null
        }
        #endregion Tenant name selection

        #region Stage name selection
        $availableStageNames = $environmentFiles | Where-Object { $_.TenantName -eq $selectedTenantName } | Select-Object -ExpandProperty StageName | Sort-Object -Unique
        Write-Verbose "$($verbosePrefix)Available stage names for tenant name '$selectedTenantName': $($availableStageNames -join ', ')"

        $selectedStageName = Read-emsPrompt -Title "Environment file selection" -Question "Please enter the stage name you want to use:" -Options $availableStageNames -DefaultOption $StageName.Value
        #endregion Stage name selection

        #region File loading and validation
        $selectedFile = $environmentFiles | Where-Object { $_.TenantName -eq $selectedTenantName -and $_.StageName -eq $selectedStageName } | Select-Object -First 1

        if (!$selectedFile) {
            throw [System.IO.FileNotFoundException]::new("Environment file for tenant name '$selectedTenantName' and stage name '$selectedStageName' not found.")
        }

        $environmentFilePath = $selectedFile.FullName
        Write-Verbose "$($verbosePrefix)Load environment file '$environmentFilePath' ..."

        try {
            $environmentFileContent = Get-Content -Path $environmentFilePath -Raw -Encoding UTF8 -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
        }
        catch [System.ArgumentException] {
            throw [System.FormatException]::new("Failed to parse JSON content from environment file '$environmentFilePath'. The file contains invalid JSON: $($_.Exception.Message)")
        }
        catch {
            throw [System.IO.IOException]::new("Failed to read environment file '$environmentFilePath': $($_.Exception.Message)")
        }
        #endregion File loading and validation

        #region Create result object
        Write-Verbose "$($verbosePrefix)>> Environment successfully loaded: $selectedTenantName.$selectedStageName"

        # Store selected values in reference parameters for backward compatibility
        $TenantName.Value = $selectedTenantName
        $StageName.Value = $selectedStageName

        Write-Output $environmentFileContent
        #endregion Create result object
    } catch {
        $errorRecord = [System.Management.Automation.ErrorRecord]::new(
            $_.Exception,
            'EnvironmentFileLoadFailed',
            [System.Management.Automation.ErrorCategory]::InvalidOperation,
            $environmentFilePath
        )
        $PSCmdlet.ThrowTerminatingError($errorRecord)
    }
}