EnhancedModuleStarterAO.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\Add-Step.ps1' -1

function Add-Step {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Description,

        [Parameter(Mandatory = $true)]
        [ScriptBlock]$Action
    )

    Begin {
        Write-EnhancedLog -Message "Starting Add-Step function" -Level "INFO"
        Log-Params -Params @{
            Description = $Description
            Action      = $Action.ToString()
        }

        $global:steps = [System.Collections.Generic.List[PSCustomObject]]::new()
    }

    Process {
        try {
            Write-EnhancedLog -Message "Adding step: $Description" -Level "INFO"
            $global:steps.Add([PSCustomObject]@{ Description = $Description; Action = $Action })
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while adding step: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Add-Step function" -Level "INFO"
    }
}

# Example usage
# Add-Step -Description "Sample step description" -Action { Write-Output "Sample action" }
#EndRegion '.\Public\Add-Step.ps1' 39
#Region '.\Public\Authenticate-GitHubCLI.ps1' -1

function Authenticate-GitHubCLI {
    <#
    .SYNOPSIS
    Authenticates with GitHub CLI using a token provided by the user or from a secrets file.
 
    .DESCRIPTION
    This function allows the user to authenticate with GitHub CLI by either entering a GitHub token manually or using a token from a secrets file located in the `$ScriptDirectory`.
 
    .PARAMETER GhPath
    The path to the GitHub CLI executable (gh.exe).
 
    .EXAMPLE
    Authenticate-GitHubCLI -GhPath "C:\Program Files\GitHub CLI\gh.exe"
    Prompts the user to choose between entering the GitHub token manually or using the token from the secrets file.
 
    .NOTES
    This function requires GitHub CLI (gh) to be installed and available at the specified path.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$GhPath,
        [Parameter(Mandatory = $false)]
        [string]$ScriptDirectory
    )

    begin {
        Write-EnhancedLog -Message "Starting Authenticate-GitHubCLI function" -Level "NOTICE"
    }

    process {
        try {
            Write-EnhancedLog -Message "Authenticating with GitHub CLI..." -Level "INFO"
            
            # Define the secrets file path
            $secretsFilePath = Join-Path -Path $ScriptDirectory -ChildPath "secrets.psd1"

            if (-not (Test-Path -Path $secretsFilePath)) {
                # If the secrets file does not exist, prompt the user to enter the token
                Write-Warning "Secrets file not found. Please enter your GitHub token."
                $secureToken = Read-Host "Enter your GitHub token" -AsSecureString
                $ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureToken)
                $token = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ptr)
                [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ptr)

                # Store the token in the secrets.psd1 file
                $secretsContent = @{
                    GitHubToken = $token
                }
                $secretsContent | Export-Clixml -Path $secretsFilePath
                Write-Host "GitHub token has been saved to $secretsFilePath." -ForegroundColor Green
            }
            else {
                # If the secrets file exists, import it
                $secrets = Import-PowerShellDataFile -Path $secretsFilePath
                $token = $secrets.GitHubToken

                if (-not $token) {
                    $errorMessage = "GitHub token not found in the secrets file."
                    Write-EnhancedLog -Message $errorMessage -Level "ERROR"
                    throw $errorMessage
                }

                Write-EnhancedLog -Message "Using GitHub token from secrets file for authentication." -Level "INFO"
            }

            # Check if GitHub CLI is already authenticated
            $authArguments = @("auth", "status", "-h", "github.com")
            $authStatus = & $GhPath $authArguments 2>&1

            if ($authStatus -notlike "*Logged in to github.com*") {
                Write-EnhancedLog -Message "GitHub CLI is not authenticated. Attempting authentication using selected method..." -Level "WARNING"

                # Authenticate using the token
                $loginArguments = @("auth", "login", "--with-token")
                echo $token | & $GhPath $loginArguments

                # Re-check the authentication status
                $authStatus = & $GhPath $authArguments 2>&1
                if ($authStatus -like "*Logged in to github.com*") {
                    Write-EnhancedLog -Message "GitHub CLI successfully authenticated." -Level "INFO"
                }
                else {
                    $errorMessage = "Failed to authenticate GitHub CLI. Please check the token and try again."
                    Write-EnhancedLog -Message $errorMessage -Level "ERROR"
                    throw $errorMessage
                }
            }
            else {
                Write-EnhancedLog -Message "GitHub CLI is already authenticated." -Level "INFO"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred during GitHub CLI authentication: $($_.Exception.Message)" -Level "ERROR"
            throw $_
        }
    }

    end {
        Write-EnhancedLog -Message "Authenticate-GitHubCLI function execution completed." -Level "NOTICE"
    }
}
#EndRegion '.\Public\Authenticate-GitHubCLI.ps1' 104
#Region '.\Public\Check-ModuleVersionStatus.ps1' -1

function Check-ModuleVersionStatus {
    <#
    .SYNOPSIS
    Checks the installed and latest versions of PowerShell modules.
 
    .DESCRIPTION
    The Check-ModuleVersionStatus function checks if the specified PowerShell modules are installed and compares their versions with the latest available version in the PowerShell Gallery. It logs the checking process and handles errors gracefully.
 
    .PARAMETER ModuleNames
    The names of the PowerShell modules to check for version status.
 
    .EXAMPLE
    $params = @{
        ModuleNames = @('Pester', 'AzureRM', 'PowerShellGet')
    }
    Check-ModuleVersionStatus @params
    Checks the version status of the specified modules.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the names of the modules to check.")]
        [ValidateNotNullOrEmpty()]
        [string[]]$ModuleNames
    )

    Begin {
        Write-EnhancedLog -Message "Starting Check-ModuleVersionStatus function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Import PowerShellGet if it's not already loaded
        Write-EnhancedLog -Message "Importing necessary modules (PowerShellGet)." -Level "INFO"
        try {
            Import-Module -Name PowerShellGet -ErrorAction SilentlyContinue
        } catch {
            Write-EnhancedLog -Message "Failed to import PowerShellGet: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }

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

    Process {
        foreach ($ModuleName in $ModuleNames) {
            try {
                Write-EnhancedLog -Message "Checking module: $ModuleName" -Level "INFO"
                
                # Get installed module details
                $installedModule = Get-Module -ListAvailable -Name $ModuleName | Sort-Object Version -Descending | Select-Object -First 1
                
                # Get latest module version from the PowerShell Gallery
                $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
                        })
                        Write-EnhancedLog -Message "Module $ModuleName is outdated. Installed: $($installedModule.Version), Latest: $($latestModule.Version)" -Level "INFO"
                    } else {
                        $results.Add([PSCustomObject]@{
                            ModuleName       = $ModuleName
                            Status           = "Up-to-date"
                            InstalledVersion = $installedModule.Version
                            LatestVersion    = $installedModule.Version
                        })
                        Write-EnhancedLog -Message "Module $ModuleName is up-to-date. Version: $($installedModule.Version)" -Level "INFO"
                    }
                } elseif (-not $installedModule) {
                    $results.Add([PSCustomObject]@{
                        ModuleName       = $ModuleName
                        Status           = "Not Installed"
                        InstalledVersion = $null
                        LatestVersion    = $null
                    })
                    Write-EnhancedLog -Message "Module $ModuleName is not installed." -Level "INFO"
                } else {
                    $results.Add([PSCustomObject]@{
                        ModuleName       = $ModuleName
                        Status           = "Not Found in Gallery"
                        InstalledVersion = $installedModule.Version
                        LatestVersion    = $null
                    })
                    Write-EnhancedLog -Message "Module $ModuleName is installed but not found in the PowerShell Gallery." -Level "WARNING"
                }
            } catch {
                Write-EnhancedLog -Message "Error occurred while checking module '$ModuleName': $($_.Exception.Message)" -Level "ERROR"
                Handle-Error -ErrorRecord $_
                throw
            }
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Check-ModuleVersionStatus function" -Level "Notice"
        # Return the results
        return $results
    }
}

# Example usage:
# $params = @{
# ModuleNames = @('Pester', 'AzureRM', 'PowerShellGet')
# }
# $versionStatuses = Check-ModuleVersionStatus @params
# $versionStatuses | Format-Table -AutoSize
#EndRegion '.\Public\Check-ModuleVersionStatus.ps1' 112
#Region '.\Public\CheckAndElevate.ps1' -1

function CheckAndElevate {
    <#
    .SYNOPSIS
    Checks if the script is running with administrative privileges and optionally elevates it if not.
 
    .DESCRIPTION
    The CheckAndElevate function checks whether the current PowerShell session is running with administrative privileges.
    It can either return the administrative status or attempt to elevate the script if it is not running as an administrator.
 
    .PARAMETER ElevateIfNotAdmin
    If set to $true, the function will attempt to elevate the script if it is not running with administrative privileges.
    If set to $false, the function will simply return the administrative status without taking any action.
 
    .EXAMPLE
    CheckAndElevate -ElevateIfNotAdmin $true
 
    Checks the current session for administrative privileges and elevates if necessary.
 
    .EXAMPLE
    $isAdmin = CheckAndElevate -ElevateIfNotAdmin $false
    if (-not $isAdmin) {
        Write-Host "The script is not running with administrative privileges."
    }
 
    Checks the current session for administrative privileges and returns the status without elevating.
     
    .NOTES
    If the script is elevated, it will restart with administrative privileges. Ensure that any state or data required after elevation is managed appropriately.
    #>


    [CmdletBinding()]
    param (
        [bool]$ElevateIfNotAdmin = $true
    )

    Begin {
        Write-EnhancedLog -Message "Starting CheckAndElevate function" -Level "NOTICE"

        # Use .NET classes for efficiency
        try {
            $isAdmin = [System.Security.Principal.WindowsPrincipal]::new([System.Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
            Write-EnhancedLog -Message "Checking for administrative privileges..." -Level "INFO"
        }
        catch {
            Write-EnhancedLog -Message "Error determining administrative status: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    Process {
        if (-not $isAdmin) {
            if ($ElevateIfNotAdmin) {
                try {
                    Write-EnhancedLog -Message "The script is not running with administrative privileges. Attempting to elevate..." -Level "WARNING"

                    $powerShellPath = Get-PowerShellPath
                    $startProcessParams = @{
                        FilePath     = $powerShellPath
                        ArgumentList = @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "`"$PSCommandPath`"")
                        Verb         = "RunAs"
                    }
                    Start-Process @startProcessParams

                    Write-EnhancedLog -Message "Script re-launched with administrative privileges. Exiting current session." -Level "INFO"
                    exit
                }
                catch {
                    Write-EnhancedLog -Message "Failed to elevate privileges: $($_.Exception.Message)" -Level "ERROR"
                    Handle-Error -ErrorRecord $_
                    throw $_
                }
            } else {
                Write-EnhancedLog -Message "The script is not running with administrative privileges and will continue without elevation." -Level "INFO"
            }
        }
        else {
            Write-EnhancedLog -Message "Script is already running with administrative privileges." -Level "INFO"
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting CheckAndElevate function" -Level "NOTICE"
        return $isAdmin
    }
}

# Example usage to check and optionally elevate:
# CheckAndElevate -ElevateIfNotAdmin $true

# Example usage to just check and return status without elevating:
# $isAdmin = CheckAndElevate -ElevateIfNotAdmin $false
#EndRegion '.\Public\CheckAndElevate.ps1' 93
#Region '.\Public\Clone-EnhancedRepos.ps1' -1

function Clone-EnhancedRepos {
    <#
    .SYNOPSIS
    Clones all repositories from a GitHub account that start with the word "Enhanced" to a specified directory using GitHub CLI.
 
    .DESCRIPTION
    This function uses GitHub CLI to list and clone repositories from a GitHub account that start with "Enhanced" into the specified directory.
 
    .PARAMETER githubUsername
    The GitHub username to retrieve repositories from.
 
    .PARAMETER targetDirectory
    The directory to clone the repositories into.
 
    .EXAMPLE
    Clone-EnhancedRepos -githubUsername "aollivierre" -targetDirectory "C:\Code\modules-beta4"
    Clones all repositories starting with "Enhanced" from the specified GitHub account to the target directory.
 
    .NOTES
    This function requires GitHub CLI (gh) and git to be installed and available in the system's PATH.
    #>


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

        [Parameter(Mandatory = $true)]
        [string]$targetDirectory,

        [Parameter(Mandatory = $false)]
        [string]$ScriptDirectory
    )

    begin {
        Write-EnhancedLog -Message "Starting Clone-EnhancedRepos function" -Level "Notice"

        # Create the target directory if it doesn't exist
        if (-not (Test-Path -Path $targetDirectory)) {
            Write-EnhancedLog -Message "Creating target directory: $targetDirectory" -Level "INFO"
            New-Item -Path $targetDirectory -ItemType Directory
        }
    }

    process {
     
   
        
        try {
            # Get the Git executable path
            Write-EnhancedLog -Message "Attempting to find Git executable path..." -Level "INFO"
            $gitPath = Get-GitPath
            if (-not $gitPath) {
                throw "Git executable not found. Please install Git or ensure it is in your PATH."
            }
            Write-EnhancedLog -Message "Git found at: $gitPath" -Level "INFO"
        
            # Set the GitHub CLI path
            $ghPath = "C:\Program Files\GitHub CLI\gh.exe"
            
            # Define arguments for GitHub CLI as an array
            $ghArguments = @("repo", "list", "aollivierre", "--json", "name,url")

            # Set the path to GitHub CLI executable
            $ghPath = "C:\Program Files\GitHub CLI\gh.exe"

            # Authenticate with GitHub CLI
            Authenticate-GitHubCLI -GhPath $ghPath -ScriptDirectory $ScriptDirectory

        
            # Execute the GitHub CLI command using the argument array
            Write-EnhancedLog -Message "Retrieving repositories for user $githubUsername using GitHub CLI..." -Level "INFO"
            $reposJson = & $ghPath $ghArguments
            Write-EnhancedLog -Message "Raw GitHub CLI output: $reposJson" -Level "DEBUG"
            
            if (-not $reposJson) {
                throw "No repositories found or an error occurred while retrieving repositories."
            }
        
            $repos = $reposJson | ConvertFrom-Json
            Write-EnhancedLog -Message "Converted JSON output: $repos" -Level "DEBUG"
        
            $filteredRepos = $repos | Where-Object { $_.name -like "Enhanced*" }
            if ($filteredRepos.Count -eq 0) {
                Write-EnhancedLog -Message "No repositories found that match 'Enhanced*'." -Level "WARNING"
            }
            Write-EnhancedLog -Message "Filtered repositories count: $($filteredRepos.Count)" -Level "INFO"
            
            # Clone each repository using the full path to Git
            foreach ($repo in $filteredRepos) {
                $repoName = $repo.name
                $repoTargetPath = Join-Path -Path $targetDirectory -ChildPath $repoName
        
                # Check if the repository already exists in the target directory
                if (Test-Path $repoTargetPath) {
                    Write-EnhancedLog -Message "Repository $repoName already exists in $repoTargetPath. Skipping clone." -Level "INFO"
                    continue
                }
        
                $repoCloneUrl = $repo.url
        
                # Define arguments for Git as an array
                $gitArguments = @("clone", $repoCloneUrl, $repoTargetPath)
        
                Write-EnhancedLog -Message "Cloning repository $repoName to $repoTargetPath..." -Level "INFO"
                & $gitPath $gitArguments
                if ($LASTEXITCODE -ne 0) {
                    throw "Failed to clone repository $repoName. Git returned exit code $LASTEXITCODE."
                }
                Write-EnhancedLog -Message "Successfully cloned repository $repoName." -Level "INFO"
            }
        
            Write-EnhancedLog -Message "Cloning process completed." -Level "INFO"
        }
        catch {
            Write-EnhancedLog -Message "Error during cloning process: $_" -Level "ERROR"
            throw $_
        }
        
        
        
        

    }

    end {
        Write-EnhancedLog -Message "Clone-EnhancedRepos function execution completed." -Level "Notice"
    }
}
#EndRegion '.\Public\Clone-EnhancedRepos.ps1' 130
#Region '.\Public\Compare-SoftwareVersion.ps1' -1

function Compare-SoftwareVersion {
    param (
        [Parameter(Mandatory = $true)]
        [version]$InstalledVersion,

        [Parameter(Mandatory = $false)]
        [version]$MinVersion = [version]"0.0.0.0",

        [Parameter(Mandatory = $false)]
        [version]$LatestVersion
    )

    $meetsMinRequirement = $InstalledVersion -ge $MinVersion
    
    if ($LatestVersion) {
        $isUpToDate = $InstalledVersion -ge $LatestVersion
    }
    else {
        $isUpToDate = $meetsMinRequirement
    }

    return [PSCustomObject]@{
        MeetsMinRequirement = $meetsMinRequirement
        IsUpToDate          = $isUpToDate
    }
}
#EndRegion '.\Public\Compare-SoftwareVersion.ps1' 27
#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-Modules.ps1' -1


function Download-Modules {
    param (
        [array]$scriptDetails  # Array of script details, including URLs
    )

    $processList = [System.Collections.Generic.List[System.Diagnostics.Process]]::new()

    foreach ($scriptDetail in $scriptDetails) {
        $process = Invoke-WebScript -url $scriptDetail.Url
        if ($process) {
            $processList.Add($process)
        }
    }

    # Optionally wait for all processes to complete
    foreach ($process in $processList) {
        $process.WaitForExit()
    }
}
#EndRegion '.\Public\Download-Modules.ps1' 21
#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

        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

        # 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' 66
#Region '.\Public\Elevate-Script.ps1' -1

function Elevate-Script {
    if (-not (Test-Admin)) {
        Write-EnhancedLog "Restarting script with elevated permissions..."
        $startProcessParams = @{
            FilePath     = "powershell.exe"
            ArgumentList = @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $PSCommandPath)
            Verb         = "RunAs"
        }
        Start-Process @startProcessParams
        exit
    }
}
#EndRegion '.\Public\Elevate-Script.ps1' 13
#Region '.\Public\Ensure-GitIsInstalled.ps1' -1

function Ensure-GitIsInstalled {
    <#
    .SYNOPSIS
    Ensures that Git is installed and meets the minimum version requirement.
 
    .DESCRIPTION
    The Ensure-GitIsInstalled function checks if Git is installed on the system and meets the specified minimum version requirement. If Git is not installed or does not meet the requirement, it attempts to install Git from the web. The function logs all steps and handles errors appropriately.
 
    .PARAMETER MinVersion
    The minimum version of Git required.
 
    .PARAMETER RegistryPath
    The registry path to check for Git installation.
 
    .PARAMETER ExePath
    The full path to the Git executable.
 
    .EXAMPLE
    $params = @{
        MinVersion = [version]"2.46.0"
        RegistryPath = "HKLM:\SOFTWARE\GitForWindows"
        ExePath = "C:\Program Files\Git\bin\git.exe"
    }
    Ensure-GitIsInstalled @params
    Ensures that Git is installed and meets the minimum version requirement.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false, HelpMessage = "Provide the minimum required version of Git.")]
        [ValidateNotNullOrEmpty()]
        [version]$MinVersion,

        [Parameter(Mandatory = $false, HelpMessage = "Provide the registry path to check for Git installation.")]
        [ValidateNotNullOrEmpty()]
        [string]$RegistryPath,

        [Parameter(Mandatory = $false, HelpMessage = "Provide the full path to the Git executable.")]
        [ValidateNotNullOrEmpty()]
        [string]$ExePath
    )

    Begin {
        Write-EnhancedLog -Message "Starting Ensure-GitIsInstalled function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Parameters for validating the installation
        $validateSoftwareParams = @{
            SoftwareName        = "Git"
            # MinVersion = $MinVersion
            # RegistryPath = $RegistryPath
            # ExePath = $ExePath
            MinVersion          = [version]"2.46.0"
            RegistryPath        = "HKLM:\SOFTWARE\GitForWindows"
            ExePath             = "C:\Program Files\Git\bin\git.exe"
            LatestVersion       = $null  # Optional, can be provided if needed
            MaxRetries          = 3      # Default value
            DelayBetweenRetries = 5      # Default value in seconds
        }
    }

    Process {
        try {
            # Validate if Git is installed and meets the version requirement
            Write-EnhancedLog -Message "Checking if Git is installed and meets the minimum version requirement." -Level "INFO"
            $validationResult = Validate-SoftwareInstallation @validateSoftwareParams

            if ($validationResult.IsInstalled) {
                Write-EnhancedLog -Message "Git version $($validationResult.InstalledVersion) is installed and meets the minimum version requirement." -Level "INFO"
                return $true
            }
            else {
                Write-EnhancedLog -Message "Git is not installed or does not meet the minimum version requirement. Installing Git..." -Level "WARNING"
                $installSuccess = Install-GitFromWeb
                return $installSuccess
            }
        }
        catch {
            Write-EnhancedLog -Message "Error ensuring Git installation: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
        Finally {
            Write-EnhancedLog -Message "Exiting Ensure-GitIsInstalled function" -Level "Notice"
        }
    }

    End {
        # No additional actions needed in the End block for this function.
    }
}

# Example usage
# $params = @{
# MinVersion = [version]"2.46.0"
# RegistryPath = "HKLM:\SOFTWARE\GitForWindows"
# ExePath = "C:\Program Files\Git\bin\git.exe"
# }
# Ensure-GitIsInstalled @params
#EndRegion '.\Public\Ensure-GitIsInstalled.ps1' 100
#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\Ensure-ModuleIsLatest.ps1' -1

function Ensure-ModuleIsLatest {
    param (
        [string]$ModuleName
    )

    Write-EnhancedLog -Message "Checking if the latest version of $ModuleName is installed..." -Level "INFO"

    

    try {

        if ($SkipCheckandElevate) {
            Write-EnhancedLog -Message "Skipping CheckAndElevate due to SkipCheckandElevate parameter." -Level "INFO"
        }
        else {
            CheckAndElevate -ElevateIfNotAdmin $true
        }
        
        # Invoke-InPowerShell5

        

        # Reset-ModulePaths

        $params = @{
            ModuleName = "$Modulename"
        }
        Install-ModuleInPS5 @params

        # Get the installed version of the module, if any
        $installedModule = Get-Module -Name $ModuleName -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1

        # Get the latest available version of the module from PSGallery
        $latestModule = Find-Module -Name $ModuleName

        if ($installedModule) {
            if ($installedModule.Version -lt $latestModule.Version) {
                Write-EnhancedLog -Message "$ModuleName version $($installedModule.Version) is installed, but version $($latestModule.Version) is available. Updating module..." -Level "WARNING"
                # Install-Module -Name $ModuleName -Scope AllUsers -Force -SkipPublisherCheck -Verbose

                $params = @{
                    ModuleName = "$Modulename"
                }
                Install-ModuleInPS5 @params

            }
            else {
                Write-EnhancedLog -Message "The latest version of $ModuleName is already installed. Version: $($installedModule.Version)" -Level "INFO"
            }
        }
        else {
            Write-EnhancedLog -Message "$ModuleName is not installed. Installing the latest version $($latestModule.Version)..." -Level "WARNING"
            # Install-Module -Name $ModuleName -Scope AllUsers -Force -SkipPublisherCheck -Verbose -AllowClobber

            $params = @{
                ModuleName = "$Modulename"
            }
            Install-ModuleInPS5 @params

        }
    }
    catch {
        Write-EnhancedLog -Message "Error occurred while checking or installing $ModuleName $_" -Level "ERROR"
        throw
    }
}
#EndRegion '.\Public\Ensure-ModuleIsLatest.ps1' 67
#Region '.\Public\Ensure-NuGetProvider.ps1' -1

function Ensure-NuGetProvider {
    <#
    .SYNOPSIS
    Ensures that the NuGet provider and PowerShellGet module are installed when running in PowerShell 5.
 
    .DESCRIPTION
    This function checks if the NuGet provider is installed when running in PowerShell 5. If not, it installs the NuGet provider and ensures that the PowerShellGet module is installed as well.
 
    .EXAMPLE
    Ensure-NuGetProvider
    Ensures the NuGet provider is installed on a PowerShell 5 system.
    #>


    [CmdletBinding()]
    param ()

    Begin {
        Write-EnhancedLog -Message "Starting Ensure-NuGetProvider function" -Level "Notice"

        Reset-ModulePaths
        
        # Log the current PowerShell version
        Write-EnhancedLog -Message "Running PowerShell version: $($PSVersionTable.PSVersion)" -Level "INFO"

        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    }

    Process {
        try {
            # Check if running in PowerShell 5
            if ($PSVersionTable.PSVersion.Major -eq 5) {
                Write-EnhancedLog -Message "Running in PowerShell version 5, checking NuGet provider..." -Level "INFO"

                # Use -ListAvailable to only check installed providers without triggering installation
                if (-not (Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue)) {
                    Write-EnhancedLog -Message "NuGet provider not found. Installing NuGet provider..." -Level "INFO"

                    # Install the NuGet provider with ForceBootstrap to bypass the prompt
                    Install-PackageProvider -Name NuGet -ForceBootstrap -Force -Confirm:$false
                    Write-EnhancedLog -Message "NuGet provider installed successfully." -Level "INFO"
                    
                    # Install the PowerShellGet module
                    $params = @{
                        ModuleName = "PowerShellGet"
                    }
                    Install-ModuleInPS5 @params

                } else {
                    Write-EnhancedLog -Message "NuGet provider is already installed." -Level "INFO"
                }
            }
            else {
                Write-EnhancedLog -Message "This script is running in PowerShell version $($PSVersionTable.PSVersion), which is not version 5. No action is taken for NuGet." -Level "INFO"
            }
        }
        catch {
            Write-EnhancedLog -Message "Error encountered during NuGet provider installation: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Ensure-NuGetProvider function" -Level "Notice"
    }
}
#EndRegion '.\Public\Ensure-NuGetProvider.ps1' 67
#Region '.\Public\Generate-SoftwareInstallSummaryReport.ps1' -1

function Generate-SoftwareInstallSummaryReport {
    param (
        [PSCustomObject[]]$installationResults
    )

    $totalSoftware = $installationResults.Count
    $successfulInstallations = $installationResults | Where-Object { $_.Status -eq "Successfully Installed" }
    $alreadyInstalled = $installationResults | Where-Object { $_.Status -eq "Already Installed" }
    $failedInstallations = $installationResults | Where-Object { $_.Status -like "Failed*" }

    Write-Host "Total Software: $totalSoftware" -ForegroundColor Cyan
    Write-Host "Successful Installations: $($successfulInstallations.Count)" -ForegroundColor Green
    Write-Host "Already Installed: $($alreadyInstalled.Count)" -ForegroundColor Yellow
    Write-Host "Failed Installations: $($failedInstallations.Count)" -ForegroundColor Red

    # Detailed Summary
    Write-Host "`nDetailed Summary:" -ForegroundColor Cyan
    $installationResults | ForEach-Object {
        Write-Host "Software: $($_.SoftwareName)" -ForegroundColor White
        Write-Host "Status: $($_.Status)" -ForegroundColor White
        Write-Host "Version Found: $($_.VersionFound)" -ForegroundColor White
        Write-Host "----------------------------------------" -ForegroundColor Gray
    }
}
#EndRegion '.\Public\Generate-SoftwareInstallSummaryReport.ps1' 25
#Region '.\Public\Get-GitPath.ps1' -1

function Get-GitPath {
    <#
    .SYNOPSIS
    Discovers the path to the Git executable on the system.
 
    .DESCRIPTION
    This function attempts to find the Git executable by checking common installation directories and the system's PATH environment variable.
 
    .EXAMPLE
    $gitPath = Get-GitPath
    if ($gitPath) {
        Write-Host "Git found at: $gitPath"
    } else {
        Write-Host "Git not found."
    }
    #>


    [CmdletBinding()]
    param ()

    try {
        # Common Git installation paths
        $commonPaths = @(
            "C:\Program Files\Git\bin\git.exe",
            "C:\Program Files (x86)\Git\bin\git.exe"
        )

        # Check the common paths
        foreach ($path in $commonPaths) {
            if (Test-Path -Path $path) {
                Write-EnhancedLog -Message "Git found at: $path" -Level "INFO"
                return $path
            }
        }

        # If not found, check if Git is in the system PATH
        $gitPathInEnv = (Get-Command git -ErrorAction SilentlyContinue).Source
        if ($gitPathInEnv) {
            Write-EnhancedLog -Message "Git found in system PATH: $gitPathInEnv" -Level "INFO"
            return $gitPathInEnv
        }

        # If Git is still not found, return $null
        Write-EnhancedLog -Message "Git executable not found." -Level "ERROR"
        return $null
    }
    catch {
        Write-EnhancedLog -Message "Error occurred while trying to find Git path: $_" -Level "ERROR"
        return $null
    }
}
#EndRegion '.\Public\Get-GitPath.ps1' 52
#Region '.\Public\Get-LatestChocoVersion.ps1' -1

function Get-LatestChocoVersion {
    <#
    .SYNOPSIS
    Retrieves the latest version of a specified package from Chocolatey.
 
    .DESCRIPTION
    This function retrieves the latest version of a package from Chocolatey, given the package name. It includes logging for each step, handles cases where the package is not found, and supports pipeline input.
 
    .PARAMETER AppName
    The name of the application/package to search for in Chocolatey.
 
    .EXAMPLE
    $apps = 'GoogleChrome', 'MicrosoftEdge', 'Firefox'
    $apps | Get-LatestChocoVersion
    Retrieves the latest versions of Google Chrome, Microsoft Edge, and Firefox from Chocolatey.
    #>


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

    Begin {
        Write-EnhancedLog -Message "Starting Get-LatestChocoVersion function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Check if choco.exe is available
        Write-EnhancedLog -Message "Checking if choco.exe is available..." -Level "INFO"
        if (-not (Get-Command choco.exe -ErrorAction SilentlyContinue)) {
            Write-EnhancedLog -Message "choco.exe not found. Ensure Chocolatey is installed and available in PATH." -Level "ERROR"
            throw "Chocolatey is not installed or not available in PATH."
        }
    }

    Process {
        Write-EnhancedLog -Message "Finding the latest version of $AppName..." -Level "INFO"

        # Perform the search and capture raw output
        $rawOutput = choco search $AppName --exact --allversions
        Write-EnhancedLog -Message "Raw search output: `n$rawOutput" -Level "DEBUG"

        # Filter and extract the version information
        $versionLines = $rawOutput | ForEach-Object {
            if ($_ -match "$AppName\s+([\d\.]+)") {
                $matches[1]
            }
        }

        if ($versionLines) {
            # Sort the versions and select the latest one
            $latestVersion = $versionLines | Sort-Object { [version]$_ } -Descending | Select-Object -First 1

            if ($latestVersion) {
                Write-EnhancedLog -Message "The latest version of $AppName available in Chocolatey is: $latestVersion" -Level "INFO"
                [PSCustomObject]@{
                    AppName       = $AppName
                    LatestVersion = $latestVersion
                }
            } else {
                Write-EnhancedLog -Message "No version information found for $AppName." -Level "WARNING"
            }
        } else {
            Write-EnhancedLog -Message "No version information found for $AppName." -Level "WARNING"
            Write-EnhancedLog -Message "Possible reasons: the package name may be incorrect, or the package may not be available in the Chocolatey repository." -Level "INFO"
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Get-LatestChocoVersion function" -Level "Notice"
    }
}

# # Example usage
# $apps = 'GoogleChrome', 'MicrosoftEdge', 'Firefox'
# $apps | Get-LatestChocoVersion
#EndRegion '.\Public\Get-LatestChocoVersion.ps1' 77
#Region '.\Public\Get-LatestWinGetversion.ps1' -1

function Add-WinGetPathToEnvironment {
    <#
    .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]@{}
        $containerMapping = @{
            Machine = [System.EnvironmentVariableTarget]::Machine
            User    = [System.EnvironmentVariableTarget]::User
        }
    }

    process {
        try {
            # Add to Machine or User PATH
            if ($Container -ne 'Session') {
                $containerType = $containerMapping[$Container]
                $existingPaths = [System.Environment]::GetEnvironmentVariable('Path', $containerType) -split ';'

                if (-not $existingPaths -contains $Path) {
                    $updatedPaths = "$($existingPaths -join ';');$Path"
                    [System.Environment]::SetEnvironmentVariable('Path', $updatedPaths, $containerType)
                    Write-EnhancedLog -Message "Added $Path to $Container PATH." -Level "INFO"
                }
            }

            # Add to Session PATH
            $existingSessionPaths = $env:Path -split ';'
            if (-not $existingSessionPaths -contains $Path) {
                $env:Path += ";$Path"
                Write-EnhancedLog -Message "Added $Path to Session PATH." -Level "INFO"
            }

            # Validate PATH update
            $wingetCmd = Get-Command "winget.exe" -ErrorAction SilentlyContinue
            if (-not $wingetCmd) {
                throw "WinGet executable not found after adding to PATH."
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
    }

    end {
        Write-EnhancedLog -Message "Exiting Add-EnvPath function" -Level "INFO"
    }
}
function Find-WinGetPath {
    <#
    .SYNOPSIS
    Finds the path to winget.exe based on the context (Machine or User) and checks for Windows Server.
 
    .DESCRIPTION
    This function finds the location of winget.exe depending on whether it's running in a machine or user context, and adjusts the environment path if running on Windows Server.
 
    .EXAMPLE
    $wingetPath = Find-WinGetPath
    #>


    [CmdletBinding()]
    param ()

    Begin {
        Write-EnhancedLog -Message "Starting Find-WinGetPath function" -Level "Notice"
    }

    Process {
        try {
            # Check if running as SYSTEM
            $isSystem = Test-RunningAsSystem
            Write-EnhancedLog -Message "Running as SYSTEM: $isSystem" -Level "INFO"

            # Check if running on Windows Server
            $isWindowsServer = (Get-WmiObject Win32_OperatingSystem).ProductType -eq 3
            Write-EnhancedLog -Message "Running on Windows Server: $isWindowsServer" -Level "INFO"

            if ($isWindowsServer) {
                # On Windows Server, use the UniGet path
                Write-EnhancedLog -Message "Windows Server detected, using UniGet path for winget..." -Level "INFO"
                $wingetPath = "C:\Program Files\UniGetUI\winget-cli_x64\winget.exe"
                Add-WinGetPathToEnvironment -Path "C:\Program Files\UniGetUI\winget-cli_x64" -Container 'Machine'
            }
            else {
                # On non-server systems running as SYSTEM, resolve the regular winget path
                if ($isSystem) {
                    Write-EnhancedLog -Message "Non-Windows Server system detected and running as SYSTEM, resolving WinGet path..." -Level "INFO"
                    $resolveWingetPath = Resolve-Path "C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_*_x64__8wekyb3d8bbwe"
                    if ($resolveWingetPath) {
                        $wingetPath = $resolveWingetPath[-1].Path + "\winget.exe"
                    }
                    else {
                        Write-EnhancedLog -Message "Failed to resolve WinGet path." -Level "ERROR"
                        throw "Failed to resolve WinGet path."
                    }
                }
                else {
                    Write-EnhancedLog -Message "Non-Windows Server and not running as SYSTEM, assuming WinGet is available in PATH." -Level "INFO"
                    $wingetPath = "winget.exe"
                }
            }

            # Validate WinGet path
            $wingetCommand = Get-Command winget.exe -ErrorAction SilentlyContinue
            if (-not $wingetCommand) {
                Write-EnhancedLog -Message "WinGet executable not found in the specified path: $wingetPath" -Level "ERROR"
                throw "WinGet executable not found."
            }

            Write-EnhancedLog -Message "WinGet path found and validated: $($wingetCommand.Path)" -Level "INFO"
            return $wingetCommand.Path
        }
        catch {
            Write-EnhancedLog -Message "Failed to find WinGet path: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Find-WinGetPath function" -Level "Notice"
    }
}
#Using Winget.exe
function Get-LatestVersionFromWinGet {
    <#
    .SYNOPSIS
    Retrieves the latest version of an application from WinGet with a retry mechanism.
 
    .DESCRIPTION
    This function queries WinGet to retrieve the latest version of a specified application. It includes a retry mechanism if the latest version is not retrieved.
 
    .PARAMETER id
    The exact WinGet package ID of the application.
 
    .PARAMETER MaxRetries
    The maximum number of retry attempts if the version retrieval fails.
 
    .PARAMETER DelayBetweenRetries
    The delay between retry attempts in seconds.
 
    .EXAMPLE
    $latestVersion = Get-LatestVersionFromWinGet -id "VideoLAN.VLC" -MaxRetries 3 -DelayBetweenRetries 5
    #>

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

        [Parameter(Mandatory = $false)]
        [int]$MaxRetries = 3,

        [Parameter(Mandatory = $false)]
        [int]$DelayBetweenRetries = 5
    )

    Begin {
        Write-EnhancedLog -Message "Starting Get-LatestVersionFromWinGet function for package ID: $id" -Level "Notice"
    }

    Process {
        $retryCount = 0
        $latestVersion = $null

        while ($retryCount -lt $MaxRetries -and -not $latestVersion) {
            try {
                $retryCount++
                Write-EnhancedLog -Message "Attempt $retryCount of $MaxRetries to retrieve the latest version for $id." -Level "INFO"

                $wingetPath = Find-WinGetPath
                $params = @{
                    FilePath               = $wingetPath
                    ArgumentList           = "show --id $id --exact --accept-source-agreements"
                    WindowStyle            = "Hidden"
                    Wait                   = $true
                    RedirectStandardOutput = "$env:TEMP\wingetOutput.txt"
                }

                Write-EnhancedLog -Message "Querying WinGet for the latest version of package ID: $id" -Level "INFO"
                Start-Process @params
                $winGetOutput = Get-Content -Path "$env:TEMP\wingetOutput.txt"
                Remove-Item -Path "$env:TEMP\wingetOutput.txt" -Force

                $latestVersion = $winGetOutput | Select-String -Pattern "version:" | ForEach-Object { $_.Line -replace '.*version:\s*(.*)', '$1' }

                if (-not $latestVersion) {
                    Write-EnhancedLog -Message "Failed to retrieve latest version. Waiting $DelayBetweenRetries seconds before retrying..." -Level "WARNING"
                    Start-Sleep -Seconds $DelayBetweenRetries
                }
                else {
                    Write-EnhancedLog -Message "WinGet latest version retrieved: $latestVersion" -Level "INFO"
                }
            }
            catch {
                Write-EnhancedLog -Message "Error occurred during version retrieval attempt $retryCount $($_.Exception.Message)" -Level "ERROR"
                if ($retryCount -lt $MaxRetries) {
                    Write-EnhancedLog -Message "Retrying in $DelayBetweenRetries seconds..." -Level "WARNING"
                    Start-Sleep -Seconds $DelayBetweenRetries
                }
                else {
                    Handle-Error -ErrorRecord $_
                    throw
                }
            }
        }

        if (-not $latestVersion) {
            Write-EnhancedLog -Message "Failed to retrieve the latest version after $MaxRetries attempts." -Level "ERROR"
            throw "Unable to retrieve the latest version from WinGet after $MaxRetries attempts."
        }

        return $latestVersion
    }

    End {
        Write-EnhancedLog -Message "Exiting Get-LatestVersionFromWinGet function" -Level "Notice"
    }
}

function Get-LatestWinGetVersion {
    <#
    .SYNOPSIS
    Compares the latest version from WinGet with an optional target version.
 
    .DESCRIPTION
    This function retrieves the latest available version of an application from WinGet and compares it with an optional target version.
 
    .PARAMETER id
    The exact WinGet package ID of the application.
 
    .PARAMETER TargetVersion
    A specific version to target for comparison (optional).
 
    .PARAMETER AcceptNewerVersion
    Indicates whether a locally installed version that is newer than the target or WinGet version is acceptable.
 
    .EXAMPLE
    $params = @{
        id = "VideoLAN.VLC"
        TargetVersion = ""
        AcceptNewerVersion = $true
    }
    $result = Get-LatestWinGetVersion @params
    #>

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

        [Parameter(Mandatory = $false)]
        [string]$TargetVersion = "",

        [Parameter(Mandatory = $false)]
        [bool]$AcceptNewerVersion = $true
    )

    Begin {
        Write-EnhancedLog -Message "Starting Get-LatestWinGetVersion function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Get the latest version from WinGet
            $latestVersion = if ($TargetVersion) { [version]$TargetVersion } else { [version](Get-LatestVersionFromWinGet -id $id) }

            $result = [PSCustomObject]@{
                LatestVersion = $latestVersion
                Status        = "Latest Version Retrieved"
                Message       = "The latest version of $id is $latestVersion."
            }

            Write-EnhancedLog -Message $result.Message -Level "INFO"
            return $result
        }
        catch {
            Write-EnhancedLog -Message "Error in Get-LatestWinGetVersion function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Get-LatestWinGetVersion function" -Level "Notice"
    }
}
#EndRegion '.\Public\Get-LatestWinGetversion.ps1' 315
#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\Get-ParentScriptName.ps1' -1

function Get-ParentScriptName {
    [CmdletBinding()]
    param ()

    try {
        # Get the current call stack
        $callStack = Get-PSCallStack

        # If there is a call stack, return the top-most script name
        if ($callStack.Count -gt 0) {
            foreach ($frame in $callStack) {
                if ($frame.ScriptName) {
                    $parentScriptName = $frame.ScriptName
                    # Write-EnhancedLog -Message "Found script in call stack: $parentScriptName" -Level "INFO"
                }
            }

            if (-not [string]::IsNullOrEmpty($parentScriptName)) {
                $parentScriptName = [System.IO.Path]::GetFileNameWithoutExtension($parentScriptName)
                return $parentScriptName
            }
        }

        # If no script name was found, return 'UnknownScript'
        Write-EnhancedLog -Message "No script name found in the call stack." -Level "WARNING"
        return "UnknownScript"
    }
    catch {
        Write-EnhancedLog -Message "An error occurred while retrieving the parent script name: $_" -Level "ERROR"
        return "UnknownScript"
    }
}
#EndRegion '.\Public\Get-ParentScriptName.ps1' 33
#Region '.\Public\Get-Platform.ps1' -1

function Get-Platform {
    if ($PSVersionTable.PSVersion.Major -ge 7) {
        return $PSVersionTable.Platform
    }
    else {
        return [System.Environment]::OSVersion.Platform
    }
}
#EndRegion '.\Public\Get-Platform.ps1' 9
#Region '.\Public\Get-PowerShellPath.ps1' -1

function Get-PowerShellPath {
    <#
    .SYNOPSIS
        Retrieves the path to the installed PowerShell executable, defaulting to PowerShell 5.
 
    .DESCRIPTION
        This function checks for the existence of PowerShell 5 and PowerShell 7 on the system.
        By default, it returns the path to PowerShell 5 unless the -UsePS7 switch is provided.
        If the specified version is not found, an error is thrown.
 
    .PARAMETER UsePS7
        Optional switch to prioritize PowerShell 7 over PowerShell 5.
 
    .EXAMPLE
        $pwshPath = Get-PowerShellPath
        Write-Host "PowerShell found at: $pwshPath"
 
    .EXAMPLE
        $pwshPath = Get-PowerShellPath -UsePS7
        Write-Host "PowerShell found at: $pwshPath"
 
    .NOTES
        Author: Abdullah Ollivierre
        Date: 2024-08-15
    #>


    [CmdletBinding()]
    param (
        [switch]$UsePS7
    )

    Begin {
        Write-EnhancedLog -Message "Starting Get-PowerShellPath function" -Level "NOTICE"
    }

    Process {
        $pwsh7Path = "C:\Program Files\PowerShell\7\pwsh.exe"
        $pwsh5Path = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"

        if ($UsePS7) {
            if (Test-Path $pwsh7Path) {
                Write-EnhancedLog -Message "PowerShell 7 found at $pwsh7Path" -Level "INFO"
                return $pwsh7Path
            } elseif (Test-Path $pwsh5Path) {
                Write-EnhancedLog -Message "PowerShell 7 not found, falling back to PowerShell 5 at $pwsh5Path" -Level "WARNING"
                return $pwsh5Path
            }
        } else {
            if (Test-Path $pwsh5Path) {
                Write-EnhancedLog -Message "PowerShell 5 found at $pwsh5Path" -Level "INFO"
                return $pwsh5Path
            } elseif (Test-Path $pwsh7Path) {
                Write-EnhancedLog -Message "PowerShell 5 not found, falling back to PowerShell 7 at $pwsh7Path" -Level "WARNING"
                return $pwsh7Path
            }
        }

        $errorMessage = "Neither PowerShell 7 nor PowerShell 5 was found on this system."
        Write-EnhancedLog -Message $errorMessage -Level "ERROR"
        throw $errorMessage
    }

    End {
        Write-EnhancedLog -Message "Exiting Get-PowerShellPath function" -Level "NOTICE"
    }
}



# # Get the path to the installed PowerShell executable
# try {
# $pwshPath = Get-PowerShellPath
# Write-Host "PowerShell executable found at: $pwshPath"
    
# # Example: Start a new PowerShell session using the found path
# Start-Process -FilePath $pwshPath -ArgumentList "-NoProfile", "-Command", "Get-Process" -NoNewWindow -Wait
# }
# catch {
# Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red
# }
#EndRegion '.\Public\Get-PowerShellPath.ps1' 81
#Region '.\Public\Handle-Error.ps1' -1

function Handle-Error {
    param (
        [Parameter(Mandatory = $true)]
        [System.Management.Automation.ErrorRecord]$ErrorRecord
    )

    try {
        if ($PSVersionTable.PSVersion.Major -ge 7) {
            $fullErrorDetails = Get-Error -InputObject $ErrorRecord | Out-String
        } else {
            $fullErrorDetails = $ErrorRecord.Exception | Format-List * -Force | Out-String
        }

        Write-EnhancedLog -Message "Exception Message: $($ErrorRecord.Exception.Message)" -Level "ERROR"
        Write-EnhancedLog -Message "Full Exception: $fullErrorDetails" -Level "ERROR"
    } catch {
        # Fallback error handling in case of an unexpected error in the try block
        Write-EnhancedLog -Message "An error occurred while handling another error. Original Exception: $($ErrorRecord.Exception.Message)" -Level "CRITICAL"
        Write-EnhancedLog -Message "Handler Exception: $($_.Exception.Message)" -Level "CRITICAL"
        Write-EnhancedLog -Message "Handler Full Exception: $($_ | Out-String)" -Level "CRITICAL"
    }
}

# # Example usage of Handle-Error
# try {
# # Intentionally cause an error for demonstration purposes
# throw "This is a test error"
# } catch {
# Handle-Error -ErrorRecord $_
# }



#EndRegion '.\Public\Handle-Error.ps1' 34
#Region '.\Public\Import-EnhancedModules.ps1' -1

function Import-EnhancedModules {
    param (
        [string]$modulePsd1Path, # Path to the PSD1 file containing the list of modules to install and import
        [string]$ScriptPath  # Path to the PSD1 file containing the list of modules to install and import
    )

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


    # Check if we need to re-launch in PowerShell 5
    # Invoke-InPowerShell5 -ScriptPath $ScriptPath
    Invoke-InPowerShell5

    # If running in PowerShell 5, reset the module paths and proceed with the rest of the script
    Reset-ModulePaths

    # Import the PSD1 data
    $moduleData = Import-PowerShellDataFile -Path $modulePsd1Path
    $modulesToImport = $moduleData.requiredModules

    foreach ($moduleName in $modulesToImport) {
        if (-not (Get-Module -ListAvailable -Name $moduleName)) {
            Write-EnhancedLog "Module $moduleName is not installed. Attempting to install..." -Level "INFO"
            Install-EnhancedModule -ModuleName $moduleName -ScriptPath $ScriptPath
        }

        Write-EnhancedLog "Importing module: $moduleName" -Level "INFO"
        try {
            Import-Module -Name $moduleName -Verbose:$true -Force:$true -Global:$true
        }
        catch {
            Write-EnhancedLog "Failed to import module $moduleName. Error: $_" -Level "ERROR"
        }
    }
}
#EndRegion '.\Public\Import-EnhancedModules.ps1' 40
#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\Initialize-Environment.ps1' -1


function Initialize-Environment {
    param (
        [string]$Mode, # Accepts either 'dev' or 'prod'
        # [string]$ExecutionMode, # Accepts either 'parallel' or 'series'
        # [string]$WindowsModulePath, # Path to the Windows module
        [string]$ModulesBasePath, # Custom modules base path,
        [PSCustomObject[]]$scriptDetails,

        [Parameter(Mandatory = $false)]
        [string]$ScriptDirectory,

        [Parameter(Mandatory = $false, HelpMessage = "Skip installation of enhanced modules.")]
        [bool]$SkipEnhancedModules = $false
    )
 
    if ($Mode -eq "dev") {
        
        $gitInstalled = Ensure-GitIsInstalled
        if ($gitInstalled) {
            Write-EnhancedLog -Message "Git installation check completed successfully." -Level "INFO"
        }
        else {
            Write-EnhancedLog -Message "Failed to install Git." -Level "ERROR"
        }

        if (-not $SkipGitRepos) {
            Manage-GitRepositories -ModulesBasePath 'C:\Code\modulesv2'
            Write-EnhancedLog -Message "Git repose checked successfully." -Level "INFO"
        }
        else {
            Write-EnhancedLog -Message "Skipping Git Repos" -Level "INFO"
        }
       
        # Call Setup-GlobalPaths with custom paths
        Setup-GlobalPaths -ModulesBasePath $ModulesBasePath
        # Check if the directory exists and contains any files (not just the directory existence)
        if (-Not (Test-Path "$global:modulesBasePath\*.*")) {
            Write-EnhancedLog -Message "Modules not found or directory is empty at $global:modulesBasePath. Initiating download..." -Level "INFO"
            # Download-Modules -scriptDetails $scriptDetails

            if (-not $SkipEnhancedModules) {
                # Download-Modules -scriptDetails $scriptDetails

                # Example usage: Call the main function with the script details
                Invoke-CloneEnhancedRepos -scriptDetails $scriptDetails -ScriptDirectory $ScriptDirectory

                Write-EnhancedLog -Message "Modules downloaded successfully." -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "Skipping module download as per the provided parameter." -Level "INFO"
            }

            # Re-check after download attempt
            if (-Not (Test-Path "$global:modulesBasePath\*.*")) {
                throw "Download failed or the modules were not placed in the expected directory."
            }
        }
        else {
            Write-EnhancedLog -Message "Source Modules already exist at $global:modulesBasePath" -Level "INFO"
        }

        Write-EnhancedLog -Message "Starting to call Import-LatestModulesLocalRepository..."
        Import-ModulesFromLocalRepository -ModulesFolderPath $global:modulesBasePath
    }
    elseif ($Mode -eq "prod") {
        # Log the start of the process
        Write-EnhancedLog -Message "Production mode selected. Importing modules..." -Level "INFO"

        Reset-ModulePaths
        # Ensure NuGet provider is installed
        # Ensure-NuGetProvider

        # Define the PSD1 file URLs and local paths
        # $psd1Url = "https://raw.githubusercontent.com/aollivierre/module-starter/main/Enhanced-modules.psd1"
        # $localPsd1Path = "$env:TEMP\enhanced-modules.psd1"

        # Download the PSD1 file
        # Download-Psd1File -url $psd1Url -destinationPath $localPsd1Path

        # Install and import modules based on the PSD1 file
        # InstallAndImportModulesPSGallery -modulePsd1Path $localPsd1Path

        # Handle third-party PS Gallery modules
        if ($SkipPSGalleryModules) {
            Write-EnhancedLog -Message "Skipping third-party PS Gallery Modules" -Level "INFO"
        }
        else {
            Write-EnhancedLog -Message "Starting PS Gallery Module installation" -Level "INFO"

            # Reset the module paths in PS5
            # Reset-ModulePaths

            # Ensure NuGet provider is installed
            # Ensure-NuGetProvider

            # Download and process the third-party modules PSD1 file
            $psd1Url = "https://raw.githubusercontent.com/aollivierre/module-starter/main/modules.psd1"
            $localPsd1Path = "$env:TEMP\modules.psd1"
    
            Download-Psd1File -url $psd1Url -destinationPath $localPsd1Path
            InstallAndImportModulesPSGallery -modulePsd1Path $localPsd1Path
        }
    }
}
#EndRegion '.\Public\Initialize-Environment.ps1' 106
#Region '.\Public\Install-EnhancedModule.ps1' -1

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

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

    # 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 command to install the module in PowerShell 5
    # $command = "& '$ps5Path' -ExecutionPolicy Bypass -Command `"Install-Module -Name '$ModuleName' -Force -SkipPublisherCheck -Scope AllUsers`""
    # Write-EnhancedLog "Constructed command for PowerShell 5: $command" -Level "DEBUG"

    # # Launch PowerShell 5 to run the module installation
    # Write-EnhancedLog "Launching PowerShell 5 to install the module: $ModuleName" -Level "INFO"
    # Invoke-Expression $command

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

    # Path to the current script
    # $ScriptPath = $MyInvocation.MyCommand.Definition

    # # Check if we need to re-launch in PowerShell 5
    # Invoke-InPowerShell5 -ScriptPath $ScriptPath

    # # If running in PowerShell 5, reset the module paths and proceed with the rest of the script
    # Reset-ModulePaths

    # }

    # 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

        $params = @{
            ModuleName = "$Modulename"
        }
        Install-ModuleInPS5 @params

        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-EnhancedModule.ps1' 57
#Region '.\Public\Install-GitFromWeb.ps1' -1

function Install-GitFromWeb {
    param (
        [string]$url = "https://raw.githubusercontent.com/aollivierre/setuplab/main/Install-Git.ps1"
    )

    Write-EnhancedLog -Message "Attempting to install Git from URL: $url" -Level "INFO"

    $process = Invoke-WebScript -url $url
    if ($process) {
        $process.WaitForExit()

        # Perform post-installation validation
        $validationParams = @{
            SoftwareName        = "Git"
            MinVersion          = [version]"2.46.0"
            RegistryPath        = "HKLM:\SOFTWARE\GitForWindows"
            ExePath             = "C:\Program Files\Git\bin\git.exe"
            MaxRetries          = 3  # Single retry after installation
            DelayBetweenRetries = 5
        }

        $postValidationResult = Validate-SoftwareInstallation @validationParams
        if ($postValidationResult.IsInstalled -and $postValidationResult.Version -ge $validationParams.MinVersion) {
            Write-EnhancedLog -Message "Git successfully installed and validated." -Level "INFO"
            return $true
        }
        else {
            Write-EnhancedLog -Message "Git installation validation failed." -Level "ERROR"
            return $false
        }
    }
    else {
        Write-EnhancedLog -Message "Failed to start the installation process for Git." -Level "ERROR"
        return $false
    }
}
#EndRegion '.\Public\Install-GitFromWeb.ps1' 37
#Region '.\Public\Install-ModuleInPS5.ps1' -1

function Install-ModuleInPS5 {
    <#
    .SYNOPSIS
    Installs a PowerShell module in PowerShell 5 and validates the installation.
 
    .DESCRIPTION
    The Install-ModuleInPS5 function installs a specified PowerShell module using PowerShell 5. It ensures that the module is installed in the correct environment and logs the entire process. It handles errors gracefully and validates the installation after completion.
 
    .PARAMETER ModuleName
    The name of the PowerShell module to install in PowerShell 5.
 
    .EXAMPLE
    $params = @{
        ModuleName = "Az"
    }
    Install-ModuleInPS5 @params
    Installs the specified PowerShell module using PowerShell 5 and logs the process.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the name of the module to install.")]
        [ValidateNotNullOrEmpty()]
        [string]$ModuleName
    )

    Begin {
        Write-EnhancedLog -Message "Starting Install-ModuleInPS5 function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Reset-ModulePaths

        # CheckAndElevate -ElevateIfNotAdmin $true

        # Path to PowerShell 5
        $ps5Path = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"

        # Validate if PowerShell 5 exists
        if (-not (Test-Path $ps5Path)) {
            throw "PowerShell 5 executable not found: $ps5Path"
        }
    }

    Process {
        try {
            if ($PSVersionTable.PSVersion.Major -eq 5) {
                # If already in PowerShell 5, install the module directly
                Write-EnhancedLog -Message "Already running in PowerShell 5, installing module directly." -Level "INFO"
                Install-Module -Name $ModuleName -Scope AllUsers -SkipPublisherCheck -AllowClobber -Force -Confirm:$false
            }
            else {
                # If not in PowerShell 5, use Start-Process to switch to PowerShell 5
                Write-EnhancedLog -Message "Preparing to install module: $ModuleName in PowerShell 5" -Level "INFO"

                $ps5Command = "Install-Module -Name $ModuleName -Scope AllUsers -SkipPublisherCheck -AllowClobber -Force -Confirm:`$false"

                # Splatting for Start-Process
                $startProcessParams = @{
                    FilePath     = $ps5Path
                    ArgumentList = "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", $ps5Command
                    Wait         = $true
                    NoNewWindow  = $true
                    PassThru     = $true
                }

                Write-EnhancedLog -Message "Starting installation of module $ModuleName in PowerShell 5" -Level "INFO"
                $process = Start-Process @startProcessParams

                if ($process.ExitCode -eq 0) {
                    Write-EnhancedLog -Message "Module '$ModuleName' installed successfully in PS5" -Level "INFO"
                }
                else {
                    Write-EnhancedLog -Message "Error occurred during module installation. Exit Code: $($process.ExitCode)" -Level "ERROR"
                }
            }
        }
        catch {
            Write-EnhancedLog -Message "Error installing module: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
        finally {
            Write-EnhancedLog -Message "Exiting Install-ModuleInPS5 function" -Level "Notice"
        }
    }

    End {
        Write-EnhancedLog -Message "Validating module installation in PS5" -Level "INFO"

        if ($PSVersionTable.PSVersion.Major -eq 5) {
            # Validate directly in PowerShell 5
            $module = Get-Module -ListAvailable -Name $ModuleName
        }
        else {
            # Use Start-Process to validate in PowerShell 5
            $ps5ValidateCommand = "Get-Module -ListAvailable -Name $ModuleName"

            $validateProcessParams = @{
                FilePath     = $ps5Path
                ArgumentList = "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", $ps5ValidateCommand
                NoNewWindow  = $true
                PassThru     = $true
                Wait         = $true
            }

            $moduleInstalled = Start-Process @validateProcessParams
            if ($moduleInstalled.ExitCode -ne 0) {
                Write-EnhancedLog -Message "Module $ModuleName validation failed in PS5" -Level "ERROR"
                throw "Module $ModuleName installation could not be validated in PS5"
            }
        }

        Write-EnhancedLog -Message "Module $ModuleName validated successfully in PS5" -Level "INFO"
    }
}

# Example usage
# $params = @{
# ModuleName = "Az"
# }
# Install-ModuleInPS5 @params
#EndRegion '.\Public\Install-ModuleInPS5.ps1' 122
#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 AllUsers
    }

    
    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

            $params = @{
                ModuleName = "$Module"
            }
            Install-ModuleInPS5 @params

            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' 36
#Region '.\Public\Install-ModuleWithPowerShell5Fallback-Archive.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"

    
# Reset-ModulePaths

# CheckAndElevate -ElevateIfNotAdmin $true

# $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"
# # Invoke-InPowerShell5

# $params = @{
# ModuleName = "$Modulename"
# }
# Install-ModuleInPS5 @params
# }

# # 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 -Confirm:$false
# 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-Archive.ps1' 39
#Region '.\Public\Install-PowerShell7FromWeb.ps1' -1


function Install-PowerShell7FromWeb {
    param (
        [string]$url = "https://raw.githubusercontent.com/aollivierre/setuplab/main/Install-PowerShell7.ps1"
    )

    Write-EnhancedLog -Message "Attempting to install PowerShell 7 from URL: $url" -Level "INFO"

    $process = Invoke-WebScript -url $url
    if ($process) {
        $process.WaitForExit()

        # Perform post-installation validation
        $validationParams = @{
            SoftwareName        = "PowerShell"
            MinVersion          = [version]"7.4.4"
            RegistryPath        = "HKLM:\SOFTWARE\Microsoft\PowerShellCore"
            ExePath             = "C:\Program Files\PowerShell\7\pwsh.exe"
            MaxRetries          = 3  # Single retry after installation
            DelayBetweenRetries = 5
        }

        $postValidationResult = Validate-SoftwareInstallation @validationParams
        if ($postValidationResult.IsInstalled -and $postValidationResult.Version -ge $validationParams.MinVersion) {
            Write-EnhancedLog -Message "PowerShell 7 successfully installed and validated." -Level "INFO"
            return $true
        }
        else {
            Write-EnhancedLog -Message "PowerShell 7 installation validation failed." -Level "ERROR"
            return $false
        }
    }
    else {
        Write-EnhancedLog -Message "Failed to start the installation process for PowerShell 7." -Level "ERROR"
        return $false
    }
}
#EndRegion '.\Public\Install-PowerShell7FromWeb.ps1' 38
#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 {
    [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
    
            # 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
                        $successModules.Add([PSCustomObject]@{
                            Name    = $moduleInfo.Name
                            Version = $moduleInfo.Version
                            Path    = $moduleInfo.ModuleBase
                        })
                        $moduleSuccessCount++
                        Write-EnhancedLog -Message "Successfully installed/updated module: $moduleName" -Level "INFO"
                    }
                    catch {
                        $failedModules.Add([PSCustomObject]@{
                            Name    = $moduleName
                            Version = "N/A"
                            Path    = "N/A"
                        })
                        $moduleFailCount++
                        Write-EnhancedLog -Message "Failed to install/update module: $moduleName. Error: $_" -Level "ERROR"
                    }
                }
            }
    
            Write-EnhancedLog -Message "Modules installation 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 | Format-Table -Property Name, Version, Path -AutoSize | Out-String | Write-Host
        }

        if ($failedModules.Count -gt 0) {
            Write-Host "Failed Modules:" -ForegroundColor Red
            $failedModules | Format-Table -Property Name, Version, Path -AutoSize | Out-String | Write-Host
        }

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

function Invoke-CloneEnhancedRepos {
    param (
        [PSCustomObject[]]$scriptDetails,
        [Parameter(Mandatory = $false)]
        [string]$ScriptDirectory
    )

    try {
        # Elevate script if needed
        Elevate-Script

        # Initialize necessary variables
        $powerShellPath = Get-PowerShellPath
        $processList = [System.Collections.Generic.List[System.Diagnostics.Process]]::new()
        $installationResults = [System.Collections.Generic.List[PSCustomObject]]::new()

        # Process each software detail
        foreach ($detail in $scriptDetails) {
            Process-SoftwareDetails -detail $detail -powerShellPath $powerShellPath -installationResults ([ref]$installationResults) -processList ([ref]$processList)
        }

        # Validate installation results after processes finish
        Validate-InstallationResults -processList ([ref]$processList) -installationResults ([ref]$installationResults) -scriptDetails $scriptDetails

        # Generate the final summary report
        Generate-SoftwareInstallSummaryReport -installationResults $installationResults

        # Example invocation to clone repositories:
        Clone-EnhancedRepos -githubUsername "aollivierre" -targetDirectory "C:\Code\modulesv2" -ScriptDirectory $ScriptDirectory
    }
    catch {
        # Capture the error details
        $errorDetails = $_ | Out-String
        Write-EnhancedLog "An error occurred: $errorDetails" -Level "ERROR"
        throw
    }
}
#EndRegion '.\Public\Invoke-CloneEnhancedRepos.ps1' 38
#Region '.\Public\invoke-CommandInPs5.ps1' -1

function Invoke-CommandInPS5 {
    <#
    .SYNOPSIS
    Executes a command in PowerShell 5 and returns the result.
 
    .DESCRIPTION
    The Invoke-CommandInPS5 function takes a command string, switches to PowerShell 5, and executes the command. It captures the exit code and logs the process. This function can be used for any command that needs to be run inside PowerShell 5.
 
    .PARAMETER Command
    The command string to be executed in PowerShell 5.
 
    .EXAMPLE
    Invoke-CommandInPS5 -Command "Upload-Win32App -Prg $Prg -Prg_Path $Prg_Path -Prg_img $Prg_img"
    Executes the Upload-Win32App command inside PowerShell 5.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the command to be executed in PowerShell 5.")]
        [ValidateNotNullOrEmpty()]
        [string]$Command
    )

    Begin {
        Write-EnhancedLog -Message "Starting Invoke-CommandInPS5 function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Path to PowerShell 5
        $ps5Path = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"

        # Validate if PowerShell 5 exists
        if (-not (Test-Path $ps5Path)) {
            throw "PowerShell 5 executable not found: $ps5Path"
        }
    }

    Process {
        try {
            Write-EnhancedLog -Message "Preparing to run command in PowerShell 5: $Command" -Level "INFO"

            # Splatting for Start-Process
            $startProcessParams = @{
                FilePath        = $ps5Path
                ArgumentList    = "-Command", $Command
                Wait            = $true
                NoNewWindow     = $true
                PassThru        = $true
            }

            Write-EnhancedLog -Message "Executing command in PowerShell 5" -Level "INFO"
            $process = Start-Process @startProcessParams

            if ($process.ExitCode -eq 0) {
                Write-EnhancedLog -Message "Command executed successfully in PS5" -Level "INFO"
            } else {
                Write-EnhancedLog -Message "Error occurred during command execution. Exit Code: $($process.ExitCode)" -Level "ERROR"
            }
        }
        catch {
            Write-EnhancedLog -Message "Error executing command: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
        finally {
            Write-EnhancedLog -Message "Exiting Invoke-CommandInPS5 function" -Level "Notice"
        }
    }

    End {
        Write-EnhancedLog -Message "Command execution in PowerShell 5 completed" -Level "INFO"
    }
}
#EndRegion '.\Public\invoke-CommandInPs5.ps1' 73
#Region '.\Public\Invoke-GitCommandWithRetry.ps1' -1


function Invoke-GitCommandWithRetry {
    param (
        [string]$GitPath,
        [string]$Arguments,
        [int]$MaxRetries = 3,
        [int]$DelayBetweenRetries = 5
    )

    for ($i = 0; $i -lt $MaxRetries; $i++) {
        try {
            # Split the arguments string into an array for correct parsing
            $argumentArray = $Arguments -split ' '
            $output = & "$GitPath" @argumentArray
            if ($output -match "fatal:") {
                Write-EnhancedLog -Message "Git command failed: $output" -Level "WARNING"
                if ($i -lt ($MaxRetries - 1)) {
                    Write-EnhancedLog -Message "Retrying in $DelayBetweenRetries seconds..." -Level "INFO"
                    Start-Sleep -Seconds $DelayBetweenRetries
                }
                else {
                    Write-EnhancedLog -Message "Git command failed after $MaxRetries retries." -Level "ERROR"
                    throw "Git command failed: $output"
                }
            }
            else {
                return $output
            }
        }
        catch {
            Write-EnhancedLog -Message "Error executing Git command: $_" -Level "ERROR"
            if ($i -lt ($MaxRetries - 1)) {
                Write-EnhancedLog -Message "Retrying in $DelayBetweenRetries seconds..." -Level "INFO"
                Start-Sleep -Seconds $DelayBetweenRetries
            }
            else {
                throw $_
            }
        }
    }
}
#EndRegion '.\Public\Invoke-GitCommandWithRetry.ps1' 42
#Region '.\Public\Invoke-InPowerShell5.ps1' -1

function Invoke-InPowerShell5 {
    <#
    .SYNOPSIS
    Relaunches the script in PowerShell 5 (x64) if the current session is not already running in PowerShell 5.
 
    .PARAMETER ScriptPath
    The full path to the script that needs to be executed in PowerShell 5 (x64).
 
    .DESCRIPTION
    This function checks if the current PowerShell session is running in PowerShell 5. If not, it relaunches the specified script in PowerShell 5 (x64) with elevated privileges.
 
    .EXAMPLE
    Invoke-InPowerShell5 -ScriptPath "C:\Scripts\MyScript.ps1"
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false, HelpMessage = "Provide the full path to the script to be run in PowerShell 5.")]
        [ValidateNotNullOrEmpty()]
        [string]$ScriptPath
    )

    Begin {
        # Log parameters
        Write-EnhancedLog -Message "Starting Invoke-InPowerShell5 function." -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Log the start of the process
        Write-EnhancedLog -Message "Checking PowerShell version." -Level "INFO"
    }

    Process {
        try {
            # Check if we're not in PowerShell 5
            if ($PSVersionTable.PSVersion.Major -ne 5) {
                Write-EnhancedLog -Message "Relaunching script in PowerShell 5 (x64)..." -Level "WARNING"

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

                # Define arguments for the Start-Process command
                $arguments = @(
                    "-NoExit"
                    "-NoProfile"
                    "-ExecutionPolicy", "Bypass"
                    "-File", "`"$PSCommandPath`""
                    # "-File", "`"$ScriptPath`""
                )

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

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

                
                write-host 'hello from PS5'

                Write-EnhancedLog -Message "PowerShell 5 (x64) process completed." -Level "NOTICE"
                # Exit
            }
            else {
                Write-EnhancedLog -Message "Already running in PowerShell 5. No need to relaunch." -Level "INFO"
            }
        }
        catch {
            Write-EnhancedLog -Message "Error occurred while relaunching script in PowerShell 5: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Invoke-InPowerShell5 function." -Level "Notice"
    }
}

# Example usage
# Invoke-InPowerShell5 -ScriptPath "C:\Scripts\MyScript.ps1"
# Invoke-InPowerShell5 -ScriptPath $PSScriptRoot
# Invoke-InPowerShell5
#EndRegion '.\Public\Invoke-InPowerShell5.ps1' 89
#Region '.\Public\Invoke-ModuleStarter.ps1' -1

function Invoke-ModuleStarter {
    <#
    .SYNOPSIS
    Initializes the environment for module development or deployment.
 
    .DESCRIPTION
    The Invoke-ModuleStarter function sets up the environment for module development or deployment by managing the installation of necessary modules, elevating privileges, and initializing other script details.
 
    .PARAMETER Mode
    Specifies the mode to run the script in (e.g., dev, prod). Default is 'dev'.
 
    .PARAMETER SkipPSGalleryModules
    Skips the installation of modules from the PowerShell Gallery if set to $true.
 
    .PARAMETER SkipCheckandElevate
    Skips the privilege elevation check if set to $true.
 
    .PARAMETER SkipPowerShell7Install
    Skips the installation of PowerShell 7 if set to $true.
 
    .PARAMETER SkipEnhancedModules
    Skips the installation of enhanced modules if set to $true.
 
    .PARAMETER SkipGitRepos
    Skips the cloning of Git repositories if set to $true.
 
    .EXAMPLE
    $params = @{
        Mode = "prod"
        SkipPSGalleryModules = $true
        SkipCheckandElevate = $false
        SkipPowerShell7Install = $false
        SkipEnhancedModules = $true
        SkipGitRepos = $false
    }
    Invoke-ModuleStarter @params
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false, HelpMessage = "Specify the script mode (dev, prod, etc.).")]
        [string]$Mode,

        [Parameter(Mandatory = $false, HelpMessage = "Skip installation of modules from PowerShell Gallery.")]
        [bool]$SkipPSGalleryModules = $false,

        [Parameter(Mandatory = $false, HelpMessage = "Skip the check and elevation to admin privileges.")]
        [bool]$SkipCheckandElevate = $false,

        [Parameter(Mandatory = $false, HelpMessage = "Skip installation of PowerShell 7.")]
        [bool]$SkipPowerShell7Install = $false,

        [Parameter(Mandatory = $false, HelpMessage = "Skip installation of enhanced modules.")]
        [bool]$SkipEnhancedModules = $false,

        [Parameter(Mandatory = $false, HelpMessage = "Skip cloning of Git repositories.")]
        [bool]$SkipGitRepos = $false,

        [Parameter(Mandatory = $false, HelpMessage = "Specify the path for the Script Directory to pass files like secrets.psd1")]
        [string]$ScriptDirectory,

        [Parameter(Mandatory = $false, HelpMessage = "Specify the execution mode for installing modules either in parallel or in series")]
        [string]$ExecutionMode
    )

    Begin {
        Write-EnhancedLog -Message "Starting Invoke-ModuleStarter function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Log the parameters
        Write-EnhancedLog -Message "The Module Starter script is running in mode: $Mode" -Level "INFO"
        Write-EnhancedLog -Message "SkipPSGalleryModules is set to: $SkipPSGalleryModules" -Level "INFO"
        Write-EnhancedLog -Message "SkipCheckandElevate is set to: $SkipCheckandElevate" -Level "INFO"
        Write-EnhancedLog -Message "SkipPowerShell7Install is set to: $SkipPowerShell7Install" -Level "INFO"
        Write-EnhancedLog -Message "SkipEnhancedModules is set to: $SkipEnhancedModules" -Level "INFO"
        Write-EnhancedLog -Message "SkipGitRepos is set to: $SkipGitRepos" -Level "INFO"

        # Report the current PowerShell version
        $psVersion = $PSVersionTable.PSVersion
        Write-EnhancedLog -Message "Current PowerShell Version: $psVersion" -Level 'INFO'
        Write-EnhancedLog -Message "Full PowerShell Version Details:"
        $PSVersionTable | Format-Table -AutoSize
    }

    Process {
        try {

            # Initialize the base hashtable without ScriptDirectory
            $initializeParams = @{
                Mode            = $Mode
                ModulesBasePath = "C:\code\modulesv2"
                scriptDetails   = @(
                    @{ Url = "https://raw.githubusercontent.com/aollivierre/setuplab/main/Install-Git.ps1"; SoftwareName = "Git"; MinVersion = [version]"2.41.0.0" },
                    @{ Url = "https://raw.githubusercontent.com/aollivierre/setuplab/main/Install-GitHubCLI.ps1"; SoftwareName = "GitHub CLI"; MinVersion = [version]"2.54.0" }
                )
                SkipEnhancedModules = $SkipEnhancedModules
            }

            # Conditionally add ScriptDirectory to the hashtable if it is not null or empty
            if ($PSBoundParameters.ContainsKey('ScriptDirectory') -and $ScriptDirectory) {
                $initializeParams.ScriptDirectory = $ScriptDirectory
            }


            # if ($PSBoundParameters.ContainsKey('ExecutionMode') -and $ExecutionMode) {
            # $initializeParams.ExecutionMode = $ExecutionMode
            # }


            # Check and elevate permissions if required
            if (-not $SkipCheckandElevate) {
                Write-EnhancedLog -Message "Checking and elevating permissions if necessary." -Level "INFO"
                CheckAndElevate -ElevateIfNotAdmin $true
            }
            else {
                Write-EnhancedLog -Message "Skipping CheckAndElevate due to SkipCheckandElevate parameter." -Level "INFO"
            }

            # Initialize environment based on the mode and other parameters
            Write-EnhancedLog -Message "Initializing environment..." -Level 'INFO'
            Initialize-Environment @initializeParams
        }
        catch {
            Write-EnhancedLog -Message "Error during Module Starter execution: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
    }

    End {
        # Setup logging
        Write-EnhancedLog -Message "Exiting Invoke-ModuleStarter function" -Level "Notice"
        Write-EnhancedLog -Message "Script Started in $Mode mode" -Level "INFO"
    }
}

# Example usage
# $params = @{
# Mode = "prod"
# SkipPSGalleryModules = $true
# SkipCheckandElevate = $false
# SkipPowerShell7Install = $false
# SkipEnhancedModules = $true
# SkipGitRepos = $false
# }
# Invoke-ModuleStarter @params
#EndRegion '.\Public\Invoke-ModuleStarter.ps1' 147
#Region '.\Public\Invoke-ScriptInPS5.ps1' -1

function Invoke-ScriptInPS5 {
    <#
    .SYNOPSIS
    Executes a script in PowerShell 5 and returns the result.
 
    .DESCRIPTION
    The Invoke-ScriptInPS5 function takes a script path, switches to PowerShell 5, and executes the script. It captures the exit code and logs the process. This function can be used for any script that needs to be run inside PowerShell 5.
 
    .PARAMETER ScriptPath
    The full path to the script to be executed in PowerShell 5.
 
    .PARAMETER ScriptArgs
    The arguments to pass to the script, if any.
 
    .EXAMPLE
    Invoke-ScriptInPS5 -ScriptPath "C:\Scripts\MyScript.ps1"
    Executes MyScript.ps1 inside PowerShell 5.
 
    .EXAMPLE
    Invoke-ScriptInPS5 -ScriptPath "C:\Scripts\MyScript.ps1" -ScriptArgs "-Param1 Value1"
    Executes MyScript.ps1 with specified parameters inside PowerShell 5.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the path to the script to be executed in PowerShell 5.")]
        [ValidateNotNullOrEmpty()]
        [string]$ScriptPath,

        [Parameter(Mandatory = $false, HelpMessage = "Provide arguments for the script being executed.")]
        [string]$ScriptArgs
    )

    Begin {
        Write-EnhancedLog -Message "Starting Invoke-ScriptInPS5 function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters


        Reset-ModulePaths

        # Path to PowerShell 5
        $ps5Path = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"

        # Validate if PowerShell 5 exists
        if (-not (Test-Path $ps5Path)) {
            throw "PowerShell 5 executable not found: $ps5Path"
        }

        # Validate if the script exists
        if (-not (Test-Path $ScriptPath)) {
            throw "Script file not found: $ScriptPath"
        }
    }

    Process {
        try {
            Write-EnhancedLog -Message "Preparing to run script in PowerShell 5: $ScriptPath with arguments: $ScriptArgs" -Level "INFO"

            # Build the argument list for Start-Process
            $ps5Command = "& '$ScriptPath' $ScriptArgs"
            $startProcessParams = @{
                FilePath        = $ps5Path
                ArgumentList    = "-Command", $ps5Command
                Wait            = $true
                NoNewWindow     = $true
                PassThru        = $true
            }

            Write-EnhancedLog -Message "Executing script in PowerShell 5" -Level "INFO"
            $process = Start-Process @startProcessParams

            if ($process.ExitCode -eq 0) {
                Write-EnhancedLog -Message "Script executed successfully in PS5" -Level "INFO"
            } else {
                Write-EnhancedLog -Message "Error occurred during script execution. Exit Code: $($process.ExitCode)" -Level "ERROR"
            }
        }
        catch {
            Write-EnhancedLog -Message "Error executing script: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
        finally {
            Write-EnhancedLog -Message "Exiting Invoke-ScriptInPS5 function" -Level "Notice"
        }
    }

    End {
        Write-EnhancedLog -Message "Script execution in PowerShell 5 completed" -Level "INFO"
    }
}
#EndRegion '.\Public\Invoke-ScriptInPS5.ps1' 92
#Region '.\Public\Invoke-WebScript.ps1' -1


function Invoke-WebScript {
    param (
        [string]$url
    )

    $powerShellPath = Get-PowerShellPath -ForcePowerShell5

    Write-EnhancedLog -Message "Validating URL: $url" -Level "INFO"

    if (Test-Url -url $url) {
        Write-EnhancedLog -Message "Running script from URL: $url" -Level "INFO"

        $startProcessParams = @{
            FilePath     = $powerShellPath
            ArgumentList = @(
                "-NoExit",
                "-NoProfile",
                "-ExecutionPolicy", "Bypass",
                "-Command", "Invoke-Expression (Invoke-RestMethod -Uri '$url')"
            )
            Verb         = "RunAs"
            PassThru     = $true
        }
        
        $process = Start-Process @startProcessParams
        
        return $process
    }
    else {
        Write-EnhancedLog -Message "URL $url is not accessible" -Level "ERROR"
        return $null
    }
}
#EndRegion '.\Public\Invoke-WebScript.ps1' 35
#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\Log-Params.ps1' -1

function Log-Params {
    <#
    .SYNOPSIS
    Logs the provided parameters and their values with the parent function name appended.
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [hashtable]$Params
    )

    Begin {
        # Get the name of the parent function
        $parentFunctionName = (Get-PSCallStack)[1].Command

        # Write-EnhancedLog -Message "Starting Log-Params function in $parentFunctionName" -Level "INFO"
    }

    Process {
        try {
            foreach ($key in $Params.Keys) {
                # Append the parent function name to the key
                $enhancedKey = "$parentFunctionName.$key"
                Write-EnhancedLog -Message "$enhancedKey $($Params[$key])" -Level "INFO"
            }
        } catch {
            Write-EnhancedLog -Message "An error occurred while logging parameters in $parentFunctionName $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        # Write-EnhancedLog -Message "Exiting Log-Params function in $parentFunctionName" -Level "INFO"
    }
}
#EndRegion '.\Public\Log-Params.ps1' 36
#Region '.\Public\Log-Step.ps1' -1

function Log-Step {
    $global:currentStep++
    $totalSteps = $global:steps.Count
    $stepDescription = $global:steps[$global:currentStep - 1].Description
    Write-Host "Step [$global:currentStep/$totalSteps]: $stepDescription" -ForegroundColor Cyan
}
#EndRegion '.\Public\Log-Step.ps1' 7
#Region '.\Public\Manage-GitRepositories.ps1' -1

function Manage-GitRepositories {
    param (
        [Parameter(Mandatory = $true)]
        [string]$ModulesBasePath
    )

    begin {
        Write-EnhancedLog -Message "Starting Manage-GitRepositories function" -Level "INFO"

        # Initialize lists for tracking repository statuses
        $reposWithPushChanges = [System.Collections.Generic.List[string]]::new()
        $reposSummary = [System.Collections.Generic.List[PSCustomObject]]::new()

        # Validate ModulesBasePath
        if (-not (Test-Path -Path $ModulesBasePath)) {
            Write-EnhancedLog -Message "Modules base path not found: $ModulesBasePath" -Level "ERROR"
            throw "Modules base path not found."
        }

        Write-EnhancedLog -Message "Found modules base path: $ModulesBasePath" -Level "INFO"

        # Get the Git path
        $GitPath = Get-GitPath
        if (-not $GitPath) {
            throw "Git executable not found."
        }

        # Set environment variable to avoid interactive Git prompts
        $env:GIT_TERMINAL_PROMPT = "0"
    }

    process {
        try {
            $repos = Get-ChildItem -Path $ModulesBasePath -Directory

            foreach ($repo in $repos) {
                Set-Location -Path $repo.FullName

                # Add the repository to Git's safe directories
                $repoPath = $repo.FullName
                $arguments = "config --global --add safe.directory `"$repoPath`""
                Invoke-GitCommandWithRetry -GitPath $GitPath -Arguments $arguments



                # Remove any existing .gitconfig.lock file to avoid rename prompt
                $lockFilePath = "$HOME\.gitconfig.lock"
                if (Test-Path $lockFilePath) {
                    Remove-Item $lockFilePath -Force
                    Write-EnhancedLog -Message "Removed .gitconfig.lock file for repository $($repo.Name)" -Level "INFO"
                }

                # Fetch the latest changes with a retry mechanism
                Invoke-GitCommandWithRetry -GitPath $GitPath -Arguments "fetch"



                # Check for pending changes
                $arguments = "status"
                Invoke-GitCommandWithRetry -GitPath $GitPath -Arguments $arguments

                if ($status -match "fatal:") {
                    Write-EnhancedLog -Message "Error during status check in repository $($repo.Name): $status" -Level "ERROR"
                    continue
                }

                $repoStatus = "Up to Date"
                if ($status -match "Your branch is behind") {
                    Write-EnhancedLog -Message "Repository $($repo.Name) is behind the remote. Pulling changes..." -Level "INFO"
                    # Pull changes if needed
                    Invoke-GitCommandWithRetry -GitPath $GitPath -Arguments "pull"
                    $repoStatus = "Pulled"
                }

                if ($status -match "Your branch is ahead") {
                    Write-EnhancedLog -Message "Repository $($repo.Name) has unpushed changes." -Level "WARNING"
                    $reposWithPushChanges.Add($repo.FullName)
                    $repoStatus = "Pending Push"
                }

                # Add the repository status to the summary list
                $reposSummary.Add([pscustomobject]@{
                        RepositoryName = $repo.Name
                        Status         = $repoStatus
                        LastChecked    = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
                    })
            }

            # Summary of repositories with pending push changes
            if ($reposWithPushChanges.Count -gt 0) {
                Write-EnhancedLog -Message "The following repositories have pending push changes:" -Level "WARNING"
                $reposWithPushChanges | ForEach-Object { Write-EnhancedLog -Message $_ -Level "WARNING" }

                Write-EnhancedLog -Message "Please manually commit and push the changes in these repositories." -Level "WARNING"
            }
            else {
                Write-EnhancedLog -Message "All repositories are up to date." -Level "INFO"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while managing Git repositories: $_" -Level "ERROR"
            throw $_
        }
    }

    end {
        # Summary output in the console with color coding
        $totalRepos = $reposSummary.Count
        $pulledRepos = $reposSummary | Where-Object { $_.Status -eq "Pulled" }
        $pendingPushRepos = $reposSummary | Where-Object { $_.Status -eq "Pending Push" }
        $upToDateRepos = $reposSummary | Where-Object { $_.Status -eq "Up to Date" }

        Write-Host "---------- Summary Report ----------" -ForegroundColor Cyan
        Write-Host "Total Repositories: $totalRepos" -ForegroundColor Cyan
        Write-Host "Repositories Pulled: $($pulledRepos.Count)" -ForegroundColor Green
        Write-Host "Repositories with Pending Push: $($pendingPushRepos.Count)" -ForegroundColor Yellow
        Write-Host "Repositories Up to Date: $($upToDateRepos.Count)" -ForegroundColor Green

        # Return to the original location
        Set-Location -Path $ModulesBasePath

        Write-EnhancedLog -Message "Manage-GitRepositories function execution completed." -Level "INFO"
    }
}
#EndRegion '.\Public\Manage-GitRepositories.ps1' 125
#Region '.\Public\Process-SoftwareDetails.ps1' -1


function Process-SoftwareDetails {
    param (
        [PSCustomObject]$detail,
        [string]$powerShellPath,
        [ref]$installationResults,
        [ref]$processList
    )

    $url = $detail.Url
    $softwareName = $detail.SoftwareName
    $minVersion = $detail.MinVersion
    $registryPath = $detail.RegistryPath

    # Validate the existing installation
    Write-EnhancedLog "Validating existing installation of $softwareName..."
    $installationCheck = if ($registryPath) {
        Validate-SoftwareInstallation -SoftwareName $softwareName -MinVersion $minVersion -MaxRetries 3 -DelayBetweenRetries 5 -RegistryPath $registryPath
    }
    else {
        Validate-SoftwareInstallation -SoftwareName $softwareName -MinVersion $minVersion -MaxRetries 3 -DelayBetweenRetries 5
    }

    if ($installationCheck.IsInstalled) {
        Write-EnhancedLog "$softwareName version $($installationCheck.Version) is already installed. Skipping installation." -Level "INFO"
        $installationResults.Value.Add([pscustomobject]@{ SoftwareName = $softwareName; Status = "Already Installed"; VersionFound = $installationCheck.Version })
    }
    else {
        if (Test-Url -url $url) {
            Log-Step
            Write-EnhancedLog "Running script from URL: $url" -Level "INFO"
            $process = Start-Process -FilePath $powerShellPath -ArgumentList @("-NoExit", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", "Invoke-Expression (Invoke-RestMethod -Uri '$url')") -Verb RunAs -PassThru
            $processList.Value.Add($process)

            $installationResults.Value.Add([pscustomobject]@{ SoftwareName = $softwareName; Status = "Installed"; VersionFound = "N/A" })
        }
        else {
            Write-EnhancedLog "URL $url is not accessible" -Level "ERROR"
            $installationResults.Value.Add([pscustomobject]@{ SoftwareName = $softwareName; Status = "Failed - URL Not Accessible"; VersionFound = "N/A" })
        }
    }
}
#EndRegion '.\Public\Process-SoftwareDetails.ps1' 43
#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 }


        Write-EnhancedLog -Message "Retrieving all available versions of module: $ModuleName" -Level "INFO"
        $allVersions = Get-Module -ListAvailable -Name $ModuleName | Sort-Object Version

        if ($allVersions -and $allVersions.Count -gt 0) {
            Write-EnhancedLog -Message "Found $($allVersions.Count) versions of the module: $ModuleName" -Level "INFO"
        }
        else {
            Write-EnhancedLog -Message "No versions of the module: $ModuleName were found." -Level "ERROR"
            return
        }

        # Identify the latest version
        $latestVersion = $allVersions | Select-Object -Last 1
        Write-EnhancedLog -Message "Latest version of the module: $ModuleName is $($latestVersion.Version)" -Level "INFO"

        # Identify the older versions
        $olderVersions = $allVersions | Where-Object { $_.Version -ne $latestVersion.Version }
        if ($olderVersions.Count -gt 0) {
            Write-EnhancedLog -Message "Found $($olderVersions.Count) older versions of the module: $ModuleName" -Level "INFO"
        }
        else {
            Write-EnhancedLog -Message "No older versions of the module: $ModuleName found." -Level "INFO"
        }

        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 -ElevateIfNotAdmin $true
                & 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' 88
#Region '.\Public\Reset-ModulePaths.ps1' -1

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

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

    process {
        try {
            # Log the start of the process
            Write-EnhancedLog -Message "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 -Message "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 -Message "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 -Message "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 -Message "Reset-ModulePaths function completed." -Level "DEBUG"
    }
}
#EndRegion '.\Public\Reset-ModulePaths.ps1' 48
#Region '.\Public\Sanitize-VersionString.ps1' -1

function Sanitize-VersionString {
    param (
        [string]$versionString
    )

    try {
        # Remove any non-numeric characters and additional segments like ".windows"
        $sanitizedVersion = $versionString -replace '[^0-9.]', '' -replace '\.\.+', '.'

        # Convert to System.Version
        $version = [version]$sanitizedVersion
        return $version
    }
    catch {
        Write-EnhancedLog -Message "Failed to convert version string: $versionString. Error: $_" -Level "ERROR"
        return $null
    }
}
#EndRegion '.\Public\Sanitize-VersionString.ps1' 19
#Region '.\Public\Setup-GlobalPaths.ps1' -1


function Setup-GlobalPaths {
    param (
        [string]$ModulesBasePath       # Path to the modules directory
    )

    # Set the modules base path and create if it doesn't exist
    if (-Not (Test-Path $ModulesBasePath)) {
        Write-EnhancedLog "ModulesBasePath '$ModulesBasePath' does not exist. Creating directory..." -Level "INFO"
        New-Item -Path $ModulesBasePath -ItemType Directory -Force
    }
    $global:modulesBasePath = $ModulesBasePath

    # Log the paths for verification
    Write-EnhancedLog "Modules Base Path: $global:modulesBasePath" -Level "INFO"
}
#EndRegion '.\Public\Setup-GlobalPaths.ps1' 17
#Region '.\Public\Test-Admin.ps1' -1

function Test-Admin {
    $currentUser = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
    return $currentUser.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
#EndRegion '.\Public\Test-Admin.ps1' 5
#Region '.\Public\Test-RunningAsSystem.ps1' -1

function Test-RunningAsSystem {
    <#
    .SYNOPSIS
    Checks if the current session is running under the SYSTEM account.
 
    .DESCRIPTION
    The Test-RunningAsSystem function checks whether the current PowerShell session is running under the Windows SYSTEM account.
    This is determined by comparing the security identifier (SID) of the current user with the SID of the SYSTEM account.
 
    .EXAMPLE
    $isSystem = Test-RunningAsSystem
    if ($isSystem) {
        Write-Host "The script is running under the SYSTEM account."
    } else {
        Write-Host "The script is not running under the SYSTEM account."
    }
 
    Checks if the current session is running under the SYSTEM account and returns the status.
 
    .NOTES
    This function is useful when determining if the script is being executed by a service or task running under the SYSTEM account.
    #>


    [CmdletBinding()]
    param ()

    Begin {
        Write-EnhancedLog -Message "Starting Test-RunningAsSystem function" -Level "NOTICE"

        # Initialize variables
        $systemSid = [System.Security.Principal.SecurityIdentifier]::new("S-1-5-18")
    }

    Process {
        try {
            Write-EnhancedLog -Message "Checking if the script is running under the SYSTEM account..." -Level "INFO"

            $currentSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User

            if ($currentSid -eq $systemSid) {
                Write-EnhancedLog -Message "The script is running under the SYSTEM account." -Level "INFO"
            } else {
                Write-EnhancedLog -Message "The script is not running under the SYSTEM account." -Level "WARNING"
            }
        }
        catch {
            Write-EnhancedLog -Message "Error determining if running as SYSTEM: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Test-RunningAsSystem function" -Level "NOTICE"
        return $currentSid -eq $systemSid
    }
}

# Usage Example
# $isSystem = Test-RunningAsSystem
# if ($isSystem) {
# Write-Host "The script is running under the SYSTEM account."
# } else {
# Write-Host "The script is not running under the SYSTEM account."
# }
#EndRegion '.\Public\Test-RunningAsSystem.ps1' 66
#Region '.\Public\Test-SoftwareInstallation.ps1' -1

function Get-SoftwareFromRegistry {
    param (
        [string]$SoftwareName,
        [string[]]$RegistryPaths
    )

    foreach ($path in $RegistryPaths) {
        Write-EnhancedLog -Message "Checking registry path: $path" -Level "INFO"

        if (Test-Path $path) {
            try {
                $subkeys = Get-ChildItem -Path $path -ErrorAction Stop
                foreach ($subkey in $subkeys) {
                    $app = Get-ItemProperty -Path $subkey.PSPath -ErrorAction Stop
                    if ($app.DisplayName -like "*$SoftwareName*") {
                        Write-EnhancedLog -Message "Found $SoftwareName at $path with version $($app.DisplayVersion)." -Level "INFO"

                        return [PSCustomObject]@{
                            IsInstalled = $true
                            Version     = $app.DisplayVersion
                        }
                    }
                }
            }
            catch {
                Write-EnhancedLog -Message "Failed to retrieve properties from registry path: $path. Error: $_" -Level "CRITICAL"
            }
        }
        else {
            Write-EnhancedLog -Message "Registry path $path does not exist." -Level "WARNING"
        }
    }

    return $null
}

function Get-SoftwareFromExe {
    param (
        [string]$ExePath,
        [string]$SoftwareName
    )

    if (Test-Path $ExePath) {
        $fileVersion = (Get-Item -Path $ExePath).VersionInfo.FileVersion
        Write-EnhancedLog -Message "Found $SoftwareName executable at $ExePath with version $fileVersion." -Level "INFO"

        return [PSCustomObject]@{
            IsInstalled = $true
            Version     = $fileVersion
        }
    }
    else {
        Write-EnhancedLog -Message "Executable path $ExePath does not exist." -Level "WARNING"
        return $null
    }
}

function Test-SoftwareInstallation {
    param (
        [Parameter(Mandatory = $true)]
        [string]$SoftwareName,

        [Parameter(Mandatory = $false)]
        [string]$RegistryPath = "",

        [Parameter(Mandatory = $false)]
        [string]$ExePath = ""
    )

    Begin {
        Write-EnhancedLog -Message "Starting Test-SoftwareInstallation function for $SoftwareName" -Level "NOTICE"

        $defaultRegistryPaths = @(
            "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
            "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall",
            "HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall"
        )

        if ($RegistryPath) {
            $defaultRegistryPaths += $RegistryPath
        }
    }

    Process {
        # Check registry paths first
        $registryResult = Get-SoftwareFromRegistry -SoftwareName $SoftwareName -RegistryPaths $defaultRegistryPaths

        if ($registryResult) {
            return $registryResult  # Immediately return the result from the registry check
        }

        # Check EXE path if not found in registry
        if ($ExePath) {
            $exeResult = Get-SoftwareFromExe -ExePath $ExePath -SoftwareName $SoftwareName

            if ($exeResult) {
                return $exeResult  # Immediately return the result from the executable check
            }
        }

        # If neither registry nor EXE path validation succeeded, return a result indicating not installed
        return [PSCustomObject]@{
            IsInstalled = $false
            Version     = $null
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Test-SoftwareInstallation function for $SoftwareName." -Level "NOTICE"
    }
}

#EndRegion '.\Public\Test-SoftwareInstallation.ps1' 113
#Region '.\Public\Test-Url.ps1' -1

function Test-Url {
    param (
        [string]$url
    )
    try {
        Invoke-RestMethod -Uri $url -Method Head -ErrorAction Stop
        return $true
    }
    catch {
        return $false
    }
}
#EndRegion '.\Public\Test-Url.ps1' 13
#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

        # Ensure-NuGetProvider

    }

    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


                    $params = @{
                        ModuleName = "$Modulename"
                    }
                    Install-ModuleInPS5 @params

                    # 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


                    $params = @{
                        ModuleName = "$Modulename"
                    }
                    Install-ModuleInPS5 @params

                    # $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' 85
#Region '.\Public\Validate-InstallationResults.ps1' -1

function Validate-InstallationResults {
    param (
        [ref]$processList,
        [ref]$installationResults,
        [PSCustomObject[]]$scriptDetails
    )

    # Wait for all processes to complete
    foreach ($process in $processList.Value) {
        $process.WaitForExit()
    }

    # Post-installation validation
    foreach ($result in $installationResults.Value) {
        if ($result.Status -eq "Installed") {
            if ($result.SoftwareName -in @("RDP", "Windows Terminal")) {
                Write-EnhancedLog "Skipping post-installation validation for $($result.SoftwareName)." -Level "INFO"
                $result.Status = "Successfully Installed"
                continue
            }

            Write-EnhancedLog "Validating installation of $($result.SoftwareName)..."
            $validationResult = Validate-SoftwareInstallation -SoftwareName $result.SoftwareName -MinVersion ($scriptDetails | Where-Object { $_.SoftwareName -eq $result.SoftwareName }).MinVersion

            if ($validationResult.IsInstalled) {
                Write-EnhancedLog "Validation successful: $($result.SoftwareName) version $($validationResult.Version) is installed." -Level "INFO"
                $result.VersionFound = $validationResult.Version
                $result.Status = "Successfully Installed"
            }
            else {
                Write-EnhancedLog "Validation failed: $($result.SoftwareName) was not found on the system." -Level "ERROR"
                $result.Status = "Failed - Not Found After Installation"
            }
        }
    }
}
#EndRegion '.\Public\Validate-InstallationResults.ps1' 37
#Region '.\Public\Validate-SoftwareInstallation.ps1' -1

function Validate-SoftwareInstallation {
    <#
    .SYNOPSIS
    Validates whether a software is installed and checks its version against specified requirements.
 
    .DESCRIPTION
    This function checks if a software is installed by searching specific registry paths or an executable path, and compares the installed version against the minimum and latest versions.
 
    .PARAMETER SoftwareName
    The name of the software to validate.
 
    .PARAMETER MinVersion
    The minimum version of the software required. Default is "0.0.0.0".
 
    .PARAMETER LatestVersion
    The latest version of the software available.
 
    .PARAMETER RegistryPath
    A specific registry path to check for the software installation.
 
    .PARAMETER ExePath
    The path to the software's executable file.
 
    .EXAMPLE
    $params = @{
        SoftwareName = "7-Zip"
        MinVersion = [version]"19.00"
        LatestVersion = [version]"24.08.00.0"
        RegistryPath = "HKLM:\SOFTWARE\7-Zip"
        ExePath = "C:\Program Files\7-Zip\7z.exe"
    }
    Validate-SoftwareInstallation @params
    #>


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

        [Parameter(Mandatory = $false)]
        [version]$MinVersion = [version]"0.0.0.0",

        [Parameter(Mandatory = $false)]
        [version]$LatestVersion,

        [Parameter(Mandatory = $false)]
        [string]$RegistryPath = "",

        [Parameter(Mandatory = $false)]
        [string]$ExePath = "",

        [Parameter(Mandatory = $false)]
        [int]$MaxRetries = 3,

        [Parameter(Mandatory = $false)]
        [int]$DelayBetweenRetries = 5
    )

    Begin {
        Write-EnhancedLog -Message "Starting validation for $SoftwareName" -Level "NOTICE"

        $retryCount = 0
        $isInstalled = $false
        $installedVersion = $null

        $result = [PSCustomObject]@{
            IsInstalled         = $false
            Version             = $null
            MeetsMinRequirement = $false
            IsUpToDate          = $false
        }
    }

    Process {
        while ($retryCount -lt $MaxRetries -and -not $isInstalled) {
            $retryCount++
            Write-EnhancedLog -Message "Attempt $retryCount of $MaxRetries to validate $SoftwareName installation." -Level "INFO"

            $installResult = Test-SoftwareInstallation -SoftwareName $SoftwareName -RegistryPath $RegistryPath -ExePath $ExePath

            if ($installResult.IsInstalled) {
                $isInstalled = $true
                $installedVersion = [version]$installResult.Version
                $result.IsInstalled = $true
                $result.Version = $installedVersion
                Write-EnhancedLog -Message "$SoftwareName is installed. Detected version: $installedVersion" -Level "INFO"
                break
            } else {
                Write-EnhancedLog -Message "Validation attempt $retryCount failed: $SoftwareName not found. Retrying in $DelayBetweenRetries seconds..." -Level "WARNING"
                Start-Sleep -Seconds $DelayBetweenRetries
            }
        }

        if (-not $isInstalled) {
            Write-EnhancedLog -Message "$SoftwareName is not installed after $MaxRetries retries." -Level "ERROR"
            return $result
        }

        # Use the Compare-SoftwareVersion function to compare versions
        Write-EnhancedLog -Message "Comparing installed version of $SoftwareName with minimum and latest versions." -Level "INFO"
        $versionComparison = Compare-SoftwareVersion -InstalledVersion $installedVersion -MinVersion $MinVersion -LatestVersion $LatestVersion

        $result.MeetsMinRequirement = $versionComparison.MeetsMinRequirement
        $result.IsUpToDate = $versionComparison.IsUpToDate

        Write-EnhancedLog -Message "Validation complete for $SoftwareName. Meets minimum requirements: $($result.MeetsMinRequirement). Is up-to-date: $($result.IsUpToDate)." -Level "INFO"

        return $result
    }
}
#EndRegion '.\Public\Validate-SoftwareInstallation.ps1' 111
#Region '.\Public\Write-EnhancedLog.ps1' -1

function Write-EnhancedLog {
    param (
        [string]$Message,
        [string]$Level = 'INFO',
        [switch]$Async = $false # Remove default value, it will be controlled by the environment variable
    )

    # Check if the Async switch is not set, then use the global variable
    if (-not $Async) {
        $Async = $global:LOG_ASYNC
        # Write-Host "Global LOG_ASYNC variable is set to $Async"
    }


    # Get the PowerShell call stack to determine the actual calling function
    $callStack = Get-PSCallStack
    $callerFunction = if ($callStack.Count -ge 2) { $callStack[1].Command } else { '<Unknown>' }

    # Get the parent script name
    $parentScriptName = Get-ParentScriptName

    # Prepare the formatted message with the actual calling function information
    $formattedMessage = "[$Level] $Message"

    # Map custom levels to PSFramework levels
    $psfLevel = switch ($Level.ToUpper()) {
        'DEBUG' { 'Debug' }
        'INFO' { 'Host' }
        'NOTICE' { 'Important' }
        'WARNING' { 'Warning' }
        'ERROR' { 'Error' }
        'CRITICAL' { 'Critical' }
        'IMPORTANT' { 'Important' }
        'OUTPUT' { 'Output' }
        'SIGNIFICANT' { 'Significant' }
        'VERYVERBOSE' { 'VeryVerbose' }
        'VERBOSE' { 'Verbose' }
        'SOMEWHATVERBOSE' { 'SomewhatVerbose' }
        'SYSTEM' { 'System' }
        'INTERNALCOMMENT' { 'InternalComment' }
        default { 'Host' }
    }

    if ($Async) {
        # Enqueue the log message for async processing
        $logItem = [PSCustomObject]@{
            Level        = $psfLevel
            Message      = $formattedMessage
            FunctionName = "$parentScriptName.$callerFunction"
        }
        $global:LogQueue.Enqueue($logItem)
    }
    else {
        # Log the message synchronously
        Write-PSFMessage -Level $psfLevel -Message $formattedMessage -FunctionName "$parentScriptName.$callerFunction"
    }
}
#EndRegion '.\Public\Write-EnhancedLog.ps1' 58
#Region '.\Public\Write-EnhancedModuleStarterLog-Archive.ps1' -1

# function Write-EnhancedModuleStarterLog {
# param (
# [string]$Message,
# [string]$Level = "INFO"
# )

# # Get the PowerShell call stack to determine the actual calling function
# $callStack = Get-PSCallStack
# $callerFunction = if ($callStack.Count -ge 2) { $callStack[1].Command } else { '<Unknown>' }

# # Get the parent script name
# $parentScriptName = Get-ParentScriptName

# # Prepare the formatted message with the actual calling function information
# $formattedMessage = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] [$Level] [$parentScriptName.$callerFunction] $Message"

# # Display the log message based on the log level using Write-Host
# switch ($Level.ToUpper()) {
# "DEBUG" { Write-Host $formattedMessage -ForegroundColor DarkGray }
# "INFO" { Write-Host $formattedMessage -ForegroundColor Green }
# "NOTICE" { Write-Host $formattedMessage -ForegroundColor Cyan }
# "WARNING" { Write-Host $formattedMessage -ForegroundColor Yellow }
# "ERROR" { Write-Host $formattedMessage -ForegroundColor Red }
# "CRITICAL" { Write-Host $formattedMessage -ForegroundColor Magenta }
# default { Write-Host $formattedMessage -ForegroundColor White }
# }

# # Append to log file
# $logFilePath = [System.IO.Path]::Combine($env:TEMP, 'Module-Starter.log')
# $formattedMessage | Out-File -FilePath $logFilePath -Append -Encoding utf8
# }
#EndRegion '.\Public\Write-EnhancedModuleStarterLog-Archive.ps1' 32