Private/Get-ModuleConfig.ps1

<#
.SYNOPSIS
    Retrieves the configuration for a PowerShell module.
 
.DESCRIPTION
    The Get-ModuleConfig function loads and returns the configuration for a PowerShell module.
    It automatically discovers the module path by traversing up from the command path to find
    the module manifest file (.psd1). The function manages both default configuration values
    and user-specific configuration overrides stored in a JSON file.
 
.PARAMETER CommandPath
    The path to the command or script file from which to determine the module location.
    This is typically passed as $PSCommandPath from the calling module.
 
.INPUTS
    None
    This function does not accept pipeline input.
 
.OUTPUTS
    System.Collections.Hashtable
    Returns a hashtable containing the module configuration settings including module name,
    version, paths, and user-configured options.
 
.EXAMPLE
    $config = Get-ModuleConfig -CommandPath $PSCommandPath
     
    Retrieves the configuration for the current module.
 
.EXAMPLE
    $config = Get-ModuleConfig -CommandPath "C:\Modules\MyModule\MyModule.psm1"
     
    Retrieves the configuration for a module at a specific path.
 
.NOTES
    Author: Nigel Tatschner
    Company: TheCodeSaiyan
    Version: 0.1.7
     
    This is a private function used internally by the tcs.core module for configuration
    management. It handles automatic creation of configuration files with default values
    and maintains version tracking for module updates.
#>

function Get-ModuleConfig {
    [OutputType([hashtable])]

    param (
        [parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        $CommandPath
    )
    try {
        Write-Verbose "CommandPath: $CommandPath"
        $BaseCommandPath = Split-Path -Path $CommandPath -Parent
        Write-Verbose "Initial BaseCommandPath: $BaseCommandPath"
        # Recursively step back through the CommandPath to find the module path that contains the module manifest file and get the module base path and module name
        while (-not (Get-ChildItem -Path $BaseCommandPath -Filter *.psd1)) {
            $ParentPath = Split-Path -Path $BaseCommandPath -Parent
            Write-Verbose "ParentPath: $ParentPath"
            if ($ParentPath -eq $BaseCommandPath) {
                Write-Verbose "ParentPath: $ParentPath is the same as BaseCommandPath: $BaseCommandPath"
                # Break the loop if the parent path is the same as the current path,
                # indicating that we've reached the root directory
                break
            }
            $BaseCommandPath = $ParentPath
            Write-Verbose "Recursive BaseCommandPath: $BaseCommandPath"
        }
        $ModuleFilePath = Join-Path -Path $BaseCommandPath -ChildPath $(Split-Path -Path $(Get-ChildItem -Path $BaseCommandPath -Filter *.psd1 | Select-Object -First 1) -Leaf)
        Write-Verbose "ModuleFilePath: $ModuleFilePath"
        $ModuleVersion = (Import-PowerShellDataFile -Path $($ModuleFilePath.Replace('.psm1','.psd1'))).ModuleVersion
        $ModulePath = Split-Path -Path $ModuleFilePath -Parent
        if ([string]::IsNullOrEmpty($ModulePath)) {
            Write-Error "ModulePath is empty or null."
            throw
        }
        Write-Verbose "ModulePath: $ModulePath"
        $ModuleName = Get-ChildItem -Path $ModuleFilePath -Filter "*.psd1" -File | Select-Object -First 1 | Select-Object -ExpandProperty BaseName
        Write-Verbose "ModuleName: $ModuleName"
        $UserPowerShellModuleConfigPath = Join-Path -Path $(Split-Path -Path $($env:PSModulePath -split ';' | ForEach-Object { if (($_ -match $([regex]::Escape($env:USERNAME))) -and ($_ -notmatch '\.')) { $_ } }) -Parent) -ChildPath 'Config'
        Write-Verbose "UserPowerShellModuleConfigPath: $UserPowerShellModuleConfigPath"
        $ModuleConfigPath = Join-Path -Path $UserPowerShellModuleConfigPath -ChildPath $ModuleName
        Write-Verbose "ModuleConfigPath: $ModuleConfigPath"
        $ModuleConfigFilePath = Join-Path -Path $ModuleConfigPath -ChildPath 'Module.Config.json'
        Write-Verbose "ModuleConfigFilePath: $ModuleConfigFilePath"
        $ConfigDefaultsPath = Join-Path -Path $(Split-Path -Path $PSScriptRoot -Parent) -ChildPath "\Config\Module.Defaults.json"
        Write-Verbose "ConfigDefaultsPath: $ConfigDefaultsPath"
        $DefaultConfig = Get-Content -Path $ConfigDefaultsPath | ConvertFrom-Json
        Write-Verbose "DefaultConfig: $DefaultConfig"
    }
    catch {
        Write-Error "CommandPath: $($CommandPath)`nError: `n$($_)`n Invocation $($_.InvocationInfo.ScriptLineNumber) $($_.InvocationInfo.ScriptName)"
        return
    }
    # Test to see if module config JSON exists and create it if it doesn't
    if (-not (Test-Path -Path $ModuleConfigFilePath)) {
        $HashTable = @{}
        $DefaultConfig.PSObject.Properties | ForEach-Object { $HashTable[$_.Name] = $_.Value }
        $HashTable.Add('ModuleName', $ModuleName)
        $HashTable.Add('ModulePath', $ModulePath)
        $HashTable.Add('ModuleVersion', $ModuleVersion)
        $HashTable.Add('ModuleConfigPath', $ModuleConfigPath)
        $HashTable.Add('ModuleConfigFilePath', $ModuleConfigFilePath)
        New-Item -Path $ModuleConfigFilePath -ItemType File -Value $($HashTable | ConvertTo-Json) -Force -Confirm:$false
    }
    else {
        $Config = Get-Content -Path $ModuleConfigFilePath | ConvertFrom-Json
        $DefaultConfig.PSObject.Properties | ForEach-Object {
            if ($Config.PSObject.Properties.Name -notcontains $_.Name) {
                $Config | Add-Member -MemberType NoteProperty -Name $_.Name -Value $_.Value
            }
        }
        # Update Module Path if it has changed
        $CurrentlyLoadedModuleVersion = (Import-PowerShellDataFile -Path $($ModuleFilePath.Replace('.psm1','.psd1'))).ModuleVersion
        if ($Config.ModuleVersion -ne $CurrentlyLoadedModuleVersion) {
            $Config.ModuleVersion = $CurrentlyLoadedModuleVersion
            $Config | ConvertTo-Json | Set-Content -Path $ModuleConfigFilePath -Force -Confirm:$false
            $Config = Get-Content -Path $ModuleConfigFilePath | ConvertFrom-Json
        }
        if ($Config.ModulePath -ne $ModulePath) {
            $Config.ModulePath = $ModulePath
            $Config | ConvertTo-Json | Set-Content -Path $ModuleConfigFilePath -Force -Confirm:$false
            $Config = Get-Content -Path $ModuleConfigFilePath | ConvertFrom-Json
        }
        $HashTable = @{}
        $Config.PSObject.Properties | ForEach-Object { $HashTable[$_.Name] = $_.Value }
        $HashTable
    }
}