ModulePath.psm1

#requires -Version 7.2
$ErrorActionPreference = 'Stop'

function Get-ModulePathConfig {
    <#
    .SYNOPSIS
    Gets the current PSModulePath configuration from powershell.config.json. Returns null if no path detected
    .LINK
    https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_powershell_config?view=powershell-7.4
    #>


    $allUsersBasePath = $PSHOME
    $currentUserBasePath = (Split-Path $profile.CurrentUserAllHosts)

    $configPaths = @{}
    foreach ($directory in $allUsersBasePath, $currentUserBasePath) {
        $configPath = Join-Path $directory 'powershell.config.json'
        if (Test-Path $configPath) {
            $config = Get-Content $configPath | ConvertFrom-Json -AsHashtable
            if ($config.PSModulePath) {
                $configPaths.$configPath = $config.PSModulePath
            }
        }
    }

    return [pscustomobject]$configPaths
}

function Set-ModulePathConfig {
    <#
    .SYNOPSIS
    Set the PSModulePath for the current user or all users in powershell.config.json
    .LINK
    https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_powershell_config?view=powershell-7.4
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        #Specify the path that you wish to become the primary PSModulePath
        [Parameter(Mandatory = $true)]
        [string]$Path,
        #Specify if you wish to set the PSModulePath at the PowerShell directory for all users, rather than for your user
        [Switch]$AllUsers,
        #If specified, we will not try to create the folder if it does not exist. You will need to create it yourself separately or PowerShell may not behave properly.
        [Switch]$NoCreate
    )

    $ConfigBasePath = $AllUsers ? $PSHOME : (Split-Path $profile.CurrentUserAllHosts)
    $ConfigPath = Join-Path $ConfigBasePath 'powershell.config.json'

    $Config = if (Test-Path $ConfigPath) {
        Get-Content $ConfigPath | ConvertFrom-Json -AsHashtable
    } else {
        $createConfig = $true
    }

    $Config ??= @{}
    $Config.PSModulePath = $Path

    # Resolve environment variables in the provided path
    $ResolvedPath = [Environment]::ExpandEnvironmentVariables($Path)

    if (-not (Test-Path $ResolvedPath) -and -not $NoCreate) {
        if ($PSCmdlet.ShouldProcess($ResolvedPath, "Create Directory")) {
            New-Item $ResolvedPath -ItemType Directory -Force | Out-Null
        }
    }

    if (-not $PSCmdlet.ShouldProcess($ConfigPath, "Set PSModulePath to $Path")) {
        return
    }

    if ($createConfig) {
        #Will create any directories required along the path too in the event they don't exist
        New-Item $ConfigPath -ItemType File -Force | Out-Null
    }

    $Config
    | ConvertTo-Json -Depth 10
    | Out-File -Encoding UTF8 -LiteralPath $ConfigPath -Force
}


function Get-ModulePath ([Switch]$AllUsers) {
    <#
    .SYNOPSIS
    Get the PSModulePath for the current user or all users, taking powershell.config.json into account.
 
    .NOTES
    This uses a private API to get the PSModulePath for the current user or all users. This will hopefully be replaced with a public API in the future: https://github.com/PowerShell/PowerShell/issues/24274
    #>

    $scopeType = [Management.Automation.Configuration.ConfigScope]
    $pscType = $scopeType.
    Assembly.
    GetType('System.Management.Automation.Configuration.PowerShellConfig')

    $pscInstance = $pscType.
    GetField('Instance', [Reflection.BindingFlags]'Static,NonPublic').
    GetValue($null)

    $getModulePathMethod = $pscType.GetMethod('GetModulePath', [Reflection.BindingFlags]'Instance,NonPublic')

    if ($AllUsers) {
        $getModulePathMethod.Invoke($pscInstance, $scopeType::AllUsers) ?? [Management.Automation.ModuleIntrinsics]::GetPSModulePath('BuiltIn')
    } else {
        $getModulePathMethod.Invoke($pscInstance, $scopeType::CurrentUser) ?? [Management.Automation.ModuleIntrinsics]::GetPSModulePath('User')
    }
}