EnhancedBoilerPlateAO.psm1

#Region '.\Public\Add-EnvPath.ps1' -1

function Add-EnvPath {
    <#
    .SYNOPSIS
    Adds a specified path to the environment PATH variable.
 
    .DESCRIPTION
    The Add-EnvPath function adds a specified path to the environment PATH variable. The path can be added to the session, user, or machine scope.
 
    .PARAMETER Path
    The path to be added to the environment PATH variable.
 
    .PARAMETER Container
    Specifies the scope of the environment variable. Valid values are 'Machine', 'User', or 'Session'.
 
    .EXAMPLE
    Add-EnvPath -Path 'C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Imaging and Configuration Designer\x86' -Container 'Machine'
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string] $Path,

        [ValidateSet('Machine', 'User', 'Session')]
        [string] $Container = 'Session'
    )

    begin {
        Write-EnhancedLog -Message "Starting Add-EnvPath function" -Level "INFO"
        Log-Params -Params @{
            Path = $Path
            Container = $Container
        }

        $envPathHashtable = [ordered]@{}
        $persistedPathsHashtable = [ordered]@{}
        $containerMapping = @{
            Machine = [System.EnvironmentVariableTarget]::Machine
            User    = [System.EnvironmentVariableTarget]::User
        }
    }

    process {
        try {
            # Update the PATH variable for User or Machine scope
            if ($Container -ne 'Session') {
                $containerType = $containerMapping[$Container]
                $existingPaths = [System.Environment]::GetEnvironmentVariable('Path', $containerType) -split ';'
                
                foreach ($pathItem in $existingPaths) {
                    $persistedPathsHashtable[$pathItem] = $null
                }

                if (-not $persistedPathsHashtable.Contains($Path)) {
                    Write-EnhancedLog -Message "Path not found in persisted paths, adding it." -Level "INFO"
                    $persistedPathsHashtable[$Path] = $null
                    [System.Environment]::SetEnvironmentVariable('Path', ($persistedPathsHashtable.Keys -join ';'), $containerType)
                }
            }

            # Update the PATH variable for the current session
            $existingSessionPaths = $env:Path -split ';'
            
            foreach ($sessionPathItem in $existingSessionPaths) {
                $envPathHashtable[$sessionPathItem] = $null
            }

            if (-not $envPathHashtable.Contains($Path)) {
                Write-EnhancedLog -Message "Path not found in session paths, adding it." -Level "INFO"
                $envPathHashtable[$Path] = $null
                $env:Path = $envPathHashtable.Keys -join ';'
            }
        } catch {
            Write-EnhancedLog -Message "An error occurred: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message "Exiting Add-EnvPath function" -Level "INFO"
        
        Write-Host "The permanent environment PATH variable is:"
        [System.Environment]::GetEnvironmentVariable('Path', [System.EnvironmentVariableTarget]::Machine) -split ';'

        Write-Host "The temporary environment PATH variable is:"
        $env:Path -split ';'
    }
}

# # Example usage
# $envPathParams = @{
# Path = 'C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Imaging and Configuration Designer\x86'
# Container = 'Machine'
# }

# Add-EnvPath @envPathParams
#EndRegion '.\Public\Add-EnvPath.ps1' 97
#Region '.\Public\Check-ModuleVersionStatus.ps1' -1


function Check-ModuleVersionStatus {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string[]]$ModuleNames
    )

    #the following modules PowerShellGet and PackageManagement has to be either automatically imported or manually imported into C:\windows\System32\WindowsPowerShell\v1.0\Modules

    Import-Module -Name PowerShellGet -ErrorAction SilentlyContinue
    # Import-Module 'C:\Program Files (x86)\WindowsPowerShell\Modules\PowerShellGet\PSModule.psm1' -ErrorAction SilentlyContinue
    # Import-Module 'C:\windows\System32\WindowsPowerShell\v1.0\Modules\PowerShellGet\PSModule.psm1' -ErrorAction SilentlyContinue
    # Import-Module 'C:\Program Files (x86)\WindowsPowerShell\Modules\PackageManagement\PackageProviderFunctions.psm1' -ErrorAction SilentlyContinue
    # Import-Module 'C:\windows\System32\WindowsPowerShell\v1.0\Modules\PackageManagement\PackageProviderFunctions.psm1' -ErrorAction SilentlyContinue
    # Import-Module 'C:\Program Files (x86)\WindowsPowerShell\Modules\PackageManagement\PackageManagement.psm1' -ErrorAction SilentlyContinue

    $results = [System.Collections.Generic.List[PSObject]]::new()  # Initialize a List to hold the results

    foreach ($ModuleName in $ModuleNames) {
        try {

            Write-Host 'Checking module '$ModuleName
            $installedModule = Get-Module -ListAvailable -Name $ModuleName | Sort-Object Version -Descending | Select-Object -First 1
            # $installedModule = Check-SystemWideModule -ModuleName 'Pester'
            $latestModule = Find-Module -Name $ModuleName -ErrorAction SilentlyContinue

            if ($installedModule -and $latestModule) {
                if ($installedModule.Version -lt $latestModule.Version) {
                    $results.Add([PSCustomObject]@{
                        ModuleName = $ModuleName
                        Status = "Outdated"
                        InstalledVersion = $installedModule.Version
                        LatestVersion = $latestModule.Version
                    })
                } else {
                    $results.Add([PSCustomObject]@{
                        ModuleName = $ModuleName
                        Status = "Up-to-date"
                        InstalledVersion = $installedModule.Version
                        LatestVersion = $installedModule.Version
                    })
                }
            } elseif (-not $installedModule) {
                $results.Add([PSCustomObject]@{
                    ModuleName = $ModuleName
                    Status = "Not Installed"
                    InstalledVersion = $null
                    LatestVersion = $null
                })
            } else {
                $results.Add([PSCustomObject]@{
                    ModuleName = $ModuleName
                    Status = "Not Found in Gallery"
                    InstalledVersion = $null
                    LatestVersion = $null
                })
            }
        } catch {
            Write-Error "An error occurred checking module '$ModuleName': $_"
        }
    }

    return $results
}

# Example usage:
# $versionStatuses = Check-ModuleVersionStatus -ModuleNames @('Pester', 'AzureRM', 'PowerShellGet')
# $versionStatuses | Format-Table -AutoSize # Display the results in a table format for readability


#EndRegion '.\Public\Check-ModuleVersionStatus.ps1' 72
#Region '.\Public\Convert-WindowsPathToLinuxPath.ps1' -1

function Convert-WindowsPathToLinuxPath {
    <#
.SYNOPSIS
    Converts a Windows file path to a Linux file path.
 
.DESCRIPTION
    This function takes a Windows file path as input and converts it to a Linux file path.
    It replaces backslashes with forward slashes and handles the drive letter.
 
.PARAMETER WindowsPath
    The full file path in Windows format that needs to be converted.
 
.EXAMPLE
    PS> Convert-WindowsPathToLinuxPath -WindowsPath 'C:\Code\CB\Entra\ARH\Get-EntraConnectSyncErrorsfromEntra copy.ps1'
    Returns '/mnt/c/Code/CB/Entra/ARH/Get-EntraConnectSyncErrorsfromEntra copy.ps1'
 
#>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$WindowsPath
    )

    Begin {
        Write-Host "Starting the path conversion process..."
    }

    Process {
        try {
            Write-Host "Input Windows Path: $WindowsPath"
            
            # Replace backslashes with forward slashes
            $linuxPath = $WindowsPath -replace '\\', '/'

            # Handle drive letter by converting "C:" to "/mnt/c"
            if ($linuxPath -match '^[A-Za-z]:') {
                $driveLetter = $linuxPath.Substring(0, 1).ToLower()
                $linuxPath = "/mnt/$driveLetter" + $linuxPath.Substring(2)
            }

            Write-Host "Converted Linux Path: $linuxPath"
            return $linuxPath
        }
        catch {
            Write-Host "Error during conversion: $_"
            throw
        }
    }

    End {
        Write-Host "Path conversion completed."
    }
}

# # Example usage
# $windowsPath = 'C:\Code\Unified365toolbox\Graph\graphcert.pfx'
# $linuxPath = Convert-WindowsPathToLinuxPath -WindowsPath $windowsPath
# Write-Host "Linux path: $linuxPath"
#EndRegion '.\Public\Convert-WindowsPathToLinuxPath.ps1' 59
#Region '.\Public\Download-Psd1File.ps1' -1

function Download-Psd1File {
    <#
    .SYNOPSIS
    Downloads a PSD1 file from a specified URL and saves it to a local destination.
 
    .DESCRIPTION
    This function downloads a PowerShell Data file (PSD1) from a given URL and saves it to the specified local path.
    If the download fails, an error is logged and the function throws an exception.
 
    .PARAMETER url
    The URL of the PSD1 file to be downloaded.
 
    .PARAMETER destinationPath
    The local path where the PSD1 file will be saved.
 
    .EXAMPLE
    Download-Psd1File -url "https://example.com/modules.psd1" -destinationPath "$env:TEMP\modules.psd1"
    Downloads the PSD1 file from the specified URL and saves it to the provided local path.
 
    .NOTES
    This function requires internet access to download the PSD1 file from the specified URL.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$url,

        [Parameter(Mandatory = $true)]
        [string]$destinationPath
    )

    begin {
        Write-EnhancedLog -Message "Starting Download-Psd1File function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Validate destination directory
        $destinationDirectory = [System.IO.Path]::GetDirectoryName($destinationPath)
        if (-not (Test-Path -Path $destinationDirectory)) {
            Write-EnhancedLog -Message "Destination directory not found at path: $destinationDirectory" -Level "ERROR"
            throw "Destination directory not found."
        }

        Write-EnhancedLog -Message "Validated destination directory at path: $destinationDirectory" -Level "INFO"
    }

    process {
        try {
            Write-EnhancedLog -Message "Downloading PSD1 file from URL: $url" -Level "INFO"
            Invoke-WebRequest -Uri $url -OutFile $destinationPath -UseBasicParsing
            Write-EnhancedLog -Message "Downloaded PSD1 file to: $destinationPath" -Level "INFO"
        }
        catch {
            Write-EnhancedLog -Message "Failed to download PSD1 file from $url. Error: $_" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    end {
        Write-EnhancedLog -Message "Download-Psd1File function execution completed." -Level "NOTICE"
    }
}
#EndRegion '.\Public\Download-Psd1File.ps1' 64
#Region '.\Public\Ensure-LoggingFunctionExists.ps1' -1

function Ensure-LoggingFunctionExists {
    param (
        # [string]$LoggingFunctionName = "Write-EnhancedLog"
        [string]$LoggingFunctionName
    )

    if (Get-Command $LoggingFunctionName -ErrorAction SilentlyContinue) {
        Write-EnhancedLog -Message "Logging works" -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
    }
    else {
        throw "$LoggingFunctionName function not found. Terminating script."
    }
}

# Example of how to call the function with the default parameter
# Ensure-LoggingFunctionExists

# Example of how to call the function with a different logging function name
# Ensure-LoggingFunctionExists -LoggingFunctionName "Write-EnhancedLog"
#EndRegion '.\Public\Ensure-LoggingFunctionExists.ps1' 20
#Region '.\Public\Get-ModulesScriptPathsAndVariables.ps1' -1

# function Get-ModulesScriptPathsAndVariables {
    

# <#
# .SYNOPSIS
# Dot-sources all PowerShell scripts in the 'Modules' folder relative to the script root.
    
# .DESCRIPTION
# This function finds all PowerShell (.ps1) scripts in a 'Modules' folder located in the script root directory and dot-sources them. It logs the process, including any errors encountered, with optional color coding.
    
# .EXAMPLE
# Dot-SourceModulesScripts
    
# Dot-sources all scripts in the 'Modules' folder and logs the process.
    
# .NOTES
# Ensure the Write-EnhancedLog function is defined before using this function for logging purposes.
# #>
# param (
# [string]$BaseDirectory
# )
    
# try {
# $ModulesFolderPath = Join-Path -Path $BaseDirectory -ChildPath "Modules"
            
# if (-not (Test-Path -Path $ModulesFolderPath)) {
# throw "Modules folder path does not exist: $ModulesFolderPath"
# }
    
# # Construct and return a PSCustomObject
# return [PSCustomObject]@{
# BaseDirectory = $BaseDirectory
# ModulesFolderPath = $ModulesFolderPath
# }
# }
# catch {
# Write-Host "Error in finding Modules script files: $_" -ForegroundColor Red
# # Optionally, you could return a PSCustomObject indicating an error state
# # return [PSCustomObject]@{ Error = $_.Exception.Message }
# }
# }
#EndRegion '.\Public\Get-ModulesScriptPathsAndVariables.ps1' 42
#Region '.\Public\Import-Modules.ps1' -1

function Import-Modules {
    param (
        [Parameter(Mandatory = $true)]
        [string[]]$Modules
    )
    
    foreach ($module in $Modules) {
        if (Get-Module -ListAvailable -Name $module) {
            # Import-Module -Name $module -Force -Verbose
            Import-Module -Name $module -Force:$true -Global:$true
            Write-EnhancedLog -Message "Module '$module' imported." -Level "INFO"
        }
        else {
            Write-EnhancedLog -Message "Module '$module' not found. Cannot import." -Level "ERROR"
        }
    }
}
#EndRegion '.\Public\Import-Modules.ps1' 18
#Region '.\Public\Import-ModulesFromLocalRepository.ps1' -1

function Import-ModulesFromLocalRepository {
    <#
    .SYNOPSIS
    Imports all modules found in the specified Modules directory.
 
    .DESCRIPTION
    This function scans the Modules directory for module folders and attempts to import the module. If a module
    file is not found or if importing fails, appropriate error messages are logged.
 
    .PARAMETER ModulesFolderPath
    The path to the folder containing the modules.
 
    .PARAMETER ScriptPath
    The path to the script directory containing the exclusion file.
 
    .EXAMPLE
    Import-ModulesFromLocalRepository -ModulesFolderPath "C:\code\Modules" -ScriptPath "C:\scripts"
    This example imports all modules found in the specified Modules directory.
    #>


    [CmdletBinding()]
    param (
        [string]$ModulesFolderPath
        # [string]$ScriptPath
    )

    Begin {
        # Get the path to the Modules directory
        $moduleDirectories = Get-ChildItem -Path $ModulesFolderPath -Directory

        Write-Host "Module directories found: $($moduleDirectories.Count)" -ForegroundColor ([ConsoleColor]::Cyan)

        # Read the modules exclusion list from the JSON file
        # $exclusionFilePath = Join-Path -Path $ScriptPath -ChildPath "modulesexclusion.json"
    # if (Test-Path -Path $exclusionFilePath) {
    # $excludedModules = Get-Content -Path $exclusionFilePath | ConvertFrom-Json
    # Write-Host "Excluded modules: $excludedModules" -ForegroundColor ([ConsoleColor]::Cyan)
    # } else {
    # $excludedModules = @()
    # Write-Host "No exclusion file found. Proceeding with all modules." -ForegroundColor ([ConsoleColor]::Yellow)
    # }
    }

    Process {
        foreach ($moduleDir in $moduleDirectories) {
            # Skip the module if it is in the exclusion list
            if ($excludedModules -contains $moduleDir.Name) {
                Write-Host "Skipping excluded module: $($moduleDir.Name)" -ForegroundColor ([ConsoleColor]::Yellow)
                continue
            }

            # Construct the path to the module file
            $modulePath = Join-Path -Path $moduleDir.FullName -ChildPath "$($moduleDir.Name).psm1"

            # Check if the module file exists
            if (Test-Path -Path $modulePath) {
                # Import the module with retry logic
                try {
                    Import-ModuleWithRetry -ModulePath $modulePath
                    Write-Host "Successfully imported module: $($moduleDir.Name)" -ForegroundColor ([ConsoleColor]::Green)
                }
                catch {
                    Write-Host "Failed to import module: $($moduleDir.Name). Error: $_" -ForegroundColor ([ConsoleColor]::Red)
                }
            }
            else {
                Write-Host "Module file not found: $modulePath" -ForegroundColor ([ConsoleColor]::Red)
            }
        }
    }

    End {
        Write-Host "Module import process completed." -ForegroundColor ([ConsoleColor]::Cyan)
    }
}
#EndRegion '.\Public\Import-ModulesFromLocalRepository.ps1' 76
#Region '.\Public\Import-ModuleWithRetry.ps1' -1

function Import-ModuleWithRetry {
    <#
    .SYNOPSIS
    Imports a PowerShell module with retries on failure.
 
    .DESCRIPTION
    This function attempts to import a specified PowerShell module, retrying the import process up to a specified number of times upon failure. It also checks if the module path exists before attempting to import.
 
    .PARAMETER ModulePath
    The path to the PowerShell module file (.psm1) that should be imported.
 
    .PARAMETER MaxRetries
    The maximum number of retries to attempt if importing the module fails. Default is 3.
 
    .PARAMETER WaitTimeSeconds
    The number of seconds to wait between retry attempts. Default is 2 seconds.
 
    .EXAMPLE
    $modulePath = "C:\Modules\MyPowerShellModule.psm1"
    Import-ModuleWithRetry -ModulePath $modulePath
 
    Tries to import the module located at "C:\Modules\MyPowerShellModule.psm1", with up to 3 retries, waiting 2 seconds between each retry.
 
    .NOTES
    This function requires the `Write-EnhancedLog` function to be defined in the script for logging purposes.
 
    .LINK
    Write-EnhancedLog
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string]$ModulePath,

        [int]$MaxRetries = 3,

        [int]$WaitTimeSeconds = 2
    )

    Begin {
        $retryCount = 0
        $isModuleLoaded = $false
        Write-Host "Starting to import module from path: $ModulePath"
        
        # Check if the module file exists before attempting to load it
        if (-not (Test-Path -Path $ModulePath -PathType Leaf)) {
            Write-Host "The module path '$ModulePath' does not exist."
            return
        }
    }

    Process {
        while (-not $isModuleLoaded -and $retryCount -lt $MaxRetries) {
            try {
                # Import-Module $ModulePath -ErrorAction Stop -Verbose -Global
                Import-Module $ModulePath -ErrorAction Stop -Global -Force:$true
                # Import-Module $ModulePath -ErrorAction Stop
                $isModuleLoaded = $true
                Write-Host "Module: $ModulePath imported successfully."
            }
            catch {
                $errorMsg = $_.Exception.Message
                Write-Host "Attempt $retryCount to load module failed: $errorMsg Waiting $WaitTimeSeconds seconds before retrying."
                Write-Host "Attempt $retryCount to load module failed with error: $errorMsg"
                Start-Sleep -Seconds $WaitTimeSeconds
            }
            finally {
                $retryCount++
            }

            if ($retryCount -eq $MaxRetries -and -not $isModuleLoaded) {
                Write-Host "Failed to import module after $MaxRetries retries."
                Write-Host "Failed to import module after $MaxRetries retries with last error: $errorMsg"
                break
            }
        }
    }

    End {
        if ($isModuleLoaded) {
            Write-Host "Module: $ModulePath loaded successfully."
        }
        else {
            Write-Host -Message "Failed to load module $ModulePath within the maximum retry limit."
        }
    }
}
#EndRegion '.\Public\Import-ModuleWithRetry.ps1' 89
#Region '.\Public\Install-Modules.ps1' -1

function Install-Modules {
    param (
        [Parameter(Mandatory = $true)]
        [string[]]$Modules
    )

    # Check if running in PowerShell 5 or in a Windows environment
    # if ($PSVersionTable.PSVersion.Major -eq 5 -or ($PSVersionTable.Platform -eq 'Win32NT' -or [System.Environment]::OSVersion.Platform -eq [System.PlatformID]::Win32NT)) {
    # # Install the NuGet package provider if the condition is met
    # Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope CurrentUser
    # }

    if ($PSVersionTable.PSVersion.Major -eq 5) {
        # Install the NuGet package provider if the condition is met
        Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope CurrentUser
    }

    
    foreach ($module in $Modules) {
        if (-not (Get-Module -ListAvailable -Name $module)) {
            # Install-Module -Name $module -Force -Scope AllUsers
            Install-Module -Name $module -Force -Scope CurrentUser
            Write-EnhancedLog -Message "Module '$module' installed." -Level "INFO" -ForegroundColor
        }
        else {
            Write-EnhancedLog -Message "Module '$module' is already installed." -Level "INFO" -ForegroundColor
        }
    }
}
#EndRegion '.\Public\Install-Modules.ps1' 30
#Region '.\Public\Install-ModuleWithPowerShell5Fallback.ps1' -1

function Install-ModuleWithPowerShell5Fallback {
    param (
        [string]$ModuleName
    )

    # Log the start of the module installation process
    Write-Enhancedlog "Starting the module installation process for: $ModuleName" -Level "NOTICE"


    $DBG

    # Check if the current PowerShell version is not 5
    if ($PSVersionTable.PSVersion.Major -ne 5) {
        Write-Enhancedlog "Current PowerShell version is $($PSVersionTable.PSVersion). PowerShell 5 is required." -Level "WARNING"

        # # Get the path to PowerShell 5
        # $ps5Path = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"
        # Write-Enhancedlog "PowerShell 5 path: $ps5Path" -Level "INFO"

        # # Construct the parameters for Start-Process
        # $startProcessParams = @{
        # FilePath = $ps5Path
        # ArgumentList = @(
        # "-NoExit",
        # "-NoProfile",
        # "-ExecutionPolicy", "Bypass",
        # "-Command", "Install-Module -Name '$ModuleName' -Force -SkipPublisherCheck -Scope AllUsers"
        # )
        # Verb = "RunAs"
        # PassThru = $true
        # }
        
        # Write-Enhancedlog "Constructed Start-Process parameters for PowerShell 5: $($startProcessParams | Out-String)" -Level "DEBUG"

        # # Launch PowerShell 5 to run the module installation
        # Write-Enhancedlog "Launching PowerShell 5 to install the module: $ModuleName" -Level "INFO"

        # $DBG
        # $process = Start-Process @startProcessParams

        # Write-Enhancedlog "Module installation command executed in PowerShell 5. Exiting current session." -Level "NOTICE"
        # return
    }

    # If already in PowerShell 5, install the module
    Write-Enhancedlog "Current PowerShell version is 5. Proceeding with module installation." -Level "INFO"
    Write-Enhancedlog "Installing module: $ModuleName in PowerShell 5" -Level "NOTICE"

    try {
        Install-Module -Name $ModuleName -Force -SkipPublisherCheck -Scope AllUsers
        Write-Enhancedlog "Module $ModuleName installed successfully in PowerShell 5." -Level "INFO"
    }
    catch {
        Write-Enhancedlog "Failed to install module $ModuleName. Error: $_" -Level "ERROR"
    }
}
#EndRegion '.\Public\Install-ModuleWithPowerShell5Fallback.ps1' 57
#Region '.\Public\Install-RequiredModules.ps1' -1

# function Install-RequiredModules {

# [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12

# # $requiredModules = @("Microsoft.Graph", "Microsoft.Graph.Authentication")
# $requiredModules = @("Microsoft.Graph.Authentication")

# foreach ($module in $requiredModules) {
# if (!(Get-Module -ListAvailable -Name $module)) {

# Write-EnhancedLog -Message "Installing module: $module" -Level "INFO" -ForegroundColor ([ConsoleColor]::Cyan)
# Install-Module -Name $module -Force
# Write-EnhancedLog -Message "Module: $module has been installed" -Level "INFO" -ForegroundColor ([ConsoleColor]::Cyan)
# }
# else {
# Write-EnhancedLog -Message "Module $module is already installed" -Level "INFO" -ForegroundColor ([ConsoleColor]::Cyan)
# }
# }


# $ImportedModules = @("Microsoft.Graph.Identity.DirectoryManagement", "Microsoft.Graph.Authentication")
    
# foreach ($Importedmodule in $ImportedModules) {
# if ((Get-Module -ListAvailable -Name $Importedmodule)) {
# Write-EnhancedLog -Message "Importing module: $Importedmodule" -Level "INFO" -ForegroundColor ([ConsoleColor]::Cyan)
# Import-Module -Name $Importedmodule
# Write-EnhancedLog -Message "Module: $Importedmodule has been Imported" -Level "INFO" -ForegroundColor ([ConsoleColor]::Cyan)
# }
# }


# }
#EndRegion '.\Public\Install-RequiredModules.ps1' 33
#Region '.\Public\InstallAndImportModulesPSGallery.ps1' -1

function InstallAndImportModulesPSGallery {
    <#
    .SYNOPSIS
    Validates, installs, and imports required PowerShell modules specified in a PSD1 file.
 
    .DESCRIPTION
    This function reads the 'modules.psd1' file from the script's directory, validates the existence of the required modules,
    installs any that are missing, and imports the specified modules into the current session.
 
    .PARAMETER modulePsd1Path
    The path to the modules.psd1 file.
 
    .EXAMPLE
    InstallAndImportModulesPSGallery -modulePsd1Path "$PSScriptRoot\modules.psd1"
    This example reads the 'modules.psd1' file, installs any missing required modules, and imports the specified modules.
 
    .NOTES
    This function relies on a properly formatted 'modules.psd1' file in the script's root directory.
    The PSD1 file should have 'RequiredModules', 'ImportedModules', and 'MyModules' arrays defined.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$modulePsd1Path
    )

    begin {
        Write-EnhancedLog -Message "Starting InstallAndImportModulesPSGallery function" -Level "INFO"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Initialize counters and lists for summary
        $moduleSuccessCount = 0
        $moduleFailCount = 0
        $successModules = [System.Collections.Generic.List[PSCustomObject]]::new()
        $failedModules = [System.Collections.Generic.List[PSCustomObject]]::new()

        # Validate PSD1 file path
        if (-not (Test-Path -Path $modulePsd1Path)) {
            Write-EnhancedLog -Message "modules.psd1 file not found at path: $modulePsd1Path" -Level "ERROR"
            throw "modules.psd1 file not found."
        }

        Write-EnhancedLog -Message "Found modules.psd1 file at path: $modulePsd1Path" -Level "INFO"
    }

    process {
        try {
            # Read and import PSD1 data
            $moduleData = Import-PowerShellDataFile -Path $modulePsd1Path
            $requiredModules = $moduleData.RequiredModules
            $importedModules = $moduleData.ImportedModules
            $myModules = $moduleData.MyModules

            # Validate, Install, and Import Modules
            if ($requiredModules) {
                Write-EnhancedLog -Message "Installing required modules: $($requiredModules -join ', ')" -Level "INFO"
                foreach ($moduleName in $requiredModules) {
                    try {
                        Update-ModuleIfOldOrMissing -ModuleName $moduleName
                        $moduleInfo = Get-Module -Name $moduleName -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1
                        $moduleDetails = [PSCustomObject]@{
                            Name    = $moduleName
                            Version = $moduleInfo.Version
                            Path    = $moduleInfo.ModuleBase
                        }
                        $successModules.Add($moduleDetails)
                        Write-EnhancedLog -Message "Successfully installed/updated module: $moduleName" -Level "INFO"
                        $moduleSuccessCount++
                    }
                    catch {
                        $moduleDetails = [PSCustomObject]@{
                            Name    = $moduleName
                            Version = "N/A"
                            Path    = "N/A"
                        }
                        $failedModules.Add($moduleDetails)
                        Write-EnhancedLog -Message "Failed to install/update module: $moduleName. Error: $_" -Level "ERROR"
                        $moduleFailCount++
                    }
                }
                Write-EnhancedLog "All module update processes have completed." -Level "NOTICE"
            }

            if ($importedModules) {
                Write-EnhancedLog -Message "Importing modules: $($importedModules -join ', ')" -Level "INFO"
                foreach ($moduleName in $importedModules) {
                    try {
                        Import-Module -Name $moduleName -Force
                        $moduleInfo = Get-Module -Name $moduleName | Select-Object -First 1
                        $moduleDetails = [PSCustomObject]@{
                            Name    = $moduleName
                            Version = $moduleInfo.Version
                            Path    = $moduleInfo.ModuleBase
                        }
                        $successModules.Add($moduleDetails)
                        Write-EnhancedLog -Message "Successfully imported module: $moduleName" -Level "INFO"
                        $moduleSuccessCount++
                    }
                    catch {
                        $moduleDetails = [PSCustomObject]@{
                            Name    = $moduleName
                            Version = "N/A"
                            Path    = "N/A"
                        }
                        $failedModules.Add($moduleDetails)
                        Write-EnhancedLog -Message "Failed to import module: $moduleName. Error: $_" -Level "ERROR"
                        $moduleFailCount++
                    }
                }
            }

            if ($myModules) {
                Write-EnhancedLog -Message "Importing custom modules: $($myModules -join ', ')" -Level "INFO"
                foreach ($moduleName in $myModules) {
                    try {
                        Import-Module -Name $moduleName -Force
                        $moduleInfo = Get-Module -Name $moduleName | Select-Object -First 1
                        $moduleDetails = [PSCustomObject]@{
                            Name    = $moduleName
                            Version = $moduleInfo.Version
                            Path    = $moduleInfo.ModuleBase
                        }
                        $successModules.Add($moduleDetails)
                        Write-EnhancedLog -Message "Successfully imported custom module: $moduleName" -Level "INFO"
                        $moduleSuccessCount++
                    }
                    catch {
                        $moduleDetails = [PSCustomObject]@{
                            Name    = $moduleName
                            Version = "N/A"
                            Path    = "N/A"
                        }
                        $failedModules.Add($moduleDetails)
                        Write-EnhancedLog -Message "Failed to import custom module: $moduleName. Error: $_" -Level "ERROR"
                        $moduleFailCount++
                    }
                }
            }

            Write-EnhancedLog -Message "Modules installation and import process completed." -Level "INFO"
        }
        catch {
            Write-EnhancedLog -Message "Error processing modules.psd1: $_" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    end {
        # Output summary report
        Write-EnhancedLog -Message "InstallAndImportModulesPSGallery function execution completed." -Level "INFO"

        Write-Host "---------- Summary Report ----------" -ForegroundColor Cyan
        Write-Host "Total Modules Processed: $($moduleSuccessCount + $moduleFailCount)" -ForegroundColor Cyan
        Write-Host "Modules Successfully Processed: $moduleSuccessCount" -ForegroundColor Green
        Write-Host "Modules Failed: $moduleFailCount" -ForegroundColor Red

        if ($successModules.Count -gt 0) {
            Write-Host "Successful Modules:" -ForegroundColor Green
            $successModules | ForEach-Object {
                Write-Host "- Name: $_.Name, Version: $_.Version, Path: $_.Path" -ForegroundColor Green
            }
        }

        if ($failedModules.Count -gt 0) {
            Write-Host "Failed Modules:" -ForegroundColor Red
            $failedModules | ForEach-Object {
                Write-Host "- Name: $_.Name, Version: $_.Version, Path: $_.Path" -ForegroundColor Red
            }
        }

        Write-Host "-----------------------------------" -ForegroundColor Cyan
    }
}
#EndRegion '.\Public\InstallAndImportModulesPSGallery.ps1' 176
#Region '.\Public\Invoke-InPowerShell5.ps1' -1

function Invoke-InPowerShell5 {
    param (
        [string]$ScriptPath
    )

    if ($PSVersionTable.PSVersion.Major -ne 5) {
        Write-EnhancedLog "Relaunching script in PowerShell 5 (x64)..." -Level "WARNING"

        # Get the path to PowerShell 5 (x64)
        $ps5x64Path = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"

        # Launch in PowerShell 5 (x64)
        $startProcessParams64 = @{
            FilePath     = $ps5x64Path
            ArgumentList = @(
                "-NoExit",
                "-NoProfile",
                "-ExecutionPolicy", "Bypass",
                "-File", "`"$ScriptPath`""
            )
            Verb         = "RunAs"
            PassThru     = $true
        }

        Write-EnhancedLog "Starting PowerShell 5 (x64) to perform the update..." -Level "NOTICE"
        $process64 = Start-Process @startProcessParams64
        $process64.WaitForExit()

        Write-EnhancedLog "PowerShell 5 (x64) process completed." -Level "NOTICE"
        Exit
    }
}
#EndRegion '.\Public\Invoke-InPowerShell5.ps1' 33
#Region '.\Public\Is-ServerCore.ps1' -1

function Is-ServerCore {
    $explorerPath = "$env:SystemRoot\explorer.exe"

    if (Test-Path $explorerPath) {
        return $false
    } else {
        return $true
    }
}

Is-ServerCore
#EndRegion '.\Public\Is-ServerCore.ps1' 12
#Region '.\Public\Remove-OldVersions.ps1' -1

function Remove-OldVersions {
    <#
    .SYNOPSIS
    Removes older versions of a specified PowerShell module.
 
    .DESCRIPTION
    The Remove-OldVersions function removes all but the latest version of the specified PowerShell module. It ensures that only the most recent version is retained.
 
    .PARAMETER ModuleName
    The name of the module for which older versions will be removed.
 
    .EXAMPLE
    Remove-OldVersions -ModuleName "Pester"
    Removes all but the latest version of the Pester module.
 
    .NOTES
    This function requires administrative access to manage modules and assumes that the CheckAndElevate function is defined elsewhere in the script.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$ModuleName
    )

    begin {
        Write-EnhancedLog -Message "Starting Remove-OldVersions function for module: $ModuleName" -Level "INFO"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        # Get all versions except the latest one
        $allVersions = Get-Module -ListAvailable -Name $ModuleName | Sort-Object Version
        $latestVersion = $allVersions | Select-Object -Last 1
        $olderVersions = $allVersions | Where-Object { $_.Version -ne $latestVersion.Version }

        foreach ($version in $olderVersions) {
            try {
                Write-EnhancedLog -Message "Removing older version $($version.Version) of $ModuleName..." -Level "INFO"
                $modulePath = $version.ModuleBase

                Write-EnhancedLog -Message "Starting takeown and icacls for $modulePath" -Level "INFO"
                Write-EnhancedLog -Message "Checking and elevating to admin if needed" -Level "INFO"
                CheckAndElevate
                & takeown.exe /F $modulePath /A /R
                & icacls.exe $modulePath /reset
                & icacls.exe $modulePath /grant "*S-1-5-32-544:F" /inheritance:d /T
                Remove-Item -Path $modulePath -Recurse -Force -Confirm:$false

                Write-EnhancedLog -Message "Removed $($version.Version) successfully." -Level "INFO"
            } catch {
                Write-EnhancedLog -Message "Failed to remove version $($version.Version) of $ModuleName at $modulePath. Error: $_" -Level "ERROR"
                Handle-Error -ErrorRecord $_
            }
        }
    }

    end {
        Write-EnhancedLog -Message "Remove-OldVersions function execution completed for module: $ModuleName" -Level "INFO"
    }
}
#EndRegion '.\Public\Remove-OldVersions.ps1' 62
#Region '.\Public\Reset-ModulePaths.ps1' -1

function Reset-ModulePaths {
    [CmdletBinding()]
    param ()

    begin {
        # Initialization block, typically used for setup tasks
        Write-EnhancedLog "Initializing Reset-ModulePaths function..." -Level "DEBUG"
    }

    process {
        try {
            # Log the start of the process
            Write-EnhancedLog "Resetting module paths to default values..." -Level "INFO"

            # Get the current user's Documents path
            $userModulesPath = [System.IO.Path]::Combine($env:USERPROFILE, 'Documents\WindowsPowerShell\Modules')

            # Define the default module paths
            $defaultModulePaths = @(
                "C:\Program Files\WindowsPowerShell\Modules",
                $userModulesPath,
                "C:\Windows\System32\WindowsPowerShell\v1.0\Modules"
            )

            # Attempt to reset the PSModulePath environment variable
            $env:PSModulePath = [string]::Join(';', $defaultModulePaths)
            Write-EnhancedLog "PSModulePath successfully set to: $($env:PSModulePath -split ';' | Out-String)" -Level "INFO"

            # Optionally persist the change for the current user
            [Environment]::SetEnvironmentVariable("PSModulePath", $env:PSModulePath, [EnvironmentVariableTarget]::User)
            Write-EnhancedLog "PSModulePath environment variable set for the current user." -Level "INFO"
        }
        catch {
            # Capture and log any errors that occur during the process
            $errorMessage = $_.Exception.Message
            Write-EnhancedLog "Error resetting module paths: $errorMessage" -Level "ERROR"

            # Optionally, you could throw the error to halt the script
            throw $_
        }
    }

    end {
        # Finalization block, typically used for cleanup tasks
        Write-EnhancedLog "Reset-ModulePaths function completed." -Level "DEBUG"
    }
}
#EndRegion '.\Public\Reset-ModulePaths.ps1' 48
#Region '.\Public\Update-ModuleIfOldOrMissing.ps1' -1

function Update-ModuleIfOldOrMissing {
    <#
    .SYNOPSIS
    Updates or installs a specified PowerShell module if it is outdated or missing.
 
    .DESCRIPTION
    The Update-ModuleIfOldOrMissing function checks the status of a specified PowerShell module and updates it if it is outdated. If the module is not installed, it installs the latest version. It also removes older versions after the update.
 
    .PARAMETER ModuleName
    The name of the module to be checked and updated or installed.
 
    .EXAMPLE
    Update-ModuleIfOldOrMissing -ModuleName "Pester"
    Checks and updates the Pester module if it is outdated or installs it if not present.
 
    .NOTES
    This function requires administrative access to manage modules and assumes that the CheckAndElevate, Check-ModuleVersionStatus, and Remove-OldVersions functions are defined elsewhere in the script.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$ModuleName
    )

    begin {
        Write-EnhancedLog -Message "Starting Update-ModuleIfOldOrMissing function for module: $ModuleName" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        $moduleStatus = Check-ModuleVersionStatus -ModuleNames @($ModuleName)
        foreach ($status in $moduleStatus) {
            switch ($status.Status) {
                "Outdated" {
                    Write-EnhancedLog -Message "Updating $ModuleName from version $($status.InstalledVersion) to $($status.LatestVersion)." -Level "WARNING"

                    # Remove older versions
                    Remove-OldVersions -ModuleName $ModuleName

                    # Install the latest version of the module
                    # Install-Module -Name $ModuleName -Force -SkipPublisherCheck -Scope AllUsers
                    Install-ModuleWithPowerShell5Fallback -ModuleName $ModuleName

                    Write-EnhancedLog -Message "$ModuleName has been updated to the latest version." -Level "INFO"
                }
                "Up-to-date" {
                    Write-EnhancedLog -Message "$ModuleName version $($status.InstalledVersion) is up-to-date. No update necessary." -Level "INFO"
                    Remove-OldVersions -ModuleName $ModuleName
                }
                "Not Installed" {
                    Write-EnhancedLog -Message "$ModuleName is not installed. Installing the latest version..." -Level "WARNING"
                    # Install-Module -Name $ModuleName -Force -SkipPublisherCheck -Scope AllUsers

                    $DBG
                    Install-ModuleWithPowerShell5Fallback -ModuleName $ModuleName
                    Write-EnhancedLog -Message "$ModuleName has been installed." -Level "INFO"
                }
                "Not Found in Gallery" {
                    Write-EnhancedLog -Message "Unable to find '$ModuleName' in the PowerShell Gallery." -Level "ERROR"
                }
            }
        }
    }

    end {
        Write-EnhancedLog -Message "Update-ModuleIfOldOrMissing function execution completed for module: $ModuleName" -Level "Notice"
    }
}
#EndRegion '.\Public\Update-ModuleIfOldOrMissing.ps1' 70