Private/Initialize-ModuleVariable.ps1

Function Initialize-ModuleVariable {

    <#
        .SYNOPSIS
            Initializes or reinitializes module-level variables for the module.

        .DESCRIPTION
            This function sets up the global $Variables variable used throughout the EguibarIT.DelegationPS module.
            The $Variables variable is a hashtable that contains simple key-value pairs as well
            as nested hashtables (e.g., WellKnownSids, HeaderDelegation, FooterDelegation).

            The function is automatically invoked on module import and can be called manually
            to refresh or reinitialize the variables. The initialization logic is broken into
            smaller helper functions to simplify maintenance and testing.

            Variables initialized include:
            - Active Directory Distinguished Name
            - Well-known SIDs for common security principals
            - Property GUIDs for AD attributes
            - Standard header and footer formats for logging
            - Module version information

            This function implements lazy loading where possible to improve module import performance.

        .PARAMETER Force
            When specified, forces reinitialization of variables even if they already exist.
            This is useful for troubleshooting or when you need to refresh the environment after
            domain changes or when testing new functionality.

        .EXAMPLE
            Initialize-ModuleVariable

            Reinitializes the module variables if required, preserving any existing values
            unless they need to be updated.

        .EXAMPLE
            Initialize-ModuleVariable -Force

            Forces complete reinitialization of all module variables, discarding any existing values
            and recreating them from scratch. Useful when troubleshooting or after domain changes.

        .INPUTS
            None. This function does not accept pipeline input.

        .OUTPUTS
            System.Void

            The function does not return any output. It sets global variables in the module scope
            that are used by other functions in the module.

        .NOTES
            Used Functions:
                Name ║ Module/Namespace
                ═══════════════════════════════════════════╬══════════════════════════════
                Set-StrictMode ║ Microsoft.PowerShell.Core
                Write-Verbose ║ Microsoft.PowerShell.Utility
                Write-Warning ║ Microsoft.PowerShell.Utility
                Write-Error ║ Microsoft.PowerShell.Utility
                Set-StrictMode ║ Microsoft.PowerShell.Core
                Get-AdObject ║ ActiveDirectory
                Import-Module ║ Microsoft.PowerShell.Core
                Get-Module ║ Microsoft.PowerShell.Core

        .NOTES
            Version: 2.0
            DateModified: 22/May/2025
            LastModifiedBy: Vicente Rodriguez Eguibar
                            vicente@eguibar.com
                            Eguibar IT
                            http://www.eguibarit.com

        .LINK
            https://github.com/vreguibar/EguibarIT.DelegationPS/blob/main/Private/Initialize-ModuleVariable.ps1

        .COMPONENT
            EguibarIT.DelegationPS

        .ROLE
            Infrastructure

        .FUNCTIONALITY
            Module Initialization
    #>


    [CmdletBinding(
        SupportsShouldProcess = $false,
        ConfirmImpact = 'Low'
    )]
    [OutputType([void])]

    Param (
        # PARAM1 SWITCH If present, forces reinitialization of variables even if they already exist.
        [Parameter(Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false,
            HelpMessage = 'Force reinitialization even if variables already exist.',
            Position = 0)]
        [Switch]
        $Force
    )

    Begin {

        Set-StrictMode -Version Latest

        ##############################
        # Module imports

        # Check if ActiveDirectory module is available
        $adModuleAvailable = Get-Module -ListAvailable -Name 'ActiveDirectory'

        try {

            if ($adModuleAvailable) {

                Import-Module -Name 'ActiveDirectory' -Force -Verbose:$false | Out-Null

            } else {
                Write-Warning -Message 'ActiveDirectory module is not available. Skipping AD-related functionality.'
            } #end If-Else

        } catch {
            Write-Error -Message ('Failed to import ActiveDirectory module: {0}' -f $_ )
        } #end Try-Catch

        ##############################
        # Variables Definition

    } #end Begin

    Process {

        if ($adModuleAvailable) {

            try {

                # Active Directory DistinguishedName
                if ($Force -or
                    $null -eq $Variables.AdDN) {
                    $Variables.AdDN = ([ADSI]'LDAP://RootDSE').DefaultNamingContext.ToString()
                } #end If

                # Configuration Naming Context
                if ($Force -or
                    $null -eq $Variables.configurationNamingContext) {
                    $Variables.configurationNamingContext = ([ADSI]'LDAP://RootDSE').configurationNamingContext.ToString()
                } #end If

                # Active Directory DistinguishedName
                if ($Force -or
                    $null -eq $Variables.defaultNamingContext) {
                    $Variables.defaultNamingContext = ([ADSI]'LDAP://RootDSE').DefaultNamingContext.ToString()
                } #end If

                # Get current DNS domain name
                if ($Force -or
                    $null -eq $Variables.DnsFqdn) {
                    $Variables.DnsFqdn = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Name
                } #end If

                # Naming Contexts
                if ($Force -or
                    $null -eq $Variables.namingContexts) {
                    $Variables.namingContexts = ([ADSI]'LDAP://RootDSE').namingContexts
                } #end If

                # Partitions Container
                if ($Force -or
                    $null -eq $Variables.PartitionsContainer) {
                    $Variables.PartitionsContainer = (([ADSI]'LDAP://RootDSE').configurationNamingContext.ToString())
                } #end If

                # Root Domain Naming Context
                if ($Force -or
                    $null -eq $Variables.rootDomainNamingContext) {
                    $Variables.rootDomainNamingContext = ([ADSI]'LDAP://RootDSE').rootDomainNamingContext.ToString()
                } #end If

                # Schema Naming Context
                if ($Force -or
                    $null -eq $Variables.SchemaNamingContext) {
                    $Variables.SchemaNamingContext = ([ADSI]'LDAP://RootDSE').SchemaNamingContext.ToString()
                } #end If

            } Catch {

                [System.Text.StringBuilder]$sb = [System.Text.StringBuilder]::new()
                [void]$sb.AppendLine( '' )
                [void]$sb.AppendLine( ' ..:: $Variables ::..' )
                [void]$sb.AppendLine( 'Something went wrong while trying to fill $Variables!' )
                [void]$sb.AppendLine( ' Ensure that:' )
                [void]$sb.AppendLine( ' * Machine is Domain Joined' )
                [void]$sb.AppendLine( ' * Active Directory is available and working' )
                [void]$sb.AppendLine( ' * Communication exist between this machine and AD' )
                [void]$sb.AppendLine( '' )

                Write-Error -Message $sb.ToString()

            } #end Try-Catch


            # Well-Known SIDs
            # Following functions must be the last ones to be called, otherwise error is thrown.
            # Hashtable containing the mappings between ClassSchema/AttributeSchema and GUID's
            If ($Variables.GuidMap.Count -eq 0) {

                Try {
                    [hashtable]$TmpMap = [hashtable]::New([StringComparer]::OrdinalIgnoreCase)
                    [hashtable]$Splat = [hashtable]::New([StringComparer]::OrdinalIgnoreCase)

                    Write-Verbose -Message '
                        The GUID map is null, empty, zero, or false.
                        Getting the GUID value of each schema class and attribute'


                    #store the GUID value of each schema class and attribute
                    $Splat = @{
                        SearchBase = $Variables.SchemaNamingContext
                        LDAPFilter = '(schemaidguid=*)'
                        Properties = 'lDAPDisplayName', 'schemaIDGUID'
                    }
                    $AllSchema = Get-ADObject @Splat

                    Write-Verbose -Message 'Processing all schema class and attribute'
                    Foreach ($item in $AllSchema) {

                        # add current Guid to $TempMap
                        $TmpMap.Add($item.lDAPDisplayName, ([System.GUID]$item.schemaIDGUID).GUID)

                    } #end ForEach

                    # Include "ALL [nullGUID]"
                    $TmpMap.Add('All', $Constants.guidNull)

                    Write-Verbose -Message '$Variables.GuidMap was empty. Adding values to it!'
                    $Variables.GuidMap = $TmpMap

                } catch {

                    [System.Text.StringBuilder]$sb = [System.Text.StringBuilder]::new()
                    [void]$sb.AppendLine( '' )
                    [void]$sb.AppendLine( ' ..:: GuidMap ::..' )
                    [void]$sb.AppendLine( 'Something went wrong while trying to fill $Variables.GuidMap!' )
                    [void]$sb.AppendLine( ' Ensure that:' )
                    [void]$sb.AppendLine( ' * Machine is Domain Joined' )
                    [void]$sb.AppendLine( ' * Active Directory is available and working' )
                    [void]$sb.AppendLine( ' * Communication exist between this machine and AD' )
                    [void]$sb.AppendLine( '' )

                    Write-Error -Message $sb.ToString()

                } #end Try-Catch
            } #end If

            # Hashtable containing the mappings between SchemaExtendedRights and GUID's
            If ($Variables.ExtendedRightsMap.Count -eq 0) {
                Try {
                    [hashtable]$TmpMap = [hashtable]::New([StringComparer]::OrdinalIgnoreCase)
                    [hashtable]$Splat = [hashtable]::New([StringComparer]::OrdinalIgnoreCase)

                    Write-Verbose -Message '
                        The Extended Rights map is null, empty, zero, or false.
                        Getting the GUID value of each Extended attribute'


                    # store the GUID value of each extended right in the forest
                    $Splat = @{
                        SearchBase = ('CN=Extended-Rights,{0}' -f $Variables.configurationNamingContext)
                        LDAPFilter = '(objectclass=controlAccessRight)'
                        Properties = 'DisplayName', 'rightsGuid'
                    }
                    $AllExtended = Get-ADObject @Splat

                    Write-Verbose -Message 'Processing all Extended attributes'
                    ForEach ($Item in $AllExtended) {

                        # add current Guid to $TempMap
                        $TmpMap.Add($Item.displayName, ([system.guid]$Item.rightsGuid).GUID)

                    } #end Foreach

                    # Include "ALL [nullGUID]"
                    $TmpMap.Add('All', $Constants.guidNull)

                    Write-Verbose -Message '$Variables.ExtendedRightsMap was empty. Adding values to it!'
                    $Variables.ExtendedRightsMap = $TmpMap

                } Catch {

                    [System.Text.StringBuilder]$sb = [System.Text.StringBuilder]::new()
                    [void]$sb.AppendLine( '' )
                    [void]$sb.AppendLine( ' ..:: ExtendedRightsMap ::..' )
                    [void]$sb.AppendLine( 'Something went wrong while trying to fill $Variables.GuidMap!' )
                    [void]$sb.AppendLine( ' Ensure that:' )
                    [void]$sb.AppendLine( ' * Machine is Domain Joined' )
                    [void]$sb.AppendLine( ' * Active Directory is available and working' )
                    [void]$sb.AppendLine( ' * Communication exist between this machine and AD' )
                    [void]$sb.AppendLine( '' )

                    Write-Error -Message $sb.ToString()

                } #end Try-Catch
            } #end If
        } #end If


    } #end Process

    End {
    } #end End
} #end Function Initialize-ModuleVariable