PIMActivation.psm1

#Requires -Version 7.0
# Note: Required modules are declared in the manifest and handled by internal dependency management
# This allows for both PowerShell Gallery automatic installation and development scenarios

# Set strict mode for better error handling
Set-StrictMode -Version Latest

#region Module Setup

# Module-level variables
$script:ModuleRoot = $PSScriptRoot
$script:ModuleName = Split-Path -Path $script:ModuleRoot -Leaf

# Token storage variables
$script:CurrentAccessToken = $null
$script:TokenExpiry = $null

# User context variables
$script:CurrentUser = $null
$script:GraphContext = $null

# Configuration variables
$script:IncludeEntraRoles = $true
$script:IncludeGroups = $true
$script:IncludeAzureResources = $false

# Startup parameters (for restarts)
$script:StartupParameters = @{}

# Restart flag
$script:RestartRequested = $false

# Policy cache
if (-not (Test-Path Variable:script:PolicyCache)) {
    $script:PolicyCache = @{}
}

# Authentication context cache
if (-not (Test-Path Variable:script:AuthenticationContextCache)) {
    $script:AuthenticationContextCache = @{}
}

# Entra policies loaded flag
if (-not (Test-Path Variable:script:EntraPoliciesLoaded)) {
    $script:EntraPoliciesLoaded = $false
}

# Role data cache to avoid repeated API calls during refresh operations
if (-not (Test-Path Variable:script:CachedEligibleRoles)) {
    $script:CachedEligibleRoles = @()
}

if (-not (Test-Path Variable:script:CachedActiveRoles)) {
    $script:CachedActiveRoles = @()
}

if (-not (Test-Path Variable:script:LastRoleFetchTime)) {
    $script:LastRoleFetchTime = $null
}

if (-not (Test-Path Variable:script:RoleCacheValidityMinutes)) {
    $script:RoleCacheValidityMinutes = 5  # Cache roles for 5 minutes
}

# Authentication context variables - now supporting multiple contexts
$script:CurrentAuthContextToken = $null  # Deprecated - kept for backwards compatibility
$script:AuthContextTokens = @{} 
$script:JustCompletedAuthContext = $null
$script:AuthContextCompletionTime = $null

# Module loading state for just-in-time loading
$script:ModuleLoadingState = @{}
$script:RequiredModuleVersions = @{
    'Microsoft.Graph.Authentication'               = '2.29.0'
    'Microsoft.Graph.Users'                        = '2.29.0'
    'Microsoft.Graph.Identity.DirectoryManagement' = '2.29.0'
    'Microsoft.Graph.Identity.Governance'          = '2.29.0'
    'Microsoft.Graph.Groups'                       = '2.29.0'
    'Microsoft.Graph.Identity.SignIns'             = '2.29.0'
}

#endregion Module Setup

#region Import Functions

# Import all functions from subdirectories
$functionFolders = [System.Collections.ArrayList]::new()
$null = $functionFolders.AddRange(@(
        'Authentication',
        'RoleManagement', 
        'UI',
        'Utilities'
    ))

# Note: Profiles folder contains placeholder functions for planned features
$null = $functionFolders.Add('Profiles')

# Import private functions from organized folders
# Temporarily suppress verbose output during function imports to reduce noise
$originalVerbosePreference = $VerbosePreference
$VerbosePreference = 'SilentlyContinue'

# Import all private functions from any subfolder
$privateRoot = Join-Path $script:ModuleRoot 'Private'
if (Test-Path -Path $privateRoot) {
    $privateFiles = Get-ChildItem -Path $privateRoot -Filter '*.ps1' -Recurse -ErrorAction SilentlyContinue | Where-Object { $_ -is [System.IO.FileInfo] }
    foreach ($file in $privateFiles) {
        try {
            . $file.FullName
        }
        catch {
            Write-Error -Message "Failed to import function $($file.FullName): $_"
        }
    }
}

# Import all public functions from any subfolder (if you ever nest them)
$publicRoot = Join-Path $script:ModuleRoot 'Public'
$Public = @()
if (Test-Path -Path $publicRoot) {
    $Public = Get-ChildItem -Path $publicRoot -Filter '*.ps1' -Recurse -ErrorAction SilentlyContinue | Where-Object { $_ -is [System.IO.FileInfo] }
    foreach ($import in $Public) {
        try {
            . $import.FullName
        }
        catch {
            Write-Error -Message "Failed to import function $($import.FullName): $_"
        }
    }
}

# Restore original verbose preference
$VerbosePreference = $originalVerbosePreference

#endregion Import Functions

#region Export Module Members

# Export public functions by filename
if ($Public) {
    $publicFiles = @($Public)
    $functionNames = $publicFiles.BaseName | Sort-Object -Unique
    if ($functionNames) {
        Export-ModuleMember -Function $functionNames -Alias *
    }
}

#endregion Export Module Members

#region Module Initialization

# Smart dependency resolution - handles both development and production scenarios
# This allows the module to work regardless of how it's imported
$script:DependenciesValidated = $false

Write-Verbose "PIMActivation module loaded. Use Start-PIMActivation to begin."
Write-Verbose "Dependencies will be validated and installed automatically when needed."

#endregion Module Initialization

#region Cleanup

# Clean up variables
Remove-Variable -Name Private, Public, functionFolders, folder, folderPath, functions, function, privateRoot, import -ErrorAction SilentlyContinue

#endregion Cleanup