EnhancedPSTools.psm1

#Region '.\Private\AppendCSVLog.ps1' -1

#Unique Tracking ID: 737397f0-c74e-4087-9b99-279b520b7448, Timestamp: 2024-03-20 12:25:26
function AppendCSVLog {
    param (
        [string]$Message,
        [string]$CSVFilePath_1001
           
    )
    
    $csvData = [PSCustomObject]@{
        TimeStamp    = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
        ComputerName = $env:COMPUTERNAME
        Message      = $Message
    }
    
    $csvData | Export-Csv -Path $CSVFilePath_1001 -Append -NoTypeInformation -Force
}
#EndRegion '.\Private\AppendCSVLog.ps1' 17
#Region '.\Private\Check-DiskSpace.ps1' -1

function Check-DiskSpace {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]$Path,
        [Parameter(Mandatory = $true)]
        [int]$RequiredSpaceGB
    )

    begin {
        Write-EnhancedLog -Message "Starting Check-DiskSpace function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            $drive = Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Root -eq ([System.IO.Path]::GetPathRoot($Path)) }
            if ($drive -and ($drive.Free -lt ($RequiredSpaceGB * 1GB))) {
                Write-EnhancedLog -Message "Not enough disk space on drive $($drive.Root). Required: $RequiredSpaceGB GB, Available: $([math]::round($drive.Free / 1GB, 2)) GB." -Level "ERROR"
                throw "Not enough disk space on drive $($drive.Root)."
            } else {
                Write-EnhancedLog -Message "Sufficient disk space available on drive $($drive.Root)." -Level "INFO"
            }
        } catch {
            Write-EnhancedLog -Message "An error occurred during disk space check: $_" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    end {
        Write-EnhancedLog -Message "Check-DiskSpace function execution completed." -Level "Notice"
    }
}
#EndRegion '.\Private\Check-DiskSpace.ps1' 35
#Region '.\Private\Clean-ExportsFolder.ps1' -1

function Clean-ExportsFolder {
    param (
        [Parameter(Mandatory = $true)]
        [string]$FolderPath
    )

    if (Test-Path -Path $FolderPath) {
        # Get all files in the folder
        $files = Get-ChildItem -Path "$FolderPath\*" -Recurse

        # Remove each file and log its name
        foreach ($file in $files) {
            try {
                Remove-Item -Path $file.FullName -Recurse -Force
                Write-EnhancedLog -Message "Deleted file: $($file.FullName)" -Level "INFO" -ForegroundColor ([ConsoleColor]::Yellow)
            } catch {
                Write-EnhancedLog -Message "Failed to delete file: $($file.FullName) - Error: $_" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
            }
        }

        Write-EnhancedLog -Message "Cleaned up existing folder at: $FolderPath" -Level "INFO" -ForegroundColor ([ConsoleColor]::Yellow)
    } else {
        # Create the folder if it does not exist
        New-Item -ItemType Directory -Path $FolderPath | Out-Null
        Write-EnhancedLog -Message "Created folder at: $FolderPath" -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
    }
}

# Example usage
# $folderPath = "C:\path\to\exports"
# Clean-ExportsFolder -FolderPath $folderPath -LogFunction Write-EnhancedLog
#EndRegion '.\Private\Clean-ExportsFolder.ps1' 32
#Region '.\Private\Convert-RsaParametersToPem.ps1' -1

function Convert-RsaParametersToPem {
    param (
        [Parameter(Mandatory = $true)]
        [System.Security.Cryptography.RSAParameters]$rsaParameters
    )

    $builder = [System.Text.StringBuilder]::new()

    $builder.AppendLine("-----BEGIN RSA PRIVATE KEY-----") | Out-Null

    # Combine all RSA parameters and convert them to Base64
    $params = @(
        $rsaParameters.Modulus,
        $rsaParameters.Exponent,
        $rsaParameters.D,
        $rsaParameters.P,
        $rsaParameters.Q,
        $rsaParameters.DP,
        $rsaParameters.DQ,
        $rsaParameters.InverseQ
    )

    foreach ($param in $params) {
        $b64 = [System.Convert]::ToBase64String($param)
        $builder.AppendLine($b64) | Out-Null
    }

    $builder.AppendLine("-----END RSA PRIVATE KEY-----") | Out-Null

    return $builder.ToString()
}
#EndRegion '.\Private\Convert-RsaParametersToPem.ps1' 32
#Region '.\Private\CreateEventSourceAndLog.ps1' -1

#Unique Tracking ID: 4362313d-3c19-4d0c-933d-99438a6da297, Timestamp: 2024-03-20 12:25:26

function CreateEventSourceAndLog {
    param (
        [string]$LogName,
        [string]$EventSource
    )
    
    
    # Validate parameters
    if (-not $LogName) {
        Write-Warning "LogName is required."
        return
    }
    if (-not $EventSource) {
        Write-Warning "Source is required."
        return
    }
    
    # Function to create event log and source
    function CreateEventLogSource($logName, $EventSource) {
        try {
            if ($PSVersionTable.PSVersion.Major -lt 6) {
                New-EventLog -LogName $logName -Source $EventSource
            }
            else {
                [System.Diagnostics.EventLog]::CreateEventSource($EventSource, $logName)
            }
            Write-Host "Event source '$EventSource' created in log '$logName'" -ForegroundColor Green
        }
        catch {
            Write-Warning "Error creating the event log. Make sure you run PowerShell as an Administrator."
        }
    }
    
    # Check if the event log exists
    if (-not (Get-WinEvent -ListLog $LogName -ErrorAction SilentlyContinue)) {
        # CreateEventLogSource $LogName $EventSource
    }
    # Check if the event source exists
    elseif (-not ([System.Diagnostics.EventLog]::SourceExists($EventSource))) {
        # Unregister the source if it's registered with a different log
        $existingLogName = (Get-WinEvent -ListLog * | Where-Object { $_.LogName -contains $EventSource }).LogName
        if ($existingLogName -ne $LogName) {
            Remove-EventLog -Source $EventSource -ErrorAction SilentlyContinue
        }
        # CreateEventLogSource $LogName $EventSource
    }
    else {
        Write-Host "Event source '$EventSource' already exists in log '$LogName'" -ForegroundColor Yellow
    }
}
    
# $LogName = (Get-Date -Format "HHmmss") + "_$LoggingDeploymentName"
# $EventSource = (Get-Date -Format "HHmmss") + "_$LoggingDeploymentName"
    
# Call the Create-EventSourceAndLog function
# CreateEventSourceAndLog -LogName $LogName -EventSource $EventSource
    
# Call the Write-CustomEventLog function with custom parameters and level
# Write-CustomEventLog -LogName $LogName -EventSource $EventSource -EventMessage "Outlook Signature Restore completed with warnings." -EventID 1001 -Level 'WARNING'
#EndRegion '.\Private\CreateEventSourceAndLog.ps1' 62
#Region '.\Private\Download-PsExec.ps1' -1

function Download-PsExec {
    <#
    .SYNOPSIS
    Downloads and extracts PsExec64.exe from the official Sysinternals PSTools package.
 
    .DESCRIPTION
    The Download-PsExec function downloads the PSTools package from the official Sysinternals website, extracts PsExec64.exe, and places it in the specified target folder.
 
    .PARAMETER TargetFolder
    The target folder where PsExec64.exe will be stored.
 
    .EXAMPLE
    $params = @{
        TargetFolder = "C:\ProgramData\SystemTools"
    }
    Download-PsExec @params
    Downloads PsExec64.exe to the specified target folder.
    #>


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

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

        # Ensure the target folder exists
        Ensure-TargetFolderExists -TargetFolder $TargetFolder
        Write-EnhancedLog -Message "Removing existing PsExec from target folder: $TargetFolder" -Level "INFO"
        
        Remove-ExistingPsExec -TargetFolder $TargetFolder
    }

    Process {
        try {
            # Define the URL for PsExec download
            $url = "https://download.sysinternals.com/files/PSTools.zip"
            # Full path for the downloaded file
            $timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
            $zipPath = Join-Path -Path $TargetFolder -ChildPath "PSTools_$timestamp.zip"


            # Download the PSTools.zip file containing PsExec with retry logic
            Write-EnhancedLog -Message "Downloading PSTools.zip from: $url to: $zipPath" -Level "INFO"
            
            $downloadParams = @{
                Source      = $url
                Destination = $zipPath
                MaxRetries  = 3
            }
            Start-FileDownloadWithRetry @downloadParams

            # Extract PsExec64.exe from the zip file
            Write-EnhancedLog -Message "Extracting PSTools.zip to: $TargetFolder\PStools" -Level "INFO"
            Expand-Archive -Path $zipPath -DestinationPath "$TargetFolder\PStools" -Force

            # Specific extraction of PsExec64.exe
            $extractedFolderPath = Join-Path -Path $TargetFolder -ChildPath "PSTools"
            $PsExec64Path = Join-Path -Path $extractedFolderPath -ChildPath "PsExec64.exe"
            $finalPath = Join-Path -Path $TargetFolder -ChildPath "PsExec64.exe"

            # Move PsExec64.exe to the desired location
            if (Test-Path -Path $PsExec64Path) {
                Write-EnhancedLog -Message "Moving PsExec64.exe from: $PsExec64Path to: $finalPath" -Level "INFO"
                Move-Item -Path $PsExec64Path -Destination $finalPath

                # Remove the downloaded zip file and extracted folder
                Write-EnhancedLog -Message "Removing downloaded zip file and extracted folder" -Level "INFO"
                Remove-Item -Path $zipPath -Force
                Remove-Item -Path $extractedFolderPath -Recurse -Force

                Write-EnhancedLog -Message "PsExec64.exe has been successfully downloaded and moved to: $finalPath" -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "PsExec64.exe not found in the extracted files." -Level "ERROR"
                throw "PsExec64.exe not found after extraction."
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Download-PsExec function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Download-PsExec function" -Level "Notice"
    }
}

# Example usage
# $params = @{
# TargetFolder = "C:\ProgramData\SystemTools"
# }
# Download-PsExec @params
#EndRegion '.\Private\Download-PsExec.ps1' 99
#Region '.\Private\Ensure-TargetFolderExists.ps1' -1

function Ensure-TargetFolderExists {
    param (
        [string]$TargetFolder
    )
    
    try {
        if (-Not (Test-Path -Path $TargetFolder)) {
            Write-EnhancedLog -Message "Target folder does not exist. Creating folder: $TargetFolder" -Level "INFO" -ForegroundColor ([ConsoleColor]::Cyan)
            New-Item -Path $TargetFolder -ItemType Directory -Force
            Write-EnhancedLog -Message "Target folder created: $TargetFolder" -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
        } else {
            Write-EnhancedLog -Message "Target folder already exists: $TargetFolder" -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
        }
    } catch {
        Write-EnhancedLog -Message "An error occurred while ensuring the target folder exists: $($_.Exception.Message)" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
        Handle-Error -ErrorRecord $_
    }
}
#EndRegion '.\Private\Ensure-TargetFolderExists.ps1' 19
#Region '.\Private\Export-EventLog.ps1' -1

#Unique Tracking ID: c00ecaca-dd4b-4c7c-b80e-566b2f627e32, Timestamp: 2024-03-20 12:25:26
function Export-EventLog {
    param (
        [Parameter(Mandatory = $true)]
        [string]$LogName,
        [Parameter(Mandatory = $true)]
        [string]$ExportPath
    )
    
    try {
        wevtutil epl $LogName $ExportPath
    
        if (Test-Path $ExportPath) {
            Write-EnhancedLog -Message "Event log '$LogName' exported to '$ExportPath'" -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
        }
        else {
            Write-EnhancedLog -Message "Event log '$LogName' not exported: File does not exist at '$ExportPath'" -Level "WARNING" -ForegroundColor ([ConsoleColor]::Yellow)
        }
    }
    catch {
        Write-EnhancedLog -Message "Error exporting event log '$LogName': $($_.Exception.Message)" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
    }
}
    
# # Example usage
# $LogName = '$LoggingDeploymentNameLog'
# # $ExportPath = 'Path\to\your\exported\eventlog.evtx'
# $ExportPath = "C:\code\$LoggingDeploymentName\exports\Logs\$logname.evtx"
# Export-EventLog -LogName $LogName -ExportPath $ExportPath
#EndRegion '.\Private\Export-EventLog.ps1' 30
#Region '.\Private\Generate-JWTAssertion.ps1' -1

function Generate-JWTAssertion {
    param (
        [Parameter(Mandatory = $true)]
        [hashtable]$jwtHeader,
        [Parameter(Mandatory = $true)]
        [hashtable]$jwtPayload,
        [Parameter(Mandatory = $true)]
        [System.Security.Cryptography.X509Certificates.X509Certificate2]$cert
    )

    $jwtHeaderJson = ($jwtHeader | ConvertTo-Json -Compress)
    $jwtPayloadJson = ($jwtPayload | ConvertTo-Json -Compress)
    $jwtHeaderEncoded = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($jwtHeaderJson)).TrimEnd('=').Replace('+', '-').Replace('/', '_')
    $jwtPayloadEncoded = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($jwtPayloadJson)).TrimEnd('=').Replace('+', '-').Replace('/', '_')

    $dataToSign = "$jwtHeaderEncoded.$jwtPayloadEncoded"
    $sha256 = [Security.Cryptography.SHA256]::Create()
    $hash = $sha256.ComputeHash([Text.Encoding]::UTF8.GetBytes($dataToSign))

    $rsa = [Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
    $signature = [Convert]::ToBase64String($rsa.SignHash($hash, [Security.Cryptography.HashAlgorithmName]::SHA256, [Security.Cryptography.RSASignaturePadding]::Pkcs1)).TrimEnd('=').Replace('+', '-').Replace('/', '_')

    return "$dataToSign.$signature"
}
#EndRegion '.\Private\Generate-JWTAssertion.ps1' 25
#Region '.\Private\Get-UnixTime.ps1' -1

function Get-UnixTime {
    param (
        [Parameter(Mandatory = $true)]
        [int]$offsetMinutes
    )

    return [int]([DateTimeOffset]::UtcNow.ToUnixTimeSeconds() + ($offsetMinutes * 60))
}
#EndRegion '.\Private\Get-UnixTime.ps1' 9
#Region '.\Private\Handle-RobocopyExitCode.ps1' -1

function Handle-RobocopyExitCode {
    <#
    .SYNOPSIS
    Handles the exit code from a Robocopy operation.
 
    .DESCRIPTION
    The Handle-RobocopyExitCode function interprets the exit code returned by a Robocopy operation and logs an appropriate message. The exit codes provide information about the success or failure of the operation and any additional conditions encountered.
 
    .PARAMETER ExitCode
    The exit code returned by Robocopy.
 
    .EXAMPLE
    Handle-RobocopyExitCode -ExitCode 0
    Logs a message indicating no files were copied, no files were mismatched, and no failures were encountered.
 
    .EXAMPLE
    Handle-RobocopyExitCode -ExitCode 1
    Logs a message indicating all files were copied successfully.
 
    .NOTES
    This function is typically used internally after executing a Robocopy command to handle and log the exit status.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [int]$ExitCode
    )

    begin {
        Write-EnhancedLog -Message "Starting Handle-RobocopyExitCode function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            switch ($ExitCode) {
                0 { Write-EnhancedLog -Message "No files were copied. No files were mismatched. No failures were encountered." -Level "INFO" }
                1 { Write-EnhancedLog -Message "All files were copied successfully." -Level "INFO" }
                2 { Write-EnhancedLog -Message "There are some additional files in the destination directory that are not present in the source directory. No files were copied." -Level "INFO" }
                3 { Write-EnhancedLog -Message "Some files were copied. Additional files were present. No failure was encountered." -Level "INFO" }
                4 { Write-EnhancedLog -Message "Some files were mismatched. No files were copied." -Level "INFO" }
                5 { Write-EnhancedLog -Message "Some files were copied. Some files were mismatched. No failure was encountered." -Level "INFO" }
                6 { Write-EnhancedLog -Message "Additional files and mismatched files exist. No files were copied." -Level "INFO" }
                7 { Write-EnhancedLog -Message "Files were copied, a file mismatch was present, and additional files were present." -Level "INFO" }
                8 { Write-EnhancedLog -Message "Several files did not copy." -Level "ERROR" }
                default { Write-EnhancedLog -Message "Robocopy failed with exit code $ExitCode" -Level "ERROR" }
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while handling the Robocopy exit code: $_" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    end {
        Write-EnhancedLog -Message "Handle-RobocopyExitCode function execution completed." -Level "Notice"
    }
}
#EndRegion '.\Private\Handle-RobocopyExitCode.ps1' 61
#Region '.\Private\Initialize-CSVDirectory.ps1' -1

function Initialize-CSVDirectory {
    param (
        [string]$deploymentName,
        [string]$computerName
    )

    $isWindowsOS = $false
    if ($PSVersionTable.PSVersion.Major -ge 6) {
        $isWindowsOS = $isWindowsOS -or ($PSVersionTable.Platform -eq 'Win32NT')
    } else {
        $isWindowsOS = $isWindowsOS -or ($env:OS -eq 'Windows_NT')
    }

    $baseScriptPath = if ($isWindowsOS) { "C:\code" } else { "/home/code" }
    $scriptPath_1001 = Join-Path -Path $baseScriptPath -ChildPath $deploymentName
    $CSVDir_1001 = Join-Path -Path $scriptPath_1001 -ChildPath "exports/CSV"
    $CSVFilePath_1001 = Join-Path -Path $CSVDir_1001 -ChildPath "$computerName"

    try {
        if (-not (Test-Path $CSVFilePath_1001)) {
            Write-Host "Did not find CSV directory at $CSVFilePath_1001" -ForegroundColor Yellow
            Write-Host "Creating CSV directory at $CSVFilePath_1001" -ForegroundColor Yellow
            New-Item -ItemType Directory -Path $CSVFilePath_1001 -Force -ErrorAction Stop | Out-Null
            Write-Host "Created CSV directory at $CSVFilePath_1001" -ForegroundColor Green
        }

        return @{
            CSVFilePath = $CSVFilePath_1001
        }
    } catch {
        Write-Host "An error occurred while initializing CSV directory: $_" -ForegroundColor Red
    }
}

# # Example usage of Initialize-CSVDirectory
# try {
# $csvInitResult = Initialize-CSVDirectory -deploymentName "$LoggingDeploymentName" -computerName $env:COMPUTERNAME
# Write-Host "CSV initialization successful. CSV directory path: $($csvInitResult.CSVFilePath)" -ForegroundColor Green
# } catch {
# Write-Host "CSV initialization failed: $_" -ForegroundColor Red
# }
#EndRegion '.\Private\Initialize-CSVDirectory.ps1' 42
#Region '.\Private\Send-TokenRequest.ps1' -1

function Send-TokenRequest {
    param (
        [Parameter(Mandatory = $true)]
        [string]$tokenEndpoint,
        [Parameter(Mandatory = $true)]
        [string]$clientId,
        [Parameter(Mandatory = $true)]
        [string]$clientAssertion
    )

    $body = @{
        client_id = $clientId
        scope = "https://graph.microsoft.com/.default"
        client_assertion = $clientAssertion
        client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
        grant_type = "client_credentials"
    }

    try {
        Write-EnhancedLog -Message "Sending request to token endpoint: $tokenEndpoint" -Level "INFO"
        $response = Invoke-RestMethod -Method Post -Uri $tokenEndpoint -ContentType "application/x-www-form-urlencoded" -Body $body
        Write-EnhancedLog -Message "Successfully obtained access token." -Level "INFO"
        return $response.access_token
    }
    catch {
        Write-EnhancedLog -Message "Error obtaining access token: $_"
        throw $_
    }
}
#EndRegion '.\Private\Send-TokenRequest.ps1' 30
#Region '.\Private\Test-Directory.ps1' -1

function Test-Directory {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]$Path
    )

    begin {
        Write-EnhancedLog -Message "Starting Test-Directory function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            if (-Not (Test-Path -Path $Path -PathType Container)) {
                Write-EnhancedLog -Message "The path '$Path' is not a valid directory." -Level "ERROR"
                throw "The path '$Path' is not a valid directory."
            } else {
                Write-EnhancedLog -Message "The path '$Path' is a valid directory." -Level "INFO"
            }
        } catch {
            Write-EnhancedLog -Message "An error occurred during directory validation: $_" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    end {
        Write-EnhancedLog -Message "Test-Directory function execution completed." -Level "Notice"
    }
}
#EndRegion '.\Private\Test-Directory.ps1' 32
#Region '.\Private\Test-Robocopy.ps1' -1


function Test-Robocopy {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]$RobocopyPath = "C:\Windows\System32\Robocopy.exe"
    )

    begin {
        Write-EnhancedLog -Message "Starting Test-Robocopy function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            if (-Not (Test-Path -Path $RobocopyPath)) {
                Write-EnhancedLog -Message "Robocopy.exe is not available at the specified path: $RobocopyPath" -Level "ERROR"
                throw "Robocopy.exe is not available at the specified path: $RobocopyPath"
            }
            else {
                Write-EnhancedLog -Message "Robocopy.exe is available at the specified path: $RobocopyPath" -Level "INFO"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred during Robocopy availability check: $_" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    end {
        Write-EnhancedLog -Message "Test-Robocopy function execution completed." -Level "Notice"
    }
}
#EndRegion '.\Private\Test-Robocopy.ps1' 35
#Region '.\Private\Validate-FileUpload.ps1' -1

function Validate-FileUpload {
    param (
        [Parameter(Mandatory = $true)]
        [string]$DocumentDriveId,

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

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

        [Parameter(Mandatory = $true)]
        [hashtable]$Headers
    )

    $url = "https://graph.microsoft.com/v1.0/drives/$DocumentDriveId/root:/$FolderName/$FileName"
    try {
        $response = Invoke-RestMethod -Headers $Headers -Uri $url -Method GET
        if ($response) {
            Write-EnhancedLog -Message "File '$FileName' exists in '$FolderName' after upload." -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
        } else {
            Write-EnhancedLog -Message "File '$FileName' does not exist in '$FolderName' after upload." -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
            throw "File '$FileName' does not exist in '$FolderName' after upload."
        }
    }
    catch {
        Write-EnhancedLog -Message "Failed to validate file '$FileName' in '$FolderName': $_" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
        throw $_
    }
}
#EndRegion '.\Private\Validate-FileUpload.ps1' 31
#Region '.\Private\Write-EventLogMessage.ps1' -1

#Unique Tracking ID: 132a90f9-6ba2-49cd-878b-279deadb8e22, Timestamp: 2024-03-20 12:25:26

function Write-EventLogMessage {
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Message,
    
        [string]$LogName = "$LoggingDeploymentName",
        [string]$EventSource,
    
        [int]$EventID = 1000  # Default event ID
    )
    
    $ErrorActionPreference = 'SilentlyContinue'
    $hadError = $false
    
    try {
        if (-not $EventSource) {
            throw "EventSource is required."
        }
    
        if ($PSVersionTable.PSVersion.Major -lt 6) {
            # PowerShell version is less than 6, use Write-EventLog
            Write-EventLog -LogName $logName -Source $EventSource -EntryType Information -EventId $EventID -Message $Message
        }
        else {
            # PowerShell version is 6 or greater, use System.Diagnostics.EventLog
            $eventLog = New-Object System.Diagnostics.EventLog($logName)
            $eventLog.Source = $EventSource
            $eventLog.WriteEntry($Message, [System.Diagnostics.EventLogEntryType]::Information, $EventID)
        }
    
        # Write-Host "Event log entry created: $Message"
    }
    catch {
        Write-Host "Error creating event log entry: $_" 
        $hadError = $true
    }
    
    if (-not $hadError) {
        # Write-Host "Event log message writing completed successfully."
    }
}
    
#EndRegion '.\Private\Write-EventLogMessage.ps1' 46
#Region '.\Public\Add-DVDDriveToVM.ps1' -1

function Add-DVDDriveToVM {
    <#
    .SYNOPSIS
    Adds a DVD drive with the specified ISO to the VM and validates the addition.
    #>

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

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

    Begin {
        Write-EnhancedLog -Message "Starting Add-DVDDriveToVM function" -Level "INFO"
        Log-Params -Params @{ VMName = $VMName; InstallMediaPath = $InstallMediaPath }
    }

    Process {
        try {
            Write-EnhancedLog -Message "Validating if the ISO is already added to VM: $VMName" -Level "INFO"
            if (Validate-ISOAdded -VMName $VMName -InstallMediaPath $InstallMediaPath) {
                Write-EnhancedLog -Message "ISO is already added to VM: $VMName" -Level "INFO"
                return
            }

            Write-EnhancedLog -Message "Adding SCSI controller to VM: $VMName" -Level "INFO"
            Add-VMScsiController -VMName $VMName -ErrorAction Stop

            Write-EnhancedLog -Message "Adding DVD drive with ISO to VM: $VMName" -Level "INFO"
            Add-VMDvdDrive -VMName $VMName -Path $InstallMediaPath -ErrorAction Stop

            Write-EnhancedLog -Message "DVD drive with ISO added to VM: $VMName" -Level "INFO"

            Write-EnhancedLog -Message "Validating the ISO addition for VM: $VMName" -Level "INFO"
            if (-not (Validate-ISOAdded -VMName $VMName -InstallMediaPath $InstallMediaPath)) {
                Write-EnhancedLog -Message "Failed to validate the ISO addition for VM: $VMName" -Level "ERROR"
                throw "ISO validation failed."
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while adding DVD drive to VM: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Add-DVDDriveToVM function" -Level "INFO"
    }
}
#EndRegion '.\Public\Add-DVDDriveToVM.ps1' 52
#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-GuidToPs1Files.ps1' -1

function Add-GuidToPs1Files {


    <#
.SYNOPSIS
Adds a unique GUID and timestamp to the top of each .ps1 file in a specified directory.
 
.DESCRIPTION
This function searches for PowerShell script files (.ps1) within a specified subdirectory of a given root directory. It then prepends a unique GUID and a timestamp to each file for tracking purposes. This is useful for marking scripts in bulk operations or deployments.
 
.PARAMETER AOscriptDirectory
The root directory under which the target program folder resides.
 
.PARAMETER programfoldername
The name of the subdirectory containing the .ps1 files to be modified.
 
.EXAMPLE
Add-GuidToPs1Files -AOscriptDirectory "d:\Scripts" -programfoldername "MyProgram"
 
Adds a tracking GUID and timestamp to all .ps1 files under "d:\Scripts\apps-winget\MyProgram".
 
.NOTES
Author: Your Name
Date: Get the current date
Version: 1.0
 
#>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        # [ValidateScript({Test-Path $_})]
        [string]$AOscriptDirectory,

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

    # Helper function for logging
    Begin {
        Write-EnhancedLog -Message "Starting to modify PowerShell files." -Level "INFO" -ForegroundColor Green
    }

    Process {
        $targetFolder = Join-Path -Path $AOscriptDirectory -ChildPath "apps-winget\$programfoldername"

        if (-Not (Test-Path -Path $targetFolder)) {
            Write-EnhancedLog -Message "The target folder does not exist: $targetFolder" -Level "ERROR" -ForegroundColor Red
            return
        }

        $ps1Files = Get-ChildItem -Path $targetFolder -Filter *.ps1 -Recurse
        if ($ps1Files.Count -eq 0) {
            Write-EnhancedLog -Message "No PowerShell files (.ps1) found in $targetFolder" -Level "WARNING" -ForegroundColor Yellow
            return
        }

        foreach ($file in $ps1Files) {
            try {
                $content = Get-Content -Path $file.FullName -ErrorAction Stop
                $pattern = '^#Unique Tracking ID: .+'
                $content = $content | Where-Object { $_ -notmatch $pattern }

                $guid = [guid]::NewGuid().ToString("D").ToLower()
                $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
                $lineToAdd = "#Unique Tracking ID: $guid, Timestamp: $timestamp"
                $newContent = $lineToAdd, $content

                Set-Content -Path $file.FullName -Value $newContent -ErrorAction Stop
                Write-EnhancedLog -Message "Modified file: $($file.FullName)" -Level "VERBOSE" -ForegroundColor Yellow
            }
            catch {
                Write-EnhancedLog -Message "Failed to modify file: $($file.FullName). Error: $($_.Exception.Message)" -Level "ERROR" -ForegroundColor Red
            }
        }
    }

    End {
        Write-EnhancedLog -Message "Completed modifications." -Level "INFO" -ForegroundColor Cyan
    }
}


# Example usage:
# Add-GuidToPs1Files -AOscriptDirectory $AOscriptDirectory
#EndRegion '.\Public\Add-GuidToPs1Files.ps1' 86
#Region '.\Public\Add-KeyCredentialToApp.ps1' -1

# Associate certificate with App Registration
function Add-KeyCredentialToApp {
    param (
        [Parameter(Mandatory = $true)]
        [string]$AppId,

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

    # Read the certificate file using the constructor
    $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($CertPath)
    $certBytes = $cert.RawData
    $base64Cert = [System.Convert]::ToBase64String($certBytes)

    # Convert certificate dates to DateTime and adjust for time zone
    $startDate = [datetime]::Parse($cert.NotBefore.ToString("o"))
    $endDate = [datetime]::Parse($cert.NotAfter.ToString("o"))

    # Adjust the start and end dates to ensure they are valid and in UTC
    $startDate = [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($startDate, [System.TimeZoneInfo]::Local.Id, 'UTC')
    $endDate = [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($endDate, [System.TimeZoneInfo]::Local.Id, 'UTC')

    # Adjust end date by subtracting one day to avoid potential end date issues
    $endDate = $endDate.AddDays(-1)

    # Prepare the key credential parameters
    $keyCredentialParams = @{
        CustomKeyIdentifier = [System.Convert]::FromBase64String([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($cert.Thumbprint.Substring(0, 32))))
        DisplayName = "GraphCert"
        EndDateTime = $endDate
        StartDateTime = $startDate
        KeyId = [Guid]::NewGuid().ToString()
        Type = "AsymmetricX509Cert"
        Usage = "Verify"
        Key = $certBytes
    }

    # Create the key credential object
    $keyCredential = [Microsoft.Graph.PowerShell.Models.MicrosoftGraphKeyCredential]::new()
    $keyCredential.CustomKeyIdentifier = $keyCredentialParams.CustomKeyIdentifier
    $keyCredential.DisplayName = $keyCredentialParams.DisplayName
    $keyCredential.EndDateTime = $keyCredentialParams.EndDateTime
    $keyCredential.StartDateTime = $keyCredentialParams.StartDateTime
    $keyCredential.KeyId = $keyCredentialParams.KeyId
    $keyCredential.Type = $keyCredentialParams.Type
    $keyCredential.Usage = $keyCredentialParams.Usage
    $keyCredential.Key = $keyCredentialParams.Key

    # Update the application with the new key credential
    try {
        Update-MgApplication -ApplicationId $AppId -KeyCredentials @($keyCredential)
        Write-Host "Key credential added successfully to the application."
    } catch {
        Write-Host "An error occurred: $_" -ForegroundColor Red
    }
}


#EndRegion '.\Public\Add-KeyCredentialToApp.ps1' 60
#Region '.\Public\Add-LocalUser-Archive.ps1' -1

# # # function Verify-GroupMembers {
# # # [CmdletBinding()]
# # # param (
# # # [Parameter(Mandatory = $true)]
# # # [array]$groupMembers
# # # )

# # # Begin {
# # # Write-EnhancedLog -Message "Starting Verify-GroupMembers function" -Level "Notice"
# # # Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
# # # }

# # # Process {
# # # Write-EnhancedLog -Message "Verifying remaining members of the group." -Level "INFO"
# # # foreach ($member in $groupMembers) {
# # # try {
# # # # Ensure the SID is valid before processing
# # # if ($member.SID) {
# # # $objSID = New-Object System.Security.Principal.SecurityIdentifier($member.SID)
# # # $objUser = $objSID.Translate([System.Security.Principal.NTAccount])
# # # Write-EnhancedLog -Message "Valid member: $($objUser.Value), SID: $($member.SID)" -Level "INFO"
# # # }
# # # else {
# # # Write-EnhancedLog -Message "Invalid or missing SID for member $($member.Name)" -Level "WARNING"
# # # }
# # # }
# # # catch {
# # # Write-EnhancedLog -Message "Unexpected error resolving member: SID $($member.SID). Error: $($_.Exception.Message)" -Level "ERROR"
# # # }
# # # }
# # # }

# # # End {
# # # Write-EnhancedLog -Message "Exiting Verify-GroupMembers function" -Level "Notice"
# # # }
# # # }




# # function Get-LocalUserAccount {
# # <#
# # .SYNOPSIS
# # Checks if a local user exists and creates the user if necessary.
# # .PARAMETER TempUser
# # The username of the temporary local user.
# # .PARAMETER TempUserPassword
# # The password for the local user.
# # .PARAMETER Description
# # The description for the local user account.
# # #>

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

# # [Parameter(Mandatory = $true)]
# # [string]$TempUserPassword,

# # [Parameter(Mandatory = $true)]
# # [string]$Description
# # )

# # Begin {
# # Write-EnhancedLog -Message "Starting Get-LocalUserAccount function for user '$TempUser'" -Level "Notice"
# # Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
# # }

# # Process {
# # try {
# # Write-EnhancedLog -Message "Checking if local user '$TempUser' exists." -Level "INFO"
# # $userExists = Get-LocalUser -Name $TempUser -ErrorAction SilentlyContinue

# # if (-not $userExists) {
# # Write-EnhancedLog -Message "Creating Local User Account '$TempUser'." -Level "INFO"
# # $Password = ConvertTo-SecureString -AsPlainText $TempUserPassword -Force
# # New-LocalUser -Name $TempUser -Password $Password -Description $Description -AccountNeverExpires
# # Write-EnhancedLog -Message "Local user account '$TempUser' created successfully." -Level "INFO"
# # }
# # else {
# # Write-EnhancedLog -Message "Local user account '$TempUser' already exists." -Level "WARNING"
# # }

# # return $userExists

# # }
# # catch {
# # Write-EnhancedLog -Message "Failed to retrieve or create user '$TempUser': $($_.Exception.Message)" -Level "ERROR"
# # throw
# # }
# # }

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

# # # # function Get-LocalGroupWithMembers {
# # # # [CmdletBinding()]
# # # # param (
# # # # [Parameter(Mandatory = $true)]
# # # # [string]$Group
# # # # )

# # # # try {
# # # # Write-EnhancedLog -Message "Retrieving group '$Group' for membership check." -Level "INFO"
# # # # $group = Get-LocalGroup -Name $Group -ErrorAction Stop

# # # # Write-EnhancedLog -Message "Group '$Group' found. Retrieving group members." -Level "INFO"
# # # # $groupMembers = Get-LocalGroupMember -Group $Group -ErrorAction Stop

# # # # Write-EnhancedLog -Message "Group '$Group' has $($groupMembers.Count) members." -Level "INFO"
# # # # return $groupMembers

# # # # }
# # # # catch {
# # # # Write-EnhancedLog -Message "Error retrieving group '$Group' or members: $($_.Exception.Message)" -Level "ERROR"
# # # # throw
# # # # }
# # # # }

# # # function Check-UserGroupMembership {
# # # [CmdletBinding()]
# # # param (
# # # [Parameter(Mandatory = $true)]
# # # [string]$TempUser,

# # # [Parameter(Mandatory = $true)]
# # # [array]$GroupMembers
# # # )

# # # try {
# # # Write-EnhancedLog -Message "Checking if user '$TempUser' is a member of the group." -Level "INFO"
# # # $userSID = (Get-LocalUser -Name $TempUser -ErrorAction Stop).SID
# # # $memberExists = $false

# # # foreach ($member in $GroupMembers) {
# # # Write-EnhancedLog -Message "Checking group member '$($member.Name)' (SID: $($member.SID))." -Level "DEBUG"
# # # if ($member.SID -eq $userSID) {
# # # Write-EnhancedLog -Message "User '$TempUser' is already a member of the group." -Level "INFO"
# # # $memberExists = $true
# # # break
# # # }
# # # }

# # # return $memberExists

# # # }
# # # catch {
# # # Write-EnhancedLog -Message "Error checking user group membership: $($_.Exception.Message)" -Level "ERROR"
# # # throw
# # # }
# # # }

# # # function Add-UserToLocalGroup {
# # # [CmdletBinding()]
# # # param (
# # # [Parameter(Mandatory = $true)]
# # # [string]$TempUser,
# # # [Parameter(Mandatory = $true)]
# # # [string]$Group
# # # )

# # # try {
# # # Write-EnhancedLog -Message "Checking if user '$TempUser' is already a member of group '$Group'." -Level "INFO"
# # # # $groupMembers = Get-LocalGroupMember -Group $Group -ErrorAction Stop
# # # $groupMembers = Get-GroupMembers -GroupName $Group -ErrorAction Stop

# # # # Check if the user is already a member of the group
# # # if ($groupMembers.Name -contains $TempUser) {
# # # Write-EnhancedLog -Message "User '$TempUser' is already a member of the group '$Group'." -Level "INFO"
# # # }
# # # else {
# # # Write-EnhancedLog -Message "Adding user '$TempUser' to group '$Group'." -Level "INFO"
# # # Add-LocalGroupMember -Group $Group -Member $TempUser -ErrorAction Stop
# # # Write-EnhancedLog -Message "User '$TempUser' added to group '$Group'." -Level "INFO"
# # # }
# # # }
# # # catch {
# # # Write-EnhancedLog -Message "Failed to add user '$TempUser' to group '$Group': $($_.Exception.Message)" -Level "ERROR"
# # # throw
# # # }
# # # }





# # # function Add-LocalUser {
# # # [CmdletBinding()]
# # # param (
# # # [Parameter(Mandatory = $true)]
# # # [string]$TempUser,

# # # [Parameter(Mandatory = $true)]
# # # [string]$TempUserPassword,

# # # [Parameter(Mandatory = $true)]
# # # [string]$Description,

# # # [Parameter(Mandatory = $true)]
# # # [string]$Group = "Administrators"
# # # )

# # # Begin {
# # # Write-EnhancedLog -Message "Starting Add-LocalUser function" -Level "Notice"
# # # Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
# # # }

# # # Process {
# # # try {
# # # # Step 1: Check or create local user
# # # $userExists = Get-LocalUserAccount -TempUser $TempUser -TempUserPassword $TempUserPassword -Description $Description

# # # # Step 2: Retrieve group members using Get-GroupMembers function instead of Get-LocalGroupMember
# # # $groupMembers = Get-GroupMembers -GroupName $Group

# # # if ($groupMembers) {
# # # # Step 3: Check if user is already a member of the group
# # # $isMember = $false
# # # foreach ($member in $groupMembers) {
# # # $accountName = Extract-AccountName -PartComponent $member.PartComponent
# # # if ($accountName -eq $TempUser) {
# # # $isMember = $true
# # # break
# # # }
# # # }

# # # # Step 4: Add user to group if not already a member
# # # if (-not $isMember) {
# # # Add-UserToLocalGroup -TempUser $TempUser -Group $Group
# # # }
# # # else {
# # # Write-EnhancedLog -Message "User '$TempUser' is already a member of the group '$Group'." -Level "WARNING"
# # # }

# # # # Step 5: Verify the group members after modification
# # # Verify-GroupMembers -groupMembers $groupMembers
# # # }
# # # else {
# # # Write-EnhancedLog -Message "No members found in group '$Group'." -Level "WARNING"
# # # }
# # # }
# # # catch {
# # # Write-EnhancedLog -Message "An error occurred in Add-LocalUser: $($_.Exception.Message)" -Level "ERROR"
# # # throw
# # # }
# # # }

# # # End {
# # # Write-EnhancedLog -Message "Exiting Add-LocalUser function" -Level "Notice"
# # # }
# # # }



# # # # function Add-LocalUser {
# # # # <#
# # # # .SYNOPSIS
# # # # Adds a local user and ensures they are part of the correct group.
# # # # .PARAMETER TempUser
# # # # The username of the temporary local user.
# # # # .PARAMETER TempUserPassword
# # # # The password for the local user.
# # # # .PARAMETER Description
# # # # The description for the local user account.
# # # # .PARAMETER Group
# # # # The group to add the local user to.
# # # # #>

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

# # # # [Parameter(Mandatory = $true)]
# # # # [string]$TempUserPassword,

# # # # [Parameter(Mandatory = $true)]
# # # # [string]$Description,

# # # # [Parameter(Mandatory = $true)]
# # # # [string]$Group
# # # # )

# # # # Begin {
# # # # Write-EnhancedLog -Message "Starting Add-LocalUser function" -Level "Notice"
# # # # Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

# # # # # Main script to clean orphaned SIDs and verify group members
      
# # # # # Step 1: Remove orphaned SIDs
# # # # # Example usage to remove orphaned SIDs from the "Administrators" group
# # # # # $params = @{
# # # # # Group = "Administrators"
# # # # # }
# # # # # Remove-OrphanedSIDs @params


# # # # # Remove-OrphanedSIDsFromAdministratorsGroup


# # # # # $GetGroupMembers = @{
# # # # # GroupName = "Administrators"
# # # # # }
# # # # # $groupMembers = Get-GroupMembers @GetGroupMembers


# # # # # $groupMembers = Get-OrphanedSIDs
# # # # $groupMembers = Get-AllGroupAccounts -GroupName "Administrators"
# # # # # $accounts | Format-Table -AutoSize



# # # # # Wait-Debugger

# # # # # Step 2: Retrieve group members
# # # # # $groupMembers = Get-LocalGroupWithMembers -Group $Group

# # # # if ($groupMembers) {
# # # # # Step 3: Verify the remaining group members
# # # # Verify-GroupMembers -groupMembers $groupMembers
# # # # }
        
# # # # Wait-Debugger

# # # # }

# # # # Process {
# # # # try {
# # # # # Step 1: Check or create local user
# # # # $userExists = Get-LocalUserAccount -TempUser $TempUser -TempUserPassword $TempUserPassword -Description $Description


# # # # $GetGroupMembers = @{
# # # # GroupName = "Administrators"
# # # # }
# # # # $groupMembers = Get-GroupMembers @GetGroupMembers

# # # # # Step 2: Retrieve group and its members
# # # # # $groupMembers = Get-LocalGroupWithMembers -Group $Group

# # # # # Step 3: Check if user is a member of the group
# # # # $isMember = Check-UserGroupMembership -TempUser $TempUser -GroupMembers $groupMembers

# # # # # Step 4: Add user to group if not a member
# # # # if (-not $isMember) {
# # # # Add-UserToLocalGroup -TempUser $TempUser -Group $Group
# # # # }

# # # # # Step 5: Remove orphaned SIDs and verify group members
# # # # # Example usage to remove orphaned SIDs from the "Administrators" group
# # # # # $params = @{
# # # # # Group = "Administrators"
# # # # # }
# # # # # Remove-OrphanedSIDs @params

# # # # # Remove-OrphanedSIDsFromAdministratorsGroup


# # # # $GetGroupMembers = @{
# # # # GroupName = "Administrators"
# # # # }
# # # # $groupMembers = Get-GroupMembers @GetGroupMembers
    

# # # # # Retrieve group members
# # # # # $groupMembers = Get-LocalGroupWithMembers -Group $Group

# # # # if ($groupMembers) {
# # # # # Verify the remaining group members
# # # # Verify-GroupMembers -groupMembers $groupMembers
# # # # }
# # # # }
# # # # catch {
# # # # Write-EnhancedLog -Message "An error occurred in Add-LocalUser: $($_.Exception.Message)" -Level "ERROR"
# # # # throw
# # # # }
# # # # }

# # # # End {
# # # # Write-EnhancedLog -Message "Exiting Add-LocalUser function" -Level "Notice"
# # # # }
# # # # }


























# # function Get-GroupMembers {
# # param (
# # [string]$GroupName = "Administrators"
# # )

# # Begin {
# # Write-EnhancedLog -Message "Retrieving members of the '$GroupName' group." -Level "INFO"
# # }

# # Process {
# # try {
# # Write-EnhancedLog -Message "Attempting to retrieve members of the '$GroupName' group." -Level "INFO"
# # $groupPattern = [regex]::Escape($GroupName)
# # $admins = Get-WmiObject -Class Win32_GroupUser | Where-Object { $_.GroupComponent -match $groupPattern }

# # $count = $admins.Count
# # Write-EnhancedLog -Message "Found $count members in the '$GroupName' group." -Level "INFO"

# # if ($count -eq 0) {
# # Write-EnhancedLog -Message "No members found in the '$GroupName' group." -Level "WARNING"
# # }

# # return $admins
# # }
# # catch {
# # Write-EnhancedLog -Message "Failed to retrieve members of the '$GroupName' group: $($_.Exception.Message)" -Level "ERROR"
# # Handle-Error -ErrorRecord $_
# # throw
# # }
# # }
# # }





# # function Verify-GroupMembers {
# # [CmdletBinding()]
# # param (
# # [Parameter(Mandatory = $true)]
# # [array]$groupMembers
# # )

# # Begin {
# # Write-EnhancedLog -Message "Starting Verify-GroupMembers function" -Level "Notice"
# # Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
# # }

# # Process {
# # Write-EnhancedLog -Message "Verifying remaining members of the group." -Level "INFO"
        
# # foreach ($member in $groupMembers) {
# # try {
# # # Attempt to extract the SID from the PartComponent property
# # $partComponent = $member.PartComponent
# # if ($partComponent -match 'Win32_UserAccount.Domain="[^"]+",Name="([^"]+)"') {
# # $accountName = $matches[1]

# # Write-EnhancedLog -Message "Attempting to resolve account: $accountName" -Level "INFO"
                    
# # # Create SID from the account name
# # $objSID = New-Object System.Security.Principal.SecurityIdentifier($accountName)
# # $objUser = $objSID.Translate([System.Security.Principal.NTAccount])
                    
# # Write-EnhancedLog -Message "Valid member: $($objUser.Value), SID: $($member.PartComponent)" -Level "INFO"
# # } else {
# # Write-EnhancedLog -Message "Invalid or missing SID for member: $($member.PartComponent)" -Level "WARNING"
# # }
# # }
# # catch {
# # Write-EnhancedLog -Message "Unable to resolve member: $($member.PartComponent). Error: $($_.Exception.Message)" -Level "ERROR"
# # }
# # }
# # }

# # End {
# # Write-EnhancedLog -Message "Exiting Verify-GroupMembers function" -Level "Notice"
# # }
# # }









# # function Add-LocalUser {
# # [CmdletBinding()]
# # param (
# # [Parameter(Mandatory = $true)]
# # [string]$TempUser,

# # [Parameter(Mandatory = $true)]
# # [string]$TempUserPassword,

# # [Parameter(Mandatory = $true)]
# # [string]$Description,

# # [Parameter(Mandatory = $true)]
# # [string]$Group = "Administrators"
# # )

# # Begin {
# # Write-EnhancedLog -Message "Starting Add-LocalUser function" -Level "Notice"
# # Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
# # }

# # Process {
# # try {
# # # Step 1: Check or create local user
# # $userExists = Get-LocalUserAccount -TempUser $TempUser -TempUserPassword $TempUserPassword -Description $Description

# # # Step 2: Retrieve group members using Get-GroupMembers
# # $groupMembers = Get-GroupMembers -GroupName $Group

# # if ($groupMembers) {
# # # Step 3: Check if user is already a member of the group
# # $isMember = $false
# # foreach ($member in $groupMembers) {
# # $accountName = Extract-AccountName -PartComponent $member.PartComponent
# # if ($accountName -eq $TempUser) {
# # $isMember = $true
# # break
# # }
# # }

# # # Step 4: Add user to group if not already a member
# # if (-not $isMember) {
# # Add-UserToLocalGroup -TempUser $TempUser -Group $Group
# # }
# # else {
# # Write-EnhancedLog -Message "User '$TempUser' is already a member of the group '$Group'." -Level "WARNING"
# # }

# # # Step 5: Verify the group members after modification
# # Verify-GroupMembers -groupMembers $groupMembers
# # }
# # else {
# # Write-EnhancedLog -Message "No members found in group '$Group'." -Level "WARNING"
# # }
# # }
# # catch {
# # Write-EnhancedLog -Message "An error occurred in Add-LocalUser: $($_.Exception.Message)" -Level "ERROR"
# # throw
# # }
# # }

# # End {
# # Write-EnhancedLog -Message "Exiting Add-LocalUser function" -Level "Notice"
# # }
# # }







# function Get-GroupMembers {
# param (
# [string]$GroupName = 'Administrators'
# )

# # Retrieve all group members via WMI
# try {
# $group = Get-WmiObject -Class Win32_Group -Filter "Name='$GroupName'"
# $members = $group.GetRelated("Win32_UserAccount")
# if (!$members) {
# Write-EnhancedLog -Message "No members found in group $GroupName." -Level "WARNING"
# }
# return $members
# }
# catch {
# Write-EnhancedLog -Message "Error retrieving members from group $GroupName $_" -Level "ERROR"
# return $null
# }
# }

# function Resolve-SID {
# param (
# [string]$AccountName
# )

# try {
# $account = Get-WmiObject -Class Win32_UserAccount -Filter "Name='$AccountName'"
# if ($account) {
# $sid = New-Object System.Security.Principal.SecurityIdentifier($account.SID)
# return $sid
# } else {
# Write-EnhancedLog -Message "Unable to resolve SID for $AccountName." -Level "WARNING"
# return $null
# }
# }
# catch {
# Write-EnhancedLog -Message "Error resolving SID for $AccountName $_" -Level "ERROR"
# return $null
# }
# }





# # # Main Logic
# # Write-EnhancedLog -Message "Starting group member verification..." -Level "NOTICE"

# # # Verify current group members
# # Verify-GroupMembers -GroupName 'Administrators'

# # # Ensure TempUser is in the Administrators group
# # Add-UserToGroup -UserName 'TempUser' -GroupName 'Administrators'

# # Write-EnhancedLog -Message "Group member verification completed." -Level "NOTICE"








#EndRegion '.\Public\Add-LocalUser-Archive.ps1' 635
#Region '.\Public\Add-Result.ps1' -1

# Function to add results to the context
function Add-Result {
    param (
        [Parameter(Mandatory = $true)]
        $Context,
        [Parameter(Mandatory = $true)]
        $Item,
        [Parameter(Mandatory = $true)]
        [string] $DeviceId,
        [Parameter(Mandatory = $true)]
        [string] $DeviceState,
        [Parameter(Mandatory = $true)]
        [bool] $HasPremiumLicense,
        [Parameter(Mandatory = $false)]
        [string]$OSVersion
    )

    try {
        $deviceName = $Item.DeviceDetail.DisplayName
        if ([string]::IsNullOrWhiteSpace($deviceName)) {
            $deviceName = "BYOD"
        }

        # Determine the compliance status
        $complianceStatus = if ($Item.DeviceDetail.IsCompliant) { "Compliant" } else { "Non-Compliant" }

        # Determine the user license
        $userLicense = if ($HasPremiumLicense) { "Microsoft 365 Business Premium" } else { "Other" }

        # Create a new Result object
        $splatNewResult = @{
            DeviceName             = $deviceName
            UserName               = $Item.UserDisplayName
            DeviceEntraID          = $DeviceId
            UserEntraID            = $Item.UserId
            DeviceOS               = $Item.DeviceDetail.OperatingSystem
            OSVersion              = $osVersion
            DeviceComplianceStatus = $complianceStatus
            DeviceStateInIntune    = $DeviceState
            TrustType              = $Item.DeviceDetail.TrustType
            UserLicense            = $userLicense

        }
        
        $result = New-Result @splatNewResult
        
        # Add the result to the context
        $Context.Results.Add($result)

        Write-EnhancedLog -Message "Successfully added result for device: $deviceName for user: $($Item.UserDisplayName)" -Level "INFO"
    }
    catch {
        Handle-Error -ErrorRecord $_
        Write-EnhancedLog -Message "Failed to add result for device: $($Item.DeviceDetail.DisplayName)" -Level "ERROR"
    }
}

# # Example of how to use the functions
# $context = New-ProcessingContext
# $deviceDetail = New-DeviceDetail -DeviceId "device1" -DisplayName "Device One" -OperatingSystem "Windows 10" -IsCompliant $true -TrustType "AzureAD"
# $item = New-SignInLog -UserDisplayName "John Doe" -UserId "user1" -DeviceDetail $deviceDetail
# Add-Result -Context $context -Item $item -DeviceId "device1" -DeviceState "Active" -HasPremiumLicense $true
#EndRegion '.\Public\Add-Result.ps1' 63
#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\Add-UserToGroup-Archive.ps1' -1




# function Add-UserToGroup {
# param (
# [string]$UserName = 'TempUser',
# [string]$GroupName = 'Administrators'
# )

# $group = [ADSI]"WinNT://./$GroupName,group"
# $user = [ADSI]"WinNT://./$UserName,user"

# # Check if user exists
# try {
# if (-not $user.PSBase.Name) {
# Write-EnhancedLog -Message "User $UserName does not exist. Cannot add to group." -Level "ERROR"
# return
# }
# }
# catch {
# Write-EnhancedLog -Message "User $UserName does not exist. Cannot add to group." -Level "ERROR"
# return
# }

# try {
# if (-not $group.PSBase.Invoke("IsMember", $user.PSBase.Path)) {
# Write-EnhancedLog -Message "Adding $UserName to $GroupName group..." -Level "INFO"
# $group.PSBase.Invoke("Add", $user.PSBase.Path)
# Write-EnhancedLog -Message "$UserName has been successfully added to $GroupName group." -Level "INFO"
# } else {
# Write-EnhancedLog -Message "$UserName is already a member of the $GroupName group." -Level "INFO"
# }
# }
# catch {
# Write-EnhancedLog -Message "Error adding $UserName to $GroupName group: $_" -Level "ERROR"
# }
# }

# # # Main Logic
# # Write-EnhancedLog -Message "Starting group member verification..." -Level "NOTICE"

# # # Verify current group members
# # Verify-GroupMembers -GroupName 'Administrators'

# # # Ensure TempUser is in the Administrators group
# # Add-UserToGroup -UserName 'TempUser' -GroupName 'Administrators'

# # Write-EnhancedLog -Message "Group member verification completed." -Level "NOTICE"
#EndRegion '.\Public\Add-UserToGroup-Archive.ps1' 49
#Region '.\Public\Analyze-CopyOperationStatus.ps1' -1

function Analyze-CopyOperationStatus {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the log folder path.")]
        [string]$LogFolder,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the status file name.")]
        [string]$StatusFileName,

        [Parameter(Mandatory = $true)]
        [int]$MaxRetries = 5,

        [Parameter(Mandatory = $true)]
        [int]$RetryInterval = 10
    )

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

    Process {
        try {
            # # Step 1: Remove existing status files
            # Remove-ExistingStatusFiles -LogFolder $LogFolder -StatusFileName $StatusFileName

            # Step 2: Find the new status file
            $statusFile = Find-NewStatusFile -LogFolder $LogFolder -StatusFileName $StatusFileName -MaxRetries $MaxRetries -RetryInterval $RetryInterval

            # Step 3: Analyze the status file
            Write-EnhancedLog -Message "Reading status file: $($statusFile.FullName)" -Level "INFO"
            $statusData = Get-Content -Path $statusFile.FullName | ConvertFrom-Json

            # Analyze the status of each operation
            Write-EnhancedLog -Message "Analyzing copy operation status from the JSON data" -Level "INFO"
            foreach ($entry in $statusData) {
                $sourcePath = $entry.SourcePath
                $backupFolderName = $entry.BackupFolderName
                $backupStatus = $entry.BackupStatus
                $timestamp = $entry.Timestamp

                if ($backupStatus -eq "Success") {
                    Write-EnhancedLog -Message "Copy operation succeeded: Source: $sourcePath, Backup Folder: $backupFolderName, Timestamp: $timestamp" -Level "INFO"
                }
                elseif ($backupStatus -eq "Failed") {
                    Write-EnhancedLog -Message "Copy operation failed: Source: $sourcePath, Backup Folder: $backupFolderName, Timestamp: $timestamp" -Level "ERROR"
                    if ($entry.VerificationResults) {
                        foreach ($result in $entry.VerificationResults) {
                            Write-EnhancedLog -Message "Discrepancy: Status: $($result.Status), Source Path: $($result.SourcePath), Expected/Actual Path: $($result.ExpectedPath -or $result.ActualPath)" -Level "WARNING"
                        }
                    }
                }
                else {
                    Write-EnhancedLog -Message "Unknown copy operation status for Source: $sourcePath, Backup Folder: $backupFolderName, Timestamp: $timestamp" -Level "WARNING"
                }
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Analyze-CopyOperationStatus function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Analyze-CopyOperationStatus function" -Level "Notice"
    }
}
#EndRegion '.\Public\Analyze-CopyOperationStatus.ps1' 69
#Region '.\Public\Analyze-OneDriveSyncUtilStatus.ps1' -1

function Analyze-OneDriveSyncUtilStatus {
    <#
    .SYNOPSIS
    Analyzes the OneDrive sync status from a JSON file.
 
    .DESCRIPTION
    The Analyze-OneDriveSyncUtilStatus function removes existing status files, finds the new status file, reads it, and categorizes the sync status as healthy, in progress, or failed based on predefined conditions.
 
    .PARAMETER LogFolder
    The path to the folder where the log files are stored. If running as SYSTEM, this will be ignored, and the function will analyze logs across all user profiles.
 
    .PARAMETER StatusFileName
    The name of the JSON file containing the OneDrive sync status.
 
    .PARAMETER MaxRetries
    The maximum number of retries to find the new status file.
 
    .PARAMETER RetryInterval
    The interval in seconds between retries.
 
    .EXAMPLE
    $result = Analyze-OneDriveSyncUtilStatus -LogFolder "C:\Users\YourUserProfile\Logs" -StatusFileName "ODSyncUtilStatus.json" -MaxRetries 5 -RetryInterval 10
    if ($result.Status -eq "Healthy") {
        # Do something if healthy
    }
    # Analyzes the OneDrive sync status from the specified JSON file and returns an object.
    #>


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

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

        [Parameter(Mandatory = $true)]
        [int]$MaxRetries = 5,

        [Parameter(Mandatory = $true)]
        [int]$RetryInterval = 10
    )

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

    Process {
        try {
            # # Step 1: Remove existing status files
            # Remove-ExistingStatusFiles -LogFolder $LogFolder -StatusFileName $StatusFileName

            # Step 2: Find the new status file
            $statusFile = Find-NewStatusFile -LogFolder $LogFolder -StatusFileName $StatusFileName -MaxRetries $MaxRetries -RetryInterval $RetryInterval

            # Step 3: Analyze the new status file
            Write-EnhancedLog -Message "Reading status file: $($statusFile.FullName)" -Level "INFO"
            $Status = Get-Content -Path $statusFile.FullName | ConvertFrom-Json

            # Define the status categories
            Write-EnhancedLog -Message "Defining status categories for analysis" -Level "INFO"
            $Success = @("Synced", "UpToDate", "Up To Date")
            $InProgress = @("Syncing", "SharedSync", "Shared Sync")
            $Failed = @("Error", "ReadOnly", "Read Only", "OnDemandOrUnknown", "On Demand or Unknown", "Paused")

            # Analyze the status and return an object
            Write-EnhancedLog -Message "Analyzing status from the JSON data" -Level "INFO"
            $StatusString = $Status.CurrentStateString
            $UserName = $Status.UserName
            $result = [PSCustomObject]@{
                UserName = $UserName
                Status   = $null
                Message  = $null
            }

            if ($StatusString -in $Success) {
                $result.Status = "Healthy"
                $result.Message = "OneDrive sync status is healthy"
                Write-EnhancedLog -Message "$($result.Message): User: $UserName, Status: $StatusString" -Level "INFO"
            }
            elseif ($StatusString -in $InProgress) {
                $result.Status = "InProgress"
                $result.Message = "OneDrive sync status is currently syncing"
                Write-EnhancedLog -Message "$($result.Message): User: $UserName, Status: $StatusString" -Level "WARNING"
            }
            elseif ($StatusString -in $Failed) {
                $result.Status = "Failed"
                $result.Message = "OneDrive sync status is in a known error state"
                Write-EnhancedLog -Message "$($result.Message): User: $UserName, Status: $StatusString" -Level "ERROR"
            }
            else {
                $result.Status = "Unknown"
                $result.Message = "Unable to determine OneDrive Sync Status"
                Write-EnhancedLog -Message "$($result.Message) for User: $UserName" -Level "WARNING"
            }

            return $result
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Analyze-OneDriveSyncUtilStatus function: $($_.Exception.Message)" -Level "ERROR"
            Write-EnhancedLog -Message "Please check if you are logged in to OneDrive and try again." -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Analyze-OneDriveSyncUtilStatus function" -Level "Notice"
    }
}



# # Step 1: Remove existing status files
# Remove-ExistingStatusFiles -LogFolder "C:\Users\YourUserProfile\Logs" -StatusFileName "ODSyncUtilStatus.json"

# # Step 2: Find the new status file
# $statusFile = Find-NewStatusFile -LogFolder "C:\Users\YourUserProfile\Logs" -StatusFileName "ODSyncUtilStatus.json" -MaxRetries 5 -RetryInterval 10

# # Step 3: Analyze the new status file
# $result = Analyze-OneDriveSyncUtilStatus -StatusFile $statusFile

# # Check the result
# if ($result.Status -eq "Healthy") {
# # Do something if healthy
# }
#EndRegion '.\Public\Analyze-OneDriveSyncUtilStatus.ps1' 128
#Region '.\Public\Apply-RegistrySettings.ps1' -1

function Apply-RegistrySettings {
    <#
    .SYNOPSIS
    Applies multiple registry settings and provides a summary of success, failures, and warnings.
 
    .DESCRIPTION
    This function applies registry settings from a provided hash table and logs the results, including a final summary of success, skipped, and failed settings.
 
    .PARAMETER RegistrySettings
    A hash table containing the registry settings to apply. The hash table keys should be registry paths, and the values should be another hash table with registry names, types, and data.
 
    .EXAMPLE
    Apply-RegistrySettings -RegistrySettings $RegistrySettings
    #>


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

    Begin {
        # Initialize counters and summary table
        $infoCount = 0
        $warningCount = 0
        $errorCount = 0
        $summaryTable = [System.Collections.Generic.List[PSCustomObject]]::new()

        Write-EnhancedLog -Message "Applying registry settings" -Level "INFO"
    }

    Process {
        foreach ($regPath in $RegistrySettings.Keys) {
            foreach ($regName in $RegistrySettings[$regPath].Keys) {
                $regSetting = $RegistrySettings[$regPath][$regName]

                $summaryRow = [PSCustomObject]@{
                    RegistryPath  = $regPath
                    RegistryName  = $regName
                    RegistryValue = if ($null -ne $regSetting["Data"]) { $regSetting["Data"] } else { "null" }
                    Status        = ""
                }

                if ($null -ne $regSetting["Data"]) {
                    Write-EnhancedLog -Message "Setting registry value $regName at $regPath" -Level "INFO"
                    $infoCount++

                    $regParams = @{
                        RegKeyPath = $regPath
                        RegValName = $regName
                        RegValType = $regSetting["Type"]
                        RegValData = $regSetting["Data"]
                    }

                    # If the data is an empty string, explicitly set it as such
                    if ($regSetting["Data"] -eq "") {
                        $regParams.RegValData = ""
                    }

                    try {
                        # Call the Set-RegistryValue function and capture the result
                        $setRegistryResult = Set-RegistryValue @regParams

                        # Build decision-making logic based on the result
                        if ($setRegistryResult -eq $true) {
                            Write-EnhancedLog -Message "Successfully set the registry value: $($regParams.RegValName) at $($regParams.RegKeyPath)" -Level "INFO"
                            $summaryRow.Status = "Success"
                        }
                        else {
                            Write-EnhancedLog -Message "Failed to set the registry value: $($regParams.RegValName) at $($regParams.RegKeyPath)" -Level "ERROR"
                            $summaryRow.Status = "Failed"
                            $errorCount++
                        }

                        Write-EnhancedLog -Message "Registry value $regName at $regPath set" -Level "INFO"
                        
                    }
                    catch {
                        Write-EnhancedLog -Message "Error setting registry value $regName at $regPath $($_.Exception.Message)" -Level "ERROR"
                        $errorCount++
                        $summaryRow.Status = "Failed"
                    }
                }
                else {
                    Write-EnhancedLog -Message "Skipping registry value $regName at $regPath due to null data" -Level "WARNING"
                    $warningCount++
                    $summaryRow.Status = "Skipped"
                }

                $summaryTable.Add($summaryRow)
            }
        }
    }

    End {
        # Final Summary Report
        Write-EnhancedLog -Message "----------------------------------------" -Level "INFO"
        Write-EnhancedLog -Message "Final Summary Report" -Level "NOTICE"
        Write-EnhancedLog -Message "Total registry settings processed: $($infoCount + $warningCount + $errorCount)" -Level "INFO"
        Write-EnhancedLog -Message "Successfully applied registry settings: $infoCount" -Level "INFO"
        Write-EnhancedLog -Message "Skipped registry settings (due to null data): $warningCount" -Level "WARNING"
        Write-EnhancedLog -Message "Failed registry settings: $errorCount" -Level "ERROR"
        Write-EnhancedLog -Message "----------------------------------------" -Level "INFO"

        # Color-coded summary for the console
        Write-Host "----------------------------------------" -ForegroundColor White
        Write-Host "Final Summary Report" -ForegroundColor Cyan
        Write-Host "Total registry settings processed: $($infoCount + $warningCount + $errorCount)" -ForegroundColor White
        Write-Host "Successfully applied registry settings: $infoCount" -ForegroundColor Green
        Write-Host "Skipped registry settings (due to null data): $warningCount" -ForegroundColor Yellow
        Write-Host "Failed registry settings: $errorCount" -ForegroundColor Red
        Write-Host "----------------------------------------" -ForegroundColor White

        # Display the summary table of registry keys and their final states
        Write-Host "Registry Settings Summary:" -ForegroundColor Cyan
        $summaryTable | Format-Table -AutoSize

        # Optionally log the summary to the enhanced log as well
        foreach ($row in $summaryTable) {
            Write-EnhancedLog -Message "RegistryPath: $($row.RegistryPath), RegistryName: $($row.RegistryName), Value: $($row.RegistryValue), Status: $($row.Status)" -Level "INFO"
        }
    }
}
#EndRegion '.\Public\Apply-RegistrySettings.ps1' 124
#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\Backup-UserFilesToOneDrive.ps1' -1

function Backup-UserFilesToOneDrive {
    <#
    .SYNOPSIS
    Backs up user files to a specified OneDrive folder and logs the results.
 
    .DESCRIPTION
    The Backup-UserFilesToOneDrive function copies files from a specified source directory to a OneDrive backup directory.
    It verifies the operation, logs the results, and saves the status to a JSON file. The function handles errors gracefully and appends the backup status to the JSON file.
 
    .PARAMETER SourcePath
    The path to the directory containing the files to be backed up.
 
    .PARAMETER BackupFolderName
    The name of the folder where the backup will be stored in the OneDrive directory.
 
    .PARAMETER Exclude
    A list of files or directories to exclude from the backup operation.
 
    .PARAMETER RetryCount
    The number of times to retry the backup operation if it fails.
 
    .PARAMETER WaitTime
    The time to wait between retry attempts, in seconds.
 
    .PARAMETER RequiredSpaceGB
    The amount of free space required at the destination in gigabytes.
 
    .PARAMETER OneDriveBackupPath
    The path to the OneDrive directory where the backup will be stored.
 
    .PARAMETER Scriptbasepath
    The base path of the script, used to determine where to store logs.
 
    .PARAMETER ClearPreviousStatus
    If set to $true, removes the existing JSON status file before starting the backup. Defaults to $false.
 
    .EXAMPLE
    Backup-UserFilesToOneDrive -SourcePath "$env:USERPROFILE\AppData\Local\Google\Chrome\User Data\Default" `
                               -BackupFolderName "ChromeBackup" `
                               -OneDriveBackupPath "$env:OneDrive\Backups" `
                               -Scriptbasepath "$PSScriptRoot" `
                               -ClearPreviousStatus $true
 
    This command backs up Chrome bookmarks to the OneDrive backup folder and removes the existing JSON status file before starting.
 
    .EXAMPLE
    Backup-UserFilesToOneDrive -SourcePath "$env:USERPROFILE\AppData\Roaming\Microsoft\Signatures" `
                               -BackupFolderName "OutlookSignatures" `
                               -OneDriveBackupPath "$env:OneDrive\Backups" `
                               -Scriptbasepath "$PSScriptRoot"
 
    This command backs up Outlook signatures to the OneDrive backup folder without clearing the existing JSON status file.
 
    .NOTES
    The function handles verification of the copy operation and appends the results to a JSON log file.
 
    .LINK
    https://docs.microsoft.com/en-us/powershell/scripting
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$SourcePath,
        [Parameter(Mandatory = $true)]
        [string]$BackupFolderName,
        [Parameter(Mandatory = $false)]
        [string[]]$Exclude,
        [Parameter(Mandatory = $false)]
        [int]$RetryCount = 2,
        [Parameter(Mandatory = $false)]
        [int]$WaitTime = 5,
        [Parameter(Mandatory = $false)]
        [int]$RequiredSpaceGB = 10,
        [Parameter(Mandatory = $true)]
        [string]$OneDriveBackupPath,
        [Parameter(Mandatory = $true)]
        [string]$Scriptbasepath
        # [Parameter(Mandatory = $false)]
        # [bool]$ClearPreviousStatus = $true
    )

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

        # Define the log file path
        $logFolder = Join-Path -Path $env:USERPROFILE -ChildPath "logs"
        $statusFile = Join-Path -Path $logFolder -ChildPath "UserFilesBackupStatus.json"


        # Ensure the log directory exists
        if (-not (Test-Path -Path $logFolder)) {
            New-Item -Path $logFolder -ItemType Directory | Out-Null
        }

        # # Clear the existing JSON status file if specified
        # if ($ClearPreviousStatus -and (Test-Path -Path $statusFile)) {
        # Remove-Item -Path $statusFile -Force
        # Write-EnhancedLog -Message "Previous JSON status file removed: $statusFile" -Level "INFO"
        # }

        # Ensure the backup directory exists
        $backupPath = Join-Path -Path $OneDriveBackupPath -ChildPath $BackupFolderName
        if (-not (Test-Path -Path $backupPath)) {
            New-Item -Path $backupPath -ItemType Directory | Out-Null
        }
    }

    Process {
        try {
            # Perform the backup operation
            $CopyFilesWithRobocopyParams = @{
                Source          = $SourcePath
                Destination     = $backupPath
                Exclude         = $Exclude
                RetryCount      = $RetryCount
                WaitTime        = $WaitTime
                RequiredSpaceGB = $RequiredSpaceGB
            }
            
            Copy-FilesWithRobocopy @CopyFilesWithRobocopyParams

            # Verify the copy operation
            $verificationResults = Verify-CopyOperation -SourcePath $SourcePath -DestinationPath $backupPath

            # $DBG

            # Determine backup status based on verification results
            $backupStatus = if ($verificationResults.Count -eq 0) { "Success" } else { "Failed" }

            # Prepare the status entry
            $status = @{
                SourcePath          = $SourcePath
                BackupFolderName    = $BackupFolderName
                BackupPath          = $backupPath
                BackupStatus        = $backupStatus
                VerificationResults = if ($verificationResults.Count -eq 0) { @() } else { $verificationResults }
                Timestamp           = (Get-Date).ToString("o")
            }

            # Load existing JSON file content if it exists, ensuring it's treated as an array
            $existingStatus = @()
            if (Test-Path -Path $statusFile) {
                $existingStatus = Get-Content -Path $statusFile | ConvertFrom-Json
                if ($existingStatus -isnot [System.Collections.ArrayList] -and $existingStatus -is [PSCustomObject]) {
                    $existingStatus = @($existingStatus)
                }
            }

            # Append the new status entry
            $updatedStatus = $existingStatus + $status

            # Save the updated status to the JSON file
            $updatedStatus | ConvertTo-Json -Depth 5 | Out-File -FilePath $statusFile -Force -Encoding utf8

            Write-EnhancedLog -Message "Backup status has been saved to $statusFile" -Level "INFO"
        }
        catch {
            $status = @{
                SourcePath       = $SourcePath
                BackupFolderName = $BackupFolderName
                BackupPath       = $backupPath
                BackupStatus     = "Failed"
                ErrorMessage     = $_.Exception.Message
                Timestamp        = (Get-Date).ToString("o")
            }

            # Load existing JSON file content if it exists, ensuring it's treated as an array
            $existingStatus = @()
            if (Test-Path -Path $statusFile) {
                $existingStatus = Get-Content -Path $statusFile | ConvertFrom-Json
                if ($existingStatus -isnot [System.Collections.ArrayList] -and $existingStatus -is [PSCustomObject]) {
                    $existingStatus = @($existingStatus)
                }
            }

            # Append the new failure entry
            $updatedStatus = $existingStatus + $status

            # Save the updated status to the JSON file
            $updatedStatus | ConvertTo-Json -Depth 5 | Out-File -FilePath $statusFile -Force -Encoding utf8

            Write-EnhancedLog -Message "An error occurred during backup: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Backup-UserFilesToOneDrive function" -Level "Notice"
    }
}
#EndRegion '.\Public\Backup-UserFilesToOneDrive.ps1' 193
#Region '.\Public\Block-UserInput.ps1' -1

function Block-UserInput {
    <#
    .SYNOPSIS
    Blocks or unblocks user input.
 
    .DESCRIPTION
    The Block-UserInput function blocks or unblocks user input using the user32.dll library. This can be useful during critical operations to prevent user interference.
 
    .PARAMETER Block
    A boolean value indicating whether to block (true) or unblock (false) user input.
 
    .EXAMPLE
    $params = @{
        Block = $true
    }
    Block-UserInput @params
    Blocks user input.
 
    .EXAMPLE
    $params = @{
        Block = $false
    }
    Block-UserInput @params
    Unblocks user input.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [bool]$Block
    )

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

    Process {
        try {
            $code = @"
    [DllImport("user32.dll")]
    public static extern bool BlockInput(bool fBlockIt);
"@

            $userInput = Add-Type -MemberDefinition $code -Name Blocker -Namespace UserInput -PassThru

            Write-EnhancedLog -Message "Blocking user input: $Block" -Level "INFO"
            $null = $userInput::BlockInput($Block)
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Block-UserInput function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Block-UserInput function" -Level "Notice"
    }
}

# # Example usage
# $params = @{
# Block = $true
# }
# Block-UserInput @params
#EndRegion '.\Public\Block-UserInput.ps1' 66
#Region '.\Public\Check-ApplicationImage.ps1' -1

function Check-ApplicationImage {
    <#
    .SYNOPSIS
    Checks for the presence of any PNG image in the program folder.
 
    .DESCRIPTION
    This function looks for any PNG image in the folder where the program resides. If found, it assigns the image path to the variable `$Prg_img`. If no image is found, a default template image is assigned. The function returns an object containing the image path.
 
    .PARAMETER Prg
    The program object containing metadata like the program ID.
 
    .PARAMETER Prg_Path
    The path to the program folder where the PNG image is searched.
 
    .EXAMPLE
    $imageDetails = Check-ApplicationImage -Prg $Prg -Prg_Path "C:\Repo\MyApp"
    Checks for any PNG image in the folder and returns the image details.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the program object.")]
        [ValidateNotNullOrEmpty()]
        [PSCustomObject]$Prg,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the path to the program folder.")]
        [ValidateNotNullOrEmpty()]
        [string]$Prg_Path
    )

    Begin {
        Write-EnhancedLog -Message "Starting Check-ApplicationImage function for program: $($Prg.id)" -Level "INFO"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Search for any .png file in the program folder
            $imageFiles = Get-ChildItem -Path $Prg_Path -Filter "*.png" -ErrorAction Stop

            if ($imageFiles.Count -gt 0) {
                # Use the first PNG found in the folder
                $Prg_img = $imageFiles[0].FullName
                Write-EnhancedLog -Message "Application image found: $Prg_img" -Level "INFO"
            }
            else {
                # Assign default image if no PNG found
                $Prg_img = Join-Path -Path $Repo_Path -ChildPath "resources\template\winget\winget-managed.png"
                Write-EnhancedLog -Message "No PNG found. Using default image: $Prg_img" -Level "INFO"
            }

            # Return the image path details as an object
            $imageDetails = [pscustomobject]@{
                ProgramID  = $Prg.id
                ImagePath  = $Prg_img
                ImageFound = ($imageFiles.Count -gt 0)
            }

            return $imageDetails
        }
        catch {
            Write-EnhancedLog -Message "Error occurred during image check: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
    }

    End {
        Write-EnhancedLog -Message "Completed Check-ApplicationImage function for program: $($Prg.id)" -Level "INFO"
    }
}



# # Run Check-ApplicationImage and store the returned object
# $imageDetails = Check-ApplicationImage -Prg $Prg

# # Access the properties of the returned object
# Write-Host "Program ID: $($imageDetails.ProgramID)"
# Write-Host "Image Path: $($imageDetails.ImagePath)"
# Write-Host "Image Found: $($imageDetails.ImageFound)"

#EndRegion '.\Public\Check-ApplicationImage.ps1' 83
#Region '.\Public\Check-DeviceStateInIntune.ps1' -1

function Initialize-HttpClient {
    param (
        [hashtable]$Headers
    )

    $httpClient = [System.Net.Http.HttpClient]::new()
    $httpClient.DefaultRequestHeaders.Add("Authorization", $Headers["Authorization"])
    return $httpClient
}

function Check-DeviceStateInIntune {
    param (
        [Parameter(Mandatory = $true)]
        [string]$EntraDeviceId,
        [Parameter(Mandatory = $true)]
        [string]$Username,
        [Parameter(Mandatory = $true)]
        [hashtable]$Headers
    )

    if ([string]::IsNullOrWhiteSpace($EntraDeviceId)) {
        return "Absent"
    }

    Write-EnhancedLog -Message "Checking device state in Intune for Entra Device ID: $EntraDeviceId for username: $Username" -ForegroundColor Cyan

    $graphApiUrl = "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?`$filter=azureADDeviceId eq '$EntraDeviceId'"
    Write-EnhancedLog -Message "Constructed Graph API URL: $graphApiUrl"

    $httpClient = Initialize-HttpClient -Headers $Headers

    try {
        $response = $httpClient.GetStringAsync($graphApiUrl).Result

        if (-not [string]::IsNullOrEmpty($response)) {
            $responseJson = [System.Text.Json.JsonDocument]::Parse($response)
            $valueProperty = $responseJson.RootElement.GetProperty("value")

            if ($valueProperty.GetArrayLength() -gt 0) {
                Write-EnhancedLog -Message "Device is present in Intune." -ForegroundColor Green
                return "Present"
            } else {
                Write-EnhancedLog -Message "Device is absent in Intune." -ForegroundColor Yellow
                return "Absent"
            }
        } else {
            Write-EnhancedLog -Message "Received empty response from Intune API." -ForegroundColor Yellow
            return "NoData"
        }
    } catch {
        Handle-Error -ErrorRecord $_
        return "Error"
    } finally {
        if ($null -ne $responseJson) {
            $responseJson.Dispose()
        }
        $httpClient.Dispose()
    }
}

# # Example usage
# $entraDeviceId = "your_device_id"
# $username = "your_username"
# $headers = @{ "Authorization" = "Bearer your_token" }

# $deviceState = Check-DeviceStateInIntune -EntraDeviceId $entraDeviceId -Username $username -Headers $headers
# Write-Output "Device State: $deviceState"
#EndRegion '.\Public\Check-DeviceStateInIntune.ps1' 68
#Region '.\Public\Check-DomainMembership.ps1' -1

function Check-DomainMembership {
    <#
    .SYNOPSIS
    Checks if the computer is part of a domain.
 
    .DESCRIPTION
    This function checks the current domain membership status of the computer by querying the Win32_ComputerSystem class.
 
    .EXAMPLE
    $isDomainJoined = Check-DomainMembership
 
    This will check if the computer is part of a domain and return a Boolean value.
 
    .NOTES
    This function returns a Boolean indicating whether the computer is part of a domain or not.
    #>


    [CmdletBinding()]
    param ()

    Begin {
        Write-EnhancedLog -Message "Starting Check-DomainMembership function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            Write-EnhancedLog -Message "Querying Win32_ComputerSystem to check domain membership status" -Level "INFO"

            # Query the computer system's domain membership status
            $ComputerSystem = Get-WmiObject -Class Win32_ComputerSystem
            $PartOfDomain = $ComputerSystem.PartOfDomain

            if ($PartOfDomain) {
                Write-EnhancedLog -Message "Device is part of a domain." -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "Device is not part of a domain." -Level "WARNING"
            }

            return $PartOfDomain
        }
        catch {
            Write-EnhancedLog -Message "Error occurred while checking domain membership: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
        finally {
            # Log exit and cleanup
            Write-EnhancedLog -Message "Exiting Check-DomainMembership function" -Level "NOTICE"
        }
    }

    End {
        Write-EnhancedLog -Message "Check-DomainMembership function completed" -Level "INFO"
    }
}
#EndRegion '.\Public\Check-DomainMembership.ps1' 58
#Region '.\Public\Check-ExistingTask.ps1' -1

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

    Begin {
        Write-EnhancedLog -Message "Starting Check-ExistingTask function" -Level "Notice"
        Log-Params -Params @{ taskName = $taskName }
    }

    Process {
        try {
            Write-EnhancedLog -Message "Checking for existing scheduled task: $taskName" -Level "INFO" -ForegroundColor Magenta
            $tasks = Get-ScheduledTask -ErrorAction SilentlyContinue | Where-Object { $_.TaskName -eq $taskName }

            if ($tasks.Count -eq 0) {
                Write-EnhancedLog -Message "No existing task named $taskName found." -Level "INFO" -ForegroundColor Yellow
                return $false
            }

            Write-EnhancedLog -Message "Task named $taskName found." -Level "INFO" -ForegroundColor Green
            return $true
        } catch {
            Write-EnhancedLog -Message "An error occurred while checking for the scheduled task: $($_.Exception.Message)" -Level "ERROR" -ForegroundColor Red
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Check-ExistingTask function" -Level "Notice"
    }
}

# # Example usage:
# $taskExists = Check-ExistingTask -taskName "AADM Launch PSADT for Interactive Migration"
# Write-Output "Task exists: $taskExists"
#EndRegion '.\Public\Check-ExistingTask.ps1' 40
#Region '.\Public\Check-IntuneEnrollment.ps1' -1

# Function to check Intune enrollment status
function Check-IntuneEnrollment {
    param (
        [string]$OMADMPath,
        [string]$EnrollmentBasePath,
        [string]$MSDMProviderID
    )

    Write-EnhancedLog -Message "Checking Intune enrollment status from registry path $OMADMPath" -Level "INFO"
    $Account = (Get-ItemProperty -Path $OMADMPath -ErrorAction SilentlyContinue).PSChildName
    if ($null -eq $Account) {
        Write-EnhancedLog -Message "No Intune enrollment account found at path $OMADMPath" -Level "WARNING"
        return $null
    }

    Write-EnhancedLog -Message "Enrollment account detected: $Account" -Level "INFO"
    $EnrollmentPath = "$EnrollmentBasePath\$Account"

    Write-EnhancedLog -Message "Checking UPN and ProviderID at enrollment path: $EnrollmentPath" -Level "INFO"
    $EnrollmentUPN = (Get-ItemProperty -Path $EnrollmentPath -ErrorAction SilentlyContinue).UPN
    $ProviderID = (Get-ItemProperty -Path $EnrollmentPath -ErrorAction SilentlyContinue).ProviderID

    if (-not $EnrollmentUPN) {
        Write-EnhancedLog -Message "Enrollment UPN not found at $EnrollmentPath" -Level "WARNING"
        return $null
    }

    if ($ProviderID -ne $MSDMProviderID) {
        Write-EnhancedLog -Message "ProviderID does not match the expected value at $EnrollmentPath" -Level "WARNING"
        return $null
    }

    return $Account
}
#EndRegion '.\Public\Check-IntuneEnrollment.ps1' 35
#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\Check-ODSyncUtilStatus.ps1' -1

function Check-ODSyncUtilStatus {
    <#
    .SYNOPSIS
    Checks the OneDrive sync status using ODSyncUtil and stores it in a JSON file.
 
    .DESCRIPTION
    The Check-ODSyncUtilStatus function calls the `Get-ODStatus.ps1` script located in the specified path, retrieves the OneDrive sync status, and saves it to a JSON file in the designated log directory.
 
    .PARAMETER ScriptPath
    The path to the directory containing the `Get-ODStatus.ps1` script.
 
    .PARAMETER LogFolderName
    The name of the folder where the log files will be stored.
 
    .PARAMETER StatusFileName
    The name of the file where the OneDrive sync status will be saved.
 
    .EXAMPLE
    $params = @{
        ScriptPath = "C:\code\IntuneDeviceMigration\DeviceMigration\Files\ODSyncUtil"
        LogFolderName = "logs"
        StatusFileName = "ODSyncUtilStatus.json"
    }
    Check-ODSyncUtilStatus @params
    #>


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

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

        [Parameter(Mandatory = $false)]
        [string]$StatusFileName = "ODSyncUtilStatus.json"
    )

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

        # Check if running with admin privileges
        $isAdmin = CheckAndElevate -ElevateIfNotAdmin $false

        if ($isAdmin) {
            Write-EnhancedLog -Message "Script is running with administrative privileges. Exiting because this operation should be run in user context." -Level "ERROR"
            throw "This script must be run in a non-administrative user context."
        }

        $DownloadODSyncUtilParams = @{
            Destination    = (Join-Path -Path $env:USERPROFILE -ChildPath "AADMigration\Files\ODSyncUtil\ODSyncUtil.exe")
            ApiUrl         = "https://api.github.com/repos/rodneyviana/ODSyncUtil/releases/latest"
            ZipFileName    = "ODSyncUtil-64-bit.zip"
            ExecutableName = "ODSyncUtil.exe"
            MaxRetries     = 3
        }
        Download-ODSyncUtil @DownloadODSyncUtilParams
        


        # Define the log file path
        # Get the parent of the parent directory of ScriptPath
        # $parentOfParentPath = (Get-Item -Path $ScriptPath).Parent.Parent.FullName

        # Define the log folder path in the user's profile directory
        $logFolder = Join-Path -Path $env:USERPROFILE -ChildPath $LogFolderName

        # Define the status file path within the log folder
        $statusFile = Join-Path -Path $logFolder -ChildPath $StatusFileName


        # Ensure the log directory exists
        if (-not (Test-Path -Path $logFolder)) {
            Write-EnhancedLog -Message "Creating log folder at $logFolder" -Level "INFO"
            New-Item -Path $logFolder -ItemType Directory | Out-Null
        }
    }

    
    Process {
        try {
            # Define the full path to Get-ODStatus.ps1
            $getODStatusScript = Join-Path -Path $ScriptPath -ChildPath "Get-ODStatus.ps1"
    
            if (-not (Test-Path $getODStatusScript)) {
                $errorMessage = "The specified script path does not contain Get-ODStatus.ps1: $getODStatusScript"
                Write-EnhancedLog -Message $errorMessage -Level "Critical"
                throw $errorMessage
            }
    
            # Temporarily change location to ScriptPath
            Write-EnhancedLog -Message "Changing to script directory: $ScriptPath" -Level "INFO"
            Push-Location -Path $ScriptPath
    
            # Run the Get-ODStatus.ps1 script and capture the output as a PowerShell object
            Write-EnhancedLog -Message "Executing $getODStatusScript with non-elevated privileges" -Level "INFO"
            $status = . $getODStatusScript
    
            # Convert the output directly to JSON and save it
            if ($status) {
                Write-EnhancedLog -Message "Saving OneDrive sync status to $statusFile" -Level "INFO"
                $status | ConvertTo-Json -Depth 3 | Out-File -FilePath $statusFile -Force -Encoding utf8
            }
            else {
                Write-EnhancedLog -Message "Failed to retrieve OneDrive sync status." -Level "ERROR"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Check-ODSyncUtilStatus function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
        finally {
            # Return to the original location
            Pop-Location
            Write-EnhancedLog -Message "Returned to the original directory" -Level "INFO"
        }
    }
    

    End {
        Write-EnhancedLog -Message "Exiting Check-ODSyncUtilStatus function" -Level "Notice"
    }
}
#EndRegion '.\Public\Check-ODSyncUtilStatus.ps1' 126
#Region '.\Public\Check-PackageAccount.ps1' -1

function Check-PackageAccount {
    <#
    .SYNOPSIS
        Prompts the user to check if the PPKG GUID account created by Windows Configuration Designer (WCD) is created in Entra ID and if it's excluded from all Conditional Access policies.
 
    .DESCRIPTION
        This function is designed to support administrators managing Provisioning Packages (PPKGs) created using Windows Configuration Designer (WCD), as detailed in the official Microsoft documentation: https://learn.microsoft.com/en-us/windows/configuration/provisioning-packages/provisioning-create-package.
        The function checks if a package account, generated during PPKG creation with an enrollment token for Entra ID, exists in Entra ID and verifies if it's excluded from all Conditional Access policies (CAPs).
        If the package_GUID account is not excluded from all CAPs, the `install-ppkg.ps1` function will encounter issues during the installation process.
 
    .PARAMETER PackageGuid
        The GUID of the package, used to construct the account email.
 
    .PARAMETER Domain
        The domain associated with the account in Entra ID.
 
    .OUTPUTS
        System.String
        A message indicating whether the account was found and if it's excluded from Conditional Access policies.
 
    .EXAMPLE
        Check-PackageAccount -PackageGuid "75cc34e6-141c-4577-8792-c238a4293408" -Domain "ictc-ctic.ca"
        This will check if the account "package_75cc34e6-141c-4577-8792-c238a4293408@ictc-ctic.ca" exists in Entra ID and is excluded from all Conditional Access policies.
 
    .NOTES
        Version: 1.2
        Author: Abdullah Ollivierre
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "The GUID of the package.")]
        [string]$PackageGuid,

        [Parameter(Mandatory = $true, HelpMessage = "The domain associated with the account in Entra ID.")]
        [string]$Domain
    )

    Begin {
        Write-EnhancedLog -Message "Initializing Check-PackageAccount function." -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Construct the package account email
            $packageAccount = "package_$PackageGuid@$Domain"
            Write-EnhancedLog -Message "Checking account: $packageAccount" -Level "INFO"

            # Prompt the user to proceed with the check
            $userResponse = Read-Host "Would you like to check if the account $packageAccount is created in Entra ID and is excluded from all Conditional Access policies? (Y/N)"
            if ($userResponse -ne 'Y') {
                Write-Host "Operation canceled by the user." -ForegroundColor Yellow
                return
            }


            # Prompt user for next steps
            $scriptOption = Read-Host "Would you like to run your own logic (type '1') or run the web script (type '2')?"
            if ($scriptOption -eq '1') {
                Write-Host "Running your own logic..." -ForegroundColor Cyan
                # User's logic here

                
                # Check if the account exists in Entra ID (simulated with a placeholder command)
                $accountExists = Get-MgUser -UserId $packageAccount -ErrorAction SilentlyContinue
                if ($null -eq $accountExists) {
                    Write-EnhancedLog -Message "Account $packageAccount does not exist in Entra ID." -Level "ERROR"
                    throw "Account not found."
                }

                Write-EnhancedLog -Message "Account $packageAccount exists in Entra ID." -Level "INFO"

                # Check if the account is excluded from all Conditional Access policies
                $excludedPolicies = Get-MgConditionalAccessPolicy | Where-Object {
                    $_.Conditions.Users.Exclude.Contains($packageAccount)
                }

                if ($excludedPolicies.Count -eq 0) {
                    Write-EnhancedLog -Message "Account $packageAccount is NOT excluded from any Conditional Access policies." -Level "WARNING"
                    Write-Host "Warning: The account is not excluded from any Conditional Access policies. The install-ppkg account will encounter issues during installation." -ForegroundColor Red
                }
                else {
                    Write-EnhancedLog -Message "Account $packageAccount is excluded from the following Conditional Access policies: $($excludedPolicies.DisplayName -join ', ')" -Level "INFO"
                    Write-Host "The account is excluded from the following Conditional Access policies: $($excludedPolicies.DisplayName -join ', ')" -ForegroundColor Green
                }

            }
            elseif ($scriptOption -eq '2') {
                Write-Host "Warning: The web script is not tested and relies on external dependencies. Please review the notes in C:\code\IntuneDeviceMigration\DeviceMigration\Scripts\Beta\Confirm-BreakGlassConditionalAccessExclusions.ps1 before proceeding." -ForegroundColor Yellow
                $confirmRun = Read-Host "Do you want to proceed with running the web script? (Y/N)"
                if ($confirmRun -eq 'Y') {
                    Write-Host "Downloading and running the web script..." -ForegroundColor Cyan
                    $scriptUrl = "https://raw.githubusercontent.com/thetolkienblackguy/EntraIdManagement/main/Confirm-BreakGlassConditionalAccessExclusions/Confirm-BreakGlassConditionalAccessExclusions.ps1"
                    $scriptContent = Invoke-WebRequest -Uri $scriptUrl -UseBasicParsing | Select-Object -ExpandProperty Content
                    Invoke-Expression $scriptContent
                }
                else {
                    Write-Host "Operation canceled by the user." -ForegroundColor Yellow
                }
            }
            else {
                Write-Host "Invalid option selected. Operation canceled." -ForegroundColor Yellow
            }

            Write-EnhancedLog -Message "Check-PackageAccount function completed successfully." -Level "NOTICE"
        }
        catch {
            Write-EnhancedLog -Message "Error occurred in Check-PackageAccount function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
        finally {
            Write-EnhancedLog -Message "Exiting Check-PackageAccount function." -Level "NOTICE"
        }
    }

    End {
        Write-EnhancedLog -Message "Check-PackageAccount function has fully completed." -Level "NOTICE"
    }
}

# # Example usage of the Check-PackageAccount function
# try {
# # Define the package GUID and domain
# $packageGuid = "75cc34e6-141c-4577-8792-c238a4293408"
# $domain = "ictc-ctic.ca"

# # Invoke the Check-PackageAccount function
# Check-PackageAccount -PackageGuid $packageGuid -Domain $domain
# }
# catch {
# Write-Host "An error occurred during the account check: $($_.Exception.Message)" -ForegroundColor Red
# }
#EndRegion '.\Public\Check-PackageAccount.ps1' 135
#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\Clear-OneDriveCache.ps1' -1

function Clear-OneDriveCache {
    <#
    .SYNOPSIS
    Clears the OneDrive cache.
   
    .DESCRIPTION
    The Clear-OneDriveCache function clears the OneDrive cache by restarting the OneDrive process.
   
    .EXAMPLE
    Clear-OneDriveCache
    Clears the OneDrive cache by restarting the OneDrive process.
    #>

  
    [CmdletBinding()]
    param ()
  
    Begin {
        Write-EnhancedLog -Message "Starting Clear-OneDriveCache function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }
  
    Process {
        try {
            Write-EnhancedLog -Message "Searching for OneDrive executable path" -Level "INFO"
            $oneDrivePath = Find-OneDrivePath

            if ($oneDrivePath) {
                Write-EnhancedLog -Message "Restarting OneDrive process to clear cache" -Level "INFO"
                Stop-Process -Name "OneDrive" -Force -ErrorAction SilentlyContinue
                Start-Process -FilePath $oneDrivePath -ErrorAction Stop
                Write-EnhancedLog -Message "Successfully restarted OneDrive process" -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "OneDrive executable not found in any known locations" -Level "WARNING"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Clear-OneDriveCache function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }
  
    End {
        Write-EnhancedLog -Message "Exiting Clear-OneDriveCache function" -Level "Notice"
    }
}

# Example usage
# Clear-OneDriveCache
#EndRegion '.\Public\Clear-OneDriveCache.ps1' 51
#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\Compile-Win32_intunewin.ps1' -1

function Compile-Win32_intunewin {
    <#
    .SYNOPSIS
    Compiles a Win32 app into an Intune Win format for deployment.
 
    .DESCRIPTION
    This function compiles a Win32 application into an Intune Win32 format. It checks for an existing application image, downloads the latest IntuneWinAppUtil if necessary, and uploads the compiled application.
 
    .PARAMETER Prg
    The application object with details needed for compilation.
 
    .PARAMETER Repo_winget
    The repository path for winget.
 
    .PARAMETER Repo_Path
    The repository path for storing resources.
 
    .PARAMETER Prg_Path
    The path to the program being compiled.
 
    .EXAMPLE
    $params = @{
        Prg = [pscustomobject]@{ id = 'exampleApp'; name = 'Example' }
        Repo_winget = "https://example.com/winget"
        Repo_Path = "C:\Repos"
        Prg_Path = "C:\Programs\ExampleApp"
    }
    Compile-Win32_intunewin @params
    Compiles the Win32 app and uploads it to Intune.
    #>


    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param(
        [Parameter(Mandatory = $true, HelpMessage = "Provide the application object containing necessary details.")]
        [ValidateNotNullOrEmpty()]
        [pscustomobject]$Prg,
    
        [Parameter(Mandatory = $true, HelpMessage = "Specify the Winget repository URL.")]
        [ValidateNotNullOrEmpty()]
        [string]$Repo_winget,
    
        [Parameter(Mandatory = $true, HelpMessage = "Specify the path to the repository for storing resources.")]
        [ValidateNotNullOrEmpty()]
        [string]$Repo_Path,
    
        [Parameter(Mandatory = $true, HelpMessage = "Provide the path to the program.")]
        [ValidateNotNullOrEmpty()]
        [string]$Prg_Path
    )
    
    

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

        # Check for the program image
        Write-EnhancedLog -Message "Checking for program image for $($Prg.id)" -Level "INFO"
        $Prg_img = if (Test-Path -Path (Join-Path -Path $Prg_Path -ChildPath "$($Prg.id).png")) {
            Join-Path -Path $Prg_Path -ChildPath "$($Prg.id).png"
        }
        else {
            "$Repo_Path\resources\template\winget\winget-managed.png"
        }
    }

    Process {
        if ($PSCmdlet.ShouldProcess("Win32 App Compilation for $($Prg.id)", "Uploading to Intune")) {
            try {
                Write-EnhancedLog -Message "Processing Win32 app: $($Prg.id)" -Level "INFO"
                # Log the program path and image path
                Write-EnhancedLog -Message "Program path: $Prg_Path" -Level "INFO"
                Write-EnhancedLog -Message "Program image: $Prg_img" -Level "INFO"
    
                # Upload the Win32 app
                Write-EnhancedLog -Message "Uploading Win32 app to Intune" -Level "INFO"
                # Upload-Win32App -Prg $Prg -Prg_Path $Prg_Path -Prg_img $Prg_img

                # Calling Upload-Win32App inside PowerShell 5
                Invoke-CommandInPS5 -Command "Upload-Win32App -Prg $Prg -Prg_Path $Prg_Path -Prg_img $Prg_img"
            }
            catch {
                Write-EnhancedLog -Message "Error during Win32 app processing: $($_.Exception.Message)" -Level "ERROR"
                Handle-Error -ErrorRecord $_
                throw
            }
        }
        else {
            Write-EnhancedLog -Message "Operation skipped due to WhatIf or confirmation." -Level "INFO"
        }
    }
    

    End {
        Write-EnhancedLog -Message "Exiting Compile-Win32_intunewin function" -Level "Notice"
    }
}
#EndRegion '.\Public\Compile-Win32_intunewin.ps1' 98
#Region '.\Public\ConfigureVM.ps1' -1

function ConfigureVM {
    <#
    .SYNOPSIS
    Configures the specified VM with the given processor count and memory settings.
    #>

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

        [Parameter(Mandatory = $true)]
        [int]$ProcessorCount
    )

    Begin {
        Write-EnhancedLog -Message "Starting Configure-VM function" -Level "INFO"
        Log-Params -Params @{ VMName = $VMName; ProcessorCount = $ProcessorCount }
    }

    Process {
        try {
            Write-EnhancedLog -Message "Configuring VM processor for VM: $VMName with $ProcessorCount processors" -Level "INFO"
            Set-VMProcessor -VMName $VMName -ExposeVirtualizationExtensions $true -Count $ProcessorCount

            Write-EnhancedLog -Message "Configuring memory for VM: $VMName" -Level "INFO"
            Set-VMMemory -VMName $VMName

            Write-EnhancedLog -Message "VM $VMName configured" -Level "INFO"
        } catch {
            Write-EnhancedLog -Message "An error occurred while configuring VM $VMName $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Configure-VM function" -Level "INFO"
    }
}
#EndRegion '.\Public\ConfigureVM.ps1' 39
#Region '.\Public\ConfigureVMBoot.ps1' -1

function ConfigureVMBoot {
    <#
    .SYNOPSIS
    Configures the boot order of the specified VM to boot from the specified differencing disk.
    #>

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

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

    Begin {
        Write-EnhancedLog -Message "Starting Configure-VMBoot function" -Level "INFO"
        Log-Params -Params @{ VMName = $VMName; DifferencingDiskPath = $DifferencingDiskPath }
    }

    Process {
        try {
            Write-EnhancedLog -Message "Retrieving hard disk drive for VM: $VMName with path: $DifferencingDiskPath" -Level "INFO"
            $VHD = Get-VMHardDiskDrive -VMName $VMName | Where-Object { $_.Path -eq $DifferencingDiskPath }

            if ($null -eq $VHD) {
                Write-EnhancedLog -Message "No hard disk drive found for VM: $VMName with the specified path: $DifferencingDiskPath" -Level "ERROR"
                throw "Hard disk drive not found."
            }

            Write-EnhancedLog -Message "Setting VM firmware for VM: $VMName to boot from the specified disk" -Level "INFO"
            Set-VMFirmware -VMName $VMName -FirstBootDevice $VHD

            Write-EnhancedLog -Message "VM boot configured for $VMName" -Level "INFO"
        } catch {
            Write-EnhancedLog -Message "An error occurred while configuring VM boot for $VMName $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Configure-VMBoot function" -Level "INFO"
    }
}
#EndRegion '.\Public\ConfigureVMBoot.ps1' 44
#Region '.\Public\Connect-GraphWithCert.ps1' -1

function Connect-GraphWithCert {
    param (
        [Parameter(Mandatory = $true)]
        [string]$tenantId,
        [Parameter(Mandatory = $true)]
        [string]$clientId,
        [Parameter(Mandatory = $true)]
        [string]$certPath,
        [Parameter(Mandatory = $true)]
        [string]$certPassword,
        [switch]$ConnectToIntune,
        [switch]$ConnectToTeams
    )

    try {
        # Log the certificate path
        Log-Params -Params @{certPath = $certPath}

        # Load the certificate from the PFX file
        $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($certPath, $certPassword)

        # Define the splat for Connect-MgGraph
        $GraphParams = @{
            ClientId    = $clientId
            TenantId    = $tenantId
            Certificate = $cert
        }

        # Log the parameters
        Log-Params -Params $GraphParams

        # Obtain access token (if needed separately)
        $accessToken = Get-MsGraphAccessTokenCert -tenantId $tenantId -clientId $clientId -certPath $certPath -certPassword $certPassword
        Log-Params -Params @{accessToken = $accessToken}

        # Connect to Microsoft Graph
        Write-EnhancedLog -message 'Calling Connect-MgGraph with client certificate file path and password' -Level 'INFO'
        Connect-MgGraph @GraphParams -NoWelcome

        # Conditional check for Intune connection
        if ($ConnectToIntune) {
            try {
                # Define the parameters for non-interactive connection to Intune
                $IntuneGraphconnectionParams = @{
                    ClientId    = $clientId
                    TenantId    = $tenantId
                    ClientCert  = $cert
                }

                # Log the connection attempt
                Write-EnhancedLog -Message "Calling Connect-MSIntuneGraph with connectionParams" -Level "WARNING"

                # Call the Connect-MSIntuneGraph function with splatted parameters
                $Session = Connect-MSIntuneGraph @IntuneGraphconnectionParams

                # Log the successful connection
                Write-EnhancedLog -Message "Connecting to Graph using Connect-MSIntuneGraph - done" -Level "INFO"
            } catch {
                Handle-Error -ErrorRecord $_
            }
        }

        # Conditional check for Teams connection
        if ($ConnectToTeams) {
            try {
                Write-EnhancedLog -Message "Connecting to Microsoft Teams" -Level "INFO"

                # Connect to Microsoft Teams using the certificate
                Connect-MicrosoftTeams -TenantId $tenantId -Certificate $cert -ApplicationId $clientId

                Write-EnhancedLog -Message "Connected to Microsoft Teams" -Level "INFO"
            } catch {
                Handle-Error -ErrorRecord $_
            }
        }

        return $accessToken
    } catch {
        Handle-Error -ErrorRecord $_
    }
}

#Note for Teams Connection you must add RBAC role like Teams Admin to the app as well in additon to the API permissions as mentioned below

# https://learn.microsoft.com/en-us/MicrosoftTeams/teams-powershell-application-authentication

# Setup Application-based authentication
# An initial onboarding is required for authentication using application objects. Application and service principal are used interchangeably, but an application is like a class object while a service principal is like an instance of the class. You can learn more about these objects at Application and service principal objects in Microsoft Entra ID.

# Sample steps for creating applications in Microsoft Entra ID are mentioned below. For detailed steps, refer to this article.

# 1- Register the application in Microsoft Entra ID.
# 2- Assign API permissions to the application.
# 2.1 For *-Cs cmdlets - the Microsoft Graph API permission needed is Organization.Read.All.
# 2.2 For Non *-Cs cmdlets - the Microsoft Graph API permissions needed are Organization.Read.All, User.Read.All, Group.ReadWrite.All, AppCatalog.ReadWrite.All, TeamSettings.ReadWrite.All, Channel.Delete.All, ChannelSettings.ReadWrite.All, ChannelMember.ReadWrite.All.
# 3. Generate a self-signed certificate.
# 4. Attach the certificate to the Microsoft Entra application.
# 5. Assign Microsoft Entra roles to the application. Refer to this Assign a role procedure, but search for the application instead of a user.
# The application needs to have the appropriate RBAC roles assigned. Because the apps are provisioned in Microsoft Entra ID, you can use any of the supported built-in roles.
#EndRegion '.\Public\Connect-GraphWithCert.ps1' 100
#Region '.\Public\Connect-ToMicrosoftGraphIfServerCore.ps1' -1

function Connect-ToMicrosoftGraphIfServerCore {
    param (
        [string[]]$Scopes
    )

    if (Is-ServerCore) {
        Write-Output "Running on Windows Server Core. Using device authentication for Microsoft Graph."
        Connect-MgGraph -Scopes $Scopes -Verbose -UseDeviceAuthentication
    } else {
        Write-Output "Not running on Windows Server Core. Using default authentication for Microsoft Graph."
        Connect-MgGraph -Scopes $Scopes -Verbose
    }
}
#EndRegion '.\Public\Connect-ToMicrosoftGraphIfServerCore.ps1' 14
#Region '.\Public\Connect-VMConsole.ps1' -1

function Connect-VMConsole {
    <#
    .SYNOPSIS
    Connects to the console of the specified VM.
    #>

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

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

        [Parameter(Mandatory = $false)]
        [int]$Count = 1
    )

    Begin {
        Write-EnhancedLog -Message "Starting Connect-VMConsole function" -Level "INFO"
        Log-Params -Params @{ VMName = $VMName; ServerName = $ServerName; Count = $Count }
    }

    Process {
        try {
            Write-EnhancedLog -Message "Validating if VM $VMName exists" -Level "INFO"
            if (-not (Validate-VMExists -VMName $VMName)) {
                Write-EnhancedLog -Message "VM $VMName does not exist. Exiting function." -Level "ERROR"
                return
            }

            Write-EnhancedLog -Message "Checking if VM $VMName is running" -Level "INFO"
            if (-not (Validate-VMStarted -VMName $VMName)) {
                Write-EnhancedLog -Message "VM $VMName is not running. Cannot connect to console." -Level "ERROR"
                return
            }

            $vmConnectArgs = "$ServerName `"$VMName`""
            if ($Count -gt 1) {
                $vmConnectArgs += " -C $Count"
            }

            Write-EnhancedLog -Message "VMConnect arguments: $vmConnectArgs" -Level "DEBUG"
            Start-Process -FilePath "vmconnect.exe" -ArgumentList $vmConnectArgs -ErrorAction Stop
            Write-EnhancedLog -Message "VMConnect launched for VM $VMName on $ServerName with count $Count." -Level "INFO"
        } catch {
            Write-EnhancedLog -Message "An error occurred while launching VMConnect for VM $VMName. $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Connect-VMConsole function" -Level "INFO"
    }
}
#EndRegion '.\Public\Connect-VMConsole.ps1' 55
#Region '.\Public\Convert-EntraDeviceIdToIntuneDeviceId.ps1' -1

function Convert-EntraDeviceIdToIntuneDeviceId {
    param (
        [Parameter(Mandatory = $true)]
        [string]$entraDeviceId,
        [hashtable]$headers
    )

    Write-EnhancedLog -Message "Converting Entra Device ID: $entraDeviceId to Intune Device ID" -Level "INFO" -ForegroundColor ([ConsoleColor]::Cyan)

    try {
        # Construct the Graph API URL to retrieve device details
        $graphApiUrl = "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?`$filter=azureADDeviceId eq '$entraDeviceId'"
        Write-Output "Constructed Graph API URL: $graphApiUrl"

        # Send the request
        $response = Invoke-WebRequest -Uri $graphApiUrl -Headers $headers -Method Get
        $data = ($response.Content | ConvertFrom-Json).value

        if ($data -and $data.Count -gt 0) {
            $intuneDeviceId = $data[0].id
            Write-EnhancedLog -Message "Converted Entra Device ID: $entraDeviceId to Intune Device ID: $intuneDeviceId" -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
            return $intuneDeviceId
        } else {
            Write-EnhancedLog -Message "No Intune Device found for Entra Device ID: $entraDeviceId" -Level "WARN" -ForegroundColor ([ConsoleColor]::Yellow)
            return $null
        }
    } catch {
        Write-EnhancedLog -Message "Error converting Entra Device ID to Intune Device ID: $_" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
        return $null
    }
}

# # Example usage
# $headers = @{ Authorization = "Bearer your-access-token" }
# $entraDeviceId = "73e94a92-fc5a-45b6-bf6c-90ce8a353c44"

# $intuneDeviceId = Convert-EntraDeviceIdToIntuneDeviceId -entraDeviceId $entraDeviceId -Headers $headers
# Write-Output "Intune Device ID: $intuneDeviceId"
#EndRegion '.\Public\Convert-EntraDeviceIdToIntuneDeviceId.ps1' 39
#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\Copy-FilesToPath.ps1' -1

function Copy-FilesToPath {
    <#
.SYNOPSIS
Copies all files and folders in the specified source directory to the specified destination path.
 
.DESCRIPTION
This function copies all files and folders located in the specified source directory to the specified destination path. It can be used to bundle necessary files and folders with the script for distribution or deployment.
 
.PARAMETER SourcePath
The source path from where the files and folders will be copied.
 
.PARAMETER DestinationPath
The destination path where the files and folders will be copied.
 
.EXAMPLE
Copy-FilesToPath -SourcePath "C:\Source" -DestinationPath "C:\Temp"
 
This example copies all files and folders in the "C:\Source" directory to the "C:\Temp" directory.
#>

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

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

    Begin {
        Write-EnhancedLog -Message "Starting the copy process from the Source Path $SourcePath to $DestinationPath" -Level "INFO"
        Log-Params -Params @{
            SourcePath = $SourcePath
            DestinationPath = $DestinationPath
        }

        # Ensure the destination directory exists
        if (-not (Test-Path -Path $DestinationPath)) {
            New-Item -Path $DestinationPath -ItemType Directory | Out-Null
        }
    }

    Process {
        try {
            # Copy all items from the source directory to the destination, including subdirectories
            $copyParams = @{
                Path        = "$SourcePath\*"
                Destination = $DestinationPath
                Recurse     = $true
                Force       = $true
                ErrorAction = "Stop"
            }
            Copy-Item @copyParams

            Write-EnhancedLog -Message "All items copied successfully from the Source Path $SourcePath to $DestinationPath." -Level "INFO"
        } catch {
            Write-EnhancedLog -Message "Error occurred during the copy process: $_" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Copy process completed." -Level "INFO"
    }
}



# # Define parameters for the function
# $sourcePath = "C:\SourceDirectory"
# $destinationPath = "C:\DestinationDirectory"

# # Call the function with the defined parameters
# Copy-FilesToPath -SourcePath $sourcePath -DestinationPath $destinationPath
#EndRegion '.\Public\Copy-FilesToPath.ps1' 74
#Region '.\Public\Copy-FilesWithRobocopy.ps1' -1

function Copy-FilesWithRobocopy {
    <#
    .SYNOPSIS
    Copies files from a source directory to a destination directory using Robocopy and verifies the operation.
 
    .DESCRIPTION
    The Copy-FilesWithRobocopy function copies files from a source directory to a destination directory based on a specified file pattern using Robocopy. It validates the source and destination directories, checks disk space, logs the Robocopy process, and verifies the copy operation. It also handles locked files by finding and killing the locking processes.
 
    .PARAMETER Source
    The source directory from which files will be copied.
 
    .PARAMETER Destination
    The destination directory to which files will be copied.
 
    .PARAMETER FilePattern
    The file pattern to match files that should be copied. If not provided, defaults to '*'.
 
    .PARAMETER RetryCount
    The number of retries if a copy fails. Default is 2.
 
    .PARAMETER WaitTime
    The wait time between retries in seconds. Default is 5.
 
    .PARAMETER RequiredSpaceGB
    The required free space in gigabytes at the destination. Default is 10 GB.
 
    .PARAMETER Exclude
    The directories or files to exclude from the copy operation.
 
    .EXAMPLE
    Copy-FilesWithRobocopy -Source "C:\Source" -Destination "C:\Destination" -FilePattern "*.txt"
    Copies all .txt files from C:\Source to C:\Destination.
 
    .EXAMPLE
    "*.txt", "*.log" | Copy-FilesWithRobocopy -Source "C:\Source" -Destination "C:\Destination"
    Copies all .txt and .log files from C:\Source to C:\Destination using pipeline input for the file patterns.
 
    .EXAMPLE
    Copy-FilesWithRobocopy -Source "C:\Source" -Destination "C:\Destination" -Exclude ".git"
    Copies files from C:\Source to C:\Destination excluding the .git folder.
 
    .NOTES
    This function relies on the following private functions:
    - Check-DiskSpace.ps1
    - Handle-RobocopyExitCode.ps1
    - Test-Directory.ps1
    - Test-Robocopy.ps1
    - Verify-CopyOperation.ps1
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Source,
        [Parameter(Mandatory = $true)]
        [string]$Destination,
        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [string]$FilePattern = '*',  # Default to '*' if not provided
        [Parameter(Mandatory = $false)]
        [int]$RetryCount = 2,
        [Parameter(Mandatory = $false)]
        [int]$WaitTime = 5,
        [Parameter(Mandatory = $false)]
        [int]$RequiredSpaceGB = 10, # Example value for required space
        [Parameter(Mandatory = $false)]
        [string[]]$Exclude
    )

    begin {
        Write-EnhancedLog -Message "Starting Copy-FilesWithRobocopy function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Validate Robocopy, source, and destination directories
        try {
            Test-Robocopy
            Test-Directory -Path $Source
            Write-EnhancedLog -Message "Validated source directory: $Source" -Level "INFO"

            Test-Directory -Path $Destination
            Write-EnhancedLog -Message "Validated destination directory: $Destination" -Level "INFO"
        }
        catch {
            Write-EnhancedLog -Message "Critical error during validation of source or destination directories or Robocopy validation." -Level "CRITICAL"
            Handle-Error -ErrorRecord $_
            throw $_
        }

        # Check disk space
        try {
            Check-DiskSpace -Path $Destination -RequiredSpaceGB $RequiredSpaceGB
        }
        catch {
            Write-EnhancedLog -Message "Critical error during disk space validation." -Level "CRITICAL"
            Handle-Error -ErrorRecord $_
            throw $_
        }

        # Prepare Robocopy log file path
        $timestamp = Get-Date -Format "yyyyMMddHHmmss"
        $logFilePath = "$env:TEMP\RobocopyLog_$timestamp.log"
        Write-EnhancedLog -Message "Robocopy log file will be saved to: $logFilePath" -Level "INFO"
    }

    process {
        try {
            $robocopyPath = "C:\Windows\System32\Robocopy.exe"
            $robocopyArgs = @(
                "`"$Source`"",
                "`"$Destination`"",
                $FilePattern,
                "/E",
                "/R:$RetryCount",
                "/W:$WaitTime",
                "/LOG:`"$logFilePath`""
            )

            # Add exclude arguments if provided
            if ($Exclude) {
                $excludeDirs = $Exclude | ForEach-Object { "/XD `"$($_)`"" }
                $excludeFiles = $Exclude | ForEach-Object { "/XF `"$($_)`"" }
                $robocopyArgs = $robocopyArgs + $excludeDirs + $excludeFiles

                # Log what is being excluded
                foreach ($item in $Exclude) {
                    Write-EnhancedLog -Message "Excluding: $item" -Level "INFO"
                }
            }

            Write-EnhancedLog -Message "Starting Robocopy process with arguments: $robocopyArgs" -Level "INFO"

            # Splatting Start-Process parameters
            $startProcessParams = @{
                FilePath     = $robocopyPath
                ArgumentList = $robocopyArgs
                NoNewWindow  = $true
                Wait         = $true
                PassThru     = $true
            }

            $process = Start-Process @startProcessParams

            if ($process.ExitCode -ne 0) {
                Write-EnhancedLog -Message "Robocopy process failed with exit code: $($process.ExitCode)" -Level "CRITICAL"
            }

            Handle-RobocopyExitCode -ExitCode $process.ExitCode
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while copying files with Robocopy: $_" -Level "ERROR"
            Handle-Error -ErrorRecord $_

            # Check if the error is due to a file being used by another process
            if ($_.Exception -match "because it is being used by another process") {
                Write-EnhancedLog -Message "Attempting to find and kill the process locking the file." -Level "WARNING"
                try {
                    # Find the process locking the file
                    $lockedFile = $_.Exception.Message -match "'(.+?)'" | Out-Null
                    $lockedFile = $matches[1]

                    # Kill the processes locking the file
                    Kill-LockingProcesses -LockedFile $lockedFile

                    # Retry the Robocopy operation
                    Write-EnhancedLog -Message "Retrying Robocopy operation after killing the locking process." -Level "INFO"
                    $process = Start-Process @startProcessParams

                    if ($process.ExitCode -ne 0) {
                        Write-EnhancedLog -Message "Robocopy process failed again with exit code: $($process.ExitCode)" -Level "CRITICAL"
                    }

                    Handle-RobocopyExitCode -ExitCode $process.ExitCode
                    Write-EnhancedLog -Message "Copy operation retried and succeeded." -Level "INFO"
                }
                catch {
                    Write-EnhancedLog -Message "Failed to find or kill the process locking the file: $lockedFile" -Level "ERROR"
                    Handle-Error -ErrorRecord $_
                    throw $_
                }
            }
            else {
                throw $_
            }
        }
    }

    end {
        Write-EnhancedLog -Message "Verifying copied files..." -Level "Notice"

        # Call Verify-CopyOperation to ensure the files were copied correctly
        Verify-CopyOperation -SourcePath $Source -DestinationPath $Destination

        Write-EnhancedLog -Message "Copy-FilesWithRobocopy function execution completed." -Level "Notice"
    }
}

# # Example usage
# $sourcePath = "C:\Source"
# $destinationPath = "C:\Destination"

# Copy-FilesWithRobocopy -Source $sourcePath -Destination $destinationPath
#EndRegion '.\Public\Copy-FilesWithRobocopy.ps1' 201
#Region '.\Public\Copy-FileToPublicAndTemp.ps1' -1

function Copy-FileToPublicAndTemp {
    <#
    .SYNOPSIS
    Copies a specified file to the public desktop and C:\temp, making it available to all users and in the temp directory.
 
    .DESCRIPTION
    This function copies a specified file to the public desktop and C:\temp, ensuring it is available to all users and also in the temp directory. Enhanced logging is used for feedback and error handling.
 
    .PARAMETER SourceFilePath
    The path of the file to be copied.
 
    .EXAMPLE
    Copy-FileToPublicAndTemp -SourceFilePath "C:\Path\To\fcremove.exe"
 
    This example copies the file "fcremove.exe" to the public desktop and C:\temp, making it available to all users and in the temp directory.
    #>


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

    begin {
        Write-EnhancedLog -Message 'Starting Copy-FileToPublicAndTemp function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            if (-not (Test-Path -Path $SourceFilePath)) {
                Write-EnhancedLog -Message "Source file not found: $SourceFilePath" -Level "ERROR"
                throw "Source file not found: $SourceFilePath"
            }

            # Define the destination paths
            $publicDesktopPath = 'C:\Users\Public\Desktop'
            $tempPath = 'C:\temp'

            # Ensure the public desktop directory exists
            $publicDesktopParams = @{
                Path      = $publicDesktopPath
                ItemType  = 'Directory'
                Force     = $true
                ErrorAction = 'Stop'
            }
            if (-not (Test-Path -Path $publicDesktopPath)) {
                Write-EnhancedLog -Message "Public desktop path not found. Creating directory." -Level "INFO"
                New-Item @publicDesktopParams | Out-Null
            }

            # Ensure the temp directory exists
            $tempParams = @{
                Path      = $tempPath
                ItemType  = 'Directory'
                Force     = $true
                ErrorAction = 'Stop'
            }
            if (-not (Test-Path -Path $tempPath)) {
                Write-EnhancedLog -Message "Temp path not found. Creating directory." -Level "INFO"
                New-Item @tempParams | Out-Null
            }

            # Copy the file to the public desktop
            $destinationFilePathPublic = Join-Path -Path $publicDesktopPath -ChildPath (Split-Path -Leaf $SourceFilePath)
            $copyParamsPublic = @{
                Path        = $SourceFilePath
                Destination = $destinationFilePathPublic
                Force       = $true
                ErrorAction = 'Stop'
            }
            Write-EnhancedLog -Message "Copying file to: $destinationFilePathPublic" -Level "INFO"
            Copy-Item @copyParamsPublic
            Write-EnhancedLog -Message "File copied to: $destinationFilePathPublic" -Level "INFO"

            # Copy the file to the temp directory
            $destinationFilePathTemp = Join-Path -Path $tempPath -ChildPath (Split-Path -Leaf $SourceFilePath)
            $copyParamsTemp = @{
                Path        = $SourceFilePath
                Destination = $destinationFilePathTemp
                Force       = $true
                ErrorAction = 'Stop'
            }
            Write-EnhancedLog -Message "Copying file to: $destinationFilePathTemp" -Level "INFO"
            Copy-Item @copyParamsTemp
            Write-EnhancedLog -Message "File copied to: $destinationFilePathTemp" -Level "INFO"

        } catch {
            Write-EnhancedLog -Message "An error occurred while copying the file: $_" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'Copy-FileToPublicAndTemp function completed' -Level 'INFO'
    }
}
#EndRegion '.\Public\Copy-FileToPublicAndTemp.ps1' 98
#Region '.\Public\Copy-InvokeAzureStorageBlobUploadFinalize.ps1' -1

function Copy-InvokeAzureStorageBlobUploadFinalize {
    <#
    .SYNOPSIS
    Copies the AzureStorageBlobUploadFinalize script to specified destination paths.
 
    .DESCRIPTION
    This function copies the `Invoke-AzureStorageBlobUploadFinalize.ps1` script from the source path to multiple destination paths. It logs the success or failure of each copy operation and returns an object containing the result of each file copy. At the end, it provides a summary report with color-coded statuses.
 
    .PARAMETER sourceFile
    The path to the source file that needs to be copied.
 
    .PARAMETER destinationPaths
    An array of destination paths where the source file will be copied.
 
    .EXAMPLE
    $copyResults = Copy-InvokeAzureStorageBlobUploadFinalize -sourceFile "C:\Code\IntuneWin32App\Private\Invoke-AzureStorageBlobUploadFinalize.ps1" -destinationPaths @("C:\Path1", "C:\Path2")
    Processes the file copy and returns the results with a summary report.
    #>


    [CmdletBinding()]
    param (
        [string]$sourceFile = "C:\Code\IntuneWin32App\Private\Invoke-AzureStorageBlobUploadFinalize.ps1",
        [string[]]$destinationPaths = @(
            "C:\Program Files\WindowsPowerShell\Modules\IntuneWin32App\1.4.4\Private\Invoke-AzureStorageBlobUploadFinalize.ps1"
        )
    )

    Begin {
        Write-EnhancedLog -Message "Starting the file copy process..." -Level "INFO"

        # Initialize counters and status list
        $totalCopies = 0
        $successfulCopies = 0
        $failedCopies = 0
        $copyStatuses = [System.Collections.Generic.List[PSCustomObject]]::new()  # Efficient list initialization
    }

    Process {
        foreach ($destination in $destinationPaths) {
            try {
                Write-EnhancedLog -Message "Copying file to $destination" -Level "INFO"
                $totalCopies++

                # Splatting Copy-Item parameters
                $CopyItemParams = @{
                    Path        = $sourceFile
                    Destination = $destination
                    Force       = $true
                }

                Copy-Item @CopyItemParams
                Write-EnhancedLog -Message "Successfully copied to $destination" -Level "INFO"
                $successfulCopies++
                $copyStatuses.Add([pscustomobject]@{ Destination = $destination; Status = "Success" })
            }
            catch {
                Write-EnhancedLog -Message "Failed to copy to $destination. Error: $_" -Level "ERROR"
                Handle-Error -ErrorRecord $_
                $failedCopies++
                $copyStatuses.Add([pscustomobject]@{ Destination = $destination; Status = "Failed" })
            }
        }
    }

    End {
        Write-EnhancedLog -Message "File copy process completed." -Level "INFO"

        # Return object with summary of copy operations
        $copyResults = [pscustomobject]@{
            TotalCopies       = $totalCopies
            SuccessfulCopies  = $successfulCopies
            FailedCopies      = $failedCopies
            CopyStatuses      = $copyStatuses
        }

        # Print final summary report
        Write-Host "Final Summary Report" -ForegroundColor Green
        Write-Host "---------------------" -ForegroundColor Green
        Write-Host "Total Copies: $totalCopies" -ForegroundColor Green
        Write-Host "Successful Copies: $successfulCopies" -ForegroundColor Green
        Write-Host "Failed Copies: $failedCopies" -ForegroundColor Red

        # Loop through copyStatuses for detailed report
        foreach ($copyStatus in $copyStatuses) {
            if ($copyStatus.Status -eq "Success") {
                Write-Host "Destination: $($copyStatus.Destination) - Status: $($copyStatus.Status)" -ForegroundColor Green
            }
            else {
                Write-Host "Destination: $($copyStatus.Destination) - Status: $($copyStatus.Status)" -ForegroundColor Red
            }
        }

        return $copyResults
    }
}

# Example Usage:
# $copyResults = Copy-InvokeAzureStorageBlobUploadFinalize -sourceFile "C:\Code\IntuneWin32App\Private\Invoke-AzureStorageBlobUploadFinalize.ps1" -destinationPaths @("C:\Path1", "C:\Path2")
# Write-Host "Total Copies: $($copyResults.TotalCopies)"
#EndRegion '.\Public\Copy-InvokeAzureStorageBlobUploadFinalize.ps1' 100
#Region '.\Public\Create-AADGroup.ps1' -1

function Create-AADGroup ($Prg) {


    # # Convert the Client Secret to a SecureString
    # $SecureClientSecret = ConvertTo-SecureString $connectionParams.ClientSecret -AsPlainText -Force

    # # Create a PSCredential object with the Client ID as the user and the Client Secret as the password
    # $ClientSecretCredential = New-Object System.Management.Automation.PSCredential ($connectionParams.ClientId, $SecureClientSecret)

    # # Connect to Microsoft Graph
    # Connect-MgGraph -TenantId $connectionParams.TenantId -ClientSecretCredential $ClientSecretCredential

    # Your code that interacts with Microsoft Graph goes here


    # Create Group
    # $grpname = "$($global:SettingsVAR.AADgrpPrefix )$($Prg.id)"
    Write-EnhancedLog -Message "setting Group Name" -Level "WARNING" -ForegroundColor ([ConsoleColor]::Yellow)
    $grpname = "SG007 - Intune - Apps - Microsoft Teams - WinGet - Windows Package Manager"
    if (!$(Get-MgGroup -Filter "DisplayName eq '$grpname'")) {
        # Write-Host " Create AAD group for assigment: $grpname" -Foregroundcolor cyan

        Write-EnhancedLog -Message " Did not find Group $grpname " -Level "WARNING" -ForegroundColor ([ConsoleColor]::Yellow)
        
        # $GrpObj = New-MgGroup -DisplayName "$grpname" -Description "App assigment: $($Prg.id) $($Prg.manager)" -MailEnabled:$False -MailNickName $grpname -SecurityEnabled
    }
    else { $GrpObj = Get-MgGroup -Filter "DisplayName eq '$grpname'" }


    Write-EnhancedLog -Message " Assign Group > $grpname < to > $($Prg.Name)" -Level "WARNING" -ForegroundColor ([ConsoleColor]::Yellow)
  


    Write-EnhancedLog -Message " calling Get-IntuneWin32App " -Level "WARNING" -ForegroundColor ([ConsoleColor]::Yellow)
    $Win32App = Get-IntuneWin32App -DisplayName "$($Prg.Name)"


    Write-EnhancedLog -Message " calling Get-IntuneWin32App - done " -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)


    Write-EnhancedLog -Message " calling Add-IntuneWin32AppAssignmentGroup " -Level "WARNING" -ForegroundColor ([ConsoleColor]::Yellow)
    Add-IntuneWin32AppAssignmentGroup -Include -ID $Win32App.id -GroupID $GrpObj.id -Intent "available" -Notification "showAll"


    Write-EnhancedLog -Message " calling Add-IntuneWin32AppAssignmentGroup - done " -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
}
#EndRegion '.\Public\Create-AADGroup.ps1' 47
#Region '.\Public\Create-AndVerifyServicePrincipal.ps1' -1

function Create-AndVerifyServicePrincipal {
    param (
        [Parameter(Mandatory = $true)]
        [string]$ClientId
    )

    try {
        Write-EnhancedLog -Message "Creating a new service principal for the application with Client ID: $ClientId" -Level "INFO"

        # Create a new service principal for the application
        New-MgServicePrincipal -AppId $ClientId

        Write-EnhancedLog -Message "Service principal created successfully." -Level "INFO"

        # Verify the creation
        $servicePrincipal = Get-MgServicePrincipal -Filter "AppId eq '$ClientId'"

        if ($null -eq $servicePrincipal) {
            Write-EnhancedLog -Message "Service principal not found after creation." -Level "ERROR"
            throw "Service principal not found after creation"
        }

        Write-EnhancedLog -Message "Service principal verified successfully." -Level "INFO"

        # Display the service principal details
        $servicePrincipal | Format-Table DisplayName, AppId, Id

        return $servicePrincipal

    } catch {
        Write-EnhancedLog -Message "An error occurred while creating or verifying the service principal." -Level "ERROR"
        Handle-Error -ErrorRecord $_
        throw $_
    }
}

# Example usage
# Create-AndVerifyServicePrincipal -ClientId "your-application-id"
#EndRegion '.\Public\Create-AndVerifyServicePrincipal.ps1' 39
#Region '.\Public\Create-AppRegistration.ps1' -1

function Create-AppRegistration {
    param (
        [string]$AppName,
        # [string]$PermsFile = "$PSScriptRoot\permissions.json"
        [string]$PermsFile
    )

    try {
        if (-Not (Test-Path $PermsFile)) {
            Write-EnhancedLog -Message "Permissions file not found: $PermsFile" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
            throw "Permissions file missing"
        }
    
        $permissions = Get-Content -Path $PermsFile -Raw | ConvertFrom-Json

        # Convert the JSON data to the required types
        $requiredResourceAccess = @()
        foreach ($perm in $permissions.permissions) {
            $resourceAccess = @()
            foreach ($access in $perm.ResourceAccess) {
                $resourceAccess += [Microsoft.Graph.PowerShell.Models.IMicrosoftGraphResourceAccess]@{
                    Id   = [Guid]$access.Id
                    Type = $access.Type
                }
            }
            $requiredResourceAccess += [Microsoft.Graph.PowerShell.Models.IMicrosoftGraphRequiredResourceAccess]@{
                ResourceAppId  = [Guid]$perm.ResourceAppId
                ResourceAccess = $resourceAccess
            }
        }

        # Connect to Graph interactively
        # Connect-MgGraph -Scopes "Application.ReadWrite.All"
    
        # Get tenant details
        $tenantDetails = Get-MgOrganization | Select-Object -First 1
    
        # Create the application
        $app = New-MgApplication -DisplayName $AppName -SignInAudience "AzureADMyOrg" -RequiredResourceAccess $requiredResourceAccess
    
        if ($null -eq $app) {
            Write-EnhancedLog -Message "App registration failed" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
            throw "App registration failed"
        }
    
        Write-EnhancedLog -Message "App registered successfully" -Level "INFO" -ForegroundColor ([ConsoleColor]::Cyan)
        return @{ App = $app; TenantDetails = $tenantDetails }
        
    }
    catch {
        Handle-Error -ErrorRecord $_
    }
}
#EndRegion '.\Public\Create-AppRegistration.ps1' 54
#Region '.\Public\Create-DetectionRule.ps1' -1

function Create-DetectionRule {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Prg_Path
    )

    Write-EnhancedLog -Message "Creating detection rule..." -Level "WARNING"
    $detectionScriptPath = Join-Path -Path $Prg_Path -ChildPath "check.ps1"
    if (-not (Test-Path -Path $detectionScriptPath)) {
        Write-Warning "Detection rule script file does not exist at path: $detectionScriptPath"
    }
    else {
        $DetectionRule = New-IntuneWin32AppDetectionRuleScript -ScriptFile $detectionScriptPath -EnforceSignatureCheck $false -RunAs32Bit $false
    }
    Write-EnhancedLog -Message "Detection rule set (calling New-IntuneWin32AppDetectionRuleScript) - done" -Level "INFO"

    return $DetectionRule
}
#EndRegion '.\Public\Create-DetectionRule.ps1' 19
#Region '.\Public\Create-EventLogSource.ps1' -1

function Create-EventLogSource {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$LogName,
        [Parameter(Mandatory = $true)]
        [string]$Source
    )

    Begin {
        Write-EnhancedLog -Message "Starting Create-EventLogSource function" -Level "INFO"
        Log-Params -Params @{
            LogName = $LogName
            Source  = $Source
        }
    }

    Process {
        try {
            if (-not (Get-EventLog -LogName $LogName -Source $Source -ErrorAction SilentlyContinue)) {
                New-EventLog -LogName $LogName -Source $Source -ErrorAction Stop
            }
        } catch {
            Write-EnhancedLog -Message "An error occurred while creating the event log source: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Create-EventLogSource function" -Level "INFO"
    }
}


# $CreateEventLogSourceParams = @{
# LogName = "Application"
# Source = "AAD_Migration_Script"
# }

# Create-EventLogSource @CreateEventLogSourceParams
#EndRegion '.\Public\Create-EventLogSource.ps1' 41
#Region '.\Public\Create-InteractiveMigrationTask.ps1' -1

function Create-InteractiveMigrationTask {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$TaskPath,
        
        [Parameter(Mandatory = $true)]
        [string]$TaskName,
        
        [Parameter(Mandatory = $true)]
        [string]$ServiceUIPath,
        
        [Parameter(Mandatory = $true)]
        [string]$ToolkitExecutablePath,
        
        [Parameter(Mandatory = $true)]
        [string]$ProcessName,
        
        [Parameter(Mandatory = $true)]
        [string]$DeploymentType,
        
        [Parameter(Mandatory = $true)]
        [string]$DeployMode,
        
        [Parameter(Mandatory = $true)]
        [string]$TaskTriggerType,
        
        [Parameter(Mandatory = $true)]
        [string]$TaskRepetitionDuration,
        
        [Parameter(Mandatory = $true)]
        [string]$TaskRepetitionInterval,
        
        [Parameter(Mandatory = $true)]
        [string]$TaskPrincipalUserId,
        
        [Parameter(Mandatory = $true)]
        [string]$TaskRunLevel,
        
        [Parameter(Mandatory = $true)]
        [string]$TaskDescription,
        
        [Parameter(Mandatory = $false)]
        [string]$Delay  # No default value is set
    )

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

    Process {
        try {
            # Unregister the task if it exists
            Unregister-ScheduledTaskWithLogging -TaskName $TaskName

            # Define the arguments for ServiceUI.exe
            $argList = "-process:$ProcessName `"$ToolkitExecutablePath`" -DeploymentType $DeploymentType -DeployMode $DeployMode"
            Write-EnhancedLog -Message "ServiceUI arguments: $argList" -Level "INFO"

            # Create the scheduled task action
            $actionParams = @{
                Execute  = $ServiceUIPath
                Argument = $argList
            }
            $action = New-ScheduledTaskAction @actionParams

            # Create the scheduled task trigger based on the type provided
            $triggerParams = @{
                $TaskTriggerType = $true
            }

            $trigger = New-ScheduledTaskTrigger @triggerParams

            # Apply the delay after creating the trigger, if provided
            if ($PSBoundParameters.ContainsKey('Delay')) {
                $trigger.Delay = $Delay
                Write-EnhancedLog -Message "Setting Delay: $Delay" -Level "INFO"
            }

            # Create the scheduled task principal
            $principalParams = @{
                UserId   = $TaskPrincipalUserId
                RunLevel = $TaskRunLevel
            }
            $principal = New-ScheduledTaskPrincipal @principalParams

            # Register the scheduled task
            $registerTaskParams = @{
                Principal   = $principal
                Action      = $action
                Trigger     = $trigger
                TaskName    = $TaskName
                Description = $TaskDescription
                TaskPath    = $TaskPath
            }
            $Task = Register-ScheduledTask @registerTaskParams

            # Set repetition properties
            # $Task.Triggers.Repetition.Duration = $TaskRepetitionDuration
            # $Task.Triggers.Repetition.Interval = $TaskRepetitionInterval
            # $Task | Set-ScheduledTask
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while creating the interactive migration task: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Create-InteractiveMigrationTask function" -Level "Notice"
    }
}

# Example usage with splatting
# $CreateInteractiveMigrationTaskParams = @{
# TaskPath = "AAD Migration"
# TaskName = "PR4B-AADM Launch PSADT for Interactive Migration"
# ServiceUIPath = "C:\ProgramData\AADMigration\ServiceUI.exe"
# ToolkitExecutablePath = "C:\ProgramData\AADMigration\PSAppDeployToolkit\Toolkit\Deploy-Application.exe"
# ProcessName = "explorer.exe"
# DeploymentType = "install"
# DeployMode = "Interactive"
# TaskTriggerType = "AtLogOn"
# TaskRepetitionDuration = "P1D" # 1 day
# TaskRepetitionInterval = "PT15M" # 15 minutes
# TaskPrincipalUserId = "NT AUTHORITY\SYSTEM"
# TaskRunLevel = "Highest"
# TaskDescription = "AADM Launch PSADT for Interactive Migration Version 1.0"
# Delay = "PT2H" # 2 hours delay before starting
# }

# Create-InteractiveMigrationTask @CreateInteractiveMigrationTaskParams
#EndRegion '.\Public\Create-InteractiveMigrationTask.ps1' 134
#Region '.\Public\Create-IntuneWinPackage.ps1' -1

function Create-IntuneWinPackage {
    <#
    .SYNOPSIS
    Creates a .intunewin package for a specified program.
 
    .DESCRIPTION
    This function creates a .intunewin package by packaging the source folder and setup file. It logs the process and handles errors. The resulting package path is returned.
 
    .PARAMETER Prg
    The program object containing metadata like the program name.
 
    .PARAMETER Prg_Path
    The path to the program source folder.
 
    .PARAMETER destinationPath
    The destination path where the .intunewin package will be created.
 
    .EXAMPLE
    $IntuneWinFile = Create-IntuneWinPackage -Prg $Prg -Prg_Path "C:\Path\To\Program" -destinationPath "C:\Path\To\Destination"
    Creates the .intunewin package and returns the file path.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the program object.")]
        [ValidateNotNullOrEmpty()]
        [pscustomobject]$Prg,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the program source path.")]
        [ValidateNotNullOrEmpty()]
        [string]$Prg_Path,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the destination path for the package.")]
        [ValidateNotNullOrEmpty()]
        [string]$destinationPath
    )

    Begin {
        Write-EnhancedLog -Message "Starting Create-IntuneWinPackage function for program: $($Prg.Name)" -Level "INFO"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            Write-EnhancedLog -Message "Creating .intunewin package..." -Level "INFO"

            $setupFile = "install.ps1"

            # Use New-IntuneWinPackage to create the package
            # New-IntuneWinPackage -SourcePath $Prg_Path -DestinationPath $destinationPath -SetupFile $setupFile -Verbose

            # $Win32AppPackage = New-IntuneWin32AppPackage -SourceFolder $Prg_Path -SetupFile $setupFile -OutputFolder $destinationPath -Verbose -Force:$true

            # Splatting for New-IntuneWin32AppPackage
            $NewIntuneWinPackageParams = @{
                SourceFolder = $Prg_Path
                SetupFile    = $setupFile
                OutputFolder = $destinationPath
                Verbose      = $true
                Force        = $true
            }

            # Use New-IntuneWin32AppPackage to create the package
            $Win32AppPackage = New-IntuneWin32AppPackage @NewIntuneWinPackageParams

            Write-EnhancedLog -Message "Package creation completed successfully." -Level "INFO"

            # Set the IntuneWin file path
            $IntuneWinFile = Join-Path -Path $destinationPath -ChildPath "install.intunewin"
            Write-EnhancedLog -Message "IntuneWinFile path set: $IntuneWinFile" -Level "INFO"

            # Return the path of the created package
            return $IntuneWinFile
        }
        catch {
            Write-EnhancedLog -Message "Error creating .intunewin package: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            Write-Host "Error creating .intunewin package: $($_.Exception.Message)" -ForegroundColor Red
            throw
        }
    }

    End {
        Write-EnhancedLog -Message "Completed Create-IntuneWinPackage function for program: $($Prg.Name)" -Level "INFO"

        # Print summary report
        Write-Host "Summary Report" -ForegroundColor Green
        Write-Host "-----------------" -ForegroundColor Green
        Write-Host "Program Name: $($Prg.Name)" -ForegroundColor Green
        Write-Host "Source Path: $Prg_Path" -ForegroundColor Green
        Write-Host "Destination Path: $destinationPath" -ForegroundColor Green
        Write-Host "IntuneWinFile: $IntuneWinFile" -ForegroundColor Green
    }
}
#EndRegion '.\Public\Create-IntuneWinPackage.ps1' 95
#Region '.\Public\Create-LocalAdminAccount.ps1' -1

function Create-LocalAdminAccount {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Username,

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

    begin {
        Write-EnhancedLog -Message 'Starting Create-LocalAdminAccount function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            # Check if the user already exists
            $userExists = Get-LocalUser -Name $Username -ErrorAction SilentlyContinue

            if (-not $userExists) {
                # Create the user account
                $securePassword = ConvertTo-SecureString -String $Password -AsPlainText -Force
                $userParams = @{
                    Name                 = $Username
                    Password             = $securePassword
                    FullName             = "FC Remove Account"
                    Description          = "Account used for FC removal process"
                    PasswordNeverExpires = $true
                    AccountNeverExpires  = $true
                }
                New-LocalUser @userParams
                Write-EnhancedLog -Message "Local administrator account '$Username' created." -Level 'INFO'
            } else {
                Write-EnhancedLog -Message "Local administrator account '$Username' already exists." -Level 'WARNING'
            }

            # Check if the user is already a member of the local Administrators group
            $group = Get-LocalGroup -Name "Administrators"
            $memberExists = $null
            try {
                $memberExists = $group | Get-LocalGroupMember | Where-Object { $_.Name -eq $Username }
            } catch {
                Write-EnhancedLog -Message "Failed to retrieve group members: $_" -Level 'ERROR'
            }

            if (-not $memberExists) {
                # Add the user to the local Administrators group
                $groupParams = @{
                    Group  = "Administrators"
                    Member = $Username
                }
                try {
                    Add-LocalGroupMember @groupParams
                    Write-EnhancedLog -Message "User '$Username' added to the Administrators group." -Level 'INFO'
                } catch [Microsoft.PowerShell.Commands.AddLocalGroupMemberCommand+MemberExistsException] {
                    Write-EnhancedLog -Message "User '$Username' is already a member of the Administrators group." -Level 'WARNING'
                }
            } else {
                Write-EnhancedLog -Message "User '$Username' is already a member of the Administrators group." -Level 'WARNING'
            }

        } catch {
            Write-EnhancedLog -Message "An error occurred while creating the local admin account or adding to Administrators group: $_" -Level 'ERROR'
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'Create-LocalAdminAccount function completed' -Level 'INFO'
    }
}

# # Define parameters for creating the local admin account
# $localAdminParams = @{
# Username = "fcremove"
# Password = "fcremove"
# }

# # Create the local admin account
# Create-LocalAdminAccount @localAdminParams
#EndRegion '.\Public\Create-LocalAdminAccount.ps1' 82
#Region '.\Public\Create-OneDriveCacheClearTask.ps1' -1

function Create-OneDriveCacheClearTask {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$TaskPath,
        [Parameter(Mandatory = $true)]
        [string]$TaskName,
        [Parameter(Mandatory = $true)]
        [string]$ScriptDirectory,
        [Parameter(Mandatory = $true)]
        [string]$ScriptName,
        [Parameter(Mandatory = $true)]
        [string]$TaskArguments,
        [Parameter(Mandatory = $true)]
        [string]$TaskRepetitionDuration,
        [Parameter(Mandatory = $true)]
        [string]$TaskRepetitionInterval,
        [Parameter(Mandatory = $true)]
        [string]$TaskPrincipalGroupId,
        [Parameter(Mandatory = $true)]
        [string]$PowerShellPath,
        [Parameter(Mandatory = $true)]
        [string]$TaskDescription,
        [Parameter(Mandatory = $true)]
        [switch]$AtLogOn
    )

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

    Process {
        try {
            # Unregister the task if it exists
            Unregister-ScheduledTaskWithLogging -TaskName $TaskName

            $arguments = $TaskArguments.Replace("{ScriptPath}", "$ScriptDirectory\$ScriptName")

            $actionParams = @{
                Execute  = $PowerShellPath
                Argument = $arguments
            }
            $action = New-ScheduledTaskAction @actionParams

            $triggerParams = @{
                AtLogOn = $AtLogOn
            }
            
            $trigger = New-ScheduledTaskTrigger @triggerParams

            $principalParams = @{
                GroupId = $TaskPrincipalGroupId
            }
            $principal = New-ScheduledTaskPrincipal @principalParams

            $registerTaskParams = @{
                Principal   = $principal
                Action      = $action
                Trigger     = $trigger
                TaskName    = $TaskName
                Description = $TaskDescription
                TaskPath    = $TaskPath
            }
            $Task = Register-ScheduledTask @registerTaskParams

            $Task.Triggers.Repetition.Duration = $TaskRepetitionDuration
            $Task.Triggers.Repetition.Interval = $TaskRepetitionInterval
            $Task | Set-ScheduledTask
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while creating the OneDrive cache clear task: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Create-OneDriveCacheClearTask function" -Level "Notice"
    }
}

# # Example usage with splatting
# $CreateOneDriveCacheClearTaskParams = @{
# TaskPath = "OneDriveTasks"
# TaskName = "Clear OneDrive Cache"
# ScriptDirectory = "C:\Scripts"
# ScriptName = "Clear-OneDriveCache.ps1"
# TaskArguments = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -file `"{ScriptPath}`""
# TaskRepetitionDuration = "P1D"
# TaskRepetitionInterval = "PT30M"
# TaskPrincipalGroupId = "BUILTIN\Users"
# PowerShellPath = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
# TaskDescription = "Clears the OneDrive cache by restarting the OneDrive process"
# AtLogOn = $true
# }

# Create-OneDriveCacheClearTask @CreateOneDriveCacheClearTaskParams
#EndRegion '.\Public\Create-OneDriveCacheClearTask.ps1' 98
#Region '.\Public\Create-OneDriveRemediationTask.ps1' -1

function Create-OneDriveRemediationTask {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$OneDriveExePath,

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

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

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

    Begin {
        Write-EnhancedLog -Message "Starting Create-OneDriveRemediationTask function" -Level "INFO"
        Log-Params -Params @{
            OneDriveExePath            = $OneDriveExePath
            ScheduledTaskName          = $ScheduledTaskName
            ScheduledTaskDescription   = $ScheduledTaskDescription
            ScheduledTaskArgumentList  = $ScheduledTaskArgumentList
        }
    }

    Process {
        try {
            # $userId = (Get-WmiObject -Class Win32_ComputerSystem).UserName
            $userId = $env:UserName
            if (-not $userId) {
                throw "Unable to retrieve the current user ID."
            }

            Write-EnhancedLog -Message "User ID retrieved: $userId" -Level "INFO"

            $actionParams = @{
                Execute  = $OneDriveExePath
            }
            if ($ScheduledTaskArgumentList) {
                $actionParams.Argument = $ScheduledTaskArgumentList
            }
            $action = New-ScheduledTaskAction @actionParams

            $trigger = New-ScheduledTaskTrigger -AtLogOn

            $principalParams = @{
                UserId = $userId
            }
            $principal = New-ScheduledTaskPrincipal @principalParams

            $taskParams = @{
                Action      = $action
                Trigger     = $trigger
                Principal   = $principal
                TaskName    = $ScheduledTaskName
                Description = $ScheduledTaskDescription
                Force       = $true
            }
            $task = Register-ScheduledTask @taskParams

            Start-ScheduledTask -TaskName $ScheduledTaskName

            # $DBG
            Start-Sleep -Seconds 5
            Unregister-ScheduledTask -TaskName $ScheduledTaskName -Confirm:$false
        } catch {
            Write-EnhancedLog -Message "An error occurred in Create-OneDriveRemediationTask function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Create-OneDriveRemediationTask function" -Level "INFO"
    }
}


# $CreateOneDriveRemediationTaskParams = @{
# OneDriveExePath = "C:\Program Files\Microsoft OneDrive\OneDrive.exe"
# ScheduledTaskName = "OneDriveRemediation"
# ScheduledTaskDescription = "Restart OneDrive to kick off KFM sync"
# ScheduledTaskArgumentList = ""
# }

# Create-OneDriveRemediationTask @CreateOneDriveRemediationTaskParams
#EndRegion '.\Public\Create-OneDriveRemediationTask.ps1' 87
#Region '.\Public\Create-OneDriveSyncUtilStatusTask.ps1' -1

function Create-OneDriveSyncUtilStatusTask {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$TaskPath,
        [Parameter(Mandatory = $true)]
        [string]$TaskName,
        [Parameter(Mandatory = $true)]
        [string]$ScriptDirectory,
        [Parameter(Mandatory = $true)]
        [string]$ScriptName,
        [Parameter(Mandatory = $true)]
        [string]$TaskArguments,
        [Parameter(Mandatory = $true)]
        [string]$TaskRepetitionDuration,
        [Parameter(Mandatory = $true)]
        [string]$TaskRepetitionInterval,
        [Parameter(Mandatory = $true)]
        [string]$TaskPrincipalGroupId,
        [Parameter(Mandatory = $true)]
        [string]$PowerShellPath,
        [Parameter(Mandatory = $true)]
        [string]$TaskDescription,
        [Parameter(Mandatory = $true)]
        [switch]$AtLogOn

    )

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

    Process {
        try {
            # Unregister the task if it exists
            Unregister-ScheduledTaskWithLogging -TaskName $TaskName

            $arguments = $TaskArguments.Replace("{ScriptPath}", "$ScriptDirectory\$ScriptName")

            $actionParams = @{
                Execute  = $PowerShellPath
                Argument = $arguments
            }
            $action = New-ScheduledTaskAction @actionParams

            $triggerParams = @{
                AtLogOn = $AtLogOn
            }
            
            $trigger = New-ScheduledTaskTrigger @triggerParams

            $principalParams = @{
                GroupId = $TaskPrincipalGroupId
            }
            $principal = New-ScheduledTaskPrincipal @principalParams

            $registerTaskParams = @{
                Principal   = $principal
                Action      = $action
                Trigger     = $trigger
                TaskName    = $TaskName
                Description = $TaskDescription
                TaskPath    = $TaskPath
            }
            $Task = Register-ScheduledTask @registerTaskParams

            $Task.Triggers.Repetition.Duration = $TaskRepetitionDuration
            $Task.Triggers.Repetition.Interval = $TaskRepetitionInterval
            $Task | Set-ScheduledTask
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while creating the OneDrive sync status task: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Create-OneDriveSyncUtilStatusTask function" -Level "Notice"
    }
}

# # # # Example usage with splatting
# $CreateOneDriveSyncUtilStatusTask = @{
# TaskPath = "AAD Migration"
# TaskName = "AADM Get OneDrive Sync Status"
# ScriptDirectory = "C:\ProgramData\AADMigration\Scripts"
# ScriptName = "Check-OneDriveSyncStatus.ps1"
# TaskArguments = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -file `"{ScriptPath}`""
# TaskRepetitionDuration = "P1D"
# TaskRepetitionInterval = "PT30M"
# TaskPrincipalGroupId = "BUILTIN\Users"
# PowerShellPath = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
# TaskDescription = "Get current OneDrive Sync Status and write to event log"
# AtLogOn = $true
# }

# Create-OneDriveSyncUtilStatusTask @CreateOneDriveSyncUtilStatusTask
#EndRegion '.\Public\Create-OneDriveSyncUtilStatusTask.ps1' 99
#Region '.\Public\Create-PostMigrationCleanupTask.ps1' -1

function Create-PostMigrationCleanupTask {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$TaskPath,
        
        [Parameter(Mandatory = $true)]
        [string]$TaskName,
        
        [Parameter(Mandatory = $true)]
        [string]$ScriptDirectory,
        
        [Parameter(Mandatory = $true)]
        [string]$ScriptName,
        
        [Parameter(Mandatory = $true)]
        [string]$TaskArguments,
        
        [Parameter(Mandatory = $true)]
        [string]$TaskPrincipalUserId,
        
        [Parameter(Mandatory = $true)]
        [string]$TaskRunLevel,
        
        [Parameter(Mandatory = $true)]
        [string]$PowerShellPath,
        
        [Parameter(Mandatory = $true)]
        [string]$TaskDescription,
        
        [Parameter(Mandatory = $true)]
        [string]$TaskTriggerType,
        
        [Parameter(Mandatory = $false)]
        [string]$Delay  # Optional delay
    )

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

    Process {
        try {
            # Validate if the task already exists before creation
            if (Validate-ScheduledTask -TaskPath $TaskPath -TaskName $TaskName) {
                Write-EnhancedLog -Message "Task '$TaskName' found before creation. It will be unregistered first." -Level "WARNING"
                Unregister-ScheduledTaskWithLogging -TaskName $TaskName
            }

            # Replace placeholder with the actual script path
            $arguments = $TaskArguments.Replace("{ScriptPath}", "$ScriptDirectory\$ScriptName")

            # Create the scheduled task action
            $actionParams = @{
                Execute  = $PowerShellPath
                Argument = $arguments
            }
            $action = New-ScheduledTaskAction @actionParams

            # Create the scheduled task trigger based on the type provided
            $triggerParams = @{
                $TaskTriggerType = $true
            }
            $trigger = New-ScheduledTaskTrigger @triggerParams

            # Apply the delay if provided
            if ($PSBoundParameters.ContainsKey('Delay')) {
                $trigger.Delay = $Delay
                Write-EnhancedLog -Message "Setting Delay: $Delay" -Level "INFO"
            }

            # Create the scheduled task principal
            $principalParams = @{
                UserId   = $TaskPrincipalUserId
                RunLevel = $TaskRunLevel
            }
            $principal = New-ScheduledTaskPrincipal @principalParams

            # Register the scheduled task
            $registerTaskParams = @{
                Principal   = $principal
                Action      = $action
                Trigger     = $trigger
                TaskName    = $TaskName
                Description = $TaskDescription
                TaskPath    = $TaskPath
            }
            $Task = Register-ScheduledTask @registerTaskParams

            Write-EnhancedLog -Message "Task '$TaskName' created successfully at '$TaskPath'." -Level "INFO"

            # Validate the task after creation
            if (Validate-ScheduledTask -TaskPath $TaskPath -TaskName $TaskName) {
                Write-EnhancedLog -Message "Task '$TaskName' created and validated successfully." -Level "INFO"
            } else {
                Write-EnhancedLog -Message "Task '$TaskName' creation failed validation." -Level "ERROR"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Create-PostMigrationCleanupTask function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Create-PostMigrationCleanupTask function" -Level "Notice"
    }
}

# # Example usage with splatting
# $CreatePostMigrationCleanupTaskParams = @{
# TaskPath = "AAD Migration"
# TaskName = "Run Post-migration cleanup"
# ScriptDirectory = "C:\ProgramData\AADMigration\Scripts"
# ScriptName = "PostRunOnce3.ps1"
# TaskArguments = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File `"{ScriptPath}`""
# TaskPrincipalUserId = "NT AUTHORITY\SYSTEM"
# TaskRunLevel = "Highest"
# PowerShellPath = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
# TaskDescription = "Run post AAD Migration cleanup"
# TaskTriggerType = "AtLogOn" # The trigger type can be passed as a parameter now
# Delay = "PT1M" # Optional delay before starting
# }

# Create-PostMigrationCleanupTask @CreatePostMigrationCleanupTaskParams
#EndRegion '.\Public\Create-PostMigrationCleanupTask.ps1' 128
#Region '.\Public\Create-PPKG.ps1' -1

function Create-PPKG {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$ICDPath,

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

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

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

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

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

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

        [Parameter(Mandatory = $false)]
        [hashtable]$Variables,

        [Parameter(Mandatory = $false)]
        [bool]$Encrypted = $false,

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

    Begin {
        Write-EnhancedLog -Message "Starting Create-PPKG function" -Level "INFO"
        Log-Params -Params @{
            ICDPath = $ICDPath
            CustomizationXMLPath = $CustomizationXMLPath
            PackagePath = $PackagePath
            ProductName = $ProductName
            StoreFile = $StoreFile
            MSPackageRoot = $MSPackageRoot
            OEMInputXML = $OEMInputXML
            Variables = $Variables
            Encrypted = $Encrypted
            Overwrite = $Overwrite
        }

        # Ensure ICD.exe exists
        if (-not (Test-Path -Path $ICDPath)) {
            throw "ICD.exe not found at: $ICDPath"
        }

        # Ensure Customization XML file exists
        if (-not (Test-Path -Path $CustomizationXMLPath)) {
            throw "Customization XML file not found at: $CustomizationXMLPath"
        }
    }

    Process {
        try {
            # Build the command line arguments using a list
            $ICD_args = [System.Collections.Generic.List[string]]::new()
            $ICD_args.Add("/Build-ProvisioningPackage")
            $ICD_args.Add("/CustomizationXML:`"$CustomizationXMLPath`"")
            $ICD_args.Add("/PackagePath:`"$PackagePath`"")

            if ($Encrypted) {
                $ICD_args.Add("+Encrypted")
            } else {
                $ICD_args.Add("-Encrypted")
            }

            if ($Overwrite) {
                $ICD_args.Add("+Overwrite")
            } else {
                $ICD_args.Add("-Overwrite")
            }

            if ($ProductName) {
                $ICD_args.Add("/ProductName:`"$ProductName`"")
            }

            if ($StoreFile) {
                $ICD_args.Add("/StoreFile:`"$StoreFile`"")
            }

            if ($MSPackageRoot) {
                $ICD_args.Add("/MSPackageRoot:`"$MSPackageRoot`"")
            }

            if ($OEMInputXML) {
                $ICD_args.Add("/OEMInputXML:`"$OEMInputXML`"")
            }

            if ($Variables) {
                foreach ($key in $Variables.Keys) {
                    $ICD_args.Add("/Variables:`"$key=$($Variables[$key])`"")
                }
            }

            $ICD_args_string = $ICD_args -join " "
            Write-EnhancedLog -Message "Running ICD.exe with arguments: $ICD_args_string" -Level "INFO"
            Start-Process -FilePath $ICDPath -ArgumentList $ICD_args_string -Wait -NoNewWindow

        } catch {
            Write-EnhancedLog -Message "An error occurred while processing the Create-PPKG function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Create-PPKG function" -Level "INFO"
    }
}

# Example usage
# $ppkgParams = @{
# ICDPath = "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Imaging and Configuration Designer\x86\ICD.exe"
# CustomizationXMLPath = "C:\code\CB\Entra\DeviceMigration\Files\customizations.xml"
# PackagePath = "C:\code\CB\Entra\DeviceMigration\Files\ProvisioningPackage.ppkg"
# Encrypted = $false
# Overwrite = $true
# }

# Create-PPKG @ppkgParams
#EndRegion '.\Public\Create-PPKG.ps1' 128
#Region '.\Public\Create-RequirementRule.ps1' -1

function Create-RequirementRule {
    Write-EnhancedLog -Message "Setting minimum requirements..." -Level "WARNING"
    $RequirementRule = New-IntuneWin32AppRequirementRule -Architecture "x64" -MinimumSupportedWindowsRelease "W10_1607"
    Write-EnhancedLog -Message "Minimum requirements set - done" -Level "INFO"

    return $RequirementRule
}
#EndRegion '.\Public\Create-RequirementRule.ps1' 8
#Region '.\Public\Create-SelfSignedCert.ps1' -1

# function Create-SelfSignedCert {
# param (
# [string]$CertName,
# [string]$CertStoreLocation = "Cert:\CurrentUser\My",
# [string]$TenantName,
# [string]$AppId
# )

# $cert = New-SelfSignedCertificate -CertStoreLocation $CertStoreLocation `
# -Subject "CN=$CertName, O=$TenantName, OU=$AppId" `
# -KeyLength 2048 `
# -NotAfter (Get-Date).AddDays(30)

# if ($null -eq $cert) {
# Write-EnhancedLog -Message "Failed to create certificate" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
# throw "Certificate creation failed"
# }
# Write-EnhancedLog -Message "Certificate created successfully" -Level "INFO" -ForegroundColor ([ConsoleColor]::Cyan)
# return $cert
# }







# function Create-SelfSignedCert {
# param (
# [string]$CertName,
# [string]$CertStoreLocation = "Cert:\CurrentUser\My",
# [string]$TenantName,
# [string]$AppId,
# # [string]$OutputPath = "C:\Certificates",
# [string]$OutputPath,
# # [string]$PfxPassword = "YourPfxPassword"
# [string]$PfxPassword
# )

# try {
# # Create output directory if it doesn't exist
# if (-not (Test-Path -Path $OutputPath)) {
# New-Item -ItemType Directory -Path $OutputPath
# }

# # Define certificate subject details
# $subject = "CN=$CertName, O=$TenantName, OU=$AppId, L=City, S=State, C=US"

# # Generate the self-signed certificate
# $cert = New-SelfSignedCertificate -CertStoreLocation $CertStoreLocation `
# -Subject $subject `
# -KeyLength 2048 `
# -KeyExportPolicy Exportable `
# -NotAfter (Get-Date).AddDays(30) `
# -KeyUsage DigitalSignature, KeyEncipherment `
# -FriendlyName "$CertName for $TenantName"

# if ($null -eq $cert) {
# Write-EnhancedLog -Message "Failed to create certificate" -Level "ERROR"
# throw "Certificate creation failed"
# }

# Write-EnhancedLog -Message "Certificate created successfully" -Level "INFO"

# # Convert password to secure string
# $securePfxPassword = ConvertTo-SecureString -String $PfxPassword -Force -AsPlainText

# # Export the certificate to a PFX file
# $pfxFilePath = Join-Path -Path $OutputPath -ChildPath "$CertName-$TenantName-$AppId.pfx"
# Export-PfxCertificate -Cert $cert -FilePath $pfxFilePath -Password $securePfxPassword

# Write-EnhancedLog -Message "PFX file created successfully at $pfxFilePath" -Level "INFO"

# # Export the private key
# $privateKeyFilePath = Join-Path -Path $OutputPath -ChildPath "$CertName-$TenantName-$AppId.key"
# $privateKey = $cert.PrivateKey
# $privateKeyBytes = [System.Convert]::ToBase64String($privateKey.ExportCspBlob($true))
# Set-Content -Path $privateKeyFilePath -Value $privateKeyBytes

# Write-EnhancedLog -Message "Private key file created successfully at $privateKeyFilePath" -Level "INFO"

# return $cert

# } catch {
# Write-EnhancedLog -Message "An error occurred while creating the self-signed certificate" -Level "ERROR"
# Handle-Error -ErrorRecord $_
# throw $_
# }
# }

# Example usage
# $cert = Create-SelfSignedCert -CertName "GraphCert" -TenantName "YourTenantName" -AppId "YourAppId" -OutputPath $OutputPath















# function Create-SelfSignedCert {
# param (
# [string]$CertName,
# [string]$CertStoreLocation = "Cert:\CurrentUser\My",
# [string]$TenantName,
# [string]$AppId,
# [string]$OutputPath,
# [string]$PfxPassword
# )

# try {
# # Create output directory if it doesn't exist
# if (-not (Test-Path -Path $OutputPath)) {
# New-Item -ItemType Directory -Path $OutputPath -Force
# }

# # Get the logged-in user for the Graph API session
# Write-EnhancedLog -Message "Fetching current user information from Microsoft Graph API." -Level "INFO"
# $currentUserResponse = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/me" -Method GET
# Write-EnhancedLog -Message "Response from Microsoft Graph API: $($currentUserResponse | ConvertTo-Json -Compress)" -Level "DEBUG"

# $currentUser = $currentUserResponse

# # $DBG

# # Define certificate subject details
# $subject = "CN=$CertName-$AppId, O=$TenantName, OU=$AppId, L=City, S=State, C=US"

# # Generate the self-signed certificate
# $certParams = @{
# CertStoreLocation = $CertStoreLocation
# Subject = $subject
# Issuer = "CN=$($currentUser.DisplayName)-$($currentUser.userPrincipalName)"
# KeyLength = 2048
# KeyExportPolicy = "Exportable"
# NotAfter = (Get-Date).AddDays(30)
# KeyUsage = "DigitalSignature, KeyEncipherment"
# FriendlyName = "$CertName-$AppId for $TenantName"
# }

# # Generate the self-signed certificate
# $cert = New-SelfSignedCertificate @certParams

# $DBG

# if ($null -eq $cert) {
# Write-EnhancedLog -Message "Failed to create certificate" -Level "ERROR"
# throw "Certificate creation failed"
# }

# Write-EnhancedLog -Message "Certificate created successfully" -Level "INFO"

# # Convert password to secure string
# $securePfxPassword = ConvertTo-SecureString -String $PfxPassword -Force -AsPlainText

# # Export the certificate to a PFX file
# # $pfxFilePath = Join-Path -Path "$OutputPath" -ChildPath "$CertName-$TenantName-$AppId.pfx"
# $pfxFilePath = $null
# # $pfxFilePath = Join-Path -Path "$OutputPath" -ChildPath "$CertName-$TenantName.pfx"
# $pfxFilePath = Join-Path -Path "$OutputPath" -ChildPath "$CertName.pfx"
# # $pfxFilePath = Join-Path -Path "$OutputPath" -ChildPath "1.pfx"

# # $pfxFilePath = "C:\Code\GraphAppwithCert\Graph\Information and Communications Technology Council_b5dae566-ad8f-44e1-9929-5669f1dbb343\c.pfx"

# # $DBG

# Export-PfxCertificate -Cert $cert -FilePath "$pfxFilePath" -Password $securePfxPassword

# Write-EnhancedLog -Message "Certificate $cert exported successfully to pfx file located in $pfxFilePath " -Level "INFO"

# $DBG

# Write-EnhancedLog -Message "PFX file created successfully at $pfxFilePath" -Level "INFO"

# # Export the private key
# # $privateKeyFilePath = Join-Path -Path "$OutputPath" -ChildPath "$CertName-$TenantName-$AppId.key"
# $privateKeyFilePath = Join-Path -Path "$OutputPath" -ChildPath "$CertName-$TenantName.key"
# $privateKey = $cert.PrivateKey

# $rsaParameters = $privateKey.ExportParameters($true)
# $privateKeyPem = Convert-RsaParametersToPem -rsaParameters $rsaParameters
# Set-Content -Path $privateKeyFilePath -Value $privateKeyPem

# Write-EnhancedLog -Message "Private key file created successfully at $privateKeyFilePath" -Level "INFO"

# return $cert

# }
# catch {
# Write-EnhancedLog -Message "An error occurred while creating the self-signed certificate" -Level "ERROR"
# Handle-Error -ErrorRecord $_
# throw $_
# }
# }



# # Example usage
# $certParams = @{
# CertName = "GraphCert"
# TenantName = "YourTenantName"
# AppId = "YourAppId"
# OutputPath = "C:\Certificates"
# PfxPassword = "YourPfxPassword"
# }
# $cert = Create-SelfSignedCert @certParams










# function Create-SelfSignedCert {
# param (
# [string]$CertName,
# [string]$CertStoreLocation = "Cert:\CurrentUser\My",
# [string]$TenantName,
# [string]$AppId,
# [string]$OutputPath,
# [string]$PfxPassword
# )

# try {
# # Get the logged-in user for the Graph API session
# Write-EnhancedLog -Message "Fetching current user information from Microsoft Graph API." -Level "INFO"
# $currentUserResponse = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/me" -Method GET
# Write-EnhancedLog -Message "Response from Microsoft Graph API: $($currentUserResponse | ConvertTo-Json -Compress)" -Level "DEBUG"

# $currentUser = $currentUserResponse

# # Create output directory if it doesn't exist
# if (-not (Test-Path -Path $OutputPath)) {
# New-Item -ItemType Directory -Path $OutputPath
# }

# # Define certificate subject details
# $subject = "CN=$CertName-$AppId, O=$TenantName, OU=$AppId, L=City, S=State, C=US"

# # Splat the parameters
# $certParams = @{
# CertStoreLocation = $CertStoreLocation
# Subject = $subject
# Issuer = "CN=$($currentUser.displayName)"
# KeyLength = 2048
# KeyExportPolicy = "Exportable"
# NotAfter = (Get-Date).AddDays(30)
# KeyUsage = @("DigitalSignature", "KeyEncipherment")
# FriendlyName = "$CertName-$AppId for $TenantName"
# }

# # Generate the self-signed certificate
# $cert = New-SelfSignedCertificate @certParams

# if ($null -eq $cert) {
# Write-EnhancedLog -Message "Failed to create certificate" -Level "ERROR"
# throw "Certificate creation failed"
# }

# Write-EnhancedLog -Message "Certificate created successfully" -Level "INFO"

# # Convert password to secure string
# $securePfxPassword = ConvertTo-SecureString -String $PfxPassword -Force -AsPlainText

# # Export the certificate to a PFX file
# $pfxFilePath = Join-Path -Path $OutputPath -ChildPath "$CertName-$TenantName-$AppId.pfx"
# Export-PfxCertificate -Cert $cert -FilePath $pfxFilePath -Password $securePfxPassword

# Write-EnhancedLog -Message "PFX file created successfully at $pfxFilePath" -Level "INFO"

# return $cert

# } catch {
# Write-EnhancedLog -Message "An error occurred while creating the self-signed certificate." -Level "ERROR"
# Handle-Error -ErrorRecord $_
# throw $_
# }
# }

# Example usage
# $scopes = @("User.Read.All", "Application.ReadWrite.All", "Directory.ReadWrite.All")
# Connect-MgGraph -Scopes $scopes

# $certParams = @{
# CertName = "GraphCert"
# TenantName = $tenantDetails.DisplayName
# AppId = $app.AppId
# OutputPath = $certexportDirectory
# PfxPassword = $certPassword
# }
# $cert = Create-SelfSignedCert @certParams






function Create-SelfSignedCert {
    param (
        [string]$CertName,
        [string]$CertStoreLocation = "Cert:\CurrentUser\My",
        [string]$TenantName,
        [string]$AppId,
        [string]$OutputPath,
        [string]$PfxPassword
    )

    try {
        # Get the logged-in user for the Graph API session
        Write-EnhancedLog -Message "Fetching current user information from Microsoft Graph API." -Level "INFO"
        $currentUserResponse = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/me" -Method GET
        Write-EnhancedLog -Message "Response from Microsoft Graph API: $($currentUserResponse | ConvertTo-Json -Compress)" -Level "DEBUG"

        $currentUser = $currentUserResponse

        # Create output directory if it doesn't exist
        if (-not (Test-Path -Path $OutputPath)) {
            New-Item -ItemType Directory -Path $OutputPath
        }

         # Define certificate subject details
        $subject = "CN=$CertName-$AppId, O=$TenantName, OU=$AppId, L=City, S=State, C=US"
        $Issuer  = "CN=$($currentUser.DisplayName)-$($currentUser.userPrincipalName)"

        # Splat the parameters
        $certParams = @{
            CertStoreLocation = $CertStoreLocation
            Subject           = $subject
            KeyLength         = 2048
            KeyExportPolicy   = "Exportable"
            NotAfter          = (Get-Date).AddDays(30)
            KeyUsage          = @("DigitalSignature", "KeyEncipherment")
            FriendlyName      = "$CertName-$AppId for $TenantName by $Issuer"
        }

        # Generate the self-signed certificate
        $cert = New-SelfSignedCertificate @certParams

        if ($null -eq $cert) {
            Write-EnhancedLog -Message "Failed to create certificate" -Level "ERROR"
            throw "Certificate creation failed"
        }

        Write-EnhancedLog -Message "Certificate created successfully" -Level "INFO"

        # Convert password to secure string
        $securePfxPassword = ConvertTo-SecureString -String $PfxPassword -Force -AsPlainText

        # Export the certificate to a PFX file
        $pfxFilePath = Join-Path -Path $OutputPath -ChildPath "$CertName-$AppId.pfx"
        Export-PfxCertificate -Cert $cert -FilePath $pfxFilePath -Password $securePfxPassword

        Write-EnhancedLog -Message "PFX file created successfully at $pfxFilePath" -Level "INFO"


        # $DBG

        return $cert

    } catch {
        Write-EnhancedLog -Message "An error occurred while creating the self-signed certificate." -Level "ERROR"
        Handle-Error -ErrorRecord $_ 
        throw $_
    }
}

# # Example usage
# $scopes = @("User.Read.All", "Application.ReadWrite.All", "Directory.ReadWrite.All")
# Connect-MgGraph -Scopes $scopes

# $certParams = @{
# CertName = "GraphCert"
# TenantName = $tenantDetails.DisplayName
# AppId = $app.AppId
# OutputPath = $certexportDirectory
# PfxPassword = $certPassword
# }
# $cert = Create-SelfSignedCert @certParams






#EndRegion '.\Public\Create-SelfSignedCert.ps1' 395
#Region '.\Public\Create-SelfSignedCertOpenSSL.ps1' -1

# We'll create the following functions:

# Get-CurrentUser
# Generate-Certificate
# Convert-CertificateToPfx
# Import-PfxCertificateToStore
# Create-SelfSignedCertOpenSSL




function Get-CurrentUser {
    try {
        Write-EnhancedLog -Message "Fetching current user information from Microsoft Graph API." -Level "INFO"
        $currentUserResponse = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/me" -Method GET
        Write-EnhancedLog -Message "Response from Microsoft Graph API: $($currentUserResponse | ConvertTo-Json -Compress)" -Level "DEBUG"
        return $currentUserResponse
    } catch {
        Write-EnhancedLog -Message "An error occurred while fetching the current user information." -Level "ERROR"
        Handle-Error -ErrorRecord $_
        throw $_
    }
}

function Convert-CertificateToPfx {
    param (
        [string]$CertKeyPath,
        [string]$CertCrtPath,
        [string]$PfxPath,
        [string]$PfxPassword
    )

    try {
        $opensslPfxCmd = "openssl pkcs12 -export -out `"$PfxPath`" -inkey `"$CertKeyPath`" -in `"$CertCrtPath`" -passout pass:$PfxPassword"
        Write-EnhancedLog -Message "Running OpenSSL command to convert certificate to PFX format: $opensslPfxCmd" -Level "INFO"

        $processInfo = New-Object System.Diagnostics.ProcessStartInfo
        $processInfo.FileName = "/bin/bash"
        $processInfo.Arguments = "-c `"$opensslPfxCmd`""
        $processInfo.RedirectStandardOutput = $true
        $processInfo.RedirectStandardError = $true
        $processInfo.UseShellExecute = $false
        $processInfo.CreateNoWindow = $true

        $process = New-Object System.Diagnostics.Process
        $process.StartInfo = $processInfo
        $process.Start() | Out-Null

        $stdout = $process.StandardOutput.ReadToEnd()
        $stderr = $process.StandardError.ReadToEnd()

        $process.WaitForExit()

        Write-EnhancedLog -Message "Standard Output: $stdout" -Level "DEBUG"
        Write-EnhancedLog -Message "Standard Error: $stderr" -Level "DEBUG"

        if ($process.ExitCode -ne 0) {
            Write-EnhancedLog -Message "OpenSSL PFX command failed with exit code $($process.ExitCode)" -Level "ERROR"
            throw "PFX file creation failed"
        }

        Write-EnhancedLog -Message "PFX file created successfully at $PfxPath" -Level "INFO"
    } catch {
        Write-EnhancedLog -Message "An error occurred while converting the certificate to PFX format." -Level "ERROR"
        Handle-Error -ErrorRecord $_
        throw $_
    }
}



function Import-PfxCertificateToStore {
    param (
        [string]$PfxPath,
        [string]$PfxPassword,
        [string]$CertStoreLocation
    )

    try {
        $securePfxPassword = ConvertTo-SecureString -String $PfxPassword -Force -AsPlainText
        $cert = Import-PfxCertificate -FilePath $PfxPath -Password $securePfxPassword -CertStoreLocation $CertStoreLocation
        Write-EnhancedLog -Message "Certificate imported successfully into store location $CertStoreLocation" -Level "INFO"
        return $cert
    } catch {
        Write-EnhancedLog -Message "An error occurred while importing the PFX certificate to the store." -Level "ERROR"
        Handle-Error -ErrorRecord $_
        throw $_
    }
}



function Create-DummyCertWithOpenSSL {
    param (
        # [string]$OutputDir = "/workspaces/cert"
        [string]$OutputDir
    )

    try {
        # Ensure the output directory exists
        if (-not (Test-Path -Path $OutputDir)) {
            New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
            Write-EnhancedLog -Message "Created output directory: $OutputDir" -Level "INFO"
        } else {
            Write-EnhancedLog -Message "Output directory already exists: $OutputDir" -Level "INFO"
        }

        # Define a simple command to run OpenSSL
        $opensslCmd = "openssl req -x509 -nodes -days 1 -newkey rsa:2048 -keyout $OutputDir/dummy.key -out $OutputDir/dummy.crt -subj '/CN=DummyCert/O=DummyOrg/C=US'"
        Write-EnhancedLog -Message "Running OpenSSL command: $opensslCmd" -Level "INFO"

        # Use Start-Process to execute the command
        $startInfo = New-Object System.Diagnostics.ProcessStartInfo
        $startInfo.FileName = "/bin/bash"
        $startInfo.Arguments = "-c `"$opensslCmd`""
        $startInfo.RedirectStandardOutput = $true
        $startInfo.RedirectStandardError = $true
        $startInfo.UseShellExecute = $false
        $startInfo.CreateNoWindow = $true

        $process = New-Object System.Diagnostics.Process
        $process.StartInfo = $startInfo
        $process.Start() | Out-Null

        # Capture the output
        $stdout = $process.StandardOutput.ReadToEnd()
        $stderr = $process.StandardError.ReadToEnd()

        $process.WaitForExit()

        # Output the results
        Write-EnhancedLog -Message "Standard Output: $stdout" -Level "INFO"
        Write-EnhancedLog -Message "Standard Error: $stderr" -Level "INFO"

        if ($process.ExitCode -ne 0) {
            Write-EnhancedLog -Message "OpenSSL command failed with exit code $($process.ExitCode)" -Level "ERROR"
            throw "Certificate creation failed"
        } else {
            Write-EnhancedLog -Message "Certificate created successfully using OpenSSL" -Level "INFO"
        }
    } catch {
        Write-EnhancedLog -Message "An error occurred while generating the certificate." -Level "ERROR"
        Handle-Error -ErrorRecord $_
        throw $_
    }
}

# # Test the function
# Create-DummyCertWithOpenSSL

# $DBG


function Create-SelfSignedCertOpenSSL {
    param (
        [string]$CertName,
        [string]$CertStoreLocation = "Cert:\CurrentUser\My",
        [string]$TenantName,
        [string]$AppId,
        [string]$OutputPath,
        [string]$PfxPassword
    )

    try {
        $currentUser = Get-CurrentUser

        # Create output directory if it doesn't exist
        if (-not (Test-Path -Path $OutputPath)) {
            New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null
        }

        Write-EnhancedLog -Message "calling Run-GenerateCertificateScript" -Level "INFO"
        $DBG
        # $certPaths = Run-GenerateCertificateScript -CertName $CertName -TenantName $TenantName -AppId $AppId -OutputPath $OutputPath -CurrentUser $currentUser -RealCertName $CertName -RealTenantName $TenantName -RealAppId $AppId

        Create-DummyCertWithOpenSSL -OutputDir $OutputPath

        Write-EnhancedLog -Message "Done calling Run-GenerateCertificateScript" -Level "INFO"

        $DBG

        $pfxFilePath = Join-Path -Path $OutputPath -ChildPath "$CertName-$AppId.pfx"

        Convert-CertificateToPfx -CertKeyPath $certPaths.KeyPath -CertCrtPath $certPaths.CrtPath -PfxPath $pfxFilePath -PfxPassword $PfxPassword

        if ($PSVersionTable.OS -match "Windows") {
            try {
                $securePfxPassword = ConvertTo-SecureString -String $PfxPassword -Force -AsPlainText
                $cert = Import-PfxCertificateToStore -FilePath $pfxFilePath -Password $securePfxPassword -CertStoreLocation $CertStoreLocation
                Write-EnhancedLog -Message "Certificate imported successfully into store location $CertStoreLocation" -Level "INFO"
            } catch {
                Write-EnhancedLog -Message "An error occurred while importing the PFX certificate to the store." -Level "ERROR"
                Handle-Error -ErrorRecord $_
                throw $_
            }
        } else {
            Write-EnhancedLog -Message "Running on a non-Windows OS, skipping the import of the PFX certificate to the store." -Level "INFO"
            $cert = $null
        }

        return $cert
    } catch {
        Write-EnhancedLog -Message "An error occurred while creating the self-signed certificate." -Level "ERROR"
        Handle-Error -ErrorRecord $_
        throw $_
    }
}
#EndRegion '.\Public\Create-SelfSignedCertOpenSSL.ps1' 208
#Region '.\Public\Create-UserFileBackupTask.ps1' -1

function Create-UserFileBackupTask {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$TaskPath,
        [Parameter(Mandatory = $true)]
        [string]$TaskName,
        [Parameter(Mandatory = $true)]
        [string]$ScriptDirectory,
        [Parameter(Mandatory = $true)]
        [string]$ScriptName,
        [Parameter(Mandatory = $true)]
        [string]$TaskArguments,
        [Parameter(Mandatory = $true)]
        [string]$TaskRepetitionDuration,
        [Parameter(Mandatory = $true)]
        [string]$TaskRepetitionInterval,
        [Parameter(Mandatory = $true)]
        [string]$TaskPrincipalGroupId,
        [Parameter(Mandatory = $true)]
        [string]$PowerShellPath,
        [Parameter(Mandatory = $true)]
        [string]$TaskDescription,
        [Parameter(Mandatory = $true)]
        [switch]$AtLogOn

    )

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

    Process {
        try {
            # Unregister the task if it exists
            Unregister-ScheduledTaskWithLogging -TaskName $TaskName

            $arguments = $TaskArguments.Replace("{ScriptPath}", "$ScriptDirectory\$ScriptName")

            $actionParams = @{
                Execute  = $PowerShellPath
                Argument = $arguments
            }
            $action = New-ScheduledTaskAction @actionParams

            $triggerParams = @{
                AtLogOn = $AtLogOn
            }
            
            $trigger = New-ScheduledTaskTrigger @triggerParams

            $principalParams = @{
                GroupId = $TaskPrincipalGroupId
            }
            $principal = New-ScheduledTaskPrincipal @principalParams

            $registerTaskParams = @{
                Principal   = $principal
                Action      = $action
                Trigger     = $trigger
                TaskName    = $TaskName
                Description = $TaskDescription
                TaskPath    = $TaskPath
            }
            $Task = Register-ScheduledTask @registerTaskParams

            $Task.Triggers.Repetition.Duration = $TaskRepetitionDuration
            $Task.Triggers.Repetition.Interval = $TaskRepetitionInterval
            $Task | Set-ScheduledTask
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while creating the OneDrive sync status task: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Create-UserFileBackupTask function" -Level "Notice"
    }
}

# # # # Example usage with splatting
# $CreateUserFileBackupTaskParams = @{
# TaskPath = "AAD Migration"
# TaskName = "Backup User Files"
# ScriptDirectory = "C:\ProgramData\AADMigration\Scripts"
# ScriptName = "BackupUserFiles.ps1"
# TaskArguments = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -file `"{ScriptPath}`""
# TaskRepetitionDuration = "P1D"
# TaskRepetitionInterval = "PT30M"
# TaskPrincipalGroupId = "BUILTIN\Users"
# PowerShellPath = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
# TaskDescription = "Backup User Files to OneDrive"
# AtLogOn = $true
# }

# Create-UserFileBackupTask @CreateUserFileBackupTaskParams
#EndRegion '.\Public\Create-UserFileBackupTask.ps1' 99
#Region '.\Public\Create-VBShiddenPS.ps1' -1



function Create-VBShiddenPS {

    <#
.SYNOPSIS
Creates a VBScript file to run a PowerShell script hidden from the user interface.
 
.DESCRIPTION
This function generates a VBScript (.vbs) file designed to execute a PowerShell script without displaying the PowerShell window. It's particularly useful for running background tasks or scripts that do not require user interaction. The path to the PowerShell script is taken as an argument, and the VBScript is created in a specified directory within the global path variable.
 
.EXAMPLE
$Path_VBShiddenPS = Create-VBShiddenPS
 
This example creates the VBScript file and returns its path.
#>



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

        [string]$DataFolder = "Data",

        [string]$FileName = "run-ps-hidden.vbs"
    )

    try {
        # Construct the full path for DataFolder and validate it manually
        $fullDataFolderPath = Join-Path -Path $Path_local -ChildPath $DataFolder
        if (-not (Test-Path -Path $fullDataFolderPath -PathType Container)) {
            throw "DataFolder does not exist or is not a directory: $fullDataFolderPath"
        }

        # Log message about creating VBScript
        Write-EnhancedLog -Message "Creating VBScript to hide PowerShell window..." -Level "INFO" -ForegroundColor Magenta

        $scriptBlock = @"
Dim shell,fso,file
 
Set shell=CreateObject("WScript.Shell")
Set fso=CreateObject("Scripting.FileSystemObject")
 
strPath=WScript.Arguments.Item(0)
 
If fso.FileExists(strPath) Then
    set file=fso.GetFile(strPath)
    strCMD="powershell -nologo -executionpolicy ByPass -command " & Chr(34) & "&{" & file.ShortPath & "}" & Chr(34)
    shell.Run strCMD,0
End If
"@


        # Combine paths to construct the full path for the VBScript
        $folderPath = $fullDataFolderPath
        $Path_VBShiddenPS = Join-Path -Path $folderPath -ChildPath $FileName

        # Write the script block to the VBScript file
        $scriptBlock | Out-File -FilePath (New-Item -Path $Path_VBShiddenPS -Force) -Force

        # Validate the VBScript file creation
        if (Test-Path -Path $Path_VBShiddenPS) {
            Write-EnhancedLog -Message "VBScript created successfully at $Path_VBShiddenPS" -Level "INFO" -ForegroundColor Green
        }
        else {
            throw "Failed to create VBScript at $Path_VBShiddenPS"
        }

        return $Path_VBShiddenPS
    }
    catch {
        Write-EnhancedLog -Message "An error occurred while creating VBScript: $_" -Level "ERROR" -ForegroundColor Red
        throw $_
    }
}
#EndRegion '.\Public\Create-VBShiddenPS.ps1' 76
#Region '.\Public\CreateAndRegisterScheduledTask.ps1' -1

function CreateAndRegisterScheduledTask {
    <#
    .SYNOPSIS
    Creates and registers a scheduled task based on the provided configuration, and executes it if necessary.
 
    .DESCRIPTION
    This function initializes variables, ensures necessary paths exist, copies files, creates a VBScript for hidden execution, and manages the execution of detection and remediation scripts. If the task does not exist, it sets up a new task environment and registers it.
 
    .PARAMETER ConfigPath
    The path to the JSON configuration file.
 
    .PARAMETER FileName
    The name of the file to be used for the VBScript.
 
    .PARAMETER Scriptroot
    The root directory where the scripts are located.
 
    .EXAMPLE
    CreateAndRegisterScheduledTask -ConfigPath "C:\Tasks\Config.json" -FileName "HiddenScript.vbs"
 
    This example creates and registers a scheduled task based on the provided configuration file and VBScript file name.
    #>


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

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

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

    begin {
        Write-EnhancedLog -Message 'Starting CreateAndRegisterScheduledTask function' -Level 'NOTICE'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Ensure the script runs with administrative privileges
        CheckAndElevate -ElevateIfNotAdmin $true   
    }

    process {
        try {

            # Load configuration from PSD1 file
            $config = Import-PowerShellDataFile -Path $ConfigPath

            # Initialize variables directly from the config
            $PackageName = $config.PackageName
            $PackageUniqueGUID = $config.PackageUniqueGUID
            $Version = $config.Version
            $ScriptMode = $config.ScriptMode
            $PackageExecutionContext = $config.PackageExecutionContext
            $RepetitionInterval = $config.RepetitionInterval
            $DataFolder = $config.DataFolder

            # Determine local path based on execution context if not provided
            if (-not $Path_local) {
                if (Test-RunningAsSystem) {
                    Write-EnhancedLog -Message "Detected SYSTEM context. Setting Path_local to $($config.PathLocalSystem)" -Level "CRITICAL"
                    $Path_local = $config.PathLocalSystem
                }
                else {
                    Write-EnhancedLog -Message "Not running as SYSTEM. Setting Path_local to $($config.PathLocalUser)" -Level "CRITICAL"
                    $Path_local = $config.PathLocalUser
                }
            }
            else {
                Write-EnhancedLog -Message "Path_local is already set to $Path_local" -Level "INFO"
            }
            

            $Path_PR = Join-Path -Path $Path_local -ChildPath "Data\$PackageName-$PackageUniqueGUID"
            $schtaskName = [string]::Format($config.TaskNameFormat, $PackageName, $PackageUniqueGUID)
            $schtaskDescription = [string]::Format($config.TaskDescriptionFormat, $Version)

            # Unregister the task if it exists
            Unregister-ScheduledTaskWithLogging -TaskName $schtaskName


            # Ensure script paths exist
            if (-not (Test-Path -Path $Path_local)) {
                New-Item -Path $Path_local -ItemType Directory -Force | Out-Null
                Write-EnhancedLog -Message "Created directory: $Path_local" -Level "CRITICAL"
            }

            if (-not (Test-Path -Path $Path_PR)) {
                New-Item -Path $Path_PR -ItemType Directory -Force | Out-Null
                Write-EnhancedLog -Message "Created directory: $Path_PR" -Level "CRITICAL"
            }

            # Copy files to path
            $CopyFilesToPathParams = @{
                SourcePath      = $Scriptroot
                DestinationPath = $Path_PR
            }
            Copy-FilesToPath @CopyFilesToPathParams

            # Verify copy operation
            $VerifyCopyOperationParams = @{
                SourcePath      = $Scriptroot
                DestinationPath = $Path_PR
            }
            Verify-CopyOperation @VerifyCopyOperationParams

            # Ensure the Data folder exists
            $DataFolderPath = Join-Path -Path $Path_local -ChildPath $DataFolder
            if (-not (Test-Path -Path $DataFolderPath -PathType Container)) {
                New-Item -ItemType Directory -Path $DataFolderPath -Force | Out-Null
                Write-EnhancedLog -Message "Data folder created at $DataFolderPath" -Level "INFO"
            }

            # Create the VBScript to run PowerShell script hidden
            try {
                $CreateVBShiddenPSParams = @{
                    Path_local = $Path_local
                    DataFolder = $DataFolder
                    FileName   = $FileName
                }
                $Path_VBShiddenPS = Create-VBShiddenPS @CreateVBShiddenPSParams

                # Validation of the VBScript file creation
                if (Test-Path -Path $Path_VBShiddenPS) {
                    Write-EnhancedLog -Message "Validation successful: VBScript file exists at $Path_VBShiddenPS" -Level "INFO"
                }
                else {
                    Write-EnhancedLog -Message "Validation failed: VBScript file does not exist at $Path_VBShiddenPS. Check script execution and permissions." -Level "WARNING"
                }
            }
            catch {
                Write-EnhancedLog -Message "An error occurred while creating VBScript: $_" -Level "ERROR"
            }

            # Check if the task exists and execute or create it accordingly
            $checkTaskParams = @{
                taskName = $schtaskName
            }

            $taskExists = Check-ExistingTask @checkTaskParams

            if ($taskExists) {
                Write-EnhancedLog -Message "Existing task found. Executing detection and remediation scripts." -Level "INFO"
                
                $executeParams = @{
                    Path_PR = $Path_PR
                }

                if ($config.ScheduleOnly -eq $true) {
                    Write-EnhancedLog -Message "Registering task with ScheduleOnly set to $($config.ScheduleOnly)" -Level "INFO"
                }
                else {
                    Write-EnhancedLog -Message "Executing detection and remediation scripts." -Level "INFO"
                    Execute-DetectionAndRemediation @executeParams
                }
            }
            else {
                Write-EnhancedLog -Message "No existing task found. Setting up new task environment." -Level "INFO"

                # Setup new task environment
                $Path_PSscript = switch ($ScriptMode) {
                    "Remediation" { Join-Path $Path_PR $config.ScriptPaths.Remediation }
                    "PackageName" { Join-Path $Path_PR $config.ScriptPaths.PackageName }
                    Default { throw "Invalid ScriptMode: $ScriptMode. Expected 'Remediation' or 'PackageName'." }
                }
                

                # Register the scheduled task
                $startTime = (Get-Date).AddMinutes($config.StartTimeOffsetMinutes).ToString("HH:mm")

                if ($config.UsePSADT) {
                    Write-EnhancedLog -Message "Setting up Schedule Task action for Service UI and PSADT" -Level "INFO"
                
                    # Define the path to the PowerShell Application Deployment Toolkit executable
                    $ToolkitExecutable = Join-Path -Path $Path_PR -ChildPath $config.ToolkitExecutablePath
                    Write-EnhancedLog -Message "ToolkitExecutable set to: $ToolkitExecutable" -Level "INFO"
                
                    # Define the path to the ServiceUI executable
                    $ServiceUIExecutable = Join-Path -Path $Path_PR -ChildPath $config.ServiceUIExecutablePath
                    Write-EnhancedLog -Message "ServiceUIExecutable set to: $ServiceUIExecutable" -Level "INFO"
                
                    # Define the deployment type from the config
                    $DeploymentType = $config.DeploymentType
                    Write-EnhancedLog -Message "DeploymentType set to: $DeploymentType" -Level "INFO"
                
                    # Define the arguments for ServiceUI.exe
                    $argList = "-process:$($config.ProcessName) `"$ToolkitExecutable`" -DeploymentType $DeploymentType"
                    Write-EnhancedLog -Message "ServiceUI arguments: $argList" -Level "CRITICAL"
                
                    # Create the scheduled task action
                    $action = New-ScheduledTaskAction -Execute $ServiceUIExecutable -Argument $argList
                    Write-EnhancedLog -Message "Scheduled Task action $action for Service UI and PSADT created." -Level "INFO"
                }
                else {
                    Write-EnhancedLog -Message "Setting up Scheduled Task action for wscript and VBS" -Level "INFO"
                
                    # Define the arguments for wscript.exe
                    $argList = "`"$Path_VBShiddenPS`" `"$Path_PSscript`""
                    Write-EnhancedLog -Message "wscript arguments: $argList" -Level "INFO"
                
                    # Define the path to wscript.exe from the config
                    $WscriptPath = $config.WscriptPath
                    Write-EnhancedLog -Message "WscriptPath set to: $WscriptPath" -Level "INFO"
                
                    # Create the scheduled task action using the path from the config
                    $action = New-ScheduledTaskAction -Execute $WscriptPath -Argument $argList
                    Write-EnhancedLog -Message "Scheduled Task action for wscript and VBS created." -Level "INFO"
                }
                

                # Define the trigger based on the TriggerType
                $trigger = switch ($config.TriggerType) {
                    "Daily" {
                        Write-EnhancedLog -Message "Trigger set to Daily at $startTime" -Level "INFO"
                        $trigger = New-ScheduledTaskTrigger -Daily -At $startTime

                        # Apply StartBoundary and Delay if provided
                        if ($config.StartBoundary) {
                            $trigger.StartBoundary = $config.StartBoundary
                            Write-EnhancedLog -Message "StartBoundary set to $($config.StartBoundary)" -Level "INFO"
                        }

                        if ($config.Delay) {
                            $trigger.Delay = $config.Delay
                            Write-EnhancedLog -Message "Delay set to $($config.Delay)" -Level "INFO"
                        }
                        $trigger
                    }
                    "Logon" {
                        if (-not $config.LogonUserId) {
                            throw "LogonUserId must be specified for Logon trigger type."
                        }
                        Write-EnhancedLog -Message "Trigger set to logon of user $($config.LogonUserId)" -Level "INFO"
                        $trigger = New-ScheduledTaskTrigger -AtLogOn

                        # Only apply StartBoundary if provided
                        if ($config.StartBoundary) {
                            $trigger.StartBoundary = $config.StartBoundary
                            Write-EnhancedLog -Message "StartBoundary set to $($config.StartBoundary)" -Level "INFO"
                        }
                        $trigger
                    }
                    "AtStartup" {
                        Write-EnhancedLog -Message "Trigger set at startup" -Level "INFO"
                        $trigger = New-ScheduledTaskTrigger -AtStartup

                        # Apply StartBoundary and Delay if provided
                        if ($config.StartBoundary) {
                            $trigger.StartBoundary = $config.StartBoundary
                            Write-EnhancedLog -Message "StartBoundary set to $($config.StartBoundary)" -Level "INFO"
                        }

                        if ($config.Delay) {
                            $trigger.Delay = $config.Delay
                            Write-EnhancedLog -Message "Delay set to $($config.Delay)" -Level "INFO"
                        }
                        $trigger
                    }
                    default {
                        throw "Invalid TriggerType specified in the configuration."
                    }
                }



                $principal = New-ScheduledTaskPrincipal -UserId $config.PrincipalUserId -LogonType $config.LogonType -RunLevel $config.RunLevel

                # Ensure the task path is set, use the default root "\" if not specified
                $taskPath = if ($config.TaskFolderPath) { $config.TaskFolderPath } else { "\" }

                # Create a hashtable for common parameters
                $registerTaskParams = @{
                    TaskName    = $schtaskName
                    Action      = $action
                    Principal   = $principal
                    Description = $schtaskDescription
                    TaskPath    = $taskPath
                    Force       = $true
                }

                # Check if RunOnDemand is true and modify the hashtable accordingly
                if ($config.RunOnDemand -eq $true) {
                    Write-EnhancedLog -Message "Registering task with RunOnDemand set to $($config.RunOnDemand) at path $taskPath" -Level "CRITICAL"
                    # Register the task without a trigger (Run on demand)
                    $task = Register-ScheduledTask @registerTaskParams
                }
                else {
                    Write-EnhancedLog -Message "Registering task with RunOnDemand set to $($config.RunOnDemand) at path $taskPath" -Level "CRITICAL"
                    # Add the Trigger to the hashtable
                    $registerTaskParams.Trigger = $trigger
                    # Register the task with the trigger
                    $task = Register-ScheduledTask @registerTaskParams
                }


                $task = Get-ScheduledTask -TaskName $schtaskName

                if ($config.Repeat -eq $true) {
                    Write-EnhancedLog -Message "Registering task with Repeat set to $($config.Repeat)" -Level "INFO"
                    $task.Triggers[0].Repetition.Interval = $RepetitionInterval
                }

                $task | Set-ScheduledTask
                if ($PackageExecutionContext -eq $config.TaskExecutionContext) {
                    $ShedService = New-Object -ComObject 'Schedule.Service'
                    $ShedService.Connect()
                    $taskFolder = $ShedService.GetFolder($config.TaskFolderPath)
                    $Task = $taskFolder.GetTask("$schtaskName")
                    $taskFolder.RegisterTaskDefinition(
                        "$schtaskName", 
                        $Task.Definition, 
                        $config.TaskRegistrationFlags, 
                        $config.TaskUserGroup, 
                        $null, 
                        $config.TaskLogonType
                    )
                }                

                Write-EnhancedLog -Message "Scheduled task $schtaskName registered successfully." -Level "INFO"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred: $_" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'CreateAndRegisterScheduledTask function completed' -Level 'NOTICE'
    }
}



# CreateAndRegisterScheduledTask -ConfigPath "C:\code\IntuneDeviceMigration\DeviceMigration\config.psd1" -FileName "HiddenScript.vbs"

# # Define the parameters using a hashtable
# $taskParams = @{
# ConfigPath = "C:\code\IntuneDeviceMigration\DeviceMigration\config.psd1"
# FileName = "HiddenScript.vbs"
# Scriptroot = "$PSScriptroot"
# }

# # Call the function with the splatted parameters
# CreateAndRegisterScheduledTask @taskParams






# # ################################################################################################################################
# # ############### CALLING AS SYSTEM to simulate Intune deployment as SYSTEM (Uncomment for debugging) ############################
# # ################################################################################################################################

# # Example usage
# $ensureRunningAsSystemParams = @{
# PsExec64Path = Join-Path -Path $PSScriptRoot -ChildPath "private\PsExec64.exe"
# ScriptPath = $MyInvocation.MyCommand.Path
# TargetFolder = Join-Path -Path $PSScriptRoot -ChildPath "private"
# }

# Ensure-RunningAsSystem @ensureRunningAsSystemParams



# # ################################################################################################################################
# # ############### END CALLING AS SYSTEM to simulate Intune deployment as SYSTEM (Uncomment for debugging) ########################
# # ################################################################################################################################



# # Example usage of Download-And-Install-ServiceUI function with splatting
# $DownloadAndInstallServiceUIparams = @{
# TargetFolder = "$PSScriptRoot"
# DownloadUrl = "https://download.microsoft.com/download/3/3/9/339BE62D-B4B8-4956-B58D-73C4685FC492/MicrosoftDeploymentToolkit_x64.msi"
# MsiFileName = "MicrosoftDeploymentToolkit_x64.msi"
# InstalledServiceUIPath = "C:\Program Files\Microsoft Deployment Toolkit\Templates\Distribution\Tools\x64\ServiceUI.exe"
# }
# Download-And-Install-ServiceUI @DownloadAndInstallServiceUIparams


# # Example usage
# $DownloadPSAppDeployToolkitparams = @{
# GithubRepository = 'PSAppDeployToolkit/PSAppDeployToolkit'
# FilenamePatternMatch = '*.zip'
# ScriptDirectory = $PSScriptRoot
# }
# Download-PSAppDeployToolkit @DownloadPSAppDeployToolkitparams



# #right before rebooting we will schedule our install script (which is our script2 or our post-reboot script to run automatically at startup under the SYSTEM account)
# # here I need to pass these in the config file (JSON or PSD1) or here in the splat but I need to have it outside of the function


# # $schedulerconfigPath = Join-Path -Path $PSScriptRoot -ChildPath "config.psd1"
# $schedulerconfigPath = "C:\code\IntuneDeviceMigration\DeviceMigration\Interactive-Migration-Task-config.psd1"
# $taskParams = @{
# ConfigPath = $schedulerconfigPath
# FileName = "run-ps-hidden.vbs"
# Scriptroot = $PSScriptRoot
# }

# CreateAndRegisterScheduledTask @taskParams


# Yes, there is a difference between `StartBoundary` and `StartTime` in the context of scheduled tasks in PowerShell.

# ### 1. **StartBoundary:**
# - **Definition:** `StartBoundary` is a property of a `ScheduledTaskTrigger` that defines the earliest time that the trigger can activate. It sets the date and time at which the task is eligible to run for the first time.
# - **Format:** `StartBoundary` is typically set in an ISO 8601 date and time format, such as `"2024-08-16T12:00:00"`.
# - **Purpose:** It's used to specify when the task becomes active. It doesn't specify a specific time the task should run every day, but rather when the task is allowed to start running, especially for triggers like `Daily`, `AtLogOn`, or `AtStartup`.
# - **Example Use Case:** If you want a task to only be eligible to run after a certain date and time, you would set `StartBoundary`.

# ### 2. **StartTime:**
# - **Definition:** `StartTime` is a property specifically associated with `Daily` or `Weekly` triggers and defines the exact time of day the task should start.
# - **Format:** `StartTime` is generally a time-only value, like `"09:00:00"`.
# - **Purpose:** It is used to specify a time of day for tasks that need to run on a recurring schedule (e.g., daily or weekly). It indicates when the task should be triggered every day (or on specified days of the week).
# - **Example Use Case:** If you want a task to run every day at 9:00 AM, you would set the `StartTime` to `"09:00:00"`.

# ### Practical Differences:
# - **StartBoundary:** Controls when the task becomes eligible to run. It’s a one-time setting that dictates when the task can first start, often used with non-recurring tasks or as a gate for when recurring tasks can start.
# - **StartTime:** Controls the exact time on a daily or weekly basis when the task should be executed. It’s used for recurring tasks that need to start at the same time every day or on specific days of the week.

# ### Example Scenario:
# If you want a task to start running every day at 9:00 AM but only start doing so from September 1, 2024, you would set:
# - `StartBoundary = "2024-09-01T00:00:00"` (the task won’t run before this date).
# - `StartTime = "09:00:00"` (the task will run at 9:00 AM daily after September 1, 2024).

# ### Conclusion:
# - **Use `StartBoundary`** to define when the task becomes eligible to start (based on date and time).
# - **Use `StartTime`** to define the time of day for recurring tasks (daily or weekly schedules).
#EndRegion '.\Public\CreateAndRegisterScheduledTask.ps1' 435
#Region '.\Public\CreateVMFolder.ps1' -1

function CreateVMFolder {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$VMPath,
        
        [Parameter(Mandatory = $true)]
        [string]$VMName
    )

    Begin {
        Write-EnhancedLog -Message "Starting CreateVMFolder function" -Level "INFO"
        Log-Params -Params @{ VMPath = $VMPath; VMName = $VMName }
    }

    Process {
        try {
            $VMFullPath = Join-Path -Path $VMPath -ChildPath $VMName
            Write-EnhancedLog -Message "Creating VM folder at path: $VMFullPath" -Level "INFO"
            New-Item -ItemType Directory -Force -Path $VMFullPath | Out-Null
            Write-EnhancedLog -Message "VM folder created at $VMFullPath" -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
            return $VMFullPath
        } catch {
            Write-EnhancedLog -Message "An error occurred while creating the VM folder: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Create-VMFolder function" -Level "INFO"
    }
}
#EndRegion '.\Public\CreateVMFolder.ps1' 33
#Region '.\Public\Define-SourcePath.ps1' -1

function Define-SourcePath {
    <#
    .SYNOPSIS
    Defines the source path for a given program.
 
    .DESCRIPTION
    This function dynamically constructs the source path for a program using the provided `Repo_winget` path and the program's ID. It logs the path and returns the path as part of an object.
 
    .PARAMETER Prg
    The program object containing metadata like the ID of the program.
 
    .PARAMETER Repo_winget
    The path to the repository (winget) where the program source resides.
 
    .EXAMPLE
    $sourcePathDetails = Define-SourcePath -Prg $Prg -Repo_winget "C:\Repo\winget"
    Defines the source path for the program and returns the path details.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the program object.")]
        [ValidateNotNullOrEmpty()]
        [PSCustomObject]$Prg,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the path to the winget repository.")]
        [ValidateNotNullOrEmpty()]
        [string]$Repo_winget
    )

    Begin {
        Write-EnhancedLog -Message "Starting Define-SourcePath function for program: $($Prg.id)" -Level "INFO"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Construct the source path using the program ID and the Repo_winget path
            $sourcePath = Join-Path -Path $Repo_winget -ChildPath $Prg.id
            $Prg_Path = $sourcePath

            # Log the source path
            Write-EnhancedLog -Message "Source path defined: $Prg_Path" -Level "INFO"

            # Return the source path as part of an object
            return [pscustomobject]@{
                ProgramID   = $Prg.id
                SourcePath  = $sourcePath
            }
        }
        catch {
            Write-EnhancedLog -Message "Error occurred during Define-SourcePath: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
    }

    End {
        Write-EnhancedLog -Message "Completed Define-SourcePath function." -Level "INFO"
    }
}




# # Run Define-SourcePath and store the returned object
# Example usage of the Define-SourcePath function
# $sourcePathDetails = Define-SourcePath -Prg $Prg -Repo_winget "C:\Repo\winget"

# # Access the returned object properties
# Write-Host "Program ID: $($sourcePathDetails.ProgramID)"
# Write-Host "Source Path: $($sourcePathDetails.SourcePath)"


# Program ID: MyProgram
# Source Path: C:\path\to\Win32Apps-DropBox\MyProgram
#EndRegion '.\Public\Define-SourcePath.ps1' 77
#Region '.\Public\Detect-BitLockerStatus.ps1' -1

function Detect-BitLockerStatus {
    [CmdletBinding()]
    param (
        [string[]]$DriveLetters = @("C:")
    )

    begin {
        Write-EnhancedLog -Message 'Starting Detect-BitLockerStatus function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        foreach ($drive in $DriveLetters) {
            try {
                $bitLockerStatus = Get-BitLockerVolume -MountPoint $drive

                if ($bitLockerStatus) {
                    $protectionStatus = $bitLockerStatus.ProtectionStatus
                    Write-EnhancedLog -Message "BitLocker status for drive $drive $protectionStatus" -Level 'INFO'
                    Write-Output "BitLocker status for drive $drive $protectionStatus"
                } else {
                    Write-EnhancedLog -Message "BitLocker status not found for drive $drive" -Level 'WARNING'
                    Write-Output "BitLocker status not found for drive $drive"
                }
            } catch {
                Handle-Error -ErrorRecord $_
            }
        }
    }

    end {
        Write-EnhancedLog -Message 'Detect-BitLockerStatus function completed' -Level 'INFO'
    }
}

# # Example usage of Detect-BitLockerStatus function with splatting
# $params = @{
# DriveLetters = @("C:", "D:")
# }

# # Call the Detect-BitLockerStatus function using splatting
# Detect-BitLockerStatus @params
#EndRegion '.\Public\Detect-BitLockerStatus.ps1' 43
#Region '.\Public\Detect-FortiClientEMSInstallation.ps1' -1

function Detect-FortiClientEMSInstallation {
    <#
    .SYNOPSIS
    Checks for FortiClientEMS installation and version.
    .PARAMETER RegistryPaths
    An array of registry paths to check.
    .PARAMETER SoftwareName
    The name of the software to search for.
    .PARAMETER ExcludedVersion
    The version of the software to exclude.
    .OUTPUTS
    A hashtable indicating whether the software is installed and its version.
    #>

    [CmdletBinding()]
    param (
        [string[]]$RegistryPaths,
        [string]$SoftwareName,
        [version]$ExcludedVersion
    )

    foreach ($path in $RegistryPaths) {
        $items = Get-ChildItem -Path $path -ErrorAction SilentlyContinue

        foreach ($item in $items) {
            $app = Get-ItemProperty -Path $item.PsPath -ErrorAction SilentlyContinue
            if ($app.DisplayName -like "*$SoftwareName*") {
                $installedVersion = New-Object Version $app.DisplayVersion
                if ($installedVersion -lt $ExcludedVersion) {
                    return @{
                        IsInstalled = $true
                        Version = $app.DisplayVersion
                        ProductCode = $app.PSChildName
                    }
                }
            }
        }
    }

    return @{IsInstalled = $false}
}
#EndRegion '.\Public\Detect-FortiClientEMSInstallation.ps1' 41
#Region '.\Public\Detect-SystemMode.ps1' -1

function Detect-SystemMode {
    [CmdletBinding()]
    param (
        [string]$RegistryPath
    )

    begin {
        Write-EnhancedLog -Message 'Starting Detect-SystemMode function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            $safeMode = Get-ItemProperty -Path $RegistryPath -ErrorAction Stop

            if ($safeMode.Option -eq 1) {
                Write-EnhancedLog -Message "System is in Safe Mode" -Level 'INFO'
                $Global:SystemMode = "Safe Mode"
            } else {
                Write-EnhancedLog -Message "System is in Normal Mode" -Level 'INFO'
                $Global:SystemMode = "Normal Mode"
            }
        } catch {
            Write-EnhancedLog -Message "System is in Normal Mode (SafeBoot key not found)" -Level 'INFO'
            $Global:SystemMode = "Normal Mode"
        }
    }

    end {
        Write-EnhancedLog -Message 'Detect-SystemMode function completed' -Level 'INFO'
    }
}

# # Example usage of Detect-SystemMode function with splatting
# $params = @{
# RegistryPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\SafeBoot\Option'
# }

# # Call the Detect-SystemMode function using splatting
# Detect-SystemMode @params

# Access the result
# $SystemMode
#EndRegion '.\Public\Detect-SystemMode.ps1' 44
#Region '.\Public\Disable-LocalUserAccounts.ps1' -1

function Disable-LocalUserAccounts {
    <#
    .SYNOPSIS
    Disables all enabled local user accounts except for default accounts.
   
    .DESCRIPTION
    The Disable-LocalUserAccounts function disables all enabled local user accounts except for default accounts.
   
    .EXAMPLE
    Disable-LocalUserAccounts
    Disables all enabled local user accounts except for default accounts.
    #>

  
    [CmdletBinding()]
    param ()
  
    Begin {
        Write-EnhancedLog -Message "Starting Disable-LocalUserAccounts function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }
  
    Process {
        try {
            # Get all enabled local user accounts except for default accounts
            $users = Get-LocalUser | Where-Object { $_.Enabled -eq $true -and $_.Name -notlike 'default*' }
            
            foreach ($user in $users) {
                Write-EnhancedLog -Message "Disabling local user account: $($user.Name)" -Level "INFO"
                Disable-LocalUser -Name $user.Name -ErrorAction Stop
                Write-EnhancedLog -Message "Successfully disabled local user account: $($user.Name)" -Level "INFO"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Disable-LocalUserAccounts function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }
  
    End {
        Write-EnhancedLog -Message "Exiting Disable-LocalUserAccounts function" -Level "Notice"
    }
}

# Example usage
# Disable-LocalUserAccounts
#EndRegion '.\Public\Disable-LocalUserAccounts.ps1' 47
#Region '.\Public\Disable-OOBEPrivacy.ps1' -1

function Disable-OOBEPrivacy {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$OOBERegistryPath,

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

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

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

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

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

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

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

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

    Begin {
        Write-EnhancedLog -Message "Starting Disable-OOBEPrivacy function" -Level "INFO"
        Log-Params -Params @{
            OOBERegistryPath      = $OOBERegistryPath
            OOBEName              = $OOBEName
            OOBEValue             = $OOBEValue
            AnimationRegistryPath = $AnimationRegistryPath
            AnimationName         = $AnimationName
            AnimationValue        = $AnimationValue
            LockRegistryPath      = $LockRegistryPath
            LockName              = $LockName
            LockValue             = $LockValue
        }
    }

    Process {
        try {
            Write-EnhancedLog -Message "Disabling privacy experience" -Level "INFO"
            if (-not (Test-Path -Path $OOBERegistryPath)) {
                New-Item -Path $OOBERegistryPath -Force | Out-Null
            }
            New-ItemProperty -Path $OOBERegistryPath -Name $OOBEName -Value $OOBEValue -PropertyType DWORD -Force -Verbose

            Write-EnhancedLog -Message "Disabling first logon animation" -Level "INFO"
            if (-not (Test-Path -Path $AnimationRegistryPath)) {
                New-Item -Path $AnimationRegistryPath -Force | Out-Null
            }
            New-ItemProperty -Path $AnimationRegistryPath -Name $AnimationName -Value $AnimationValue -PropertyType DWORD -Force -Verbose

            Write-EnhancedLog -Message "Removing lock screen" -Level "INFO"
            if (-not (Test-Path -Path $LockRegistryPath)) {
                New-Item -Path $LockRegistryPath -Force | Out-Null
            }
            New-ItemProperty -Path $LockRegistryPath -Name $LockName -Value $LockValue -PropertyType DWORD -Force -Verbose
        } catch {
            Write-EnhancedLog -Message "An error occurred while disabling OOBE privacy: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Disable-OOBEPrivacy function" -Level "INFO"
    }
}

# # Example usage with splatting
# $DisableOOBEPrivacyParams = @{
# OOBERegistryPath = 'HKLM:\Software\Policies\Microsoft\Windows\OOBE'
# OOBEName = 'DisablePrivacyExperience'
# OOBEValue = '1'
# AnimationRegistryPath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
# AnimationName = 'EnableFirstLogonAnimation'
# AnimationValue = '0'
# LockRegistryPath = 'HKLM:\Software\Policies\Microsoft\Windows\Personalization'
# LockName = 'NoLockScreen'
# LockValue = '1'
# }

# Disable-OOBEPrivacy @DisableOOBEPrivacyParams
#EndRegion '.\Public\Disable-OOBEPrivacy.ps1' 91
#Region '.\Public\Disable-ScheduledTaskByPath.ps1' -1

function Disable-ScheduledTaskByPath {
    <#
    .SYNOPSIS
    Disables a scheduled task by its name and path.
 
    .DESCRIPTION
    This function disables a scheduled task specified by its task name and path.
 
    .PARAMETER TaskName
    The name of the scheduled task.
 
    .PARAMETER TaskPath
    The path of the scheduled task.
 
    .EXAMPLE
    Disable-ScheduledTaskByPath -TaskName "TaskName" -TaskPath "\Folder\"
 
    This will disable the task "TaskName" in the folder "\Folder\" in Task Scheduler.
 
    .NOTES
    This function is useful for disabling tasks located in specific subfolders within the Task Scheduler.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the name of the scheduled task.")]
        [ValidateNotNullOrEmpty()]
        [string]$TaskName,
        
        [Parameter(Mandatory = $true, HelpMessage = "Provide the path of the scheduled task.")]
        [ValidateNotNullOrEmpty()]
        [string]$TaskPath
    )

    Begin {
        Write-EnhancedLog -Message "Starting Disable-ScheduledTaskByPath function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            Write-EnhancedLog -Message "Searching for scheduled task with Name: $TaskName and Path: $TaskPath" -Level "INFO"

            # Search for the scheduled task by TaskName and TaskPath
            $Task = Get-ScheduledTask | Where-Object { $_.TaskPath -eq $TaskPath -and $_.TaskName -eq $TaskName }

            if ($Task) {
                # Disable the found scheduled task
                Write-EnhancedLog -Message "Scheduled task found: '$($Task.TaskPath + $Task.TaskName)'. Disabling the task." -Level "INFO"
                Disable-ScheduledTask -TaskName ($Task.TaskPath + $Task.TaskName)
                Write-EnhancedLog -Message "Scheduled task '$($Task.TaskPath + $Task.TaskName)' has been successfully disabled." -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "Scheduled task '$TaskPath$TaskName' not found." -Level "WARNING"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while disabling the scheduled task: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
        finally {
            Write-EnhancedLog -Message "Exiting Disable-ScheduledTaskByPath function" -Level "NOTICE"
        }
    }

    End {
        Write-EnhancedLog -Message "Disable-ScheduledTaskByPath function completed" -Level "INFO"
    }
}
#EndRegion '.\Public\Disable-ScheduledTaskByPath.ps1' 71
#Region '.\Public\Dismount-VHDX.ps1' -1

function Dismount-VHDX {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$VHDXPath
    )

    Begin {
        Write-EnhancedLog -Message "Starting Dismount-VHDX function" -Level "INFO"
        Log-Params -Params @{ VHDXPath = $VHDXPath }
    }

    Process {
        try {
            Write-EnhancedLog -Message "Validating if the VHDX is mounted: $VHDXPath" -Level "INFO"
            $isMounted = Validate-VHDMount -VHDXPath $VHDXPath
            Write-EnhancedLog -Message "Validation result: VHDX is mounted = $isMounted" -Level "INFO"

            if ($isMounted) {
                Write-EnhancedLog -Message "Checking for dependent VMs using the VHDX: $VHDXPath" -Level "INFO"
                $dependentVMs = Get-DependentVMs -VHDXPath $VHDXPath
                $runningVMs = $dependentVMs | Where-Object { $_.State -eq 'Running' }
                
                if ($runningVMs.Count -gt 0) {
                    Write-EnhancedLog -Message "Found running VMs using the VHDX. Skipping dismount." -Level "WARNING"
                    foreach ($vm in $runningVMs) {
                        Write-EnhancedLog -Message "Running dependent VM: $($vm.Name)" -Level "WARNING"
                    }
                    return
                }

                Write-EnhancedLog -Message "No running VMs found using the VHDX. Proceeding with dismount." -Level "INFO"
                Dismount-VHD -Path $VHDXPath -ErrorAction Stop
                Write-EnhancedLog -Message "VHDX dismounted successfully." -Level "INFO"
            } else {
                Write-EnhancedLog -Message "$VHDXPath is already dismounted or not mounted." -Level "INFO"
            }
        } catch {
            Write-EnhancedLog -Message "An error occurred while dismounting the VHDX: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Dismount-VHDX function" -Level "INFO"
    }
}
#EndRegion '.\Public\Dismount-VHDX.ps1' 48
#Region '.\Public\Download-ADKOffline.ps1' -1

function Download-ADKOffline {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$ADKUrl = "https://go.microsoft.com/fwlink/?linkid=2271337",

        [Parameter(Mandatory = $true)]
        [string]$DownloadPath = "$env:TEMP\adksetup.exe",

        [Parameter(Mandatory = $true)]
        [string]$OfflinePath = "$env:TEMP\ADKOffline"
    )

    Begin {
        Write-EnhancedLog -Message "Starting Download-ADKOffline function" -Level "INFO"
        Log-Params -Params @{
            ADKUrl = $ADKUrl
            DownloadPath = $DownloadPath
            OfflinePath = $OfflinePath
        }
    }

    Process {
        try {
            # Download the ADK setup file
            Write-EnhancedLog -Message "Downloading ADK from: $ADKUrl to: $DownloadPath" -Level "INFO"
            Invoke-WebRequest -Uri $ADKUrl -OutFile $DownloadPath

            # Create offline path if it does not exist
            if (-not (Test-Path -Path $OfflinePath)) {
                New-Item -ItemType Directory -Path $OfflinePath -Force
            }

            # Download the ADK components for offline installation
            Write-EnhancedLog -Message "Downloading ADK components for offline installation to: $OfflinePath" -Level "INFO"
            Start-Process -FilePath $DownloadPath -ArgumentList "/quiet", "/layout $OfflinePath" -Wait -NoNewWindow
        } catch {
            Write-EnhancedLog -Message "An error occurred while processing the Download-ADKOffline function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Download-ADKOffline function" -Level "INFO"
    }
}

# Example usage
# $adkParams = @{
# ADKUrl = 'https://go.microsoft.com/fwlink/?linkid=2271337'
# DownloadPath = "$env:TEMP\adksetup.exe"
# OfflinePath = "$env:TEMP\ADKOffline"
# }

# Download-ADKOffline @adkParams
#EndRegion '.\Public\Download-ADKOffline.ps1' 56
#Region '.\Public\Download-And-Install-ServiceUI.ps1' -1

function Download-And-Install-ServiceUI {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$TargetFolder,

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

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

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

    begin {
        # Log the start of the function
        Write-EnhancedLog -Message "Starting Download-And-Install-ServiceUI function" -Level "INFO"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Validate MDT installation before attempting to download or install
        $mdtValidationParams = @{
            SoftwareName        = "Microsoft Deployment Toolkit"
            MinVersion          = [version]"6.3.8456.1000"  # The version from the screenshot
            # RegistryPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{MDT ProductCode}" # You need to replace this with the actual product code for MDT from the registry
            # ExePath = "C:\Program Files\Microsoft Deployment Toolkit\Templates\Distribution\Tools\x64\ServiceUI.exe" # Path to the MDT executable
            MaxRetries          = 3
            DelayBetweenRetries = 5
        }

        $mdtValidationResult = Validate-SoftwareInstallation @mdtValidationParams

        if ($mdtValidationResult.IsInstalled) {
            Write-EnhancedLog -Message "MDT is already installed and meets the minimum version requirement." -Level "INFO"
            $skipInstallation = $true
        }
        else {
            Write-EnhancedLog -Message "MDT is not installed or does not meet the minimum version requirement. Proceeding with installation." -Level "INFO"
            $skipInstallation = $false
        }
    }

    process {
        if (-not $skipInstallation) {
            # Set up paths for download and installation
            $timestamp = Get-Date -Format "yyyyMMddHHmmss"
            $msiPath = Join-Path -Path $([System.IO.Path]::GetTempPath()) -ChildPath "$($timestamp)_$MsiFileName"
            $finalPath = Join-Path -Path $TargetFolder -ChildPath "ServiceUI.exe"

            try {
                # Download the MSI file
                $downloadParams = @{
                    Source      = $DownloadUrl
                    Destination = $msiPath
                    MaxRetries  = 3
                }
                Start-FileDownloadWithRetry @downloadParams

                # Install the MDT MSI package
                $installParams = @{
                    FilePath     = "msiexec.exe"
                    ArgumentList = "/i `"$msiPath`" /quiet /norestart"
                    Wait         = $true
                }
                Write-EnhancedLog -Message "Installing MDT MSI from: $msiPath" -Level "INFO"
                Start-Process @installParams

                # Validate MDT installation after installation
                $mdtValidationResult = Validate-SoftwareInstallation @mdtValidationParams

                if (-not $mdtValidationResult.IsInstalled) {
                    Write-EnhancedLog -Message "MDT installation failed or does not meet the minimum version requirement." -Level "ERROR"
                    throw "MDT installation failed."
                }

                Write-EnhancedLog -Message "MDT installed successfully." -Level "INFO"

                # Remove the downloaded MSI file
                Write-EnhancedLog -Message "Removing downloaded MSI file: $msiPath" -Level "INFO"
                Remove-Item -Path $msiPath -Force
            }
            catch {
                Write-EnhancedLog -Message "An error occurred: $_" -Level "ERROR"
                Handle-Error -ErrorRecord $_
                throw $_
            }
        }


        # Set the final path where ServiceUI.exe should be located
        $finalPath = Join-Path -Path $TargetFolder -ChildPath "ServiceUI.exe"

        # Copy ServiceUI.exe to the target folder
        if (Test-Path -Path $InstalledServiceUIPath) {
            Write-EnhancedLog -Message "Copying ServiceUI.exe from $InstalledServiceUIPath to $finalPath" -Level "INFO"
            Copy-Item -Path $InstalledServiceUIPath -Destination $TargetFolder -Force

            # Debug: Check the value of $finalPath
            Write-EnhancedLog -Message "Final path for verification: $finalPath" -Level "INFO"

            # Verify the copy operation
            if (Test-Path -Path $finalPath) {
                Write-EnhancedLog -Message "Copy verification completed successfully." -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "Copy verification failed: ServiceUI.exe not found at $finalPath" -Level "ERROR"
                throw "ServiceUI.exe not found after copy operation."
            }
        }
        else {
            Write-EnhancedLog -Message "ServiceUI.exe not found at: $InstalledServiceUIPath" -Level "ERROR"
            throw "ServiceUI.exe not found."
        }

    }

    end {
        # Log the end of the function
        Write-EnhancedLog -Message "Download-And-Install-ServiceUI function execution completed." -Level "INFO"
    }
}


# $params = @{
# TargetFolder = "C:\temp";
# DownloadUrl = "https://download.microsoft.com/download/3/3/9/339BE62D-B4B8-4956-B58D-73C4685FC492/MicrosoftDeploymentToolkit_x64.msi";
# MsiFileName = "MicrosoftDeploymentToolkit_x64.msi";
# InstalledServiceUIPath = "C:\Program Files\Microsoft Deployment Toolkit\Templates\Distribution\Tools\x64\ServiceUI.exe"
# }
# Download-And-Install-ServiceUI @params
#EndRegion '.\Public\Download-And-Install-ServiceUI.ps1' 132
#Region '.\Public\Download-Handle.ps1' -1

function Download-Handle {
    <#
    .SYNOPSIS
    Downloads and extracts Handle64.exe from the official Sysinternals Handle package.
 
    .DESCRIPTION
    The Download-Handle function downloads the Handle package from the official Sysinternals website, extracts Handle64.exe, and places it in the specified target folder.
 
    .PARAMETER TargetFolder
    The target folder where Handle64.exe will be stored.
 
    .EXAMPLE
    $params = @{
        TargetFolder = "C:\ProgramData\SystemTools"
    }
    Download-Handle @params
    Downloads Handle64.exe to the specified target folder.
    #>


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

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

        # Ensure the target folder exists
        Ensure-TargetFolderExists -TargetFolder $TargetFolder
        Write-EnhancedLog -Message "Removing existing Handle from target folder: $TargetFolder" -Level "INFO"
        
        Remove-ExistingHandle -TargetFolder $TargetFolder
    }

    Process {
        try {
            # Define the URL for Handle download
            $url = "https://download.sysinternals.com/files/Handle.zip"
            # Full path for the downloaded file
            $timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
            $zipPath = Join-Path -Path $TargetFolder -ChildPath "Handle_$timestamp.zip"

            # Download the Handle.zip file with retry logic
            Write-EnhancedLog -Message "Downloading Handle.zip from: $url to: $zipPath" -Level "INFO"
            
            $downloadParams = @{
                Source      = $url
                Destination = $zipPath
                MaxRetries  = 3
            }
            Start-FileDownloadWithRetry @downloadParams

            # Extract Handle64.exe from the zip file
            Write-EnhancedLog -Message "Extracting Handle.zip to: $TargetFolder\Handle" -Level "INFO"
            Expand-Archive -Path $zipPath -DestinationPath "$TargetFolder\Handle" -Force

            # Specific extraction of Handle64.exe
            $extractedFolderPath = Join-Path -Path $TargetFolder -ChildPath "Handle"
            $Handle64Path = Join-Path -Path $extractedFolderPath -ChildPath "Handle64.exe"
            $finalPath = Join-Path -Path $TargetFolder -ChildPath "Handle64.exe"

            # Move Handle64.exe to the desired location
            if (Test-Path -Path $Handle64Path) {
                Write-EnhancedLog -Message "Moving Handle64.exe from: $Handle64Path to: $finalPath" -Level "INFO"
                Move-Item -Path $Handle64Path -Destination $finalPath

                # Remove the downloaded zip file and extracted folder
                Write-EnhancedLog -Message "Removing downloaded zip file and extracted folder" -Level "INFO"
                Remove-Item -Path $zipPath -Force
                Remove-Item -Path $extractedFolderPath -Recurse -Force

                Write-EnhancedLog -Message "Handle64.exe has been successfully downloaded and moved to: $finalPath" -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "Handle64.exe not found in the extracted files." -Level "ERROR"
                throw "Handle64.exe not found after extraction."
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Download-Handle function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Download-Handle function" -Level "Notice"
    }
}

# Example usage
# $params = @{
# TargetFolder = "C:\ProgramData\SystemTools"
# }
# Download-Handle @params
#EndRegion '.\Public\Download-Handle.ps1' 98
#Region '.\Public\Download-InstallMDT.ps1' -1

# function Download-InstallMDT {
# [CmdletBinding()]
# param (
# [Parameter(Mandatory = $true)]
# [string]$Url,

# [Parameter(Mandatory = $true)]
# [string]$Destination,

# [Parameter(Mandatory = $true)]
# [string]$FilesFolder
# )

# Begin {
# Write-EnhancedLog -Message "Starting Download-Install-MDT function" -Level "INFO"
# Log-Params -Params @{
# Url = $Url
# Destination = $Destination
# FilesFolder = $FilesFolder
# }
# }

# Process {
# try {
# # Download and install Microsoft Deployment Toolkit
# Invoke-WebRequest -Uri $Url -OutFile $Destination
# Start-Process -FilePath $Destination -ArgumentList "/quiet" -Wait

# # Copy ServiceUI.exe to Files folder
# Copy-Item -Path "C:\Program Files\Microsoft Deployment Toolkit\Templates\Distribution\Tools\x64\ServiceUI.exe" -Destination $FilesFolder
# } catch {
# Write-EnhancedLog -Message "An error occurred while processing the Download-InstallMDT function: $($_.Exception.Message)" -Level "ERROR"
# Handle-Error -ErrorRecord $_
# }
# }

# End {
# Write-EnhancedLog -Message "Exiting Download-Install-MDT function" -Level "INFO"
# }
# }

# # Example usage
# # Download-InstallMDT -Url 'https://download.microsoft.com/download/9/e/1/9e1e94ec-5463-46b7-9f3c-b225034c3a70/MDT_KB4564442.exe' -Destination 'C:\YourPath\Files\MDT.exe' -FilesFolder 'C:\YourPath\Files'
#EndRegion '.\Public\Download-InstallMDT.ps1' 44
#Region '.\Public\Download-MigrationTool.ps1' -1

function Download-MigrationTool {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Url,

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

    Begin {
        Write-EnhancedLog -Message "Starting Download-MigrationTool function" -Level "INFO"
        Log-Params -Params @{
            Url = $Url
            Destination = $Destination
        }
    }

    Process {
        try {
            # Download Migration Tool
            Invoke-WebRequest -Uri $Url -OutFile $Destination
        } catch {
            Write-EnhancedLog -Message "An error occurred while processing the Download-MigrationTool function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Download-MigrationTool function" -Level "INFO"
    }
}

# Example usage
# Download-MigrationTool -Url "https://example.com/tool.zip" -Destination "C:\path\to\destination"
#EndRegion '.\Public\Download-MigrationTool.ps1' 36
#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-ODSyncUtil.ps1' -1

function Download-ODSyncUtil {
    <#
    .SYNOPSIS
    Downloads and extracts the latest ODSyncUtil from the OneDrive Sync Utility GitHub repository for Windows 11.
 
    .DESCRIPTION
    The Download-ODSyncUtil function retrieves the latest release of ODSyncUtil from the GitHub repository, downloads the ZIP file, extracts it, and places the executable in the specified destination folder.
 
    .PARAMETER Destination
    The destination folder where ODSyncUtil.exe will be stored.
 
    .PARAMETER ApiUrl
    The GitHub API URL to retrieve the latest release information.
 
    .PARAMETER ZipFileName
    The name of the ZIP file to be downloaded (e.g., "ODSyncUtil-64-bit.zip").
 
    .PARAMETER ExecutableName
    The name of the executable to be extracted from the ZIP file (e.g., "ODSyncUtil.exe").
 
    .PARAMETER MaxRetries
    The maximum number of retries for the download process.
 
    .EXAMPLE
    $params = @{
        Destination = "C:\YourPath\Files\ODSyncUtil.exe"
        ApiUrl = "https://api.github.com/repos/rodneyviana/ODSyncUtil/releases/latest"
        ZipFileName = "ODSyncUtil-64-bit.zip"
        ExecutableName = "ODSyncUtil.exe"
        MaxRetries = 3
    }
    Download-ODSyncUtil @params
    Downloads and extracts ODSyncUtil.exe to the specified destination folder.
    #>


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

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

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

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

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

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

    }

    Process {
        try {
            # Get the latest release info from GitHub
            Write-EnhancedLog -Message "Retrieving latest release info from GitHub API: $ApiUrl" -Level "INFO"
            $releaseInfo = Invoke-RestMethod -Uri $ApiUrl

            # Find the download URL for the specified ZIP file
            $downloadUrl = $releaseInfo.assets | Where-Object { $_.name -eq $ZipFileName } | Select-Object -ExpandProperty browser_download_url

            if (-not $downloadUrl) {
                $errorMessage = "No matching file found for $ZipFileName"
                Write-EnhancedLog -Message $errorMessage -Level "Critical"
                throw $errorMessage
            }

            # Define the ZIP file path
            $zipFilefolder = Split-Path -Path $Destination -Parent
            $zipFilePath = Join-Path -Path (Split-Path -Path $Destination -Parent) -ChildPath $ZipFileName


            #Remove the Existing Zip Folder Folder if found
            if (Test-Path -Path $zipFilefolder) {
                Write-EnhancedLog -Message "Found $zipFilefolder. Removing it..." -Level "INFO"
                try {
                    Remove-Item -Path $zipFilefolder -Recurse -Force
                    Write-EnhancedLog -Message "Successfully removed $zipFilefolder." -Level "INFO"
                }
                catch {
                    Write-EnhancedLog -Message "Failed to remove $zipFilefolder $($_.Exception.Message)" -Level "ERROR"
                    Handle-Error -ErrorRecord $_
                    throw $_
                }
            }
            else {
                Write-EnhancedLog -Message "$zipFilefolder not found. No action required." -Level "INFO"
            }

            # $DBG


            # Define the splatting parameters for the download
            $downloadParams = @{
                Source      = $downloadUrl
                Destination = $zipFilePath
                MaxRetries  = $MaxRetries
            }

            Write-EnhancedLog -Message "Downloading $ZipFileName from: $downloadUrl to: $zipFilePath" -Level "INFO"
            Start-FileDownloadWithRetry @downloadParams

            # Extract the executable from the ZIP file
            Write-EnhancedLog -Message "Extracting $ZipFileName to: $(Split-Path -Path $Destination -Parent)" -Level "INFO"
            Expand-Archive -Path $zipFilePath -DestinationPath (Split-Path -Path $Destination -Parent) -Force

            # Move the extracted executable to the desired location
            $extractedExePath = Join-Path -Path (Split-Path -Path $Destination -Parent) -ChildPath $ExecutableName
            if (Test-Path -Path $extractedExePath) {
                Write-EnhancedLog -Message "Moving $ExecutableName to: $Destination" -Level "INFO"
                Move-Item -Path $extractedExePath -Destination $Destination -Force

                # Remove the downloaded ZIP file and the extracted folder
                # Write-EnhancedLog -Message "Cleaning up: Removing downloaded ZIP file from $zipFilePath and extracted files from $extractedExePath" -Level "INFO"
                # Remove-Item -Path $zipFilePath -Force
                # Remove-Item -Path (Split-Path -Path $extractedExePath -Parent) -Recurse -Force
            }
            else {
                $errorMessage = "$ExecutableName not found after extraction."
                Write-EnhancedLog -Message $errorMessage -Level "Critical"
                throw $errorMessage
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Download-ODSyncUtil function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Download-ODSyncUtil function" -Level "Notice"
    }
}

# # # # Example usage
# $params = @{
# Destination = "C:\code\IntuneDeviceMigration\DeviceMigration\Files\ODSyncUtil\ODSyncUtil.exe"
# ApiUrl = "https://api.github.com/repos/rodneyviana/ODSyncUtil/releases/latest"
# ZipFileName = "ODSyncUtil-64-bit.zip"
# ExecutableName = "ODSyncUtil.exe"
# MaxRetries = 3
# }
# Download-ODSyncUtil @params
#EndRegion '.\Public\Download-ODSyncUtil.ps1' 152
#Region '.\Public\Download-ODT.ps1' -1

function Get-LatestODTUrl {
    <#
    .SYNOPSIS
    Retrieves the latest Office Deployment Tool (ODT) download URL from the Microsoft website.
 
    .DESCRIPTION
    This function attempts to scrape the latest ODT download link from the Microsoft download page. It includes a retry mechanism and waits between attempts if the request fails.
 
    .PARAMETER MaxRetries
    The maximum number of retries if the web request fails.
 
    .PARAMETER RetryInterval
    The number of seconds to wait between retries.
 
    .EXAMPLE
    $odtUrl = Get-LatestODTUrl -MaxRetries 3 -RetryInterval 5
    if ($odtUrl) { Write-Host "ODT URL: $odtUrl" }
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false, HelpMessage = "Maximum number of retries.")]
        [int]$MaxRetries = 3,

        [Parameter(Mandatory = $false, HelpMessage = "Number of seconds to wait between retries.")]
        [int]$RetryInterval = 5
    )

    Begin {
        Write-EnhancedLog -Message "Starting Get-LatestODTUrl function" -Level "NOTICE"
        Write-EnhancedLog -Message "MaxRetries: $MaxRetries, RetryInterval: $RetryInterval seconds" -Level "INFO"

        $odtPageUrl = "https://www.microsoft.com/en-us/download/details.aspx?id=49117"
        $attempt = 0
        $odtDownloadLink = $null
    }

    Process {
        while ($attempt -lt $MaxRetries -and -not $odtDownloadLink) {
            try {
                $attempt++
                Write-EnhancedLog -Message "Attempt $attempt of $MaxRetries to retrieve ODT URL..." -Level "INFO"

                # Use Invoke-WebRequest with UseBasicParsing to avoid the IE engine
                $response = Invoke-WebRequest -Uri $odtPageUrl -UseBasicParsing -ErrorAction Stop
                Write-EnhancedLog -Message "Successfully retrieved page content on attempt $attempt." -Level "INFO"

                # Search for the ODT download link in the page content
                $odtDownloadLink = $response.Links | Where-Object { $_.href -match "download.microsoft.com" } | Select-Object -First 1

                if ($odtDownloadLink) {
                    Write-EnhancedLog -Message "ODT download link found: $($odtDownloadLink.href)" -Level "INFO"
                    return $odtDownloadLink.href
                } else {
                    Write-EnhancedLog -Message "ODT download link not found on attempt $attempt." -Level "WARNING"
                }
            }
            catch {
                Write-EnhancedLog -Message "Failed to retrieve ODT download page on attempt $attempt $($_.Exception.Message)" -Level "ERROR"
            }

            # Wait before the next attempt
            if ($attempt -lt $MaxRetries) {
                Write-EnhancedLog -Message "Waiting $RetryInterval seconds before retrying..." -Level "INFO"
                Start-Sleep -Seconds $RetryInterval
            }
        }

        # If all attempts fail
        Write-EnhancedLog -Message "Exceeded maximum retry attempts. Failed to retrieve ODT download link." -Level "ERROR"
        return $null
    }

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

# # Example usage with retries
# $odtUrlParams = @{
# MaxRetries = 3
# RetryInterval = 5
# }
# $latestODTUrl = Get-LatestODTUrl @odtUrlParams

# if ($latestODTUrl) {
# Write-Host "Latest ODT URL: $latestODTUrl"
# } else {
# Write-Host "Failed to get the latest ODT URL." -ForegroundColor Red
# }

# # Wait-Debugger


function Download-ODT {
    <#
    .SYNOPSIS
    Downloads the Office Deployment Tool (ODT) and extracts it into a timestamped temp folder.
 
    .DESCRIPTION
    This function dynamically retrieves the latest ODT download URL using the Get-LatestODTUrl function, downloads the Office Deployment Tool (ODT) from that URL, runs it to extract the contents, and returns the status and full path to the `setup.exe` file.
 
    .PARAMETER DestinationDirectory
    The directory where the ODT will be extracted (default is a timestamped folder in temp).
 
    .PARAMETER MaxRetries
    The maximum number of retries for downloading the ODT file.
 
    .EXAMPLE
    $params = @{
        DestinationDirectory = "C:\Temp"
        MaxRetries = 3
    }
    $odtInfo = Download-ODT @params
    if ($odtInfo.Status -eq 'Success') {
        Write-Host "ODT setup.exe located at: $($odtInfo.FullPath)"
    }
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false, HelpMessage = "Provide the destination directory for extraction.")]
        [string]$DestinationDirectory = ([System.IO.Path]::GetTempPath()),

        [Parameter(Mandatory = $false, HelpMessage = "Maximum number of retries for downloading.")]
        [int]$MaxRetries = 3
    )

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

        # Generate a timestamped folder for extraction
        $timestamp = Get-Date -Format "yyyyMMddHHmmss"
        $tempFolder = Join-Path -Path $DestinationDirectory -ChildPath "ODT_$timestamp"
        $tempExePath = Join-Path -Path $tempFolder -ChildPath "ODT.exe"

        # Initialize result object
        $result = [pscustomobject]@{
            Status   = "Failure"
            FullPath = $null
        }

        # Ensure destination folder exists
        if (-not (Test-Path $tempFolder)) {
            New-Item -Path $tempFolder -ItemType Directory -Force | Out-Null
            Write-EnhancedLog -Message "Created temporary folder: $tempFolder" -Level "INFO"
        } else {
            Write-EnhancedLog -Message "Temporary folder already exists: $tempFolder" -Level "INFO"
        }
    }

    Process {
        try {
            # Fetch the latest ODT URL dynamically
            Write-EnhancedLog -Message "Fetching the latest ODT URL dynamically..." -Level "INFO"
            $odtDownloadUrlParams = @{
                MaxRetries    = $MaxRetries
                RetryInterval = 5
            }
            $ODTDownloadUrl = Get-LatestODTUrl @odtDownloadUrlParams

            if (-not $ODTDownloadUrl) {
                Write-EnhancedLog -Message "Failed to retrieve the latest ODT download URL." -Level "ERROR"
                throw "Failed to retrieve the latest ODT download URL."
            }

            # Splatting download parameters
            $downloadParams = @{
                Source      = $ODTDownloadUrl
                Destination = $tempExePath
                MaxRetries  = $MaxRetries
            }

            Write-EnhancedLog -Message "Downloading ODT from $ODTDownloadUrl to $tempExePath" -Level "INFO"
            Start-FileDownloadWithRetry @downloadParams

            # Unblock and verify the downloaded file
            Write-EnhancedLog -Message "Unblocking downloaded file: $tempExePath" -Level "INFO"
            Unblock-File -Path $tempExePath

            # Run the executable to extract files
            Write-EnhancedLog -Message "Extracting ODT using $tempExePath" -Level "INFO"
            $startProcessParams = @{
                FilePath     = $tempExePath
                ArgumentList = "/quiet /extract:$tempFolder"
                Wait         = $true
            }
            Start-Process @startProcessParams

            # Locate setup.exe in the extracted files
            $setupExePath = Get-ChildItem -Path $tempFolder -Recurse -Filter "setup.exe" | Select-Object -First 1

            if ($setupExePath) {
                Write-EnhancedLog -Message "ODT downloaded and extracted successfully. Found setup.exe at: $($setupExePath.FullName)" -Level "INFO"
                $result.Status = "Success"
                $result.FullPath = $setupExePath.FullName
            } else {
                Write-EnhancedLog -Message "Error: setup.exe not found in the extracted files." -Level "ERROR"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred during the download or extraction: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
        finally {
            # Clean up the downloaded .exe file
            if (Test-Path $tempExePath) {
                Write-EnhancedLog -Message "Cleaning up temporary exe file: $tempExePath" -Level "INFO"
                Remove-Item -Path $tempExePath -Force
            }
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Download-ODT function" -Level "NOTICE"
        # Return the result object with status and full path of setup.exe
        return $result
    }
}

# # Example usage:
# $params = @{
# DestinationDirectory = "$env:TEMP"
# MaxRetries = 3
# }
# $odtInfo = Download-ODT @params

# if ($odtInfo.Status -eq 'Success') {
# Write-Host "ODT setup.exe located at: $($odtInfo.FullPath)"
# } else {
# Write-Host "Failed to download or extract ODT." -ForegroundColor Red
# }

#EndRegion '.\Public\Download-ODT.ps1' 236
#Region '.\Public\Download-OneDriveLib.ps1' -1

function Download-OneDriveLib {
    <#
    .SYNOPSIS
    Downloads the latest OneDriveLib.dll from the OneDrive Sync Util GitHub repository.
 
    .DESCRIPTION
    The Download-OneDriveLib function retrieves the latest release of OneDriveLib.dll from the GitHub repository of the OneDrive Sync Util and downloads it to the specified destination folder.
 
    .PARAMETER Destination
    The destination folder where OneDriveLib.dll will be stored.
 
    .PARAMETER ApiUrl
    The GitHub API URL to retrieve the latest release information.
 
    .PARAMETER FileName
    The name of the file to be downloaded (e.g., "OneDriveLib.dll").
 
    .PARAMETER MaxRetries
    The maximum number of retries for the download process.
 
    .EXAMPLE
    $params = @{
        Destination = "C:\YourPath\Files\OneDriveLib.dll"
        ApiUrl = "https://api.github.com/repos/rodneyviana/ODSyncService/releases/latest"
        FileName = "OneDriveLib.dll"
        MaxRetries = 3
    }
    Download-OneDriveLib @params
    Downloads OneDriveLib.dll to the specified destination folder.
    #>


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

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

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

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

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

    Process {
        try {
            # Get the latest release info from GitHub
            Write-EnhancedLog -Message "Retrieving latest release info from GitHub API: $ApiUrl" -Level "INFO"
            $releaseInfo = Invoke-RestMethod -Uri $ApiUrl

            # Find the download URL for the specified file
            $downloadUrl = $releaseInfo.assets | Where-Object { $_.name -eq $FileName } | Select-Object -ExpandProperty browser_download_url

            if (-not $downloadUrl) {
                $errorMessage = "No matching file found for $FileName"
                Write-EnhancedLog -Message $errorMessage -Level "Critical"
                throw $errorMessage
            }

            # Define the splatting parameters for the download
            $downloadParams = @{
                Source      = $downloadUrl
                Destination = $Destination
                MaxRetries  = $MaxRetries
            }

            Write-EnhancedLog -Message "Downloading $FileName from: $downloadUrl to: $Destination" -Level "INFO"
            Start-FileDownloadWithRetry @downloadParams
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Download-OneDriveLib function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Download-OneDriveLib function" -Level "Notice"
    }
}

# # Example usage
# $params = @{
# Destination = "C:\YourPath\Files\OneDriveLib.dll"
# ApiUrl = "https://api.github.com/repos/rodneyviana/ODSyncService/releases/latest"
# FileName = "OneDriveLib.dll"
# MaxRetries = 3
# }
# Download-OneDriveLib @params
#EndRegion '.\Public\Download-OneDriveLib.ps1' 97
#Region '.\Public\Download-OneDriveSetup.ps1' -1

# Function to download OneDrive setup with retry logic
function Download-OneDriveSetup {
    <#
    .SYNOPSIS
        Downloads the OneDrive setup executable.
 
    .DESCRIPTION
        Downloads the OneDrive setup executable from the specified URL to the given destination path.
        Uses the Start-FileDownloadWithRetry function for robust download handling with retries.
 
    .PARAMETER ODSetupUri
        The URL of the OneDrive setup executable.
 
    .PARAMETER ODSetupPath
        The file path where the OneDrive setup executable will be saved.
 
    .EXAMPLE
        Download-OneDriveSetup -ODSetupUri "https://go.microsoft.com/fwlink/?linkid=844652" -ODSetupPath "C:\Temp\OneDriveSetup.exe"
 
    .NOTES
        Author: Abdullah Ollivierre
        Date: 2024-08-15
    #>


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

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

    Begin {
        Write-EnhancedLog -Message "Starting Download-OneDriveSetup function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        Write-EnhancedLog -Message "Starting Start-FileDownloadWithRetry function" -Level "NOTICE"
        Start-FileDownloadWithRetry -Source $ODSetupUri -Destination $ODSetupPath -MaxRetries 3
        Write-EnhancedLog -Message "Downloaded OneDrive setup to $ODSetupPath" -Level "INFO"
    }

    End {
        Write-EnhancedLog -Message "Exiting Download-OneDriveSetup function" -Level "NOTICE"
    }
}
# Function to install OneDrive
#EndRegion '.\Public\Download-OneDriveSetup.ps1' 50
#Region '.\Public\Download-PSAppDeployToolkit.ps1' -1

function Download-PSAppDeployToolkit {
    <#
    .SYNOPSIS
    Downloads and installs the PSAppDeployToolkit from a GitHub repository with customizations.
 
    .DESCRIPTION
    The Download-PSAppDeployToolkit function downloads the latest release of the PSAppDeployToolkit from the specified GitHub repository. It handles the file download, extraction, and customization, and performs verification on the extracted files and copied customizations.
 
    .PARAMETER GithubRepository
    The GitHub repository from which to download the PSAppDeployToolkit.
 
    .PARAMETER FilenamePatternMatch
    The filename pattern to match for the PSAppDeployToolkit zip file.
 
    .PARAMETER DestinationDirectory
    The directory where the toolkit will be extracted.
 
    .PARAMETER CustomizationsPath
    The path to the folder containing customization files (e.g., .ps1, .png).
 
    .EXAMPLE
    $params = @{
        GithubRepository = 'PSAppDeployToolkit/PSAppDeployToolkit';
        FilenamePatternMatch = '*.zip';
        DestinationDirectory = 'C:\temp\psadt1';
        CustomizationsPath = 'C:\code\IntuneDeviceMigration\DeviceMigration\PSADT-Customizations'
    }
    Download-PSAppDeployToolkit @params
    Downloads the latest PSAppDeployToolkit and applies customizations.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the GitHub repository.")]
        [ValidateNotNullOrEmpty()]
        [string]$GithubRepository,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the filename pattern to match.")]
        [ValidateNotNullOrEmpty()]
        [string]$FilenamePatternMatch,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the destination directory.")]
        [ValidateNotNullOrEmpty()]
        [string]$DestinationDirectory,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the path to customizations.")]
        [ValidateNotNullOrEmpty()]
        [string]$CustomizationsPath
    )

    Begin {
        Write-EnhancedLog -Message "Starting Download-PSAppDeployToolkit function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
        
        # Construct GitHub release URI
        $psadtReleaseUri = "https://api.github.com/repos/$GithubRepository/releases/latest"
        Write-EnhancedLog -Message "GitHub release URI: $psadtReleaseUri" -Level "INFO"
        
    }

    Process {
        try {
            # Fetch the download URL from GitHub
            Write-EnhancedLog -Message "Fetching the latest release information from GitHub" -Level "INFO"
            $psadtDownloadUri = (Invoke-RestMethod -Method GET -Uri $psadtReleaseUri).assets |
            Where-Object { $_.name -like $FilenamePatternMatch } |
            Select-Object -ExpandProperty browser_download_url

            if (-not $psadtDownloadUri) {
                throw "No matching file found for pattern: $FilenamePatternMatch"
            }
            Write-EnhancedLog -Message "Found matching download URL: $psadtDownloadUri" -Level "INFO"

            # Prepare paths and download the file
            $timestamp = Get-Date -Format "yyyyMMddHHmmss"
            $tempDownloadPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "PSAppDeployToolkit_$timestamp.zip"
            Write-EnhancedLog -Message "Temporary download path: $tempDownloadPath" -Level "INFO"

            # Download with retries
            $downloadParams = @{
                Source      = $psadtDownloadUri
                Destination = $tempDownloadPath
                MaxRetries  = 3
            }
            Start-FileDownloadWithRetry @downloadParams

            # Unblock and verify downloaded file
            Write-EnhancedLog -Message "Unblocking file at $tempDownloadPath" -Level "INFO"
            Unblock-File -Path $tempDownloadPath

            # Handle extraction
            $finalDestinationPath = Join-Path -Path $DestinationDirectory -ChildPath "PSAppDeployToolkit"
            Write-EnhancedLog -Message "Final destination path: $finalDestinationPath" -Level "INFO"

            if (Test-Path $finalDestinationPath) {
                $removeParams = @{
                    Path               = $finalDestinationPath
                    ForceKillProcesses = $true
                    MaxRetries         = 5
                    RetryInterval      = 10
                }
                Remove-EnhancedItem @removeParams
                Write-EnhancedLog -Message "Removed existing destination path: $finalDestinationPath" -Level "INFO"
            }

            Write-EnhancedLog -Message "Extracting files to $finalDestinationPath" -Level "INFO"
            Expand-Archive -Path $tempDownloadPath -DestinationPath $finalDestinationPath -Force

            # Apply customizations
            Write-EnhancedLog -Message "Applying customizations from $CustomizationsPath" -Level "INFO"
            Apply-PSADTCustomizations -DestinationPath $finalDestinationPath -CustomizationsPath $CustomizationsPath
        }
        catch {
            Write-EnhancedLog -Message "Error in Process block: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
        finally {
            # Clean up temporary download file
            Write-EnhancedLog -Message "Removing temporary download file: $tempDownloadPath" -Level "INFO"
            $removeTempParams = @{
                Path               = $tempDownloadPath
                ForceKillProcesses = $true
                MaxRetries         = 3
                RetryInterval      = 5
            }
            Remove-EnhancedItem @removeTempParams
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Download-PSAppDeployToolkit function" -Level "Notice"
    }
}

function Apply-PSADTCustomizations {
    param (
        [string]$DestinationPath,
        [string]$CustomizationsPath
    )

    Write-EnhancedLog -Message "Copying PS1 and PNG files from customization folder to Toolkit" -Level "INFO"

    # Copy .ps1 files
    Copy-Item -Path (Join-Path $CustomizationsPath '*.ps1') -Destination "$DestinationPath\Toolkit" -Force


    # Copy .xml files
    Copy-Item -Path (Join-Path $CustomizationsPath '*.xml') -Destination "$DestinationPath\Toolkit" -Force

    # Copy .png files
    $appDeployToolkitFolder = "$DestinationPath\Toolkit\AppDeployToolkit"
    if (-not (Test-Path $appDeployToolkitFolder)) {
        Write-EnhancedLog -Message "Error: The AppDeployToolkit folder does not exist at $appDeployToolkitFolder" -Level "ERROR"
        throw "The AppDeployToolkit folder does not exist."
    }

    Copy-Item -Path (Join-Path $CustomizationsPath '*.png') -Destination $appDeployToolkitFolder -Force

    # Verification
    Verify-PSADTFileCopy -CustomizationsPath $CustomizationsPath -DestinationPath $DestinationPath
}

function Verify-PSADTFileCopy {
    param (
        [string]$CustomizationsPath,
        [string]$DestinationPath
    )

    # Verify PS1 files
    Write-EnhancedLog -Message "Verifying PS1 file copy operation..." -Level "INFO"
    $ps1Files = Get-ChildItem -Path (Join-Path $CustomizationsPath '*.ps1')
    $ps1CopyVerification = [System.Collections.Generic.List[string]]::new()

    foreach ($file in $ps1Files) {
        $destinationFile = Join-Path "$DestinationPath\Toolkit" $file.Name
        if (-not (Test-Path $destinationFile)) {
            $ps1CopyVerification.Add("$($file.Name) was not copied.")
        }
    }

    if ($ps1CopyVerification.Count -gt 0) {
        Write-EnhancedLog -Message "Discrepancies found during PS1 file copy verification: $($ps1CopyVerification -join ', ')" -Level "ERROR"
    }
    else {
        Write-EnhancedLog -Message "PS1 file copy verification completed successfully with no discrepancies." -Level "INFO"
    }

    # Verify PNG files
    Write-EnhancedLog -Message "Verifying PNG file copy operation..." -Level "INFO"
    $pngFiles = Get-ChildItem -Path (Join-Path $CustomizationsPath '*.png')
    $pngCopyVerification = [System.Collections.Generic.List[string]]::new()

    foreach ($file in $pngFiles) {
        $destinationFile = Join-Path "$DestinationPath\Toolkit\AppDeployToolkit" $file.Name
        if (-not (Test-Path $destinationFile)) {
            $pngCopyVerification.Add("$($file.Name) was not copied.")
        }
    }

    if ($pngCopyVerification.Count -gt 0) {
        Write-EnhancedLog -Message "Discrepancies found during PNG file copy verification: $($pngCopyVerification -join ', ')" -Level "ERROR"
    }
    else {
        Write-EnhancedLog -Message "PNG file copy verification completed successfully with no discrepancies." -Level "INFO"
    }
}
#EndRegion '.\Public\Download-PSAppDeployToolkit.ps1' 208
#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\Enable-LocalUserAccounts.ps1' -1

function Enable-LocalUserAccounts {
    <#
    .SYNOPSIS
    Enables all local user accounts.
   
    .DESCRIPTION
    The Enable-LocalUserAccounts function enables all local user accounts, without any filtering.
   
    .EXAMPLE
    Enable-LocalUserAccounts
    Enables all local user accounts.
    #>

  
    [CmdletBinding()]
    param ()
  
    Begin {
        Write-EnhancedLog -Message "Starting Enable-LocalUserAccounts function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }
  
    Process {
        try {
            # Get all local user accounts
            $users = Get-LocalUser
            
            foreach ($user in $users) {
                Write-EnhancedLog -Message "Enabling local user account: $($user.Name)" -Level "INFO"
                Enable-LocalUser -Name $user.Name -ErrorAction Stop
                Write-EnhancedLog -Message "Successfully enabled local user account: $($user.Name)" -Level "INFO"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Enable-LocalUserAccounts function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }
  
    End {
        Write-EnhancedLog -Message "Exiting Enable-LocalUserAccounts function" -Level "Notice"
    }
}

# Example usage
# Enable-LocalUserAccounts
#EndRegion '.\Public\Enable-LocalUserAccounts.ps1' 47
#Region '.\Public\EnableVMTPM.ps1' -1

function EnableVMTPM {
    <#
    .SYNOPSIS
    Enables TPM for the specified VM.
    #>

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

    Begin {
        Write-EnhancedLog -Message "Starting Enable-VMTPM function" -Level "INFO"
        Log-Params -Params @{ VMName = $VMName }
    }

    Process {
        try {
            Write-EnhancedLog -Message "Retrieving HGS Guardian" -Level "INFO"
            $owner = Get-HgsGuardian -Name "UntrustedGuardian"

            Write-EnhancedLog -Message "Creating new HGS Key Protector" -Level "INFO"
            $kp = New-HgsKeyProtector -Owner $owner -AllowUntrustedRoot

            Write-EnhancedLog -Message "Setting VM Key Protector for VM: $VMName" -Level "INFO"
            Set-VMKeyProtector -VMName $VMName -KeyProtector $kp.RawData

            Write-EnhancedLog -Message "Enabling TPM for VM: $VMName" -Level "INFO"
            Enable-VMTPM -VMName $VMName

            Write-EnhancedLog -Message "TPM enabled for $VMName" -Level "INFO"
        } catch {
            Write-EnhancedLog -Message "An error occurred while enabling TPM for VM $VMName $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Enable-VMTPM function" -Level "INFO"
    }
}
#EndRegion '.\Public\EnableVMTPM.ps1' 42
#Region '.\Public\Ensure-ExportsFolder.ps1' -1

function Ensure-ExportsFolder {
    param (
        [Parameter(Mandatory = $true)]
        [string]$BasePath,
        [Parameter(Mandatory = $true)]
        [string]$ExportsFolderName,
        [Parameter(Mandatory = $true)]
        [string]$ExportSubFolderName
    )

    # Construct the full path to the exports folder
    $ExportsBaseFolderPath = Join-Path -Path $BasePath -ChildPath $ExportsFolderName
    $ExportsFolderPath = Join-Path -Path $ExportsBaseFolderPath -ChildPath $ExportSubFolderName

    # Check if the base exports folder exists
    if (-Not (Test-Path -Path $ExportsBaseFolderPath)) {
        # Create the base exports folder
        New-Item -ItemType Directory -Path $ExportsBaseFolderPath | Out-Null
        Write-EnhancedLog -Message "Created base exports folder at: $ExportsBaseFolderPath" -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
    }


    if (-Not (Test-Path -Path $ExportsFolderPath)) {
        # Create the base exports folder
        New-Item -ItemType Directory -Path $ExportsFolderPath | Out-Null
        Write-EnhancedLog -Message "Created base exports folder at: $ExportsFolderPath" -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
    }

    # Ensure the subfolder is clean
    Clean-ExportsFolder -FolderPath $ExportsFolderPath

    # Return the full path of the exports folder
    return $ExportsFolderPath
}
#EndRegion '.\Public\Ensure-ExportsFolder.ps1' 35
#Region '.\Public\Ensure-GitIsInstalled.ps1' -1

function Ensure-GitIsInstalled {
    param (
        [version]$MinVersion = [version]"2.46.0",
        [string]$RegistryPath = "HKLM:\SOFTWARE\GitForWindows",
        [string]$ExePath = "C:\Program Files\Git\bin\git.exe"
    )

    Write-EnhancedLog -Message "Checking if Git is installed and meets the minimum version requirement." -Level "INFO"

    # Use the Validate-SoftwareInstallation function to check if Git is installed and meets the version requirement
    $validationResult = Validate-SoftwareInstallation -SoftwareName "Git" -MinVersion $MinVersion -RegistryPath $RegistryPath -ExePath $ExePath

    if ($validationResult.IsInstalled) {
        Write-EnhancedLog -Message "Git version $($validationResult.Version) 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
    }
}
#EndRegion '.\Public\Ensure-GitIsInstalled.ps1' 23
#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\Ensure-RunningAsSystem.ps1' -1

function Ensure-RunningAsSystem {
    <#
    .SYNOPSIS
    Ensures that the script is running as the SYSTEM user, invoking it with PsExec if not.
 
    .DESCRIPTION
    The Ensure-RunningAsSystem function checks if the current session is running as SYSTEM. If it is not, it attempts to re-run the script as SYSTEM using PsExec.
 
    .PARAMETER PsExec64Path
    The path to the PsExec64 executable.
 
    .PARAMETER ScriptPath
    The path to the script that needs to be executed as SYSTEM.
 
    .PARAMETER TargetFolder
    The target folder where PsExec and other required files will be stored.
 
    .EXAMPLE
    $params = @{
        PsExec64Path = "C:\Tools\PsExec64.exe"
        ScriptPath = "C:\Scripts\MyScript.ps1"
        TargetFolder = "C:\ProgramData\SystemScripts"
    }
    Ensure-RunningAsSystem @params
    Ensures the script is running as SYSTEM.
    #>


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

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

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

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

        Write-EnhancedLog -Message "Calling Test-RunningAsSystem" -Level "INFO"
    }

    Process {
        try {
            if (-not (Test-RunningAsSystem)) {
                Write-EnhancedLog -Message "Current session is not running as SYSTEM. Attempting to invoke as SYSTEM..." -Level "WARNING"

                # Ensure the target folder exists
                if (-not (Test-Path -Path $TargetFolder)) {
                    New-Item -Path $TargetFolder -ItemType Directory | Out-Null
                    Write-EnhancedLog -Message "Created target folder: $TargetFolder" -Level "INFO"
                }

                $PsExec64Path = Join-Path -Path $TargetFolder -ChildPath "PsExec64.exe"

                $invokeParams = @{
                    PsExec64Path       = $PsExec64Path
                    ScriptPathAsSYSTEM = $ScriptPath
                    TargetFolder       = $TargetFolder
                    UsePowerShell5     = $true
                }

                Invoke-AsSystem @invokeParams
            }
            else {
                Write-EnhancedLog -Message "Session is already running as SYSTEM." -Level "INFO"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Ensure-RunningAsSystem function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Ensure-RunningAsSystem function" -Level "Notice"
    }
}

# Example usage
# $params = @{
# PsExec64Path = "C:\Tools\PsExec64.exe"
# ScriptPath = "C:\Scripts\MyScript.ps1"
# TargetFolder = "C:\ProgramData\SystemScripts"
# }
# Ensure-RunningAsSystem @params
#EndRegion '.\Public\Ensure-RunningAsSystem.ps1' 92
#Region '.\Public\Ensure-ScriptPathsExist.ps1' -1



function Ensure-ScriptPathsExist {


    <#
.SYNOPSIS
Ensures that all necessary script paths exist, creating them if they do not.
 
.DESCRIPTION
This function checks for the existence of essential script paths and creates them if they are not found. It is designed to be called after initializing script variables to ensure the environment is correctly prepared for the script's operations.
 
.PARAMETER Path_local
The local path where the script's data will be stored. This path varies based on the execution context (system vs. user).
 
.PARAMETER Path_PR
The specific path for storing package-related files, constructed based on the package name and unique GUID.
 
.EXAMPLE
Ensure-ScriptPathsExist -Path_local $global:Path_local -Path_PR $global:Path_PR
 
This example ensures that the paths stored in the global variables $Path_local and $Path_PR exist, creating them if necessary.
#>

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

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

    try {
        # Ensure Path_local exists
        if (-not (Test-Path -Path $Path_local)) {
            New-Item -Path $Path_local -ItemType Directory -Force | Out-Null
            Write-EnhancedLog -Message "Created directory: $Path_local" -Level "INFO" -ForegroundColor ([System.ConsoleColor]::Green)
        }

        # Ensure Path_PR exists
        if (-not (Test-Path -Path $Path_PR)) {
            New-Item -Path $Path_PR -ItemType Directory -Force | Out-Null
            Write-EnhancedLog -Message "Created directory: $Path_PR" -Level "INFO" -ForegroundColor ([System.ConsoleColor]::Green)
        }
    }
    catch {
        Write-EnhancedLog -Message "An error occurred while ensuring script paths exist: $_" -Level "ERROR" -ForegroundColor ([System.ConsoleColor]::Red)
    }
}
#EndRegion '.\Public\Ensure-ScriptPathsExist.ps1' 50
#Region '.\Public\EnsureUntrustedGuardianExists.ps1' -1

function EnsureUntrustedGuardianExists {
    <#
    .SYNOPSIS
    Ensures that an untrusted guardian exists. If not, creates one.
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]$GuardianName = 'UntrustedGuardian'
    )

    Begin {
        Write-EnhancedLog -Message "Starting Ensure-UntrustedGuardianExists function" -Level "INFO"
        Log-Params -Params @{ GuardianName = $GuardianName }
    }

    Process {
        try {
            Write-EnhancedLog -Message "Checking for the existence of the guardian: $GuardianName" -Level "INFO"
            $guardian = Get-HgsGuardian -Name $GuardianName -ErrorAction SilentlyContinue

            if ($null -eq $guardian) {
                Write-EnhancedLog -Message "Guardian $GuardianName not found. Creating..." -Level "WARNING"
                New-HgsGuardian -Name $GuardianName -GenerateCertificates
                Write-EnhancedLog -Message "Guardian $GuardianName created successfully" -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
            } else {
                Write-EnhancedLog -Message "Guardian $GuardianName already exists" -Level "INFO"
            }
        } catch {
            Write-EnhancedLog -Message "An error occurred while checking or creating the guardian: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Ensure-UntrustedGuardianExists function" -Level "INFO"
    }
}
#EndRegion '.\Public\EnsureUntrustedGuardianExists.ps1' 39
#Region '.\Public\Enter-SafeModeBasedOnDetection.ps1' -1

function Enter-SafeModeBasedOnDetection {
    [CmdletBinding()]
    param (
        [string]$RegistryPath,
        [string]$BCDeditPath,
        [string]$ArgumentTemplate
    )

    begin {
        Write-EnhancedLog -Message 'Starting Enter-SafeModeBasedOnDetection function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            # Detect system mode
            $params = @{
                RegistryPath = $RegistryPath
            }
            Detect-SystemMode @params

            if ($Global:SystemMode -eq "Normal Mode") {
                # Construct the arguments
                $arguments = $ArgumentTemplate

                # Execute the bcdedit command to enable Safe Mode
                Write-EnhancedLog -Message "Executing bcdedit with arguments: $arguments" -Level 'INFO'
                Start-Process -FilePath $BCDeditPath -ArgumentList $arguments -Wait

                Write-EnhancedLog -Message 'Successfully set the system to boot into Safe Mode on next restart' -Level 'INFO'
            } else {
                Write-EnhancedLog -Message 'System is already in Safe Mode' -Level 'INFO'
            }
        } catch {
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'Enter-SafeModeBasedOnDetection function completed' -Level 'INFO'
    }
}

# # Example usage of Enter-SafeModeBasedOnDetection function with splatting
# $params = @{
# RegistryPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\SafeBoot\Option'
# BCDeditPath = 'bcdedit.exe'
# ArgumentTemplate = '/set {current} safeboot minimal'
# }

# # Call the Enter-SafeModeBasedOnDetection function using splatting
# Enter-SafeModeBasedOnDetection @params
#EndRegion '.\Public\Enter-SafeModeBasedOnDetection.ps1' 53
#Region '.\Public\Escrow-BitLockerKey.ps1' -1

function Escrow-BitLockerKey {
    <#
    .SYNOPSIS
    Escrows the BitLocker recovery key to Azure AD.
 
    .DESCRIPTION
    The Escrow-BitLockerKey function tests if BitLocker is enabled on the specified drive, retrieves the key protector ID, and escrows the BitLocker recovery key to Azure AD.
 
    .PARAMETER DriveLetter
    The drive letter of the BitLocker protected drive.
 
    .EXAMPLE
    $params = @{
        DriveLetter = "C:"
    }
    Escrow-BitLockerKey @params
    Escrows the BitLocker recovery key for drive C: to Azure AD.
    #>


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

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

    Process {
        try {
            $bitlockerVolume = Test-Bitlocker -BitlockerDrive $DriveLetter
            $keyProtectorId = Get-KeyProtectorId -BitlockerDrive $DriveLetter
            Invoke-BitlockerEscrow -BitlockerDrive $DriveLetter -BitlockerKey $keyProtectorId
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Escrow-BitLockerKey function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Escrow-BitLockerKey function" -Level "Notice"
    }
}

# # Example usage
# $params = @{
# DriveLetter = "C:"
# }
# Escrow-BitLockerKey @params
#EndRegion '.\Public\Escrow-BitLockerKey.ps1' 54
#Region '.\Public\Execute-DetectionAndRemediation.ps1' -1


function Execute-DetectionAndRemediation {

    <#
.SYNOPSIS
Executes detection and remediation scripts located in a specified directory.
 
.DESCRIPTION
This function navigates to the specified directory and executes the detection script. If the detection script exits with a non-zero exit code, indicating a positive detection, the remediation script is then executed. The function uses enhanced logging for status messages and error handling to manage any issues that arise during execution.
 
.PARAMETER Path_PR
The path to the directory containing the detection and remediation scripts.
 
.EXAMPLE
Execute-DetectionAndRemediation -Path_PR "C:\Scripts\MyTask"
This example executes the detection and remediation scripts located in "C:\Scripts\MyTask".
#>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        # [ValidateScript({Test-Path $_ -PathType 'Container'})]
        [string]$Path_PR
    )

    try {
        Write-EnhancedLog -Message "Executing detection and remediation scripts in $Path_PR..." -Level "INFO" -ForegroundColor Magenta
        Set-Location -Path $Path_PR

        # Execution of the detection script
        & .\detection.ps1
        if ($LASTEXITCODE -ne 0) {
            Write-EnhancedLog -Message "Detection positive, remediation starts now." -Level "INFO" -ForegroundColor Green
            & .\remediation.ps1
        }
        else {
            Write-EnhancedLog -Message "Detection negative, no further action needed." -Level "INFO" -ForegroundColor Yellow
        }
    }
    catch {
        Write-EnhancedLog -Message "An error occurred during detection and remediation execution: $_" -Level "ERROR" -ForegroundColor Red
        throw $_
    }
}

#EndRegion '.\Public\Execute-DetectionAndRemediation.ps1' 46
#Region '.\Public\Execute-MigrationCleanupTasks.ps1' -1

function Execute-MigrationCleanupTasks {
    <#
    .SYNOPSIS
    Executes post-run operations for the third phase of the migration process.
   
    .DESCRIPTION
    The Execute-MigrationCleanupTasks function performs cleanup tasks after migration, including removing temporary user accounts, disabling local user accounts, removing scheduled tasks, clearing OneDrive cache, and setting registry values.
   
    .PARAMETER TempUser
    The name of the temporary user account to be removed.
   
    .PARAMETER RegistrySettings
    A hashtable of registry settings to be applied.
   
    .PARAMETER MigrationDirectories
    An array of directories to be removed as part of migration cleanup.
 
    .PARAMETER Mode
    Specifies the mode in which the script should run. Options are "Dev" for development mode or "Prod" for production mode. This determines whether the `Disable-LocalUserAccounts` function will skip certain accounts.
   
    .EXAMPLE
    $params = @{
        TempUser = "TempUser"
        RegistrySettings = @{
            "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" = @{
                "dontdisplaylastusername" = @{
                    "Type" = "DWORD"
                    "Data" = "0"
                }
                "legalnoticecaption" = @{
                    "Type" = "String"
                    "Data" = $null
                }
                "legalnoticetext" = @{
                    "Type" = "String"
                    "Data" = $null
                }
            }
            "HKLM:\Software\Policies\Microsoft\Windows\Personalization" = @{
                "NoLockScreen" = @{
                    "Type" = "DWORD"
                    "Data" = "0"
                }
            }
        }
        MigrationDirectories = @(
            "C:\ProgramData\AADMigration\Files",
            "C:\ProgramData\AADMigration\Scripts",
            "C:\ProgramData\AADMigration\Toolkit"
        )
        Mode = "Dev"
    }
    Execute-MigrationCleanupTasks @params
    Executes the post-run operations in Dev mode.
    #>

  
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$TempUser,
  
        [Parameter(Mandatory = $true)]
        [hashtable]$RegistrySettings,
  
        [Parameter(Mandatory = $true)]
        [string[]]$MigrationDirectories,

        [Parameter(Mandatory = $true)]
        [ValidateSet("Dev", "Prod")]
        [string]$Mode
    )
  
    Begin {
        Write-EnhancedLog -Message "Starting Execute-MigrationCleanupTasks function in $Mode mode" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }
  
    Process {
        try {
            # Remove temporary user account
            Write-EnhancedLog -Message "Removing temporary user account: $TempUser" -Level "INFO"
            $removeUserParams = @{
                UserName = $TempUser
            }
            Remove-LocalUserAccount @removeUserParams
            Write-EnhancedLog -Message "Temporary user account $TempUser removed" -Level "INFO"
  
            Manage-LocalUserAccounts -Mode $Mode
  
            # Set registry values
            # Write-EnhancedLog -Message "Applying registry settings" -Level "INFO"
            # foreach ($regPath in $RegistrySettings.Keys) {
            # foreach ($regName in $RegistrySettings[$regPath].Keys) {
            # $regSetting = $RegistrySettings[$regPath][$regName]

            # if ($null -ne $regSetting["Data"]) {
            # Write-EnhancedLog -Message "Setting registry value $regName at $regPath" -Level "INFO"
                    
            # $regParams = @{
            # RegKeyPath = $regPath
            # RegValName = $regName
            # RegValType = $regSetting["Type"]
            # RegValData = $regSetting["Data"]
            # }
                    
            # # If the data is an empty string, explicitly set it as such
            # if ($regSetting["Data"] -eq "") {
            # $regParams.RegValData = ""
            # }
                    
            # Set-RegistryValue @regParams
            # Write-EnhancedLog -Message "Registry value $regName at $regPath set" -Level "INFO"
            # }
            # else {
            # Write-EnhancedLog -Message "Skipping registry value $regName at $regPath due to null data" -Level "WARNING"
            # }
                    
            # }
            # }


            # Apply the registry settings using the defined hash table
            Apply-RegistrySettings -RegistrySettings $RegistrySettings
            



            # #Region Set registry values

            # # Initialize counters and summary table
            # $infoCount = 0
            # $warningCount = 0
            # $errorCount = 0
            # # Initialize the summary table using a .NET List for better performance
            # $summaryTable = [System.Collections.Generic.List[PSCustomObject]]::new()

            # # Set registry values
            # Write-EnhancedLog -Message "Applying registry settings" -Level "INFO"
            # foreach ($regPath in $RegistrySettings.Keys) {
            # foreach ($regName in $RegistrySettings[$regPath].Keys) {
            # $regSetting = $RegistrySettings[$regPath][$regName]

            # $summaryRow = [PSCustomObject]@{
            # RegistryPath = $regPath
            # RegistryName = $regName
            # RegistryValue = if ($null -ne $regSetting["Data"]) { $regSetting["Data"] } else { "null" }
            # Status = ""
            # }

            # if ($null -ne $regSetting["Data"]) {
            # Write-EnhancedLog -Message "Setting registry value $regName at $regPath" -Level "INFO"
            # $infoCount++

            # $regParams = @{
            # RegKeyPath = $regPath
            # RegValName = $regName
            # RegValType = $regSetting["Type"]
            # RegValData = $regSetting["Data"]
            # }

            # # If the data is an empty string, explicitly set it as such
            # if ($regSetting["Data"] -eq "") {
            # $regParams.RegValData = ""
            # }

            # try {
            # # Set-RegistryValue @regParams

            # # Call the Set-RegistryValue function and capture the result
            # $setRegistryResult = Set-RegistryValue @regParams

            # # Build decision-making logic based on the result
            # if ($setRegistryResult -eq $true) {
            # Write-EnhancedLog -Message "Successfully set the registry value: $regValName at $regKeyPath" -Level "INFO"
            # $summaryRow.Status = "Success"
            # }
            # else {
            # Write-EnhancedLog -Message "Failed to set the registry value: $regValName at $regKeyPath" -Level "ERROR"
            # $summaryRow.Status = "Failed"
            # }

            # Write-EnhancedLog -Message "Registry value $regName at $regPath set" -Level "INFO"
                            
            # }
            # catch {
            # Write-EnhancedLog -Message "Error setting registry value $regName at $regPath $($_.Exception.Message)" -Level "ERROR"
            # $errorCount++
            # $summaryRow.Status = "Failed"
            # }
            # }
            # else {
            # Write-EnhancedLog -Message "Skipping registry value $regName at $regPath due to null data" -Level "WARNING"
            # $warningCount++
            # $summaryRow.Status = "Skipped"
            # }

            # $summaryTable.Add($summaryRow)
            # }
            # }

            # # Final Summary Report
            # Write-EnhancedLog -Message "----------------------------------------" -Level "INFO"
            # Write-EnhancedLog -Message "Final Summary Report" -Level "NOTICE"
            # Write-EnhancedLog -Message "Total registry settings processed: $($infoCount + $warningCount + $errorCount)" -Level "INFO"
            # Write-EnhancedLog -Message "Successfully applied registry settings: $infoCount" -Level "INFO"
            # Write-EnhancedLog -Message "Skipped registry settings (due to null data): $warningCount" -Level "WARNING"
            # Write-EnhancedLog -Message "Failed registry settings: $errorCount" -Level "ERROR"
            # Write-EnhancedLog -Message "----------------------------------------" -Level "INFO"

            # # Color-coded summary for the console
            # Write-Host "----------------------------------------" -ForegroundColor White
            # Write-Host "Final Summary Report" -ForegroundColor Cyan
            # Write-Host "Total registry settings processed: $($infoCount + $warningCount + $errorCount)" -ForegroundColor White
            # Write-Host "Successfully applied registry settings: $infoCount" -ForegroundColor Green
            # Write-Host "Skipped registry settings (due to null data): $warningCount" -ForegroundColor Yellow
            # Write-Host "Failed registry settings: $errorCount" -ForegroundColor Red
            # Write-Host "----------------------------------------" -ForegroundColor White

            # # Display the summary table of registry keys and their final states
            # Write-Host "Registry Settings Summary:" -ForegroundColor Cyan
            # $summaryTable | Format-Table -AutoSize

            # # Optionally log the summary to the enhanced log as well
            # foreach ($row in $summaryTable) {
            # Write-EnhancedLog -Message "RegistryPath: $($row.RegistryPath), RegistryName: $($row.RegistryName), Value: $($row.RegistryValue), Status: $($row.Status)" -Level "INFO"
            # }


            # #endRegion Set registry values
  


            
  
            # Remove scheduled tasks
            Write-EnhancedLog -Message "Removing scheduled tasks in TaskPath: AAD Migration" -Level "INFO"

            # Retrieve all tasks in the "AAD Migration" path
            $tasks = Get-ScheduledTask -TaskPath "\AAD Migration\" -ErrorAction SilentlyContinue

            # Loop through each task and unregister it using the Unregister-ScheduledTaskWithLogging function
            foreach ($task in $tasks) {
                Unregister-ScheduledTaskWithLogging -TaskName $task.TaskName
            }

            Write-EnhancedLog -Message "Scheduled tasks removed from TaskPath: AAD Migration" -Level "INFO"
  
        
  
            # Clear OneDrive cache
            Write-EnhancedLog -Message "Clearing OneDrive cache" -Level "INFO"
            # Clear-OneDriveCache

            $CreateOneDriveCacheClearTaskParams = @{
                TaskPath               = "AAD Migration"
                TaskName               = "Clear OneDrive Cache"
                ScriptDirectory        = "C:\ProgramData\AADMigration\Scripts"
                ScriptName             = "ClearOneDriveCache.Task.ps1"
                TaskArguments          = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -file `"{ScriptPath}`""
                TaskRepetitionDuration = "P1D"
                TaskRepetitionInterval = "PT30M"
                TaskPrincipalGroupId   = "BUILTIN\Users"
                PowerShellPath         = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
                TaskDescription        = "Clears the OneDrive cache by restarting the OneDrive process"
                AtLogOn                = $true
            }

            Create-OneDriveCacheClearTask @CreateOneDriveCacheClearTaskParams


            $taskParams = @{
                TaskPath = "\AAD Migration"
                TaskName = "Clear OneDrive Cache"
            }

            # Trigger OneDrive Sync Status Scheduled Task
            Trigger-ScheduledTask @taskParams


            # Remove migration files
            Write-EnhancedLog -Message "Removing migration directories: $MigrationDirectories" -Level "INFO"
            $removeFilesParams = @{
                Directories = $MigrationDirectories
            }
            Remove-MigrationFiles @removeFilesParams
            Write-EnhancedLog -Message "Migration directories removed: $MigrationDirectories" -Level "INFO"


            Write-EnhancedLog -Message "OneDrive cache cleared" -Level "INFO"
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Execute-MigrationCleanupTasks function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }
  
    End {
        Write-EnhancedLog -Message "Exiting Execute-MigrationCleanupTasks function" -Level "Notice"
    }
}
#EndRegion '.\Public\Execute-MigrationCleanupTasks.ps1' 302
#Region '.\Public\Execute-MigrationToolkit.ps1' -1

function Execute-MigrationToolkit {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$ServiceUI,

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

    Begin {
        Write-EnhancedLog -Message "Starting Execute-MigrationToolkit function" -Level "INFO"
        Log-Params -Params @{
            ServiceUI = $ServiceUI
            ExePath   = $ExePath
        }
    }

    Process {
        try {
            $targetProcesses = @(Get-WmiObject -Query "Select * FROM Win32_Process WHERE Name='explorer.exe'" -ErrorAction SilentlyContinue)
            if ($targetProcesses.Count -eq 0) {
                Write-EnhancedLog -Message "No user logged in, running without ServiceUI" -Level "INFO"
                Start-Process -FilePath $ExePath -ArgumentList '-DeployMode "NonInteractive"' -Wait -NoNewWindow
            } else {
                foreach ($targetProcess in $targetProcesses) {
                    $Username = $targetProcess.GetOwner().User
                    Write-EnhancedLog -Message "$Username logged in, running with ServiceUI" -Level "INFO"
                }
                Start-Process -FilePath $ServiceUI -ArgumentList "-Process:explorer.exe $ExePath" -NoNewWindow
            }
        } catch {
            $ErrorMessage = $_.Exception.Message
            Write-EnhancedLog -Message "An error occurred: $ErrorMessage" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Install Exit Code = $LASTEXITCODE" -Level "INFO"
        Write-EnhancedLog -Message "Exiting Execute-MigrationToolkit function" -Level "INFO"
        Exit $LASTEXITCODE
    }
}

# # Define paths
# $ToolkitPaths = @{
# ServiceUI = "C:\ProgramData\AADMigration\Files\ServiceUI.exe"
# ExePath = "C:\ProgramData\AADMigration\Toolkit\Deploy-Application.exe"
# }

# # Example usage with splatting
# Execute-MigrationToolkit @ToolkitPaths
#EndRegion '.\Public\Execute-MigrationToolkit.ps1' 54
#Region '.\Public\Exit-SafeModeBasedOnDetection.ps1' -1

function Exit-SafeModeBasedOnDetection {
    [CmdletBinding()]
    param (
        [string]$RegistryPath,
        [string]$BCDeditPath,
        [string]$ArgumentTemplate
    )

    begin {
        Write-EnhancedLog -Message 'Starting Exit-SafeModeBasedOnDetection function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            # Detect system mode
            $params = @{
                RegistryPath = $RegistryPath
            }
            Detect-SystemMode @params

            if ($Global:SystemMode -eq "Safe Mode") {
                # Construct the arguments
                $arguments = $ArgumentTemplate

                # Execute the bcdedit command to disable Safe Mode
                Write-EnhancedLog -Message "Executing bcdedit with arguments: $arguments" -Level 'INFO'
                Start-Process -FilePath $BCDeditPath -ArgumentList $arguments -Wait

                Write-EnhancedLog -Message 'Successfully set the system to boot into Normal Mode on next restart' -Level 'INFO'
            } else {
                Write-EnhancedLog -Message 'System is already in Normal Mode' -Level 'INFO'
            }
        } catch {
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'Exit-SafeModeBasedOnDetection function completed' -Level 'INFO'
    }
}

# # Example usage of Exit-SafeModeBasedOnDetection function with splatting
# $params = @{
# RegistryPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\SafeBoot\Option'
# BCDeditPath = 'bcdedit.exe'
# ArgumentTemplate = '/deletevalue {current} safeboot'
# }

# # Call the Exit-SafeModeBasedOnDetection function using splatting
# Exit-SafeModeBasedOnDetection @params
#EndRegion '.\Public\Exit-SafeModeBasedOnDetection.ps1' 53
#Region '.\Public\Export-Data.ps1' -1

function Export-Data {
    <#
.SYNOPSIS
Exports data to various formats including CSV, JSON, XML, HTML, PlainText, Excel, PDF, Markdown, and YAML.
 
.DESCRIPTION
The Export-Data function exports provided data to multiple file formats based on switches provided. It supports CSV, JSON, XML, GridView (for display only), HTML, PlainText, Excel, PDF, Markdown, and YAML formats. This function is designed to work with any PSObject.
 
.PARAMETER Data
The data to be exported. This parameter accepts input of type PSObject.
 
.PARAMETER BaseOutputPath
The base path for output files without file extension. This path is used to generate filenames for each export format.
 
.PARAMETER IncludeCSV
Switch to include CSV format in the export.
 
.PARAMETER IncludeJSON
Switch to include JSON format in the export.
 
.PARAMETER IncludeXML
Switch to include XML format in the export.
 
.PARAMETER IncludeGridView
Switch to display the data in a GridView.
 
.PARAMETER IncludeHTML
Switch to include HTML format in the export.
 
.PARAMETER IncludePlainText
Switch to include PlainText format in the export.
 
.PARAMETER IncludePDF
Switch to include PDF format in the export. Requires intermediate HTML to PDF conversion.
 
.PARAMETER IncludeExcel
Switch to include Excel format in the export.
 
.PARAMETER IncludeMarkdown
Switch to include Markdown format in the export. Custom or use a module if available.
 
.PARAMETER IncludeYAML
Switch to include YAML format in the export. Requires 'powershell-yaml' module.
 
.EXAMPLE
PS> $data = Get-Process | Select-Object -First 10
PS> Export-Data -Data $data -BaseOutputPath "C:\exports\mydata" -IncludeCSV -IncludeJSON
 
This example exports the first 10 processes to CSV and JSON formats.
#>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [psobject]$Data,

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

        [switch]$IncludeCSV,
        [switch]$IncludeJSON,
        [switch]$IncludeXML,
        [switch]$IncludeGridView,
        [switch]$IncludeHTML,
        [switch]$IncludePlainText,
        [switch]$IncludePDF, # Requires intermediate HTML to PDF conversion
        [switch]$IncludeExcel,
        [switch]$IncludeMarkdown, # Custom or use a module if available
        [switch]$IncludeYAML  # Requires 'powershell-yaml' module
    )

    Begin {




        # $modules = @('ImportExcel', 'powershell-yaml' , 'PSWriteHTML')
        # Install-MissingModules -RequiredModules $modules -Verbose


        # Setup the base path without extension
        Write-Host "BaseOutputPath before change: '$BaseOutputPath'"
        $basePathWithoutExtension = [System.IO.Path]::ChangeExtension($BaseOutputPath, $null)

        # Remove extension manually if it exists
        $basePathWithoutExtension = if ($BaseOutputPath -match '\.') {
            $BaseOutputPath.Substring(0, $BaseOutputPath.LastIndexOf('.'))
        }
        else {
            $BaseOutputPath
        }

        # Ensure no trailing periods
        $basePathWithoutExtension = $basePathWithoutExtension.TrimEnd('.')
    }

    Process {
        try {
            if ($IncludeCSV) {
                $csvPath = "$basePathWithoutExtension.csv"
                $Data | Export-Csv -Path $csvPath -NoTypeInformation
            }

            if ($IncludeJSON) {
                $jsonPath = "$basePathWithoutExtension.json"
                $Data | ConvertTo-Json -Depth 10 | Set-Content -Path $jsonPath
            }

            if ($IncludeXML) {
                $xmlPath = "$basePathWithoutExtension.xml"
                $Data | Export-Clixml -Path $xmlPath
            }

            if ($IncludeGridView) {
                $Data | Out-GridView -Title "Data Preview"
            }

            if ($IncludeHTML) {
                # Assumes $Data is the dataset you want to export to HTML
                # and $basePathWithoutExtension is prepared earlier in your script
                
                $htmlPath = "$basePathWithoutExtension.html"
                
                # Convert $Data to HTML using PSWriteHTML
                New-HTML -Title "Data Export Report" -FilePath $htmlPath -ShowHTML {
                    New-HTMLSection -HeaderText "Data Export Details" -Content {
                        New-HTMLTable -DataTable $Data -ScrollX -HideFooter
                    }
                }
            
                Write-Host "HTML report generated: '$htmlPath'"
            }
            

            if ($IncludePlainText) {
                $txtPath = "$basePathWithoutExtension.txt"
                $Data | Out-String | Set-Content -Path $txtPath
            }

            if ($IncludeExcel) {
                $excelPath = "$basePathWithoutExtension.xlsx"
                $Data | Export-Excel -Path $excelPath
            }

            # Assuming $Data holds the objects you want to serialize to YAML
            if ($IncludeYAML) {
                $yamlPath = "$basePathWithoutExtension.yaml"
    
                # Check if the powershell-yaml module is loaded
                if (Get-Module -ListAvailable -Name powershell-yaml) {
                    Import-Module powershell-yaml

                    # Process $Data to handle potentially problematic properties
                    $processedData = $Data | ForEach-Object {
                        $originalObject = $_
                        $properties = $_ | Get-Member -MemberType Properties
                        $clonedObject = New-Object -TypeName PSObject

                        foreach ($prop in $properties) {
                            try {
                                $clonedObject | Add-Member -MemberType NoteProperty -Name $prop.Name -Value $originalObject.$($prop.Name) -ErrorAction Stop
                            }
                            catch {
                                # Optionally handle or log the error. Skipping problematic property.
                                $clonedObject | Add-Member -MemberType NoteProperty -Name $prop.Name -Value "Error serializing property" -ErrorAction SilentlyContinue
                            }
                        }

                        return $clonedObject
                    }

                    # Convert the processed data to YAML and save it with UTF-16 LE encoding
                    $processedData | ConvertTo-Yaml | Set-Content -Path $yamlPath -Encoding Unicode
                    Write-Host "YAML export completed successfully: $yamlPath"
                }
                else {
                    Write-Warning "The 'powershell-yaml' module is not installed. YAML export skipped."
                }
            }

            if ($IncludeMarkdown) {
                # You'll need to implement or find a ConvertTo-Markdown function or use a suitable module
                $markdownPath = "$basePathWithoutExtension.md"
                $Data | ConvertTo-Markdown | Set-Content -Path $markdownPath
            }

            if ($IncludePDF) {
                # Convert HTML to PDF using external tool
                # This is a placeholder for the process. You will need to generate HTML first and then convert it.
                $pdfPath = "$basePathWithoutExtension.pdf"
                # Assuming you have a Convert-HtmlToPdf function or a similar mechanism
                $htmlPath = "$basePathWithoutExtension.html"
                $Data | ConvertTo-Html | Convert-HtmlToPdf -OutputPath $pdfPath
            }

        }
        catch {
            Write-Error "An error occurred during export: $_"
        }
    }

    End {
        Write-Verbose "Export-Data function execution completed."
    }
}
#EndRegion '.\Public\Export-Data.ps1' 206
#Region '.\Public\Export-RegistryKeys.ps1' -1

function Export-RegistryKeys {
    [CmdletBinding()]
    param (
        [string]$ScriptDirectory,
        [string]$RegistryKeyPath
    )

    begin {
        Write-EnhancedLog -Message 'Starting Export-RegistryKeys function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            if (-not (Test-Path $ScriptDirectory)) {
                Write-EnhancedLog -Message "Script directory not found: $ScriptDirectory" -Level 'ERROR'
                return
            }

            $timestamp = (Get-Date).ToString("yyyyMMddHHmmss")
            $exportFilePath = Join-Path -Path $ScriptDirectory -ChildPath "RegistryExport_$timestamp.reg"

            $arguments = "export `"$RegistryKeyPath`" `"$exportFilePath`" /y"
            $startProcessParams = @{
                FilePath = "reg.exe"
                ArgumentList = $arguments
                Wait = $true
            }

            Write-EnhancedLog -Message "Exporting registry key: $RegistryKeyPath to file: $exportFilePath" -Level 'INFO'
            
            Start-Process @startProcessParams

            if (Test-Path $exportFilePath) {
                Write-EnhancedLog -Message "Registry key export completed successfully: $exportFilePath" -Level 'INFO'
                
                # Validate the exported registry keys
                $validateParams = @{
                    RegistryFilePath = $exportFilePath
                }
                Validate-RegistryKeys @validateParams
            } else {
                Write-EnhancedLog -Message "Failed to export registry key: $RegistryKeyPath" -Level 'ERROR'
            }
        }
        catch {
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'Export-RegistryKeys function completed' -Level 'INFO'
    }
}

# # Usage Example with Splatting
# $scriptDirectory = "C:\Path\To\Your\Export\Directory"
# $params = @{
# ScriptDirectory = $scriptDirectory
# RegistryKeyPath = 'HKEY_LOCAL_MACHINE\SOFTWARE\Fortinet\FortiClient\Sslvpn\Tunnels'
# }
# Export-RegistryKeys @params
#EndRegion '.\Public\Export-RegistryKeys.ps1' 63
#Region '.\Public\Export-SignInLogs.ps1' -1

function Export-SignInLogs {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$ScriptRoot,
        [Parameter(Mandatory = $true)]
        [string]$ExportsFolderName,
        [Parameter(Mandatory = $true)]
        [string]$ExportSubFolderName,
        [Parameter(Mandatory = $true)]
        [hashtable]$headers,
        [Parameter(Mandatory = $true)]
        [string]$url
    
    )

    # Ensure the exports folder is clean before exporting
    $exportFolder = Ensure-ExportsFolder -BasePath $ScriptRoot -ExportsFolderName $ExportsFolderName -ExportSubFolderName $ExportSubFolderName

    # Get the sign-in logs (assuming you have a way to fetch these logs)
    # $signInLogs = Get-SignInLogs # Replace with the actual command to get sign-in logs

    $signInLogs = Get-SignInLogs -url $url -Headers $headers

    # Check if there are no sign-in logs
    if ($signInLogs.Count -eq 0) {
        Write-EnhancedLog -Message "NO sign-in logs found." -Level "WARNING"
        return
    }

    # Generate a timestamp for the export
    $timestamp = Get-Date -Format "yyyyMMddHHmmss"
    $baseOutputPath = Join-Path -Path $exportFolder -ChildPath "SignInLogs_$timestamp"

    # Setup parameters for Export-Data using splatting
    $exportParams = @{
        Data             = $signInLogs
        BaseOutputPath   = $baseOutputPath
        # IncludeCSV = $true
        IncludeJSON      = $true
        # IncludeXML = $true
        # IncludePlainText = $true
        # IncludeExcel = $true
        # IncludeYAML = $true
    }

    # Call the Export-Data function with splatted parameters
    Export-Data @exportParams
    Write-EnhancedLog -Message "Data export completed successfully." -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
}


# # Define the root path where the scripts and exports are located
# $scriptRoot = "C:\MyScripts"

# # Optionally, specify the names for the exports folder and subfolder
# $exportsFolderName = "CustomExports"
# $exportSubFolderName = "CustomSignInLogs"

# # Call the function to export sign-in logs to XML (and other formats)
# Export-SignInLogsToXML -ScriptRoot $scriptRoot -ExportsFolderName $exportsFolderName -ExportSubFolderName $exportSubFolderName
#EndRegion '.\Public\Export-SignInLogs.ps1' 62
#Region '.\Public\Export-VPNConnectionsToXML.ps1' -1

function Export-VPNConnectionsToXML {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$ExportFolder
    )

    try {
        # Get the list of current VPN connections
        $vpnConnections = Get-VpnConnection -AllUserConnection

        # Check if there are no VPN connections
        if ($vpnConnections.Count -eq 0) {
            Write-EnhancedLog -Message "NO VPN connections found." -Level "WARNING"
            return
        }

        # Generate a timestamp for the export
        $timestamp = Get-Date -Format "yyyyMMddHHmmss"
        $baseOutputPath = Join-Path -Path $ExportFolder -ChildPath "VPNExport_$timestamp"

        # Setup parameters for Export-Data using splatting
        $splatExportParams = @{
            Data             = $vpnConnections
            BaseOutputPath   = $baseOutputPath
            IncludeCSV       = $true
            IncludeJSON      = $true
            IncludeXML       = $true
            IncludePlainText = $true
            # IncludeExcel = $true
            IncludeYAML      = $true
        }

        # Call the Export-Data function with splatted parameters
        Export-Data @splatExportParams
        Write-EnhancedLog -Message "Data export completed successfully." -Level "INFO"
    }
    catch {
        Handle-Error -ErrorRecord $_
        Write-EnhancedLog -Message "Failed to export VPN connections." -Level "ERROR"
    }
}
#EndRegion '.\Public\Export-VPNConnectionsToXML.ps1' 43
#Region '.\Public\ExportAndProcessSignInLogs.ps1' -1

function ExportAndProcessSignInLogs {
    param (
        [Parameter(Mandatory = $true)]
        [string]$ScriptRoot,
        [Parameter(Mandatory = $true)]
        [string]$ExportsFolderName,
        [Parameter(Mandatory = $true)]
        [string]$ExportSubFolderName,
        [Parameter(Mandatory = $true)]
        [string]$url,
        [Parameter(Mandatory = $true)]
        [hashtable]$Headers
    )

    try {
        $ExportSignInLogsparams = @{
            ScriptRoot         = $ScriptRoot
            ExportsFolderName  = $ExportsFolderName
            ExportSubFolderName= $ExportSubFolderName
            url                = $url
            Headers            = $Headers
        }

        # Ask user if they want to export fresh sign-in logs
        $exportFreshLogs = Read-Host "Would you like to export fresh logs? (yes/no)"

        if ($exportFreshLogs -eq 'yes') {
            Export-SignInLogs @ExportSignInLogsparams
        }

        $subFolderPath = Join-Path -Path $ScriptRoot -ChildPath $ExportsFolderName
        $subFolderPath = Join-Path -Path $subFolderPath -ChildPath $ExportSubFolderName

        Write-EnhancedLog -Message "Looking for JSON files in $subFolderPath" -Level "DEBUG"

        $latestJsonFile = Find-LatestJsonFile -Directory $subFolderPath

        if ($latestJsonFile) {
            Write-EnhancedLog -Message "Latest JSON file found: $latestJsonFile" -Level "DEBUG"
            $signInLogs = Load-SignInLogs -JsonFilePath $latestJsonFile
            if ($signInLogs.Count -gt 0) {
                Write-EnhancedLog -Message "Sign-in logs found in $latestJsonFile. Starting to process it" -Level "INFO"
                # Process-AllDevices -Json $signInLogs -Headers $Headers
                return $signInLogs
            } else {
                Write-EnhancedLog -Message "No sign-in logs found in $latestJsonFile." -Level "WARNING"
                return @()
            }
        } else {
            Write-EnhancedLog -Message "No JSON file found to load sign-in logs." -Level "WARNING"
            return @()
        }
    } catch {
        Handle-Error -ErrorRecord $_
        return @()
    }
}
#EndRegion '.\Public\ExportAndProcessSignInLogs.ps1' 58
#Region '.\Public\ExportCertificatetoCER.ps1' -1

    # # Define the function
    # function ExportCertificatetoCER {
    # param (
    # [Parameter(Mandatory = $true)]
    # [string]$CertThumbprint,

    # [Parameter(Mandatory = $true)]
    # [string]$ExportDirectory
    # )

    # try {
    # # Get the certificate from the current user's personal store
    # $cert = Get-Item -Path "Cert:\CurrentUser\My\$CertThumbprint"
        
    # # Ensure the export directory exists
    # if (-not (Test-Path -Path $ExportDirectory)) {
    # New-Item -ItemType Directory -Path $ExportDirectory -Force
    # }

    # # Dynamically create a file name using the certificate subject name and current timestamp
    # $timestamp = (Get-Date).ToString("yyyyMMddHHmmss")
    # $subjectName = $cert.SubjectName.Name -replace "[^a-zA-Z0-9]", "_"
    # $fileName = "${subjectName}_$timestamp"

    # # Set the export file path
    # $certPath = Join-Path -Path $ExportDirectory -ChildPath "$fileName.cer"
        
    # # Export the certificate to a file (DER encoded binary format with .cer extension)
    # $cert | Export-Certificate -FilePath $certPath -Type CERT -Force | Out-Null

    # # Output the export file path
    # Write-EnhancedLog -Message "Certificate exported to: $certPath"

    # # Return the export file path
    # return $certPath
    # }
    # catch {
    # Write-Host "Failed to export certificate: $_" -ForegroundColor Red
    # }
    # }


    function ExportCertificatetoCER {
        param (
            [Parameter(Mandatory = $true)]
            [string]$CertThumbprint,
    
            [Parameter(Mandatory = $true)]
            [string]$ExportDirectory,

            [Parameter(Mandatory = $true)]
            [string]$Certname
        )
    
        try {
            Write-EnhancedLog -Message "Starting certificate export process for thumbprint: $CertThumbprint" -Level "INFO"
            
            # Get the certificate from the current user's personal store
            $cert = Get-Item -Path "Cert:\CurrentUser\My\$CertThumbprint"
            if (-not $cert) {
                Write-EnhancedLog -Message "Certificate with thumbprint $CertThumbprint not found." -Level "ERROR"
                throw "Certificate with thumbprint $CertThumbprint not found."
            }
            Write-EnhancedLog -Message "Certificate with thumbprint $CertThumbprint found." -Level "INFO"
    
            # Ensure the export directory exists
            if (-not (Test-Path -Path $ExportDirectory)) {
                Write-EnhancedLog -Message "Export directory $ExportDirectory does not exist. Creating directory." -Level "INFO"
                New-Item -ItemType Directory -Path $ExportDirectory -Force
            }
            Write-EnhancedLog -Message "Using export directory: $ExportDirectory" -Level "INFO"
    
            # Dynamically create a file name using the certificate subject name and current timestamp
            $timestamp = (Get-Date).ToString("yyyyMMddHHmmss")
            # $subjectName = $cert.SubjectName.Name -replace "[^a-zA-Z0-9]", "_"
            # $fileName = "${subjectName}_$timestamp"
            $fileName = "${certname}_$timestamp"
    
            # Set the export file path
            $certPath = Join-Path -Path $ExportDirectory -ChildPath "$fileName.cer"
            Write-EnhancedLog -Message "Export file path set to: $certPath" -Level "INFO"
    
            # Export the certificate to a file (DER encoded binary format with .cer extension)

            # $DBG

            Export-Certificate -Cert $cert -FilePath $certPath -Type CERT -Force | Out-Null
            Write-EnhancedLog -Message "Certificate successfully exported to: $certPath" -Level "INFO"
    
            # Return the export file path
            return $certPath
        }
        catch {
            $errorMessage = $_.Exception.Message
            Write-EnhancedLog -Message "Failed to export certificate: $errorMessage" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }
    


#EndRegion '.\Public\ExportCertificatetoCER.ps1' 103
#Region '.\Public\Fetch-DeviceStateWithRetry.ps1' -1

function Fetch-DeviceStateWithRetry {
    param (
        [Parameter(Mandatory = $true)]
        [string]$DeviceId,
        [Parameter(Mandatory = $true)]
        [string]$Username,
        [Parameter(Mandatory = $true)]
        [hashtable]$Headers
    )

    $retryCount = 0
    $maxRetries = 3
    $fetchSuccess = $false
    $deviceState = "Unknown"

    do {
        try {
            $deviceState = Check-DeviceStateInIntune -entraDeviceId $DeviceId -username $Username -Headers $Headers
            $fetchSuccess = $true
        } catch {
            Write-EnhancedLog -Message "Failed to check device state for device ID: $DeviceId. Attempt $($retryCount + 1) of $maxRetries" -Level "ERROR"
            $retryCount++
            Start-Sleep -Seconds 2
        }
    } while (-not $fetchSuccess -and $retryCount -lt $maxRetries)

    if (-not $fetchSuccess) {
        Write-EnhancedLog -Message "Failed to check device state for device ID: $DeviceId after $maxRetries attempts." -Level "ERROR"
    }

    return $deviceState
}
#EndRegion '.\Public\Fetch-DeviceStateWithRetry.ps1' 33
#Region '.\Public\Fetch-OSVersion.ps1' -1

function Fetch-OSVersion {
    param (
        [Parameter(Mandatory = $true)]
        [string]$DeviceId,
        [Parameter(Mandatory = $true)]
        [hashtable]$Headers
    )

    Begin {
        Write-EnhancedLog -Message "Starting Fetch-OSVersion function for Device ID: $DeviceId" -Level "INFO"
        Log-Params -Params @{ DeviceId = $DeviceId }
    }

    Process {
        $uri = "https://graph.microsoft.com/v1.0/devices?$filter=deviceId eq '$DeviceId'"
        $httpClient = Initialize-HttpClient -Headers $Headers

        try {
            Write-EnhancedLog -Message "Fetching OS version from URL: $uri" -Level "INFO"

            $response = $httpClient.GetStringAsync($uri).Result
            if (-not [string]::IsNullOrEmpty($response)) {
                $responseJson = [System.Text.Json.JsonDocument]::Parse($response)
                $deviceDetails = $responseJson.RootElement.GetProperty("value").EnumerateArray() | Where-Object { $_.GetProperty("deviceId").GetString() -eq $DeviceId }

                if ($deviceDetails) {
                    $osVersion = $deviceDetails.GetProperty("operatingSystemVersion").GetString()
                    Write-EnhancedLog -Message "OS Version for Device ID $DeviceId $osVersion" -Level "INFO"
                    $responseJson.Dispose()
                    return $osVersion
                } else {
                    Write-EnhancedLog -Message "No matching device found for Device ID $DeviceId" -Level "WARNING" -ForegroundColor Yellow
                    return $null
                }
            } else {
                Write-EnhancedLog -Message "Received empty response from OS version API for Device ID: $DeviceId." -Level "WARNING" -ForegroundColor Yellow
                return $null
            }
        } catch {
            Write-EnhancedLog -Message "An error occurred while fetching OS version: $($_.Exception.Message)" -Level "ERROR" -ForegroundColor Red
            Handle-Error -ErrorRecord $_
            return $null
        } finally {
            $httpClient.Dispose()
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Fetch-OSVersion function for Device ID: $DeviceId" -Level "INFO"
    }
}
#EndRegion '.\Public\Fetch-OSVersion.ps1' 52
#Region '.\Public\Fetch-OSVersionWithRetry.ps1' -1


function Fetch-OSVersionWithRetry {
    param (
        [Parameter(Mandatory = $true)]
        [string]$DeviceId,
        [Parameter(Mandatory = $true)]
        [hashtable]$Headers
    )

    $retryCount = 0
    $maxRetries = 3
    $fetchSuccess = $false
    $osVersion = "Unknown"

    do {
        try {
            $osVersion = Fetch-OSVersion -DeviceId $DeviceId -Headers $Headers
            $fetchSuccess = $true
        } catch {
            Write-EnhancedLog -Message "Failed to fetch OS version for device ID: $DeviceId. Attempt $($retryCount + 1) of $maxRetries" -Level "ERROR"
            $retryCount++
            Start-Sleep -Seconds 2
        }
    } while (-not $fetchSuccess -and $retryCount -lt $maxRetries)

    if (-not $fetchSuccess) {
        Write-EnhancedLog -Message "Failed to fetch OS version for device ID: $DeviceId after $maxRetries attempts." -Level "ERROR"
    }

    return $osVersion
}
#EndRegion '.\Public\Fetch-OSVersionWithRetry.ps1' 32
#Region '.\Public\Fetch-UserLicense.ps1' -1

function Fetch-UserLicense {
    param (
        [Parameter(Mandatory = $true)]
        [string]$UserId,
        [Parameter(Mandatory = $true)]
        [string]$Username,
        [Parameter(Mandatory = $true)]
        [hashtable]$Headers
    )

    try {
        Write-EnhancedLog -Message "Fetching licenses for user: $Username with ID: $UserId" -ForegroundColor Cyan
        $userLicenses = Get-UserLicenses -userId $UserId -username $Item.userDisplayName  -Headers $Headers
        return $userLicenses
    } catch {
        Handle-Error -ErrorRecord $_
        # return $null
    }
}
#EndRegion '.\Public\Fetch-UserLicense.ps1' 20
#Region '.\Public\Fetch-UserLicensesWithRetry.ps1' -1


function Fetch-UserLicensesWithRetry {
    param (
        [Parameter(Mandatory = $true)]
        [string]$UserId,
        [Parameter(Mandatory = $true)]
        [string]$Username,
        [Parameter(Mandatory = $true)]
        [hashtable]$Headers
    )

    $retryCount = 0
    $maxRetries = 3
    $fetchSuccess = $false
    $userLicenses = @()

    do {
        try {
            $userLicenses = Fetch-UserLicense -UserId $UserId -Username $Username -Headers $Headers
            $fetchSuccess = $true
        } catch {
            Write-EnhancedLog -Message "Failed to fetch licenses for user: $Username. Attempt $($retryCount + 1) of $maxRetries" -Level "ERROR"
            $retryCount++
            Start-Sleep -Seconds 2
        }
    } while (-not $fetchSuccess -and $retryCount -lt $maxRetries)

    if (-not $fetchSuccess) {
        Write-EnhancedLog -Message "Failed to fetch licenses for user: $Username after $maxRetries attempts." -Level "ERROR"
    }

    return $userLicenses
}
#EndRegion '.\Public\Fetch-UserLicensesWithRetry.ps1' 34
#Region '.\Public\Find-LatestJsonFile.ps1' -1

# Function to find the latest JSON file in the specified directory
function Find-LatestJsonFile {
    param (
        [Parameter(Mandatory = $true)]
        [string]$Directory
    )

    $jsonFiles = Get-ChildItem -Path $Directory -Filter *.json | Sort-Object LastWriteTime -Descending

    if ($jsonFiles.Count -gt 0) {
        return $jsonFiles[0].FullName
    } else {
        Write-EnhancedLog -Message "No JSON files found in $Directory." -Level "ERROR"
        # return $null
    }
}


#EndRegion '.\Public\Find-LatestJsonFile.ps1' 19
#Region '.\Public\Find-LockingProcesses.ps1' -1

function Find-LockingProcesses {
    <#
    .SYNOPSIS
    Finds processes that are locking a specified file.
 
    .DESCRIPTION
    The Find-LockingProcesses function identifies processes that are locking a specified file by checking the modules loaded by each process.
 
    .PARAMETER LockedFile
    The path to the locked file.
 
    .EXAMPLE
    $lockingProcesses = Find-LockingProcesses -LockedFile "C:\Path\To\LockedFile.txt"
    Finds processes locking the specified file and returns the processes.
 
    .NOTES
    This function relies on the Get-Process cmdlet and its ability to enumerate loaded modules.
    #>


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

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

    Process {
        try {
            Write-EnhancedLog -Message "Finding processes locking file: $LockedFile" -Level "INFO"
            $lockingProcesses = Get-Process | Where-Object { $_.Modules | Where-Object { $_.FileName -eq $LockedFile } }
            if ($lockingProcesses) {
                foreach ($process in $lockingProcesses) {
                    Write-EnhancedLog -Message "Process locking the file: $($process.ProcessName) (ID: $($process.Id))" -Level "INFO"
                }
            } else {
                Write-EnhancedLog -Message "No processes found locking the file: $LockedFile" -Level "INFO"
            }
            return $lockingProcesses
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while finding locking processes: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Find-LockingProcesses function" -Level "Notice"
    }
}

# Example usage
# $lockingProcesses = Find-LockingProcesses -LockedFile "C:\Path\To\LockedFile.txt"
#EndRegion '.\Public\Find-LockingProcesses.ps1' 57
#Region '.\Public\Find-NewStatusFile.ps1' -1

function Find-NewStatusFile {
    <#
    .SYNOPSIS
    Finds the status file in user profiles or system context with retries.
 
    .DESCRIPTION
    This function searches for a specified status file in user profiles or system context (depending on whether the script is running as SYSTEM).
    It will retry the search up to a defined number of times with a specified interval between retries if the file is not found on the first attempt.
 
    .PARAMETER LogFolder
    The folder name within the user profile or system context where the log file is expected to be found.
 
    .PARAMETER StatusFileName
    The name of the status file to be located.
 
    .PARAMETER MaxRetries
    The maximum number of retry attempts to find the status file.
 
    .PARAMETER RetryInterval
    The time (in seconds) to wait between retry attempts.
 
    .EXAMPLE
    Find-NewStatusFile -LogFolder "logs" -StatusFileName "ODSyncUtilStatus.json" -MaxRetries 5 -RetryInterval 10
 
    This command will search for the status file "ODSyncUtilStatus.json" in the "logs" folder, retrying up to 5 times with a 10-second interval between retries.
 
    .NOTES
    Author: Abdullah Ollivierre
    Date: 2024-09-06
 
    .LINK
    https://github.com/yourproject/documentation
 
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            HelpMessage = "Specify the log folder path.")]
        [string]$LogFolder,

        [Parameter(Mandatory = $true,
            HelpMessage = "Specify the status file name.")]
        [string]$StatusFileName,

        [Parameter(Mandatory = $true,
            HelpMessage = "Specify the maximum number of retries.")]
        [ValidateRange(1, 100)]
        [int]$MaxRetries,

        [Parameter(Mandatory = $true,
            HelpMessage = "Specify the interval (in seconds) between retries.")]
        [ValidateRange(1, 60)]
        [int]$RetryInterval
    )

    Begin {
        Write-EnhancedLog -Message "Starting Find-NewStatusFile function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
        $isSystem = Test-RunningAsSystem
        $fileFound = $false
        $statusFile = $null
    }

    Process {
        $retryCount = 0

        try {
            while ($retryCount -lt $MaxRetries -and -not $fileFound) {
                Write-EnhancedLog -Message "Attempt $($retryCount + 1) of $MaxRetries to find status file" -Level "INFO"

                if ($isSystem) {
                    $userProfiles = Get-ChildItem 'C:\Users' -Directory | Where-Object { $_.Name -notlike "Public" -and $_.Name -notlike "Default*" }
                    foreach ($profile in $userProfiles) {
                        $profileLogFolder = Join-Path -Path $profile.FullName -ChildPath $LogFolder
                        $profileStatusFile = Join-Path -Path $profileLogFolder -ChildPath $StatusFileName
                        Write-EnhancedLog -Message "Checking status file in profile: $($profile.FullName)" -Level "INFO"
                        if (Test-Path -Path $profileStatusFile) {
                            $fileFound = $true
                            $statusFile = Get-Item -Path $profileStatusFile
                            Write-EnhancedLog -Message "Status file found in $($statusFile.FullName)" -Level "INFO"
                            break
                        }
                    }
                }
                else {
                    $logFolder = Join-Path -Path $env:USERPROFILE -ChildPath $LogFolder
                    $statusFile = Join-Path -Path $logFolder -ChildPath $StatusFileName
                    Write-EnhancedLog -Message "Checking status file in current user's profile: $logFolder" -Level "INFO"

                    if (Test-Path -Path $statusFile) {
                        $fileFound = $true
                        Write-EnhancedLog -Message "Status file found: $statusFile" -Level "INFO"
                    }
                }

                if (-not $fileFound) {
                    Write-EnhancedLog -Message "Status file not found. Retrying in $RetryInterval seconds..." -Level "WARNING"
                    Start-Sleep -Seconds $RetryInterval
                    $retryCount++
                }
            }

            if (-not $fileFound) {
                $errorMessage = "Status file not found after $MaxRetries retries."
                Write-EnhancedLog -Message $errorMessage -Level "ERROR"
                Write-EnhancedLog -Message "Please check if you are logged in to OneDrive and try again." -Level "ERROR"
                throw [System.Exception]::new($errorMessage)
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Find-NewStatusFile: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_  # Rethrow the error to halt the script if necessary
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Find-NewStatusFile function" -Level "NOTICE"
        return $statusFile
    }
}
#EndRegion '.\Public\Find-NewStatusFile.ps1' 123
#Region '.\Public\Find-OneDrivePath.ps1' -1

function Find-OneDrivePath {
    <#
    .SYNOPSIS
    Finds the path to the OneDrive executable in common installation directories.
   
    .DESCRIPTION
    The Find-OneDrivePath function searches for the OneDrive executable in various common installation directories and returns the path if found.
   
    .EXAMPLE
    $oneDrivePath = Find-OneDrivePath
    #>


    [CmdletBinding()]
    param ()

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

    Process {
        try {
            $possiblePaths = @(
                "C:\Program Files\Microsoft OneDrive\OneDrive.exe",
                "C:\Program Files (x86)\Microsoft OneDrive\OneDrive.exe",
                "$env:LOCALAPPDATA\Microsoft\OneDrive\OneDrive.exe",
                "$env:LOCALAPPDATA\Microsoft\OneDrive\Update\OneDriveSetup.exe",
                "C:\Users\$env:USERNAME\AppData\Local\Microsoft\OneDrive\OneDrive.exe"
            )

            foreach ($path in $possiblePaths) {
                if (Test-Path -Path $path) {
                    Write-EnhancedLog -Message "Found OneDrive at: $path" -Level "INFO"
                    return $path
                }
            }

            Write-EnhancedLog -Message "OneDrive executable not found in common directories." -Level "WARNING"
            return $null
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Find-OneDrivePath function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Find-OneDrivePath function" -Level "Notice"
    }
}

# Example usage
# $oneDrivePath = Find-OneDrivePath
#EndRegion '.\Public\Find-OneDrivePath.ps1' 55
#Region '.\Public\Find-UninstallString.ps1' -1

       # Function to find the uninstall string from the registry
       function Find-UninstallString {
        param (
            [string[]]$UninstallKeys,
            [string]$ApplicationName
        )

        try {
            foreach ($key in $UninstallKeys) {
                $items = Get-ChildItem -Path $key -ErrorAction SilentlyContinue
                foreach ($item in $items) {
                    $app = Get-ItemProperty -Path $item.PsPath
                    if ($app.DisplayName -like $ApplicationName) {
                        Write-EnhancedLog -Message "Found application: $($app.DisplayName) with product ID: $($app.PSChildName)" -Level 'INFO'
                        return $app.PSChildName.Trim('{}')
                    }
                }
            }
            Write-EnhancedLog -Message "No matching application found for: $ApplicationName" -Level 'WARNING'
        } catch {
            Handle-Error -ErrorRecord $_
        }
        return $null
    }
#EndRegion '.\Public\Find-UninstallString.ps1' 25
#Region '.\Public\Generate-LicenseReports.ps1' -1

# Function to generate reports based on user licenses
function Generate-LicenseReports {
    param (
        [Parameter(Mandatory = $true)]
        [System.Collections.Generic.List[PSCustomObject]]$Results,
        [Parameter(Mandatory = $true)]
        [string]$PSScriptRoot,
        [Parameter(Mandatory = $true)]
        [string]$ExportsFolderName
    )

    # Remove duplicates based on UserEntraID
    $uniqueResults = $Results | Sort-Object -Property UserEntraID -Unique

    # Generate reports for users with and without Business Premium licenses
    $premiumLicenses = $uniqueResults | Where-Object { $_.UserLicense -eq 'Microsoft 365 Business Premium' }
    $nonPremiumLicenses = $uniqueResults | Where-Object { $_.UserLicense -ne 'Microsoft 365 Business Premium' }

    $premiumLicenses | Export-Csv "$PSScriptRoot/$ExportsFolderName/Report_PremiumLicenses.csv" -NoTypeInformation
    $nonPremiumLicenses | Export-Csv "$PSScriptRoot/$ExportsFolderName/Report_NonPremiumLicenses.csv" -NoTypeInformation

    # Output totals to console
    Write-EnhancedLog -Message "Total users with Business Premium licenses: $($premiumLicenses.Count)" -Level "INFO"
    Write-EnhancedLog -Message "Total users without Business Premium licenses: $($nonPremiumLicenses.Count)" -Level "INFO"

    Write-EnhancedLog -Message "Generated reports for users with and without Business Premium licenses." -Level "INFO"
}

# # Example usage
# $Json = @() # Your JSON data here
# $Headers = @{} # Your actual headers
# $PSScriptRoot = "C:\Path\To\ScriptRoot" # Update to your script root path
# $ExportsFolderName = "CustomExports"

# $results = Process-AllDevices -Json $Json -Headers $Headers

# # Generate and export the reports
# Generate-LicenseReports -Results $results -PSScriptRoot $PSScriptRoot -ExportsFolderName $ExportsFolderName
#EndRegion '.\Public\Generate-LicenseReports.ps1' 39
#Region '.\Public\Generate-PII-RemovedReport.ps1' -1

# Function to generate a report for PII Removed cases
function Generate-PII-RemovedReport {
    param (
        [Parameter(Mandatory = $true)]
        [System.Collections.Generic.List[PSCustomObject]]$Results,
        [Parameter(Mandatory = $true)]
        [string]$PSScriptRoot,
        [Parameter(Mandatory = $true)]
        [string]$ExportsFolderName
    )

    # Filter results for PII Removed (external) cases
    $piiRemovedResults = $Results | Where-Object { $_.DeviceStateInIntune -eq 'External' }

    # Export the results to a CSV file
    $piiRemovedResults | Export-Csv "$PSScriptRoot/$ExportsFolderName/Report_PIIRemoved.csv" -NoTypeInformation

    # Output totals to console
    Write-EnhancedLog -Message "Total users with PII Removed (external Azure AD/Entra ID tenants): $($piiRemovedResults.Count)" -Level "Warning"
    Write-EnhancedLog -Message "Generated report for users with PII Removed (external Azure AD/Entra ID tenants." -Level "INFO"
}

# # Example usage
# $Json = @() # Your JSON data here
# $Headers = @{} # Your actual headers
# $PSScriptRoot = "C:\Path\To\ScriptRoot" # Update to your script root path
# $ExportsFolderName = "CustomExports"

# $results = Process-AllDevices -Json $Json -Headers $Headers

# # Generate and export the PII Removed report
# Generate-PII-RemovedReport -Results $results -PSScriptRoot $PSScriptRoot -ExportsFolderName $ExportsFolderName
#EndRegion '.\Public\Generate-PII-RemovedReport.ps1' 33
#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-AllGroupAccounts-Archive.ps1' -1

# function Get-AllGroupAccounts {
# param (
# [string]$GroupName = "Administrators"
# )

# Begin {
# Write-EnhancedLog -Message "Starting Get-AllGroupAccounts function for group '$GroupName'" -Level "Notice"
# # Initialize a list to store all accounts
# $allAccounts = [System.Collections.Generic.List[PSCustomObject]]::new()
# }

# Process {
# # Get all group members
# $admins = Get-GroupMembers -GroupName $GroupName

# if ($admins.Count -gt 0) {
# foreach ($admin in $admins) {
# # Extract the account name from the PartComponent
# $accountName = Extract-AccountName -PartComponent $admin.PartComponent

# if ($accountName) {
# # Try to resolve the account to check if it's orphaned
# $resolved = Resolve-Account -AccountName $accountName

# # Add the account to the list, flagging if it is orphaned or not
# $allAccounts.Add([pscustomobject]@{
# AccountName = $accountName
# SID = $admin.PartComponent
# IsOrphaned = -not $resolved
# })
                    
# # Log the status of the account
# if ($resolved) {
# Write-EnhancedLog -Message "Resolved account: $accountName" -Level "INFO"
# } else {
# Write-EnhancedLog -Message "Orphaned account detected: $accountName" -Level "WARNING"
# }
# }
# }
# }
# else {
# Write-EnhancedLog -Message "No members found in the '$GroupName' group." -Level "WARNING"
# }
# }

# End {
# if ($allAccounts.Count -eq 0) {
# Write-EnhancedLog -Message "No accounts found in the '$GroupName' group." -Level "INFO"
# }
# else {
# Write-EnhancedLog -Message "All accounts retrieved from '$GroupName':" -Level "INFO"

# # Output the accounts to the log properly formatted as a string
# $accountsSummary = $allAccounts | Out-String
# Write-EnhancedLog -Message $accountsSummary -Level "INFO"
# }

# Write-EnhancedLog -Message "Exiting Get-AllGroupAccounts function" -Level "Notice"
# return $allAccounts
# }
# }
#EndRegion '.\Public\Get-AllGroupAccounts-Archive.ps1' 62
#Region '.\Public\Get-AppInfoFromJson.ps1' -1

function Get-AppInfoFromJson {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$jsonPath
    )

    Begin {
        Write-EnhancedLog -Message "Starting Get-AppInfoFromJson function" -Level "INFO"
        Log-Params -Params @{ jsonPath = $jsonPath }
    }

    Process {
        try {
            # Check if the file exists
            if (-Not (Test-Path -Path $jsonPath)) {
                Write-Error "The file at path '$jsonPath' does not exist."
                return
            }

            # Read the JSON content from the file
            Write-EnhancedLog -Message "Reading JSON content from file: $jsonPath" -Level "INFO"
            $jsonContent = Get-Content -Path $jsonPath -Raw

            # Check if the JSON content is empty
            if (-Not $jsonContent) {
                Write-EnhancedLog -Message "The JSON content is empty." -Level "ERROR"
                return
            }

            # Convert the JSON content to a PowerShell object
            Write-EnhancedLog -Message "Converting JSON content to PowerShell object" -Level "INFO"
            $appData = ConvertFrom-Json -InputObject $jsonContent

            # Check if the appData is empty or null
            if (-Not $appData) {
                Write-EnhancedLog -Message "The JSON content did not contain any data." -Level "ERROR"
                return
            }

            # Extract the required information
            Write-EnhancedLog -Message "Extracting required information from JSON data" -Level "INFO"
            $extractedData = $appData | ForEach-Object {
                [PSCustomObject]@{
                    Id              = $_.Id
                    DisplayName     = $_.DisplayName
                    AppId           = $_.AppId
                    SignInAudience  = $_.SignInAudience
                    PublisherDomain = $_.PublisherDomain
                }
            }

            # Return the extracted data
            return $extractedData
        } catch {
            Write-EnhancedLog -Message "An error occurred while processing the JSON content: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Get-AppInfoFromJson function" -Level "INFO"
    }
}
#EndRegion '.\Public\Get-AppInfoFromJson.ps1' 65
#Region '.\Public\Get-AppName.ps1' -1

# Function to read the application name from app.json and append a timestamp
function Get-AppName {
    param (
        [string]$AppJsonFile
    )

    if (-Not (Test-Path $AppJsonFile)) {
        Write-EnhancedLog -Message "App JSON file not found: $AppJsonFile" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
        throw "App JSON file missing"
    }

    $appConfig = Get-Content -Path $AppJsonFile | ConvertFrom-Json
    $baseAppName = $appConfig.AppName
    $timestamp = (Get-Date).ToString("yyyyMMddHHmmss")
    $uniqueAppName = "$baseAppName-$timestamp"

    Write-EnhancedLog -Message "Generated unique app name: $uniqueAppName" -Level "INFO" -ForegroundColor ([ConsoleColor]::Cyan)
    return $uniqueAppName
}
#EndRegion '.\Public\Get-AppName.ps1' 20
#Region '.\Public\Get-CustomWin32AppName.ps1' -1


function Get-CustomWin32AppName {
    [CmdletBinding()]
    param(
        [string]$PRGID
    )
    process {
        if (-not [string]::IsNullOrWhiteSpace($PRGID)) {
            return $PRGID  # Directly return PRGID if it's valid
        }
        else {
            return "DefaultAppName"  # Fallback if PRGID is not provided
        }
    }
}


# Get-CustomWin32AppName
#EndRegion '.\Public\Get-CustomWin32AppName.ps1' 19
#Region '.\Public\Get-DependentVMs.ps1' -1

function Get-DependentVMs {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$VHDXPath
    )

    Begin {
        Write-EnhancedLog -Message "Starting Get-DependentVMs function" -Level "INFO"
        Log-Params -Params @{ VHDXPath = $VHDXPath }
    }

    Process {
        try {
            Write-EnhancedLog -Message "Retrieving all VMs" -Level "INFO"
            $allVMs = Get-VM
            Write-EnhancedLog -Message "Total VMs found: $($allVMs.Count)" -Level "INFO"

            $dependentVMs = [System.Collections.Generic.List[PSObject]]::new()

            foreach ($vm in $allVMs) {
                $hardDrives = $vm.HardDrives
                foreach ($hd in $hardDrives) {
                    $parentPath = (Get-VHD -Path $hd.Path).ParentPath
                    if ($parentPath -eq $VHDXPath) {
                        $dependentVMs.Add($vm)
                        Write-EnhancedLog -Message "Dependent VM: $($vm.Name)" -Level "INFO"
                        break
                    }
                }
            }

            Write-EnhancedLog -Message "Total dependent VMs using VHDX $VHDXPath $($dependentVMs.Count)" -Level "INFO"
            return $dependentVMs
        } catch {
            Write-EnhancedLog -Message "An error occurred while retrieving dependent VMs: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            return [System.Collections.Generic.List[PSObject]]::new()
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Get-DependentVMs function" -Level "INFO"
    }
}
#EndRegion '.\Public\Get-DependentVMs.ps1' 46
#Region '.\Public\Get-DSRegStatus.ps1' -1


function Get-DSRegStatus {
    <#
    .SYNOPSIS
    Checks the device's join status (Workgroup, Azure AD Joined, Hybrid Joined, or On-prem Joined) and MDM enrollment (Intune Enrolled or Not).
 
    .DESCRIPTION
    The Get-DSRegStatus function runs the dsregcmd /status command and parses its output to determine the device's join status and whether it is enrolled in Microsoft Intune.
 
    .NOTES
    Version: 1.3
    Author: Abdullah Ollivierre
    Creation Date: 2024-08-15
    #>


    [CmdletBinding()]
    param ()

    Begin {
        Write-EnhancedLog -Message "Starting Get-DSRegStatus function" -Level "Notice"
    }

    Process {
        try {
            # Execute dsregcmd /status
            Write-EnhancedLog -Message "Running dsregcmd /status" -Level "INFO"
            $dsregcmdOutput = dsregcmd /status

            # Parse dsregcmd output to determine join status
            Write-EnhancedLog -Message "Parsing dsregcmd output" -Level "INFO"

            $isAzureADJoined = $dsregcmdOutput -match '.*AzureAdJoined\s*:\s*YES'
            $isHybridJoined = $dsregcmdOutput -match '.*DomainJoined\s*:\s*YES' -and $isAzureADJoined
            $isOnPremJoined = $dsregcmdOutput -match '.*DomainJoined\s*:\s*YES' -and -not $isAzureADJoined
            $isWorkgroup = -not ($isAzureADJoined -or $isHybridJoined -or $isOnPremJoined)

            # Determine MDM enrollment status
            $isMDMEnrolled = $dsregcmdOutput -match '.*MDMUrl\s*:\s*(https://manage\.microsoft\.com|https://enrollment\.manage\.microsoft\.com)'

            # Log the parsed results
            Write-EnhancedLog -Message "Join status parsed: Workgroup: $isWorkgroup, AzureAD: $isAzureADJoined, Hybrid: $isHybridJoined, OnPrem: $isOnPremJoined" -Level "INFO"
            Write-EnhancedLog -Message "MDM enrollment status: $isMDMEnrolled" -Level "INFO"
        }
        catch {
            Write-EnhancedLog -Message "Error while parsing dsregcmd output: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
    }

    End {
        Write-EnhancedLog -Message "Returning parsed device status" -Level "INFO"
        return @{
            IsWorkgroup     = $isWorkgroup
            IsAzureADJoined = $isAzureADJoined
            IsHybridJoined  = $isHybridJoined
            IsOnPremJoined  = $isOnPremJoined
            IsMDMEnrolled   = $isMDMEnrolled
        }
    }
}


#Here is an example for a decision-making tree

# # Main script execution block
# $dsregStatus = Get-DSRegStatus

# # Determine and output the join status
# if ($dsregStatus.IsWorkgroup) {
# Write-Output "Device is Workgroup joined (not Azure AD, Hybrid, or On-prem Joined)."
# } elseif ($dsregStatus.IsAzureADJoined -and -not $dsregStatus.IsHybridJoined) {
# Write-Output "Device is Azure AD Joined."
# } elseif ($dsregStatus.IsHybridJoined) {
# Write-Output "Device is Hybrid Joined (both On-prem and Azure AD Joined)."
# } elseif ($dsregStatus.IsOnPremJoined) {
# Write-Output "Device is On-prem Joined only."
# }

# # Determine and output the MDM enrollment status
# if ($dsregStatus.IsMDMEnrolled) {
# Write-Output "Device is Intune Enrolled."
# } else {
# Write-Output "Device is NOT Intune Enrolled."
# }

# # Exit code based on Azure AD and MDM status
# if ($dsregStatus.IsAzureADJoined -and -not $dsregStatus.IsHybridJoined -and $dsregStatus.IsMDMEnrolled) {
# Write-Output "Device is Azure AD Joined and Intune Enrolled. No migration needed."
# exit 0 # Do not migrate: Device is Azure AD Joined and Intune Enrolled
# } else {
# # Migrate: All other cases where the device is not 100% Azure AD joined or is hybrid/on-prem joined
# exit 1
# }
#EndRegion '.\Public\Get-DSRegStatus.ps1' 95
#Region '.\Public\Get-EnhancedLocalGroupMembers.ps1' -1

function Get-EnhancedLocalGroupMembers {
    <#
    .SYNOPSIS
    Retrieves and logs members of a specified local group, differentiating between user and system accounts.
 
    .DESCRIPTION
    The Get-EnhancedLocalGroupMembers function retrieves all members of a specified local group. It logs the retrieval process, handles errors gracefully, and differentiates between user, system accounts, and built-in groups. It also provides a summary report with success and failure counts.
 
    .PARAMETER GroupName
    The name of the local group to retrieve members from (default is "Administrators").
 
    .PARAMETER PassThru
    Allows pipeline support and passes the objects to the pipeline for further processing.
 
    .EXAMPLE
    Get-EnhancedLocalGroupMembers -GroupName "Administrators" | Format-Table
    Retrieves members of the "Administrators" group and formats the output as a table.
 
    .EXAMPLE
    "Administrators", "Users" | Get-EnhancedLocalGroupMembers
    Retrieves members from multiple groups using pipeline input.
    #>


    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="Medium")]
    param (
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Name of the local group to retrieve members from.")]
        [ValidateNotNullOrEmpty()]
        [string]$GroupName = "Administrators",

        [Parameter(Mandatory = $false, HelpMessage = "Passes objects down the pipeline.")]
        [switch]$PassThru
    )

    Begin {
        Write-EnhancedLog -Message "Starting Get-EnhancedLocalGroupMembers function for group '$GroupName'" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Initialize a System.Collections.Generic.List object to store group members efficiently
        $groupMembers = [System.Collections.Generic.List[PSCustomObject]]::new()
        $successCount = 0
        $failureCount = 0
    }

    Process {
        if ($PSCmdlet.ShouldProcess("Group '$GroupName'", "Retrieve group members")) {
            try {
                $group = [ADSI]"WinNT://./$GroupName,group"
                if (!$group) {
                    Write-EnhancedLog -Message "Group '$GroupName' not found." -Level "ERROR"
                    throw "Group '$GroupName' not found."
                }
                Write-EnhancedLog -Message "Group '$GroupName' found. Retrieving members..." -Level "INFO"

                $members = $group.psbase.Invoke("Members")
                foreach ($member in $members) {
                    try {
                        # Get the full name of the account
                        $accountName = $member.GetType().InvokeMember("Name", 'GetProperty', $null, $member, $null)

                        # Determine if it's a user or system account
                        $accountType = if ($accountName -match "^NT AUTHORITY") {
                            "System Account"
                        } elseif ($accountName -match "^BUILTIN") {
                            "Built-in Group"
                        } else {
                            "User Account"
                        }

                        # Create a custom object for each account and add it to the list efficiently
                        $groupMember = [PSCustomObject]@{
                            Account = $accountName
                            Type    = $accountType
                        }

                        $groupMembers.Add($groupMember)
                        $successCount++
                        
                        # Log the account details
                        Write-EnhancedLog -Message "Account: $accountName, Type: $accountType" -Level "INFO"

                        # Output the object to the pipeline if PassThru is enabled
                        if ($PassThru) {
                            $groupMember
                        }
                    }
                    catch {
                        Write-EnhancedLog -Message "Failed to process member: $($_.Exception.Message)" -Level "WARNING"
                        $failureCount++
                    }
                }
            }
            catch {
                Write-EnhancedLog -Message "Error retrieving group members for group '$GroupName': $($_.Exception.Message)" -Level "ERROR"
                Handle-Error -ErrorRecord $_
                throw
            }
        } else {
            Write-EnhancedLog -Message "Operation skipped due to WhatIf or Confirm." -Level "INFO"
        }
    }

    End {
        Write-EnhancedLog -Message "Finalizing the member retrieval process for group '$GroupName'" -Level "INFO"
        if ($groupMembers.Count -gt 0) {
            Write-EnhancedLog -Message "Successfully retrieved members for group '$GroupName'." -Level "INFO"
        }
        else {
            Write-EnhancedLog -Message "No members found for group '$GroupName'." -Level "WARNING"
        }

        # Output the summary report
        Show-SummaryReport -SuccessCount $successCount -FailureCount $failureCount

        # Output the list of group members only if PassThru is not enabled
        if (-not $PassThru) {
            $groupMembers
        }
    }
}

# Summary Report Function
function Show-SummaryReport {
    param (
        [int]$SuccessCount,
        [int]$FailureCount
    )

    $totalCount = $SuccessCount + $FailureCount

    # Output the summary with color coding
    Write-Host "Summary Report" -ForegroundColor Cyan
    Write-Host "Total Members Processed: $totalCount" -ForegroundColor Yellow
    Write-Host "Success: $SuccessCount" -ForegroundColor Green
    Write-Host "Failures: $FailureCount" -ForegroundColor Red
}

# Example usage:
# $params = @{
# GroupName = "Administrators"
# }
# Get-EnhancedLocalGroupMembers @params

# Example pipeline usage:
# "Administrators", "Users" | Get-EnhancedLocalGroupMembers -PassThru




























# function Add-UserToLocalAdminGroup {
# [CmdletBinding(SupportsShouldProcess = $true)]
# param (
# [Parameter(Mandatory = $true, HelpMessage = "Specify the username to add to the local admin group.")]
# [string]$Username
# )

# Process {
# # Use ADSI to add the user to the local administrators group
# if ($PSCmdlet.ShouldProcess("Administrators group", "Adding user '$Username'")) {
# try {
# $adminGroup = [ADSI]"WinNT://./Administrators,group"
# $user = [ADSI]"WinNT://./$Username,user"
# $adminGroup.Add($user.PSBase.Path)
# Write-Host "Successfully added $Username to the local Administrators group." -ForegroundColor Green
# }
# catch {
# Write-Host "Failed to add user: $($_.Exception.Message)" -ForegroundColor Red
# }
# }
# }
# }


# function Verify-UserMembershipBefore {
# [CmdletBinding()]
# param (
# [Parameter(Mandatory = $true, HelpMessage = "Specify the username to verify.")]
# [string]$Username
# )

# Process {
# Write-Host "Verifying if '$Username' is a member of the local Administrators group before operation..." -ForegroundColor Yellow

# # Get the list of current members
# $admins = Get-EnhancedLocalGroupMembers -GroupName "Administrators"
# $userMembership = $admins | Where-Object { $_.Account -eq $Username }

# if ($userMembership) {
# Write-Host "User '$Username' is already a member of the local Administrators group." -ForegroundColor Cyan
# return $true
# }
# else {
# Write-Host "User '$Username' is NOT a member of the local Administrators group." -ForegroundColor Red
# return $false
# }
# }
# }




# function Verify-UserMembershipAfter {
# [CmdletBinding()]
# param (
# [Parameter(Mandatory = $true, HelpMessage = "Specify the username to verify.")]
# [string]$Username
# )

# Process {
# Write-Host "Verifying if '$Username' has been successfully added to the local Administrators group..." -ForegroundColor Yellow

# # Get the list of current members
# $admins = Get-EnhancedLocalGroupMembers -GroupName "Administrators"
# $userMembership = $admins | Where-Object { $_.Account -eq $Username }

# if ($userMembership) {
# Write-Host "User '$Username' has been successfully added to the local Administrators group." -ForegroundColor Green
# return $true
# }
# else {
# Write-Host "User '$Username' was NOT added to the local Administrators group." -ForegroundColor Red
# return $false
# }
# }
# }





# function Verify-GroupMemberships {
# [CmdletBinding()]
# param (
# [Parameter(Mandatory = $true, HelpMessage = "Specify the username to verify.")]
# [string]$Username
# )

# Process {
# Write-Host "Verifying group memberships for user '$Username'..." -ForegroundColor Yellow

# try {
# $user = [ADSI]"WinNT://./$Username,user"
# $groups = $user.Invoke("Groups")

# foreach ($group in $groups) {
# $groupName = $group.GetType().InvokeMember("Name", 'GetProperty', $null, $group, $null)
# Write-Host "$Username is a member of group: $groupName" -ForegroundColor Cyan
# }
# }
# catch {
# Write-Host "Failed to retrieve group memberships for '$Username': $($_.Exception.Message)" -ForegroundColor Red
# }
# }
# }





# # Verifying before
# $beforeMember = Verify-UserMembershipBefore -Username "Admin-Abdullah"
# Verify-GroupMemberships -Username "Admin-Abdullah"

# # Add user if not already a member
# if (-not $beforeMember) {
# Add-UserToLocalAdminGroup -Username "Admin-Abdullah"
# }

# # Verifying after
# Verify-UserMembershipAfter -Username "Admin-Abdullah"
# Verify-GroupMemberships -Username "Admin-Abdullah"






#EndRegion '.\Public\Get-EnhancedLocalGroupMembers.ps1' 301
#Region '.\Public\Get-FriendlyNamesForPermissions.ps1' -1

#need to the test the following first

function Get-FriendlyNamesForPermissions {
    param (
        [string]$tenantId,
        [string]$clientId,
        [string]$clientSecret,
        [string]$permissionsFile
    )

    # Function to get access token
    function Get-MsGraphAccessToken {
        param (
            [string]$tenantId,
            [string]$clientId,
            [string]$clientSecret
        )

        $body = @{
            grant_type    = "client_credentials"
            client_id     = $clientId
            client_secret = $clientSecret
            scope         = "https://graph.microsoft.com/.default"
        }

        $response = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" -ContentType "application/x-www-form-urlencoded" -Body $body
        return $response.access_token
    }

    # Load permissions from the JSON file
    if (Test-Path -Path $permissionsFile) {
        $permissions = Get-Content -Path $permissionsFile | ConvertFrom-Json
    }
    else {
        Write-Error "Permissions file not found: $permissionsFile"
        throw "Permissions file not found: $permissionsFile"
    }

    # Get access token
    $accessToken = Get-MsGraphAccessToken -tenantId $tenantId -clientId $clientId -clientSecret $clientSecret

    # Create header for Graph API requests
    $headers = @{
        Authorization = "Bearer $accessToken"
    }

    # Translate IDs to friendly names
    foreach ($permission in $permissions) {
        $id = $permission.Id
        $url = "https://graph.microsoft.com/v1.0/servicePrincipals?$filter=appRoles/id eq '$id' or oauth2PermissionScopes/id eq '$id'&$select=displayName"
        $response = Invoke-RestMethod -Method Get -Uri $url -Headers $headers
        $friendlyName = $response.value[0].displayName
        $permission | Add-Member -MemberType NoteProperty -Name FriendlyName -Value $friendlyName
    }

    return $permissions
}

# # Example usage
# $tenantId = "your-tenant-id"
# $clientId = "your-client-id"
# $clientSecret = "your-client-secret"
# $permissionsFilePath = Join-Path -Path $PSScriptRoot -ChildPath "permissions.json"

# $friendlyPermissions = Get-FriendlyNamesForPermissions -tenantId $tenantId -clientId $clientId -clientSecret $clientSecret -permissionsFile $permissionsFilePath
# $friendlyPermissions | Format-Table -AutoSize
#EndRegion '.\Public\Get-FriendlyNamesForPermissions.ps1' 67
#Region '.\Public\Get-FunctionModule.ps1' -1

function Get-FunctionModule {
    param (
        [string]$FunctionName
    )

    # Get all imported modules
    $importedModules = Get-Module

    # Iterate through the modules to find which one exports the function
    foreach ($module in $importedModules) {
        if ($module.ExportedFunctions[$FunctionName]) {
            return $module.Name
        }
    }

    # If the function is not found in any module, return null
    return $null
}
#EndRegion '.\Public\Get-FunctionModule.ps1' 19
#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-KeyProtectorId.ps1' -1

function Get-KeyProtectorId {
    <#
    .SYNOPSIS
    Retrieves the key protector ID for the specified drive.
 
    .DESCRIPTION
    The Get-KeyProtectorId function retrieves the key protector ID for the specified BitLocker protected drive.
 
    .PARAMETER BitlockerDrive
    The drive letter of the BitLocker protected drive.
 
    .EXAMPLE
    Get-KeyProtectorId -BitlockerDrive "C:"
    Retrieves the key protector ID for drive C:.
    #>


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

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

    Process {
        try {
            $bitlockerVolume = Get-BitLockerVolume -MountPoint $BitlockerDrive
            $keyProtector = $bitlockerVolume.KeyProtector | Where-Object { $_.KeyProtectorType -eq 'RecoveryPassword' }
            Write-EnhancedLog -Message "Retrieved key protector ID for drive: $BitlockerDrive" -Level "INFO"
            return $keyProtector.KeyProtectorId
        }
        catch {
            Write-EnhancedLog -Message "Failed to retrieve key protector ID for drive: $BitlockerDrive" -Level "ERROR"
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Get-KeyProtectorId function" -Level "Notice"
    }
}
#EndRegion '.\Public\Get-KeyProtectorId.ps1' 45
#Region '.\Public\Get-LockingProcess.ps1' -1


function Get-LockingProcess {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$FilePath,

        [Parameter(Mandatory = $true)]
        [string]$HandlePath  # Path to handle64.exe
    )

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

        $DownloadHandleParams = @{
            TargetFolder = "C:\ProgramData\SystemTools"
        }
        Download-Handle @DownloadHandleParams
        


        # Validate the HandlePath
        if (-not (Test-Path -Path $HandlePath)) {
            Write-EnhancedLog -Message "Handle executable not found at path: $HandlePath" -Level "ERROR"
            throw "Handle executable not found at path: $HandlePath"
        }

        Write-EnhancedLog -Message "Handle executable found at path: $HandlePath" -Level "INFO"
    }

    Process {
        try {
            Write-EnhancedLog -Message "Identifying processes locking file: $FilePath using Handle" -Level "INFO"
            $handleOutput = &"$HandlePath" $FilePath 2>&1
            $processes = @()

            if ($handleOutput) {
                Write-EnhancedLog -Message "Processing output from Handle" -Level "INFO"
                foreach ($line in $handleOutput) {
                    if ($line -match 'pid:\s*(\d+)\s*type:\s*\w+\s*([^\s]+)\s*') {
                        $processId = $matches[1]
                        $processName = $matches[2]
                        $processes += [PSCustomObject]@{
                            ProcessId   = $processId
                            ProcessName = $processName
                        }
                        Write-EnhancedLog -Message "Found locking process: ID = $processId, Name = $processName" -Level "INFO"
                    }
                }
            }
            else {
                Write-EnhancedLog -Message "No output received from Handle. No locking processes found for file: $FilePath" -Level "WARNING"
            }

            return $processes
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while identifying locking processes: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

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

function Get-MsGraphAccessToken {
    param (
        [string]$tenantId,
        [string]$clientId,
        [string]$clientSecret
    )

    $tokenEndpoint = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
    $body = @{
        client_id     = $clientId
        scope         = "https://graph.microsoft.com/.default"
        client_secret = $clientSecret
        grant_type    = "client_credentials"
    }

    $httpClient = New-Object System.Net.Http.HttpClient
    $bodyString = ($body.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" }) -join '&'

    try {
        $content = New-Object System.Net.Http.StringContent($bodyString, [System.Text.Encoding]::UTF8, "application/x-www-form-urlencoded")
        $response = $httpClient.PostAsync($tokenEndpoint, $content).Result

        if (-not $response.IsSuccessStatusCode) {
            Write-EnhancedLog -Message "HTTP request failed with status code: $($response.StatusCode)" -Level "ERROR"
            return $null
        }

        $responseContent = $response.Content.ReadAsStringAsync().Result
        $accessToken = (ConvertFrom-Json $responseContent).access_token

        if ($accessToken) {
            Write-EnhancedLog -Message "Access token retrieved successfully" -Level "INFO"
            return $accessToken
        }
        else {
            Write-EnhancedLog -Message "Failed to retrieve access token, response was successful but no token was found." -Level "ERROR"
            return $null
        }
    }
    catch {
        Write-EnhancedLog -Message "Failed to execute HTTP request or process results: $_" -Level "ERROR"
        return $null
    }
}
#EndRegion '.\Public\Get-MsGraphAccessToken.ps1' 45
#Region '.\Public\Get-MsGraphAccessTokenCert.ps1' -1

function Get-MsGraphAccessTokenCert {
    param (
        [Parameter(Mandatory = $true)]
        [string]$tenantId,
        [Parameter(Mandatory = $true)]
        [string]$clientId,
        [Parameter(Mandatory = $true)]
        [string]$certPath,
        [Parameter(Mandatory = $true)]
        [string]$certPassword
    )

    $tokenEndpoint = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"

    # Load the certificate
    $cert = Load-Certificate -certPath $certPath -certPassword $certPassword

    # Create JWT header
    $jwtHeader = @{
        alg = "RS256"
        typ = "JWT"
        x5t = [Convert]::ToBase64String($cert.GetCertHash())
    }

    $now = [System.DateTime]::UtcNow
    Write-EnhancedLog -Message "Current UTC Time: $now"

    # Get nbf and exp times
    $nbfTime = Get-UnixTime -offsetMinutes -5  # nbf is 5 minutes ago
    $expTime = Get-UnixTime -offsetMinutes 55  # exp is 55 minutes from now

    Write-EnhancedLog -Message "nbf (not before) time: $nbfTime"
    Write-EnhancedLog -Message "exp (expiration) time: $expTime"

    # Create JWT payload
    $jwtPayload = @{
        aud = $tokenEndpoint
        exp = $expTime
        iss = $clientId
        jti = [guid]::NewGuid().ToString()
        nbf = $nbfTime
        sub = $clientId
    }

    Write-EnhancedLog -Message "JWT Payload: $(ConvertTo-Json $jwtPayload -Compress)"

    # Generate JWT assertion
    $clientAssertion = Generate-JWTAssertion -jwtHeader $jwtHeader -jwtPayload $jwtPayload -cert $cert

    # Send token request
    return Send-TokenRequest -tokenEndpoint $tokenEndpoint -clientId $clientId -clientAssertion $clientAssertion
}


# # Example usage of Get-MsGraphAccessTokenCert
# $tenantId = "b5dae566-ad8f-44e1-9929-5669f1dbb343"
# $clientId = "8230c33e-ff30-419c-a1fc-4caf98f069c9"
# $certPath = "C:\Code\appgallery\Intune-Win32-Deployer\apps-winget-repo\PR4B_ExportVPNtoSPO-v1\PR4B-ExportVPNtoSPO-v2\graphcert.pfx"
# $certPassword = "somepassword"
# $accessToken = Get-MsGraphAccessTokenCert -tenantId $tenantId -clientId $clientId -certPath $certPath -certPassword $certPassword
# Write-Host "Access Token: $accessToken"
#EndRegion '.\Public\Get-MsGraphAccessTokenCert.ps1' 62
#Region '.\Public\Get-NextVMNamePrefix.ps1' -1

function Get-NextVMNamePrefix {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [PSCustomObject]$Config
    )

    Begin {
        Write-EnhancedLog -Message "Starting Get-NextVMNamePrefix function" -Level "INFO"
    }

    Process {
        try {
            Write-EnhancedLog -Message "Retrieving the most recent VM" -Level "INFO"
            $mostRecentVM = Get-VM | Sort-Object -Property CreationTime -Descending | Select-Object -First 1
            $prefixNumber = 0

            if ($null -ne $mostRecentVM) {
                Write-EnhancedLog -Message "Most recent VM found: $($mostRecentVM.Name)" -Level "INFO"
                if ($mostRecentVM.Name -match '^\d+') {
                    $prefixNumber = [int]$matches[0]
                    Write-EnhancedLog -Message "Extracted prefix number: $prefixNumber" -Level "INFO"
                } else {
                    Write-EnhancedLog -Message "Most recent VM name does not start with a number" -Level "INFO"
                }
            } else {
                Write-EnhancedLog -Message "No existing VMs found" -Level "INFO"
            }

            $nextPrefixNumber = $prefixNumber + 1
            $newVMNamePrefix = $Config.VMNamePrefixFormat -f $nextPrefixNumber
            Write-EnhancedLog -Message "Generated new VM name prefix: $newVMNamePrefix" -Level "INFO"

            return $newVMNamePrefix
        } catch {
            Write-EnhancedLog -Message "An error occurred in Get-NextVMNamePrefix: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Get-NextVMNamePrefix function completed" -Level "INFO"
    }
}
#EndRegion '.\Public\Get-NextVMNamePrefix.ps1' 45
#Region '.\Public\Get-OrphanedSIDs-Archive.ps1' -1

# # function Get-OrphanedSIDs {
# # <#
# # .SYNOPSIS
# # Identifies orphaned SIDs in the 'Administrators' group.

# # .DESCRIPTION
# # The Get-OrphanedSIDs function retrieves members of the 'Administrators' group and checks if each member resolves to a valid user or group. Orphaned SIDs are identified when the account cannot be resolved, and those SIDs are returned as the output.

# # .EXAMPLE
# # $orphanedSIDs = Get-OrphanedSIDs
# # Outputs a list of orphaned SIDs from the 'Administrators' group.
# # #>

# # [CmdletBinding()]
# # param (
# # [string]$GroupName = "Administrators"
# # )

# # Begin {
# # Write-EnhancedLog -Message "Starting Get-OrphanedSIDs function" -Level "Notice"
        
# # # Initialize a list for storing orphaned SIDs efficiently
# # $orphanedSIDs = [System.Collections.Generic.List[PSCustomObject]]::new()

# # Write-EnhancedLog -Message "Retrieving members of the '$GroupName' group." -Level "INFO"
# # }

# # Process {
# # # Use WMI to retrieve members of the specified group
# # # Use single quotes for the string and concatenate $GroupName
  
# # try {
# # # Log the start of the retrieval process
# # Write-EnhancedLog -Message "Attempting to retrieve members of the '$GroupName' group." -Level "INFO"

# # $groupPattern = [regex]::Escape($GroupName)
# # $admins = Get-WmiObject -Class Win32_GroupUser | Where-Object { $_.GroupComponent -match $groupPattern }
        
# # # Log the count of members found
# # $count = $admins.Count
# # Write-EnhancedLog -Message "Found $count members in the '$GroupName' group." -Level "INFO"
        
# # if ($count -gt 0) {
# # # Log details of each group member
# # Write-EnhancedLog -Message "Listing all members of the '$GroupName' group:" -Level "INFO"
# # foreach ($admin in $admins) {
# # # Extract the username from the PartComponent property
# # if ($admin.PartComponent -match 'Win32_UserAccount.Domain="[^"]+",Name="([^"]+)"') {
# # $accountName = $matches[1]
                        
# # # Log the extracted account name
# # Write-EnhancedLog -Message "Member: $accountName" -Level "INFO"
# # }
# # else {
# # # Log a message if the PartComponent does not match the expected pattern
# # Write-EnhancedLog -Message "Could not extract a valid account name from: $($admin.PartComponent)" -Level "WARNING"
# # }
# # }
                
# # }
# # else {
# # Write-EnhancedLog -Message "No members found in the '$GroupName' group." -Level "WARNING"
# # }
# # }
# # catch {
# # # Log the error if retrieval fails
# # Write-EnhancedLog -Message "Failed to retrieve members of the '$GroupName' group: $($_.Exception.Message)" -Level "ERROR"
# # Handle-Error -ErrorRecord $_
        
# # # Re-throw the exception to handle it further upstream if needed
# # throw
# # }

# # # Iterate over each group member
# # foreach ($admin in $admins) {
# # # Extract the account name from the PartComponent
# # if ($admin.PartComponent -match 'Win32_UserAccount.Domain="[^"]+",Name="([^"]+)"') {
# # $accountName = $matches[1]

# # # Try to resolve the account to see if it's a valid user or group
# # try {
# # $account = [ADSI]"WinNT://$($env:COMPUTERNAME)/$accountName"
# # Write-EnhancedLog -Message "Resolved account: $accountName" -Level "INFO"
# # }
# # catch {
# # # Add orphaned SIDs to the list
# # $orphanedSIDs.Add([pscustomobject]@{
# # AccountName = $accountName
# # SID = $_.PartComponent
# # })
# # Write-EnhancedLog -Message "Orphaned SID detected: $accountName" -Level "Warning"
# # }
# # }
# # }
# # }

# # End {
# # if ($orphanedSIDs.Count -eq 0) {
# # Write-EnhancedLog -Message "No orphaned SIDs found in the '$GroupName' group." -Level "INFO"
# # }
# # else {
# # Write-EnhancedLog -Message "Orphaned SIDs found: $($orphanedSIDs | Format-Table -AutoSize)" -Level "Warning"
# # }

# # Write-EnhancedLog -Message "Exiting Get-OrphanedSIDs function" -Level "Notice"
# # return $orphanedSIDs
# # }
# # }



# function Extract-AccountName {
# param (
# [string]$PartComponent
# )

# try {
# if ($PartComponent -match 'Win32_UserAccount.Domain="[^"]+",Name="([^"]+)"') {
# return $matches[1]
# }
# else {
# Write-EnhancedLog -Message "Could not extract a valid account name from: $PartComponent" -Level "WARNING"
# return $null
# }
# }
# catch {
# Write-EnhancedLog -Message "Failed to extract account name from PartComponent: $($_.Exception.Message)" -Level "ERROR"
# Handle-Error -ErrorRecord $_
# throw
# }
# }






# function Resolve-Account {
# param (
# [string]$AccountName
# )

# try {
# $account = [ADSI]"WinNT://$($env:COMPUTERNAME)/$AccountName"
# Write-EnhancedLog -Message "Resolved account: $AccountName" -Level "INFO"
# return $true
# }
# catch {
# Write-EnhancedLog -Message "Orphaned SID detected: $AccountName" -Level "Warning"
# return $false
# }
# }
















# function Get-OrphanedSIDs {
# param (
# [string]$GroupName = "Administrators"
# )

# Begin {
# Write-EnhancedLog -Message "Starting Get-OrphanedSIDs function" -Level "Notice"
# $orphanedSIDs = [System.Collections.Generic.List[PSCustomObject]]::new()
# }

# Process {
# $admins = Get-GroupMembers -GroupName $GroupName

# if ($admins.Count -gt 0) {
# foreach ($admin in $admins) {
# $accountName = Extract-AccountName -PartComponent $admin.PartComponent

# if ($accountName) {
# $resolved = Resolve-Account -AccountName $accountName
# if (-not $resolved) {
# $orphanedSIDs.Add([pscustomobject]@{
# AccountName = $accountName
# SID = $admin.PartComponent
# })
# }
# }
# }
# }
# }

# End {
# if ($orphanedSIDs.Count -eq 0) {
# Write-EnhancedLog -Message "No orphaned SIDs found in the '$GroupName' group." -Level "INFO"
# }
# else {
# Write-EnhancedLog -Message "Orphaned SIDs found: $($orphanedSIDs | Format-Table -AutoSize)" -Level "Warning"
# }

# Write-EnhancedLog -Message "Exiting Get-OrphanedSIDs function" -Level "Notice"
# return $orphanedSIDs
# }
# }
#EndRegion '.\Public\Get-OrphanedSIDs-Archive.ps1' 211
#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\Get-PSFCSVLogFilePath.ps1' -1

function Get-PSFCSVLogFilePath {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0, HelpMessage = "Specify the base path where the logs will be stored.")]
        [string]$LogsPath,

        [Parameter(Mandatory = $true, Position = 1, HelpMessage = "Specify the job name to be used in the log file name.")]
        [string]$JobName,

        [Parameter(Mandatory = $true, Position = 2, HelpMessage = "Specify the name of the parent script.")]
        [string]$parentScriptName
    )

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

        # Ensure the destination directory exists
        if (-not (Test-Path -Path $LogsPath)) {
            New-Item -ItemType Directory -Path $LogsPath -Force | Out-Null
            Write-EnhancedLog -Message "Created Logs directory at: $LogsPath" -Level "INFO"
        }
    }

    Process {
        try {
            # Get the current username
            $username = if ($env:USERNAME) { $env:USERNAME } else { "UnknownUser" }
            Write-EnhancedLog -Message "Current username: $username" -Level "INFO"

            # Log the parent script name
            Write-EnhancedLog -Message "Script name: $parentScriptName" -Level "INFO"

            # Check if running as SYSTEM
            $isSystem = Test-RunningAsSystem
            Write-EnhancedLog -Message "Is running as SYSTEM: $isSystem" -Level "INFO"

            # Get the current date for folder creation
            $currentDate = Get-Date -Format "yyyy-MM-dd"

            # Construct the hostname and timestamp for the log filename
            $hostname = $env:COMPUTERNAME
            $timestamp = Get-Date -Format "yyyy-MM-dd-HH-mm-ss"
            $logFolderPath = "$LogsPath\$currentDate\$parentScriptName"

            # Ensure the log directory exists
            if (-not (Test-Path -Path $logFolderPath)) {
                New-Item -Path $logFolderPath -ItemType Directory -Force | Out-Null
                Write-EnhancedLog -Message "Created directory for log file: $logFolderPath" -Level "INFO"
            }

            # Generate log file path based on context
            $logFilePath = if ($isSystem) {
                "$logFolderPath\$hostname-$JobName-SYSTEM-$parentScriptName-log-$timestamp.csv"
            }
            else {
                "$logFolderPath\$hostname-$JobName-$username-$parentScriptName-log-$timestamp.csv"
            }

            $logFilePath = Sanitize-LogFilePath -LogFilePath $logFilePath

            # Validate the log file path before using it
            Validate-LogFilePath -LogFilePath $logFilePath

            Write-EnhancedLog -Message "Generated PSFramework CSV log file path: $logFilePath" -Level "INFO"
            return $logFilePath
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Get-PSFCSVLogFilePath: $_" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_  # Re-throw the error after logging it
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Get-PSFCSVLogFilePath function" -Level "NOTICE"
    }
}
#EndRegion '.\Public\Get-PSFCSVLogFilePath.ps1' 79
#Region '.\Public\Get-Secrets.ps1' -1

function Get-Secrets {
    <#
.SYNOPSIS
Loads secrets from a JSON file.
 
.DESCRIPTION
This function reads a JSON file containing secrets and returns an object with these secrets.
 
.PARAMETER SecretsPath
The path to the JSON file containing secrets. If not provided, the default is "secrets.json" in the same directory as the script.
 
.EXAMPLE
$secrets = Get-Secrets -SecretsPath "C:\Path\To\secrets.json"
 
This example loads secrets from the specified JSON file.
 
.NOTES
If the SecretsPath parameter is not provided, the function assumes the JSON file is named "secrets.json" and is located in the same directory as the script.
#>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        # [string]$SecretsPath = (Join-Path -Path $PSScriptRoot -ChildPath "secrets.json")
        [string]$SecretsPath
    )

    try {
        Write-EnhancedLog -Message "Attempting to load secrets from path: $SecretsPath" -Level "INFO" -ForegroundColor ([ConsoleColor]::Cyan)

        # Check if the secrets file exists
        if (-not (Test-Path -Path $SecretsPath)) {
            Write-EnhancedLog -Message "Secrets file not found at path: $SecretsPath" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
            throw "Secrets file not found at path: $SecretsPath"
        }

        # Load and parse the secrets file
        $secrets = Get-Content -Path $SecretsPath -Raw | ConvertFrom-Json
        Write-EnhancedLog -Message "Successfully loaded secrets from path: $SecretsPath" -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
        
        return $secrets
    }
    catch {
        Write-EnhancedLog -Message "Error loading secrets from path: $SecretsPath. Error: $_" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
        throw $_
    }
}
#EndRegion '.\Public\Get-Secrets.ps1' 47
#Region '.\Public\Get-SharePointDocumentDriveId.ps1' -1

function Get-SharePointDocumentDriveId {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$SiteObjectId,

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

        [Parameter(Mandatory = $true)]
        [hashtable]$Headers
    )

    try {
        # Get the subsite ID
        $url = "https://graph.microsoft.com/v1.0/groups/$SiteObjectId/sites/root"
        $subsiteID = (Invoke-RestMethod -Headers $Headers -Uri $url -Method GET).id
        Write-EnhancedLog -Message "Retrieved subsite ID: $subsiteID" -Level "INFO"

        # Get the drives
        $url = "https://graph.microsoft.com/v1.0/sites/$subsiteID/drives"
        $drives = Invoke-RestMethod -Headers $Headers -Uri $url -Method GET
        Write-EnhancedLog -Message "Retrieved drives for subsite ID: $subsiteID" -Level "INFO"

        # Find the document drive ID
        $documentDriveId = ($drives.value | Where-Object { $_.name -eq $DocumentDriveName }).id

        if ($documentDriveId) {
            Write-EnhancedLog -Message "Found document drive ID: $documentDriveId" -Level "INFO"
            return $documentDriveId
        } else {
            Write-EnhancedLog -Message "Document drive '$DocumentDriveName' not found." -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
            throw "Document drive '$DocumentDriveName' not found."
        }
    }
    catch {
        Write-EnhancedLog -Message "Failed to get document drive ID: $_" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
        throw $_
    }
}


# # Example usage
# $headers = @{
# "Authorization" = "Bearer YOUR_ACCESS_TOKEN"
# "Content-Type" = "application/json"
# }

# $siteObjectId = "your_site_object_id"
# $documentDriveName = "Documents"

# Get-SharePointDocumentDriveId -SiteObjectId $siteObjectId -DocumentDriveName $documentDriveName -Headers $headers
#EndRegion '.\Public\Get-SharePointDocumentDriveId.ps1' 53
#Region '.\Public\Get-SignInLogs.ps1' -1

function Get-SignInLogs {
    param (
        [string]$url,
        [hashtable]$headers
    )

    $allLogs = @()

    while ($url) {
        try {
            Write-EnhancedLog -Message "Requesting URL: $url" -Level "INFO" -ForegroundColor ([ConsoleColor]::Cyan)
            # Make the API request
            $response = Invoke-WebRequest -Uri $url -Headers $headers -Method Get
            $data = ($response.Content | ConvertFrom-Json)

            # Collect the logs
            $allLogs += $data.value

            # Check for pagination
            $url = $data.'@odata.nextLink'
        } catch {
            Write-EnhancedLog -Message "Error: $($_.Exception.Message)" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
            break
        }
    }

    return $allLogs
}
#EndRegion '.\Public\Get-SignInLogs.ps1' 29
#Region '.\Public\Get-TenantDetails.ps1' -1

function Get-TenantDetails {
    try {
        # Retrieve the organization details
        $organization = Get-MgOrganization

        # Extract the required details
        $tenantName = $organization.DisplayName
        $tenantId = $organization.Id

        # Initialize tenantDomain
        $tenantDomain = $null

        # Search for a verified domain matching the onmicrosoft.com pattern
        foreach ($domain in $organization.VerifiedDomains) {
            if ($domain.Name -match '\.onmicrosoft\.com$') {
                $tenantDomain = $domain.Name
                break
            }
        }

        # Adjust the tenant domain if necessary
        if ($tenantDomain -match '\.mail\.onmicrosoft\.com$') {
            $tenantDomain = $tenantDomain -replace '\.mail\.onmicrosoft\.com$', '.onmicrosoft.com'
        }

        if ($null -eq $tenantDomain) {
            throw "No onmicrosoft.com domain found."
        }

        # Output tenant summary
        Write-EnhancedLog -Message "Tenant Name: $tenantName" -Level "INFO"
        Write-EnhancedLog -Message "Tenant ID: $tenantId" -Level "INFO"
        Write-EnhancedLog -Message "Tenant Domain: $tenantDomain" -Level "INFO"


        # Return the extracted details
        return @{
            TenantName = $tenantName
            TenantId = $tenantId
            TenantDomain = $tenantDomain
        }
    } catch {
        Handle-Error -ErrorRecord $_
        Write-EnhancedLog -Message "Failed to retrieve tenant details" -Level "ERROR"
        return $null
    }
}

# # Example usage
# $tenantDetails = Get-TenantDetails

# if ($null -ne $tenantDetails) {
# $tenantName = $tenantDetails.TenantName
# $tenantId = $tenantDetails.TenantId
# $tenantDomain = $tenantDetails.TenantDomain

# # Use the tenant details as needed
# Write-EnhancedLog -Message "Using Tenant Details outside the function" -Level "INFO"
# Write-EnhancedLog -Message "Tenant Name: $tenantName" -Level "INFO"
# Write-EnhancedLog -Message "Tenant ID: $tenantId" -Level "INFO"
# Write-EnhancedLog -Message "Tenant Domain: $tenantDomain" -Level "INFO"
# } else {
# Write-EnhancedLog -Message "Tenant details could not be retrieved." -Level "ERROR"
# }
#EndRegion '.\Public\Get-TenantDetails.ps1' 65
#Region '.\Public\Get-TranscriptFilePath.ps1' -1

function Get-TranscriptFilePath {
    <#
    .SYNOPSIS
    Generates a file path for storing PowerShell transcripts.
 
    .DESCRIPTION
    The Get-TranscriptFilePath function constructs a unique transcript file path based on the provided transcript directory, job name, and parent script name. It ensures the transcript directory exists, handles context (e.g., SYSTEM account), and logs each step of the process.
 
    .PARAMETER TranscriptsPath
    The base directory where transcript files will be stored.
 
    .PARAMETER JobName
    The name of the job or task, used to distinguish different log files.
 
    .PARAMETER ParentScriptName
    The name of the parent script that is generating the transcript.
 
    .EXAMPLE
    $params = @{
        TranscriptsPath = 'C:\Transcripts'
        JobName = 'BackupJob'
        ParentScriptName = 'BackupScript.ps1'
    }
    Get-TranscriptFilePath @params
    Generates a transcript file path for a script called BackupScript.ps1.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the base path for transcripts.")]
        [ValidateNotNullOrEmpty()]
        [string]$TranscriptsPath,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the job name.")]
        [ValidateNotNullOrEmpty()]
        [string]$JobName,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the parent script name.")]
        [ValidateNotNullOrEmpty()]
        [string]$ParentScriptName
    )

    Begin {
        # Log the start of the function
        Write-EnhancedLog -Message "Starting Get-TranscriptFilePath function..." -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Ensure the destination directory exists
        if (-not (Test-Path -Path $TranscriptsPath)) {
            New-Item -ItemType Directory -Path $TranscriptsPath -Force | Out-Null
            Write-EnhancedLog -Message "Created Transcripts directory at: $TranscriptsPath" -Level "INFO"
        }
    }

    Process {
        try {
            # Get the current username or fallback to "UnknownUser"
            $username = if ($env:USERNAME) { $env:USERNAME } else { "UnknownUser" }
            Write-EnhancedLog -Message "Current username: $username" -Level "INFO"

            # Log the provided parent script name
            Write-EnhancedLog -Message "Parent script name: $ParentScriptName" -Level "INFO"

            # Check if running as SYSTEM
            $isSystem = Test-RunningAsSystem
            Write-EnhancedLog -Message "Is running as SYSTEM: $isSystem" -Level "INFO"

            # Get the current date for folder structure
            $currentDate = Get-Date -Format "yyyy-MM-dd"
            Write-EnhancedLog -Message "Current date for transcript folder: $currentDate" -Level "INFO"

            # Construct the hostname and timestamp for the log file name
            $hostname = $env:COMPUTERNAME
            $timestamp = Get-Date -Format "yyyy-MM-dd-HH-mm-ss"
            $logFolderPath = Join-Path -Path $TranscriptsPath -ChildPath "$currentDate\$ParentScriptName"

            # Ensure the log directory exists
            if (-not (Test-Path -Path $logFolderPath)) {
                New-Item -Path $logFolderPath -ItemType Directory -Force | Out-Null
                Write-EnhancedLog -Message "Created directory for transcript logs: $logFolderPath" -Level "INFO"
            }

            # Generate log file path based on context (SYSTEM or user)
            $logFilePath = if ($isSystem) {
                "$logFolderPath\$hostname-$JobName-SYSTEM-$ParentScriptName-transcript-$timestamp.log"
            }
            else {
                "$logFolderPath\$hostname-$JobName-$username-$ParentScriptName-transcript-$timestamp.log"
            }

            Write-EnhancedLog -Message "Constructed log file path: $logFilePath" -Level "INFO"

            # Sanitize and validate the log file path
            $logFilePath = Sanitize-LogFilePath -LogFilePath $logFilePath
            Validate-LogFilePath -LogFilePath $logFilePath
            Write-EnhancedLog -Message "Log file path sanitized and validated: $logFilePath" -Level "INFO"

            # Return the constructed file path
            return $logFilePath
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Get-TranscriptFilePath: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Get-TranscriptFilePath function" -Level "NOTICE"
    }
}
#EndRegion '.\Public\Get-TranscriptFilePath.ps1' 112
#Region '.\Public\Get-UserLicenses.ps1' -1

function Initialize-HttpClient {
    param (
        [hashtable]$Headers
    )

    $httpClient = [System.Net.Http.HttpClient]::new()
    $httpClient.DefaultRequestHeaders.Add("Authorization", $Headers["Authorization"])
    return $httpClient
}


function Get-UserLicenses {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$UserId,
        [Parameter(Mandatory = $true)]
        [string]$Username,
        [Parameter(Mandatory = $true)]
        [hashtable]$Headers
    )

    Begin {
        Write-EnhancedLog -Message "Starting Get-UserLicenses function" -Level "INFO"
        Log-Params -Params @{ UserId = $UserId; Username = $Username }
    }

    Process {
        $licenses = [System.Collections.Generic.List[string]]::new()
        $uri = "https://graph.microsoft.com/v1.0/users/$UserId/licenseDetails"
        $httpClient = Initialize-HttpClient -Headers $Headers

        try {
            Write-EnhancedLog -Message "Fetching licenses for user ID: $UserId with username: $Username" -Level "INFO" -ForegroundColor ([ConsoleColor]::Cyan)

            $response = $httpClient.GetStringAsync($uri).Result
            if (-not [string]::IsNullOrEmpty($response)) {
                $responseJson = [System.Text.Json.JsonDocument]::Parse($response)
                $valueProperty = $responseJson.RootElement.GetProperty("value")
                foreach ($license in $valueProperty.EnumerateArray()) {
                    $skuId = $license.GetProperty("skuId").GetString()
                    $licenses.Add($skuId)
                    Write-EnhancedLog -Message "Found license for user: $Username with SKU ID: $skuId" -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
                }
                $responseJson.Dispose()
            } else {
                Write-EnhancedLog -Message "Received empty response from license API." -Level "WARNING" -ForegroundColor ([ConsoleColor]::Yellow)
            }
        } catch {
            Write-EnhancedLog -Message "An error occurred while fetching licenses: $($_.Exception.Message)" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
            Handle-Error -ErrorRecord $_
            throw
        } finally {
            $httpClient.Dispose()
        }

        return $licenses
    }

    End {
        Write-EnhancedLog -Message "Exiting Get-UserLicenses function" -Level "INFO"
    }
}





# # Example usage
# $userId = "your_user_id"
# $username = "your_username"
# $headers = @{ "Authorization" = "Bearer your_token" }

# $licenses = Get-UserLicenses -UserId $userId -Username $username -Headers $headers
# Write-Output "Licenses: $($licenses -join ', ')"



#EndRegion '.\Public\Get-UserLicenses.ps1' 79
#Region '.\Public\Grant-AdminConsentToAllPermissions.ps1' -1

# using namespace System.Collections.Generic
# function Grant-AdminConsentToAllPermissions {
# param(
# [string]$AppDisplayName
# )

# $App = Get-MgApplication -Filter "DisplayName eq '$AppDisplayName'"

# $sp = Get-MgServicePrincipal -Filter "AppId eq '$($App.AppId)'"

# # $DBG

# foreach ($resourceAccess in $App.RequiredResourceAccess) {
# $resourceSp = Get-MgServicePrincipal -Filter "AppId eq '$($resourceAccess.ResourceAppId)'"
# if (!$resourceSp) {
# throw "Please cleanup permissions in the Azure portal for the app '$App.AppId', it contains permissions for removed App."
# }
# $scopesIdToValue = @{}
# $resourceSp.PublishedPermissionScopes | ForEach-Object { $scopesIdToValue[$_.Id] = $_.Value }
# [HashSet[string]]$requiredScopes = $resourceAccess.ResourceAccess | ForEach-Object { $scopesIdToValue[$_.Id] }
# $grant = Get-MgOauth2PermissionGrant -Filter "ClientId eq '$($sp.Id)' and ResourceId eq '$($resourceSp.Id)'"
# $newGrantRequired = $true
# if ($grant) {
# [HashSet[string]]$grantedScopes = $grant.Scope.Split(" ")
# if (!$requiredScopes.IsSubsetOf($grantedScopes)) {
# Write-Host "Revoking grant for '$($resourceSp.DisplayName)'"
# Remove-MgOauth2PermissionGrant -OAuth2PermissionGrantId $grant.Id
# }
# else {
# $newGrantRequired = $false
# }
# }
# if ($newGrantRequired) {

# $consentExpiry = ([datetime]::Now.AddYears(10))
# $scopesToGrant = $requiredScopes -join " "
# Write-Host "Issuing grant for '$($resourceSp.DisplayName)', scope = $scopesToGrant"
# New-MgOauth2PermissionGrant -ClientId $sp.Id -ConsentType "AllPrincipals" `
# -ResourceId $resourceSp.Id -Scope $scopesToGrant `
# -ExpiryTime $consentExpiry | Out-Null
# }
# }
# }


# # Grant-AdminConsentToAllPermissions -AppDisplayName 'GraphApp-Test001-20240618142134'
#EndRegion '.\Public\Grant-AdminConsentToAllPermissions.ps1' 47
#Region '.\Public\Grant-AdminConsentToApiPermissions.ps1' -1

# function Grant-AdminConsentToApiPermissions {
# param (
# [Parameter(Mandatory = $true)]
# # [string]$AppId,
# [string]$clientId
# )

# try {



# Create-AndVerifyServicePrincipal -ClientId $clientId

# # $DBG


# Write-EnhancedLog -Message "Granting admin consent to API permissions for App ID: $clientId" -Level "INFO"

# # Retrieve and list all service principals for debugging
# # $allServicePrincipals = Get-MgServicePrincipal
# # Write-Output "All Service Principals:"
# # $allServicePrincipals | Format-Table DisplayName, AppId, Id

# # Retrieve the service principal for the application
# # $servicePrincipal = $allServicePrincipals | Where-Object { $_.AppId -eq $clientId }

# $servicePrincipal = Get-MgServicePrincipal -Filter "AppId eq '$clientId'"

# # $DBG

# if ($null -eq $servicePrincipal) {
# Write-EnhancedLog -Message "Service principal not found for the specified application ID." -Level "ERROR"
# throw "Service principal not found"
# }

# Write-EnhancedLog -Message "Service principal for app ID: $clientId retrieved successfully." -Level "INFO"

# # Retrieve the service principal ID
# $servicePrincipalId = $servicePrincipal.Id

# # Retrieve all API permissions (OAuth2PermissionGrants) for the service principal
# $apiPermissions = Get-MgServicePrincipalOauth2PermissionGrant -ServicePrincipalId $servicePrincipalId

# # $DBG

# Write-EnhancedLog -Message "API permissions retrieved successfully." -Level "INFO"

# # Grant admin consent to each permission
# foreach ($permission in $apiPermissions) {
# Update-MgServicePrincipalOauth2PermissionGrant -ServicePrincipalId $servicePrincipalId -Id $permission.Id -ConsentType "AllPrincipals"
# }

# Write-EnhancedLog -Message "Admin consent granted to all API permissions." -Level "INFO"
# }
# catch {
# Write-EnhancedLog -Message "An error occurred while granting admin consent." -Level "ERROR"
# Handle-Error -ErrorRecord $_
# throw $_
# }
# }








# function Grant-AdminConsentToApiPermissions {
# param (
# [Parameter(Mandatory = $true)]
# [string]$clientId
# )

# try {
# Write-EnhancedLog -Message "Starting the process to grant admin consent to API permissions for App ID: $clientId" -Level "INFO"

# # Create and verify the service principal
# Create-AndVerifyServicePrincipal -ClientId $clientId

# Write-EnhancedLog -Message "Granting admin consent to API permissions for App ID: $clientId" -Level "INFO"

# # Retrieve the service principal for the application
# $servicePrincipal = Get-MgServicePrincipal -Filter "AppId eq '$clientId'"

# if ($null -eq $servicePrincipal) {
# Write-EnhancedLog -Message "Service principal not found for the specified application ID." -Level "ERROR"
# throw "Service principal not found"
# }

# Write-EnhancedLog -Message "Service principal for app ID: $clientId retrieved successfully." -Level "INFO"

# # Retrieve the service principal ID
# $servicePrincipalId = $servicePrincipal.Id

# # Retrieve all API permissions (OAuth2PermissionGrants) for the service principal
# $apiPermissions = Get-MgServicePrincipalOauth2PermissionGrant -ServicePrincipalId $servicePrincipalId

# Write-EnhancedLog -Message "API permissions retrieved: $($apiPermissions.Count)" -Level "INFO"

# if ($apiPermissions.Count -eq 0) {
# Write-EnhancedLog -Message "No API permissions found for the service principal with ID: $servicePrincipalId" -Level "WARNING"
# }

# # Grant admin consent to each permission
# foreach ($permission in $apiPermissions) {
# Write-EnhancedLog -Message "Granting admin consent for permission ID: $($permission.Id) with scope: $($permission.Scope)" -Level "INFO"
# Update-MgServicePrincipalOauth2PermissionGrant -ServicePrincipalId $servicePrincipalId -Id $permission.Id -ConsentType "AllPrincipals"
# Write-EnhancedLog -Message "Admin consent granted for permission ID: $($permission.Id) with scope: $($permission.Scope)" -Level "INFO"
# }

        
# } catch {
# Write-EnhancedLog -Message "An error occurred while granting admin consent." -Level "ERROR"
# Handle-Error -ErrorRecord $_
# throw $_
# }
# }

# Example usage
# Grant-AdminConsentToApiPermissions -ClientId "your-application-id"


# Example usage
# Grant-AdminConsentToApiPermissions -AppId "65a3ee49-f480-4cde-9d55-a4a952084bf7"













# function Grant-AdminConsentToApiPermissions {
# param (
# [Parameter(Mandatory = $true)]
# [string]$clientId,
# $headers
# )

# try {
# Write-EnhancedLog -Message "Starting the process to grant admin consent to API permissions for App ID: $clientId" -Level "INFO"

# # Create and verify the service principal
# Create-AndVerifyServicePrincipal -ClientId $clientId

# Write-EnhancedLog -Message "Granting admin consent to API permissions for App ID: $clientId" -Level "INFO"

# # Retrieve the service principal for the application
# $servicePrincipal = Get-MgServicePrincipal -Filter "AppId eq '$clientId'"

# if ($null -eq $servicePrincipal) {
# Write-EnhancedLog -Message "Service principal not found for the specified application ID." -Level "ERROR"
# throw "Service principal not found"
# }

# Write-EnhancedLog -Message "Service principal for app ID: $clientId retrieved successfully." -Level "INFO"

# # Retrieve the service principal ID
# $servicePrincipalId = $servicePrincipal.Id

# # Define the permissions to be granted
# $permissions = "User.Read.All Group.Read.All" # Replace with the required permissions

# # Retrieve the Microsoft Graph service principal
# $graphServicePrincipal = Get-MgServicePrincipal -Filter "displayName eq 'Microsoft Graph'" -Select id

# if ($null -eq $graphServicePrincipal) {
# Write-EnhancedLog -Message "Microsoft Graph service principal not found." -Level "ERROR"
# throw "Microsoft Graph service principal not found"
# }

# $resourceId = $graphServicePrincipal.Id

# # Grant the permissions
# $body = @{
# clientId = $servicePrincipalId
# consentType = "AllPrincipals"
# resourceId = $resourceId
# scope = $permissions
# }

# # $headers = @{
# # "Authorization" = "Bearer $AccessToken"
# # "Content-Type" = "application/json"
# # }

# $response = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/oauth2PermissionGrants" -Method POST -Headers $headers -Body ($body | ConvertTo-Json)

# Write-EnhancedLog -Message "Admin consent granted successfully." -Level "INFO"
# return $response

# } catch {
# Write-EnhancedLog -Message "An error occurred while granting admin consent." -Level "ERROR"
# Handle-Error -ErrorRecord $_
# throw $_
# }
# }

# Example usage
# Grant-AdminConsentToApiPermissions -ClientId "your-application-id"




# function Grant-AdminConsentToApiPermissions {
# param (
# [Parameter(Mandatory = $true)]
# [string]$clientId
# )

# try {
# Write-EnhancedLog -Message "Starting the process to grant admin consent to API permissions for App ID: $clientId" -Level "INFO"

# # Create and verify the service principal
# Create-AndVerifyServicePrincipal -ClientId $clientId

# Write-EnhancedLog -Message "Granting admin consent to API permissions for App ID: $clientId" -Level "INFO"

# # Retrieve the service principal for the application
# $servicePrincipal = Get-MgServicePrincipal -Filter "AppId eq '$clientId'"

# if ($null -eq $servicePrincipal) {
# Write-EnhancedLog -Message "Service principal not found for the specified application ID." -Level "ERROR"
# throw "Service principal not found"
# }

# Write-EnhancedLog -Message "Service principal for app ID: $clientId retrieved successfully." -Level "INFO"

# # Retrieve the service principal ID
# $servicePrincipalId = $servicePrincipal.Id

# # Define the permissions to be granted
# $permissions = "User.Read.All Group.Read.All" # Replace with the required permissions

# # Retrieve the Microsoft Graph service principal
# $graphServicePrincipal = Get-MgServicePrincipal -Filter "displayName eq 'Microsoft Graph'" -Select id

# if ($null -eq $graphServicePrincipal) {
# Write-EnhancedLog -Message "Microsoft Graph service principal not found." -Level "ERROR"
# throw "Microsoft Graph service principal not found"
# }

# $resourceId = $graphServicePrincipal.Id

# # Grant the permissions
# $body = @{
# clientId = $servicePrincipalId
# consentType = "AllPrincipals"
# resourceId = $resourceId
# scope = $permissions
# }

# $response = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/oauth2PermissionGrants" -Method POST -Body ($body | ConvertTo-Json) -ContentType "application/json"

# $DBG

# Write-EnhancedLog -Message "Admin consent granted successfully." -Level "INFO"
# return $response

# } catch {
# Write-EnhancedLog -Message "An error occurred while granting admin consent." -Level "ERROR"
# Handle-Error -ErrorRecord $_
# throw $_
# }
# }

# # Example usage
# $scopes = @("Application.ReadWrite.All", "Directory.ReadWrite.All")
# Connect-MgGraph -Scopes $scopes

# Grant-AdminConsentToApiPermissions -ClientId "your-application-id"




# function Grant-AdminConsentToApiPermissions {
# param (
# [Parameter(Mandatory = $true)]
# [string]$clientId
# )

# try {
# Write-EnhancedLog -Message "Starting the process to grant admin consent to API permissions for App ID: $clientId" -Level "INFO"

# # Create and verify the service principal
# Create-AndVerifyServicePrincipal -ClientId $clientId

# Write-EnhancedLog -Message "Granting admin consent to API permissions for App ID: $clientId" -Level "INFO"

# # Retrieve the service principal for the application
# $servicePrincipal = Get-MgServicePrincipal -Filter "AppId eq '$clientId'"

# if ($null -eq $servicePrincipal) {
# Write-EnhancedLog -Message "Service principal not found for the specified application ID." -Level "ERROR"
# throw "Service principal not found"
# }

# Write-EnhancedLog -Message "Service principal for app ID: $clientId retrieved successfully." -Level "INFO"

# # Retrieve the service principal ID
# $servicePrincipalId = $servicePrincipal.Id

# # Define the permissions to be granted (application permissions)
# $permissions = @("Directory.Read.All", "Directory.ReadWrite.All") # Replace with the required permissions

# # Retrieve the Microsoft Graph service principal
# $graphServicePrincipal = Get-MgServicePrincipal -Filter "displayName eq 'Microsoft Graph'" -Select id,appRoles

# if ($null -eq $graphServicePrincipal) {
# Write-EnhancedLog -Message "Microsoft Graph service principal not found." -Level "ERROR"
# throw "Microsoft Graph service principal not found"
# }

# $resourceId = $graphServicePrincipal.Id
# $appRoles = $graphServicePrincipal.AppRoles

# # Find the IDs of the required permissions
# $requiredRoles = $appRoles | Where-Object { $permissions -contains $_.Value } | Select-Object -ExpandProperty Id

# if ($requiredRoles.Count -eq 0) {
# Write-EnhancedLog -Message "No matching app roles found for the specified permissions." -Level "ERROR"
# throw "No matching app roles found"
# }

# Write-EnhancedLog -Message "App roles to be granted: $($requiredRoles -join ', ')" -Level "INFO"

# # Grant the app roles (application permissions)
# foreach ($roleId in $requiredRoles) {
# $body = @{
# principalId = $servicePrincipalId
# resourceId = $resourceId
# appRoleId = $roleId
# }

# $response = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$resourceId/appRoleAssignedTo" -Method POST -Body ($body | ConvertTo-Json) -ContentType "application/json"

            

# Write-EnhancedLog -Message "Granted app role with ID: $roleId" -Level "INFO"
# }

# $DBG

# Write-EnhancedLog -Message "Admin consent granted successfully." -Level "INFO"
# return $response

# } catch {
# Write-EnhancedLog -Message "An error occurred while granting admin consent." -Level "ERROR"
# Handle-Error -ErrorRecord $_
# throw $_
# }
# }

# Example usage
# $scopes = @("Application.ReadWrite.All", "Directory.ReadWrite.All")
# Connect-MgGraph -Scopes $scopes

# Grant-AdminConsentToApiPermissions -ClientId "your-application-id"







# function Grant-AdminConsentToApiPermissions {
# param (
# [Parameter(Mandatory = $true)]
# [string]$clientId,
# [Parameter(Mandatory = $true)]
# [string]$SPPermissionsPath
# )

# try {
# Write-EnhancedLog -Message "Starting the process to grant admin consent to API permissions for App ID: $clientId" -Level "INFO"

# # Load permissions from JSON file
# $permissionsFile = Join-Path -Path $SPPermissionsPath -ChildPath "SPPermissions.json"
# if (-not (Test-Path -Path $permissionsFile)) {
# Write-EnhancedLog -Message "Permissions file not found: $permissionsFile" -Level "ERROR"
# throw "Permissions file not found"
# }

# $permissionsJson = Get-Content -Path $permissionsFile -Raw | ConvertFrom-Json
# $permissions = $permissionsJson.permissions | Where-Object { $_.granted -eq $true } | Select-Object -ExpandProperty name

# Write-EnhancedLog -Message "Permissions to be granted: $($permissions -join ', ')" -Level "INFO"

# # Create and verify the service principal
# Create-AndVerifyServicePrincipal -ClientId $clientId

# Write-EnhancedLog -Message "Granting admin consent to API permissions for App ID: $clientId" -Level "INFO"

# # Retrieve the service principal for the application
# $servicePrincipal = Get-MgServicePrincipal -Filter "AppId eq '$clientId'"

# if ($null -eq $servicePrincipal) {
# Write-EnhancedLog -Message "Service principal not found for the specified application ID." -Level "ERROR"
# throw "Service principal not found"
# }

# Write-EnhancedLog -Message "Service principal for app ID: $clientId retrieved successfully." -Level "INFO"

# # Retrieve the service principal ID
# $servicePrincipalId = $servicePrincipal.Id

# # Retrieve the Microsoft Graph service principal
# $graphServicePrincipal = Get-MgServicePrincipal -Filter "displayName eq 'Microsoft Graph'" -Select id,appRoles

# if ($null -eq $graphServicePrincipal) {
# Write-EnhancedLog -Message "Microsoft Graph service principal not found." -Level "ERROR"
# throw "Microsoft Graph service principal not found"
# }

# $resourceId = $graphServicePrincipal.Id
# $appRoles = $graphServicePrincipal.AppRoles

# # Find the IDs of the required permissions
# $requiredRoles = $appRoles | Where-Object { $permissions -contains $_.Value } | Select-Object -ExpandProperty Id

# if ($requiredRoles.Count -eq 0) {
# Write-EnhancedLog -Message "No matching app roles found for the specified permissions." -Level "ERROR"
# throw "No matching app roles found"
# }

# Write-EnhancedLog -Message "App roles to be granted: $($requiredRoles -join ', ')" -Level "INFO"

# # Ensure the access token has the necessary scopes
# $context = Get-MgContext -ErrorAction Stop
# $token = $context.AuthContext.AccessToken
# $decodedToken = $token | ConvertFrom-Json -ErrorAction Stop
# $tokenScopes = $decodedToken.scp.Split(' ')

# if (-not $tokenScopes -contains "AppRoleAssignment.ReadWrite.All") {
# Write-EnhancedLog -Message "Access token does not contain the necessary scope: AppRoleAssignment.ReadWrite.All" -Level "ERROR"
# throw "Insufficient privileges"
# }

# # $DBG

# # Grant the app roles (application permissions)
# foreach ($roleId in $requiredRoles) {
# $body = @{
# principalId = $servicePrincipalId
# resourceId = $resourceId
# appRoleId = $roleId
# }

# $response = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$resourceId/appRoleAssignedTo" -Method POST -Body ($body | ConvertTo-Json) -ContentType "application/json"

# Write-EnhancedLog -Message "Granted app role with ID: $roleId" -Level "INFO"
# }

# Write-EnhancedLog -Message "Admin consent granted successfully." -Level "INFO"
# return $response

# } catch {
# Write-EnhancedLog -Message "An error occurred while granting admin consent." -Level "ERROR"
# Handle-Error -ErrorRecord $_
# throw $_
# }
# }

# Example usage
# $scopes = @("Application.ReadWrite.All", "Directory.ReadWrite.All", "AppRoleAssignment.ReadWrite.All")
# Connect-MgGraph -Scopes $scopes

# Grant-AdminConsentToApiPermissions -ClientId "your-application-id"







# function Grant-AdminConsentToApiPermissions {
# param (
# [Parameter(Mandatory = $true)]
# [string]$clientId
# )

# try {
# Write-EnhancedLog -Message "Starting the process to grant admin consent to API permissions for App ID: $clientId" -Level "INFO"

# # Load permissions from JSON file
# $permissionsFile = Join-Path -Path $SPPermissionsPath -ChildPath "SPPermissions.json"
# if (-not (Test-Path -Path $permissionsFile)) {
# Write-EnhancedLog -Message "Permissions file not found: $permissionsFile" -Level "ERROR"
# throw "Permissions file not found"
# }

# $permissionsJson = Get-Content -Path $permissionsFile -Raw | ConvertFrom-Json
# $permissions = $permissionsJson.permissions | Where-Object { $_.granted -eq $true } | Select-Object -ExpandProperty name

# Write-EnhancedLog -Message "Permissions to be granted: $($permissions -join ', ')" -Level "INFO"

# # Create and verify the service principal
# Create-AndVerifyServicePrincipal -ClientId $clientId

# Write-EnhancedLog -Message "Granting admin consent to API permissions for App ID: $clientId" -Level "INFO"

# # Retrieve the service principal for the application
# $servicePrincipal = Get-MgServicePrincipal -Filter "AppId eq '$clientId'"

# if ($null -eq $servicePrincipal) {
# Write-EnhancedLog -Message "Service principal not found for the specified application ID." -Level "ERROR"
# throw "Service principal not found"
# }

# Write-EnhancedLog -Message "Service principal for app ID: $clientId retrieved successfully." -Level "INFO"

# # Retrieve the service principal ID
# $servicePrincipalId = $servicePrincipal.Id

# # Retrieve the Microsoft Graph service principal
# $graphServicePrincipal = Get-MgServicePrincipal -Filter "displayName eq 'Microsoft Graph'" -Select id,appRoles

# if ($null -eq $graphServicePrincipal) {
# Write-EnhancedLog -Message "Microsoft Graph service principal not found." -Level "ERROR"
# throw "Microsoft Graph service principal not found"
# }

# $resourceId = $graphServicePrincipal.Id
# $appRoles = $graphServicePrincipal.AppRoles

# # Find the IDs of the required permissions
# $requiredRoles = $appRoles | Where-Object { $permissions -contains $_.Value } | Select-Object -ExpandProperty Id

# if ($requiredRoles.Count -eq 0) {
# Write-EnhancedLog -Message "No matching app roles found for the specified permissions." -Level "ERROR"
# throw "No matching app roles found"
# }

# Write-EnhancedLog -Message "App roles to be granted: $($requiredRoles -join ', ')" -Level "INFO"

# # # Ensure the access token has the necessary scopes
# # $context = Get-MgContext -ErrorAction Stop
# # if ($null -eq $context) {
# # Write-EnhancedLog -Message "Microsoft Graph context is null." -Level "ERROR"
# # throw "Microsoft Graph context is null"
# # }

# # $token = $context.AuthContext.AccessToken
# # if ($null -eq $token) {
# # Write-EnhancedLog -Message "Access token is null." -Level "ERROR"
# # throw "Access token is null"
# # }

# # $decodedToken = $token | ConvertFrom-Json -ErrorAction Stop
# # $tokenScopes = $decodedToken.scp.Split(' ')

# # if (-not $tokenScopes -contains "AppRoleAssignment.ReadWrite.All") {
# # Write-EnhancedLog -Message "Access token does not contain the necessary scope: AppRoleAssignment.ReadWrite.All" -Level "ERROR"
# # throw "Insufficient privileges"
# # }

# # Grant the app roles (application permissions)
# foreach ($roleId in $requiredRoles) {
# $body = @{
# principalId = $servicePrincipalId
# resourceId = $resourceId
# appRoleId = $roleId
# }

# $response = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$resourceId/appRoleAssignedTo" -Method POST -Body ($body | ConvertTo-Json) -ContentType "application/json"

# Write-EnhancedLog -Message "Granted app role with ID: $roleId" -Level "INFO"
# }

# Write-EnhancedLog -Message "Admin consent granted successfully." -Level "INFO"
# return $response

# } catch {
# Write-EnhancedLog -Message "An error occurred while granting admin consent." -Level "ERROR"
# Handle-Error -ErrorRecord $_
# throw $_
# }
# }

# # Example usage
# $scopes = @("Application.ReadWrite.All", "Directory.ReadWrite.All", "AppRoleAssignment.ReadWrite.All")
# Connect-MgGraph -Scopes $scopes

# Grant-AdminConsentToApiPermissions -ClientId "your-application-id"








# function Grant-AdminConsentToApiPermissions {
# param (
# [Parameter(Mandatory = $true)]
# [string]$clientId,
# $SPPermissionsPath
# )

# try {
# Write-EnhancedLog -Message "Starting the process to grant admin consent to API permissions for App ID: $clientId" -Level "INFO"

# # Load permissions from JSON file
# $permissionsFile = Join-Path -Path $SPPermissionsPath -ChildPath "SPPermissions.json"
# if (-not (Test-Path -Path $permissionsFile)) {
# Write-EnhancedLog -Message "Permissions file not found: $permissionsFile" -Level "ERROR"
# throw "Permissions file not found"
# }

# $permissionsJson = Get-Content -Path $permissionsFile -Raw | ConvertFrom-Json
# $permissions = $permissionsJson.permissions | Where-Object { $_.granted -eq $true } | Select-Object -ExpandProperty name

# Write-EnhancedLog -Message "Permissions to be granted: $($permissions -join ', ')" -Level "INFO"

# # Create and verify the service principal
# Create-AndVerifyServicePrincipal -ClientId $clientId

# Write-EnhancedLog -Message "Granting admin consent to API permissions for App ID: $clientId" -Level "INFO"

# # Retrieve the service principal for the application
# $servicePrincipal = Get-MgServicePrincipal -Filter "AppId eq '$clientId'"

# if ($null -eq $servicePrincipal) {
# Write-EnhancedLog -Message "Service principal not found for the specified application ID." -Level "ERROR"
# throw "Service principal not found"
# }

# Write-EnhancedLog -Message "Service principal for app ID: $clientId retrieved successfully." -Level "INFO"

# # Retrieve the service principal ID
# $servicePrincipalId = $servicePrincipal.Id

# # Retrieve the Microsoft Graph service principal
# $graphServicePrincipal = Get-MgServicePrincipal -Filter "displayName eq 'Microsoft Graph'" -Select id,appRoles

# if ($null -eq $graphServicePrincipal) {
# Write-EnhancedLog -Message "Microsoft Graph service principal not found." -Level "ERROR"
# throw "Microsoft Graph service principal not found"
# }

# $resourceId = $graphServicePrincipal.Id
# $appRoles = $graphServicePrincipal.AppRoles

# # Find the IDs of the required permissions
# $requiredRoles = $appRoles | Where-Object { $permissions -contains $_.Value } | Select-Object -ExpandProperty Id

# if ($requiredRoles.Count -eq 0) {
# Write-EnhancedLog -Message "No matching app roles found for the specified permissions." -Level "ERROR"
# throw "No matching app roles found"
# }

# Write-EnhancedLog -Message "App roles to be granted: $($requiredRoles -join ', ')" -Level "INFO"

# # Grant the app roles (application permissions)
# foreach ($roleId in $requiredRoles) {
# $body = @{
# principalId = $servicePrincipalId
# resourceId = $resourceId
# appRoleId = $roleId
# }

# $response = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$resourceId/appRoleAssignedTo" -Method POST -Body ($body | ConvertTo-Json) -ContentType "application/json"

# Write-EnhancedLog -Message "Granted app role with ID: $roleId" -Level "INFO"
# }

# # $DBG

# Write-EnhancedLog -Message "Admin consent granted successfully." -Level "INFO"
# return $response

# } catch {
# Write-EnhancedLog -Message "An error occurred while granting admin consent." -Level "ERROR"
# Handle-Error -ErrorRecord $_
# throw $_
# }
# }

# # Example usage
# $scopes = @("Application.ReadWrite.All", "Directory.ReadWrite.All", "AppRoleAssignment.ReadWrite.All")
# Connect-MgGraph -Scopes $scopes

# Grant-AdminConsentToApiPermissions -ClientId "your-application-id"










function Grant-AdminConsentToApiPermissions {
    param (
        [Parameter(Mandatory = $true)]
        [string]$clientId,

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

    try {
        Write-EnhancedLog -Message "Starting the process to grant admin consent to API permissions for App ID: $clientId" -Level "INFO"

        # Load permissions from JSON file
        $permissionsFile = Join-Path -Path $SPPermissionsPath -ChildPath "SPPermissions.json"
        if (-not (Test-Path -Path $permissionsFile)) {
            Write-EnhancedLog -Message "Permissions file not found: $permissionsFile" -Level "ERROR"
            throw "Permissions file not found"
        }

        $permissionsJson = Get-Content -Path $permissionsFile -Raw | ConvertFrom-Json
        $permissions = $permissionsJson.permissions | Where-Object { $_.granted -eq $true } | Select-Object -ExpandProperty name

        Write-EnhancedLog -Message "Permissions to be granted: $($permissions -join ', ')" -Level "INFO"

        # Create and verify the service principal
        Create-AndVerifyServicePrincipal -ClientId $clientId

        Write-EnhancedLog -Message "Granting admin consent to API permissions for App ID: $clientId" -Level "INFO"

        # Retrieve the service principal for the application
        $servicePrincipal = Get-MgServicePrincipal -Filter "AppId eq '$clientId'"

        if ($null -eq $servicePrincipal) {
            Write-EnhancedLog -Message "Service principal not found for the specified application ID." -Level "ERROR"
            throw "Service principal not found"
        }

        Write-EnhancedLog -Message "Service principal for app ID: $clientId retrieved successfully." -Level "INFO"

        # Retrieve the service principal ID
        $servicePrincipalId = $servicePrincipal.Id

        # Retrieve the Microsoft Graph service principal
        $graphServicePrincipal = Get-MgServicePrincipal -Filter "displayName eq 'Microsoft Graph'" -Select id,appRoles

        if ($null -eq $graphServicePrincipal) {
            Write-EnhancedLog -Message "Microsoft Graph service principal not found." -Level "ERROR"
            throw "Microsoft Graph service principal not found"
        }

        $resourceId = $graphServicePrincipal.Id
        $appRoles = $graphServicePrincipal.AppRoles

        # Find the IDs of the required permissions
        $requiredRoles = $appRoles | Where-Object { $permissions -contains $_.Value } | Select-Object Id, Value

        if ($requiredRoles.Count -eq 0) {
            Write-EnhancedLog -Message "No matching app roles found for the specified permissions." -Level "ERROR"
            throw "No matching app roles found"
        }

        Write-EnhancedLog -Message "App roles to be granted: $($requiredRoles.Value -join ', ')" -Level "INFO"

        # Grant the app roles (application permissions)
        foreach ($role in $requiredRoles) {
            $body = @{
                principalId = $servicePrincipalId
                resourceId  = $resourceId
                appRoleId   = $role.Id
            }

            $response = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$resourceId/appRoleAssignedTo" -Method POST -Body ($body | ConvertTo-Json) -ContentType "application/json"

            Write-EnhancedLog -Message "Granted app role: $($role.Value) with ID: $($role.Id)" -Level "INFO"
        }
        # $DBG

        Write-EnhancedLog -Message "Admin consent granted successfully." -Level "INFO"
        return $response

    } catch {
        Write-EnhancedLog -Message "An error occurred while granting admin consent." -Level "ERROR"
        Handle-Error -ErrorRecord $_ 
        throw $_
    }
}

# # Example usage
# $scopes = @("Application.ReadWrite.All", "Directory.ReadWrite.All", "AppRoleAssignment.ReadWrite.All")
# Connect-MgGraph -Scopes $scopes

# Grant-AdminConsentToApiPermissions -ClientId "your-application-id"








#EndRegion '.\Public\Grant-AdminConsentToApiPermissions.ps1' 800
#Region '.\Public\Grant-AdminConsentToDelegatedPermissions.ps1' -1

# function Grant-AdminConsentToDelegatedPermissions {
# param (
# [Parameter(Mandatory = $true)]
# [string]$AppId,
# [Parameter(Mandatory = $true)]
# [string]$Permissions,
# [Parameter(Mandatory = $true)]
# [string]$AccessToken
# )

# try {
# Write-EnhancedLog -Message "Granting tenant-wide admin consent to API permissions for App ID: $AppId" -Level "INFO"

# # Retrieve the service principal for Microsoft Graph
# $graphServicePrincipal = Get-MgServicePrincipal -Filter "displayName eq 'Microsoft Graph'" -Select id,displayName,appId,oauth2PermissionScopes

# if ($null -eq $graphServicePrincipal) {
# Write-EnhancedLog -Message "Service principal for Microsoft Graph not found." -Level "ERROR"
# throw "Service principal for Microsoft Graph not found"
# }

# Write-EnhancedLog -Message "Microsoft Graph service principal retrieved successfully." -Level "INFO"

# # Retrieve the service principal for the client application
# $clientServicePrincipal = Get-MgServicePrincipal -Filter "appId eq '$AppId'"

# if ($null -eq $clientServicePrincipal) {
# Write-EnhancedLog -Message "Service principal not found for the specified application ID." -Level "ERROR"
# throw "Service principal not found"
# }

# Write-EnhancedLog -Message "Service principal for client application retrieved successfully." -Level "INFO"

# # Grant the delegated permissions to the client enterprise application
# $body = @{
# clientId = $clientServicePrincipal.Id
# consentType = "AllPrincipals"
# resourceId = $graphServicePrincipal.Id
# scope = $Permissions
# }

# $headers = @{
# "Authorization" = "Bearer $AccessToken"
# "Content-Type" = "application/json"
# }

# $response = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/oauth2PermissionGrants" -Method POST -Headers $headers -Body ($body | ConvertTo-Json)

# Write-EnhancedLog -Message "Tenant-wide admin consent granted successfully." -Level "INFO"

# return $response

# } catch {
# Write-EnhancedLog -Message "An error occurred while granting tenant-wide admin consent." -Level "ERROR"
# Handle-Error -ErrorRecord $_
# throw $_
# }
# }

# # Example usage
# # $accessToken = "your-access-token"
# # Grant-AdminConsentToDelegatedPermissions -AppId "08216f27-1d3d-4a9f-9406-80f957e7fca6" -Permissions "User.Read.All Group.Read.All" -AccessToken $accessToken
#EndRegion '.\Public\Grant-AdminConsentToDelegatedPermissions.ps1' 63
#Region '.\Public\Grant-AdminConsentUsingAzCli.ps1' -1

# function Grant-AdminConsentUsingAzCli {
# param (
# [Parameter(Mandatory = $true)]
# [string]$AppId
# )

# try {
# Write-EnhancedLog -Message "Granting admin consent to Azure AD application with App ID: $AppId using Azure CLI" -Level "INFO"



# #First download and install Az CLI

# # $ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi; Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet'; Remove-Item .\AzureCLI.msi


# # Run the following to fix the issue mentioned here https://github.com/Azure/azure-cli/issues/28997
# az account clear
# az config set core.enable_broker_on_windows=false

# # Now login (it will open up a web browser window to login as normal)
# #If you have an Azure Subscription
# # az login


# #If you DO NOT have an Azure Subscription
# az login --allow-no-subscriptions

# # $DBG

# # Execute the Azure CLI command to grant admin consent
# $azCliCommand = "az ad app permission admin-consent --id $AppId"
# $output = Invoke-Expression -Command $azCliCommand

# # $DBG

# if ($output -match "deprecated") {
# Write-EnhancedLog -Message "The 'admin-consent' command is deprecated. Use 'az ad app permission grant' instead." -Level "WARNING"
# }

# Write-EnhancedLog -Message "Admin consent granted successfully using Azure CLI." -Level "INFO"

# az logout

# return $output

# } catch {
# Write-EnhancedLog -Message "An error occurred while granting admin consent using Azure CLI." -Level "ERROR"
# Handle-Error -ErrorRecord $_
# throw $_
# }
# }

# # Example usage
# # Grant-AdminConsentUsingAzCli -AppId "your-application-id"



# #Readme
# # https://github.com/Azure/azure-cli/issues/28997

# # Solution: (After installing azure-cli-2.61.0-x64.msi the az command will become available through the PATH ENV Variable located in C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin now to fix the az login issue mentioned follow these steps carefully

# # Step 1: open CMD or PoweShell 5 or 7 (in VS Code, Terminal or normal Shell) AS Admin
# # Step 2: Run the following command az account clear (you will get nothing back)
# # Step 3: Run the following command az config set core.enable_broker_on_windows=false (you will get "A web browser has been opened at https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize. Please continue the login in the web browser. If no web browser is available or if the web browser fails to open, use device code flow with az login --use-device-code.") and then a web login page will open
# # Step4: Once you login it will say in the webpage

# # You have logged into Microsoft Azure!
# # You can close this window, or we will redirect you to the Azure CLI documentation in 1 minute.



# #Example Output


# # C:\Users\Administrator>az account clear

# # C:\Users\Administrator>az login
# # Please select the account you want to log in with.
# # User cancelled the Accounts Control Operation.. Status: Response_Status.Status_UserCanceled, Error code: 0, Tag: 528315210
# # Please explicitly log in with:
# # az login

# # C:\Users\Administrator>az account clear

# # C:\Users\Administrator>az config set core.enable_broker_on_windows=false
# # Command group 'config' is experimental and under development. Reference and support levels: https://aka.ms/CLI_refstatus

# # C:\Users\Administrator>az login
# # A web browser has been opened at https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize. Please continue the login in the web browser. If no web browser is available or if the web browser fails to open, use device code flow with `az login --use-device-code`.

# # Retrieving tenants and subscriptions for the selection...
# # The following tenants don't contain accessible subscriptions. Use `az login --allow-no-subscriptions` to have tenant level access.
# # 5784dc53-9279-4dc4-8b3d-cf6e8d4f9c50 'CASN'
# # No subscriptions found for NovaAdmin_AOllivierre@casn.ca.

# # C:\Users\Administrator>






# # az login --allow-no-subscriptions in pwsh at 05:40:25
# # A web browser has been opened at https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize. Please continue the login in the web browser. If no web browser is available or if the web browser fails to open, use device code flow with `az login --use-device-code`.

# # Retrieving tenants and subscriptions for the selection...
# # The following tenants don't contain accessible subscriptions. Use `az login --allow-no-subscriptions` to have tenant level access.
# # 5784dc53-9279-4dc4-8b3d-cf6e8d4f9c50 'CASN'

# # [Tenant and subscription selection]

# # No Subscription name Subscription ID Tenant
# # ----- ------------------------- ------------------------------------ ------------------------------------
# # [1] * N/A(tenant level account) 5784dc53-9279-4dc4-8b3d-cf6e8d4f9c50 5784dc53-9279-4dc4-8b3d-cf6e8d4f9c50

# # The default is marked with an *; the default tenant is '5784dc53-9279-4dc4-8b3d-cf6e8d4f9c50' and subscription is 'N/A(tenant level account)' (5784dc53-9279-4dc4-8b3d-cf6e8d4f9c50).

# # Select a subscription and tenant (Type a number or Enter for no changes): 1

# # Tenant: 5784dc53-9279-4dc4-8b3d-cf6e8d4f9c50
# # Subscription: N/A(tenant level account) (5784dc53-9279-4dc4-8b3d-cf6e8d4f9c50)

# # [Announcements]
# # With the new Azure CLI login experience, you can select the subscription you want to use more easily. Learn more about it and its configuration at https://go.microsoft.com/fwlink/?linkid=2271236

# # If you encounter any problem, please open an issue at https://aka.ms/azclibug

# # [Warning] The login output has been updated. Please be aware that it no longer displays the full list of available subscriptions by default.
#EndRegion '.\Public\Grant-AdminConsentUsingAzCli.ps1' 131
#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\Handle-ExternalAADTenant.ps1' -1

function Handle-ExternalAADTenant {
    param (
        [Parameter(Mandatory = $true)]
        [PSCustomObject]$Item,
        [Parameter(Mandatory = $true)]
        [PSCustomObject]$Context,
        [string]$UniqueId
    )

    if ([string]::Equals($Item.deviceDetail.deviceId, "{PII Removed}", [System.StringComparison]::OrdinalIgnoreCase)) {
        if ($Context.UniqueDeviceIds.Add($UniqueId)) {
            Write-EnhancedLog -Message "External Azure AD tenant detected for user: $($Item.userDisplayName)" -Level "INFO"
            Add-Result -Context $Context -Item $Item -DeviceId "N/A" -DeviceState "External" -HasPremiumLicense $false -OSVersion $null
        }
        return $true
    }
    return $false
}
#EndRegion '.\Public\Handle-ExternalAADTenant.ps1' 19
#Region '.\Public\Handle-PSFLogging.ps1' -1

function Copy-PSFLogs {
    [CmdletBinding()]
    param (
        [string]$SourcePath,
        [string]$DestinationPath
    )

    Begin {
        Write-EnhancedLog -Message "Starting Copy-PSFLogs function..." -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        if (Test-Path -Path $SourcePath) {
            try {
                Copy-Item -Path "$SourcePath*" -Destination $DestinationPath -Recurse -Force -ErrorAction Stop
                Write-EnhancedLog -Message "Log files successfully copied from $SourcePath to $DestinationPath" -Level "INFO"
            }
            catch {
                Write-EnhancedLog -Message "Failed to copy logs from $SourcePath. Error: $_" -Level "ERROR"
            }
        }
        else {
            Write-EnhancedLog -Message "Log path not found: $SourcePath" -Level "WARNING"
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Copy-PSFLogs function" -Level "NOTICE"
    }
}
function Remove-PSFLogs {
    [CmdletBinding()]
    param (
        [string]$SourcePath
    )

    Begin {
        Write-EnhancedLog -Message "Starting Remove-PSFLogs function..." -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        if (Test-Path -Path $SourcePath) {
            try {
                Remove-Item -Path "$SourcePath*" -Recurse -Force -ErrorAction Stop
                Write-EnhancedLog -Message "Logs successfully removed from $SourcePath" -Level "INFO"
            }
            catch {
                Write-EnhancedLog -Message "Failed to remove logs from $SourcePath. Error: $_" -Level "ERROR"
            }
        }
        else {
            Write-EnhancedLog -Message "Log path not found: $SourcePath" -Level "WARNING"
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Remove-PSFLogs function" -Level "NOTICE"
    }
}
function Handle-PSFLogging {
    [CmdletBinding()]
    param (
        [string]$SystemSourcePathWindowsPS = "C:\Windows\System32\config\systemprofile\AppData\Roaming\WindowsPowerShell\PSFramework\Logs\",
        [string]$SystemSourcePathPS = "C:\Windows\System32\config\systemprofile\AppData\Roaming\PowerShell\PSFramework\Logs\",
        [string]$UserSourcePathWindowsPS = "$env:USERPROFILE\AppData\Roaming\WindowsPowerShell\PSFramework\Logs\",
        [string]$UserSourcePathPS = "$env:USERPROFILE\AppData\Roaming\PowerShell\PSFramework\Logs\",
        [string]$PSFPath = "C:\Logs\PSF",
        [string]$ParentScriptName,
        [string]$JobName,
        [bool]$SkipSYSTEMLogCopy = $false,
        [bool]$SkipSYSTEMLogRemoval = $false
    )

    Begin {
        Write-EnhancedLog -Message "Starting Handle-PSFLogging function..." -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Get the current username and script name
        $username = if ($env:USERNAME) { $env:USERNAME } else { "UnknownUser" }
        $scriptName = [System.IO.Path]::GetFileNameWithoutExtension($MyInvocation.ScriptName)
        if (-not $scriptName) {
            $scriptName = "UnknownScript"
        }

        # Get the current date for folder creation
        $currentDate = Get-Date -Format "yyyy-MM-dd"
        $logFolderPath = "$PSFPath\$currentDate\$scriptName"

        # Ensure the destination directory exists
        if (-not (Test-Path -Path $logFolderPath)) {
            New-Item -ItemType Directory -Path $logFolderPath -Force
            Write-EnhancedLog -Message "Created destination directory at $logFolderPath" -Level "INFO"
        }
    }

    Process {
        # Copy logs from both SYSTEM profile paths
        
        if (-not $SkipSYSTEMLogCopy) {
            Copy-PSFLogs -SourcePath $SystemSourcePathWindowsPS -DestinationPath $logFolderPath
            Write-EnhancedLog -Message "Copied SYSTEM logs from $SystemSourcePathWindowsPS to $logFolderPath." -Level "INFO"
        
            Copy-PSFLogs -SourcePath $SystemSourcePathPS -DestinationPath $logFolderPath
            Write-EnhancedLog -Message "Copied SYSTEM logs from $SystemSourcePathPS to $logFolderPath." -Level "INFO"
        }
        else {
            Write-EnhancedLog -Message "Skipping SYSTEM log copy as per the provided parameter." -Level "INFO"
        }

        # Copy logs from the user's profile paths for both PowerShell versions
        Copy-PSFLogs -SourcePath $UserSourcePathWindowsPS -DestinationPath $logFolderPath
        Copy-PSFLogs -SourcePath $UserSourcePathPS -DestinationPath $logFolderPath

        # Verify that the files have been copied
        if (Test-Path -Path $logFolderPath) {
            Write-EnhancedLog -Message "Logs successfully processed to $logFolderPath" -Level "INFO"
        }
        else {
            Write-EnhancedLog -Message "Failed to process log files." -Level "ERROR"
        }

        # Remove logs from the SYSTEM profile paths
        if (-not $SkipSYSTEMLogRemoval) {
            # Remove logs from the SYSTEM profile paths
            Remove-PSFLogs -SourcePath $SystemSourcePathWindowsPS
            Write-EnhancedLog -Message "Removed SYSTEM logs from $SystemSourcePathWindowsPS." -Level "INFO"
            
            Remove-PSFLogs -SourcePath $SystemSourcePathPS
            Write-EnhancedLog -Message "Removed SYSTEM logs from $SystemSourcePathPS." -Level "INFO"
        }
        else {
            Write-EnhancedLog -Message "Skipping SYSTEM log removal as per the provided parameter." -Level "INFO"
        }

        # Remove logs from the User profile paths
        Remove-PSFLogs -SourcePath $UserSourcePathWindowsPS
        Remove-PSFLogs -SourcePath $UserSourcePathPS

        # Rename SYSTEM logs in PSF to append SYSTEM to easily identify these files
        $RenamePSFLogFilesParams = @{
            LogDirectoryPath = $logFolderPath 
            ParentScriptName = $ParentScriptName
            JobName          = $jobName
        }
        Rename-PSFLogFilesWithUsername @RenamePSFLogFilesParams
    }

    End {
        Write-EnhancedLog -Message "Exiting Handle-PSFLogging function" -Level "NOTICE"
    }
}
# $HandlePSFLoggingParams = @{
# SystemSourcePathWindowsPS = "C:\Windows\System32\config\systemprofile\AppData\Roaming\WindowsPowerShell\PSFramework\Logs\"
# SystemSourcePathPS = "C:\Windows\System32\config\systemprofile\AppData\Roaming\PowerShell\PSFramework\Logs\"
# UserSourcePathWindowsPS = "$env:USERPROFILE\AppData\Roaming\WindowsPowerShell\PSFramework\Logs\"
# UserSourcePathPS = "$env:USERPROFILE\AppData\Roaming\PowerShell\PSFramework\Logs\"
# PSFPath = "C:\Logs\PSF"
# }

# Handle-PSFLogging @HandlePSFLoggingParams
#EndRegion '.\Public\Handle-PSFLogging.ps1' 163
#Region '.\Public\Import-CertificateIfNotExist.ps1' -1

function Import-CertificateIfNotExist {
    param (
        [Parameter(Mandatory = $true)]
        [string]$CertPath,
        
        [Parameter(Mandatory = $true)]
        [string]$CertPassword
    )

    try {
        Write-EnhancedLog -Message "Starting certificate import process." -Level "INFO"
        
        # Load the PFX file using the constructor
        $securePassword = ConvertTo-SecureString -String $CertPassword -AsPlainText -Force
        $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($CertPath, $securePassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet)

        # Check if the certificate already exists in the local machine store
        $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My", "LocalMachine")
        $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly)
        $existingCert = $store.Certificates | Where-Object { $_.Thumbprint -eq $cert.Thumbprint }
        $store.Close()

        if ($existingCert) {
            Write-EnhancedLog -Message "Certificate already exists in the local machine store." -Level "INFO"
        } else {
            Write-EnhancedLog -Message "Certificate does not exist in the local machine store. Importing certificate..." -Level "INFO"

            # Open the store with write access and add the certificate
            $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
            $store.Add($cert)
            $store.Close()

            Write-EnhancedLog -Message "Certificate imported successfully." -Level "INFO"
        }
    } catch {
        Write-EnhancedLog -Message "An error occurred during the certificate import process." -Level "ERROR"
        Handle-Error -ErrorRecord $_
        throw $_
    }
}

# Example usage
# $params = @{
# CertPath = "C:\Path\To\Your\Certificate.pfx"
# CertPassword = "YourPfxPassword"
# }

# Import-CertificateIfNotExist @params
#EndRegion '.\Public\Import-CertificateIfNotExist.ps1' 49
#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-FortiClientConfig.ps1' -1

function Import-FortiClientConfig {
    [CmdletBinding()]
    param (
        [string]$ScriptRoot,
        [string]$FortiClientPath,
        [string]$ConfigFileExtension,
        [string]$FCConfigExecutable,
        [string]$ArgumentTemplate
    )

    begin {
        Write-EnhancedLog -Message 'Starting Import-FortiClientConfig function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            # Find the configuration file in the root of the script directory
            $xmlConfigFile = Get-ChildItem -Path $ScriptRoot -Filter $ConfigFileExtension | Select-Object -First 1

            if (-not $xmlConfigFile) {
                Write-EnhancedLog -Message "No configuration file found in the script directory: $ScriptRoot" -Level 'ERROR'
                Write-Output "No configuration file found in the script directory: $ScriptRoot"
                return
            }

            # Check if the FortiClient directory exists
            if (-not (Test-Path -Path $FortiClientPath)) {
                Write-EnhancedLog -Message "FortiClient directory not found at path: $FortiClientPath" -Level 'ERROR'
                Write-Output "FortiClient directory not found at path: $FortiClientPath"
                return
            }

            # Set location to FortiClient directory
            Set-Location -Path $FortiClientPath

            # Execute the FCConfig.exe with the specified arguments
            $fcConfigPath = Join-Path -Path $FortiClientPath -ChildPath $FCConfigExecutable
            $arguments = $ArgumentTemplate -replace '{ConfigFilePath}', $xmlConfigFile.FullName
            Start-Process -FilePath $fcConfigPath -ArgumentList $arguments -Wait

            Write-EnhancedLog -Message 'FCConfig process completed' -Level 'INFO'
            Write-Output "FCConfig process completed"
        } catch {
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'Import-FortiClientConfig function completed' -Level 'INFO'
    }
}


# # Example usage of Import-FortiClientConfig function with splatting
# $importParams = @{
# ScriptRoot = $PSScriptRoot
# FortiClientPath = "C:\Program Files\Fortinet\FortiClient"
# ConfigFileExtension = "*.xml"
# FCConfigExecutable = "FCConfig.exe"
# ArgumentTemplate = "-m all -f `{ConfigFilePath}` -o import -i 1"
# }

# # Call the Import-FortiClientConfig function using splatting
# Import-FortiClientConfig @importParams
#EndRegion '.\Public\Import-FortiClientConfig.ps1' 66
#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\Import-RegistryFilesInScriptRoot.ps1' -1

function Import-RegistryFilesInScriptRoot {
    [CmdletBinding()]
    param (
        [string]$Filter,
        [string]$FilePath,
        [string]$Arguments,
        $scriptDirectory
    )

    begin {
        Write-EnhancedLog -Message 'Starting Import-RegistryFilesInScriptRoot function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            $registryFiles = Get-ChildItem -Path $scriptDirectory -Filter $Filter

            if ($registryFiles.Count -eq 0) {
                Write-EnhancedLog -Message "No registry files found in the directory: $scriptDirectory" -Level 'WARNING'
                return
            }

            foreach ($registryFile in $registryFiles) {
                $registryFilePath = $registryFile.FullName

                if (Test-Path $registryFilePath) {
                    Write-EnhancedLog -Message "Found registry file: $registryFilePath" -Level 'INFO'
                    $startProcessParams = @{
                        FilePath = $FilePath
                        ArgumentList = $Arguments
                        Wait = $true
                    }
                    Start-Process @startProcessParams
                    Write-EnhancedLog -Message "Registry file import process completed for: $registryFilePath" -Level 'INFO'

                    # Validate the registry keys
                    Validate-RegistryKeys -RegistryFilePath $registryFilePath
                }
                else {
                    Write-EnhancedLog -Message "Registry file not found at path: $registryFilePath" -Level 'ERROR'
                }
            }
        }
        catch {
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'Import-RegistryFilesInScriptRoot function completed' -Level 'INFO'
    }
}


# # Define parameters for splatting
# $params = @{
# Filter = "*.reg"
# FilePath = "reg.exe"
# Args = "import `"$registryFilePath`""
# }

# # Call the Import-RegistryFilesInScriptRoot function using splatting
# Import-RegistryFilesInScriptRoot @params
#EndRegion '.\Public\Import-RegistryFilesInScriptRoot.ps1' 65
#Region '.\Public\Import-VPNConnection.ps1' -1

function Import-VPNConnection {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$XmlFilePath
    )

    try {
        Write-EnhancedLog -Message "Script starting..." -Level "INFO"

        # Load the XML file
        [xml]$vpnConfig = Get-Content -Path $XmlFilePath

        # Extract VPN connection details from the XML
        $connectionName = $vpnConfig.Objs.Obj.Props.S | Where-Object { $_.N -eq "Name" } | Select-Object -ExpandProperty '#text'
        $serverAddress = $vpnConfig.Objs.Obj.Props.S | Where-Object { $_.N -eq "ServerAddress" } | Select-Object -ExpandProperty '#text'
        $tunnelType = $vpnConfig.Objs.Obj.Props.S | Where-Object { $_.N -eq "TunnelType" } | Select-Object -ExpandProperty '#text'
        $encryptionLevel = $vpnConfig.Objs.Obj.Props.S | Where-Object { $_.N -eq "EncryptionLevel" } | Select-Object -ExpandProperty '#text'
        $splitTunneling = $vpnConfig.Objs.Obj.Props.B | Where-Object { $_.N -eq "SplitTunneling" } | Select-Object -ExpandProperty '#text'
        $rememberCredential = $vpnConfig.Objs.Obj.Props.B | Where-Object { $_.N -eq "RememberCredential" } | Select-Object -ExpandProperty '#text'
        $useWinlogonCredential = $vpnConfig.Objs.Obj.Props.B | Where-Object { $_.N -eq "UseWinlogonCredential" } | Select-Object -ExpandProperty '#text'
        $idleDisconnectSeconds = $vpnConfig.Objs.Obj.Props.U32 | Where-Object { $_.N -eq "IdleDisconnectSeconds" } | Select-Object -ExpandProperty '#text'
        # $authenticationMethod = $vpnConfig.Objs.Obj.Props.Obj.LST.S | Select-Object -ExpandProperty '#text'
        $authenticationMethod = $vpnConfig.Objs.Obj.Props.Obj | Where-Object { $_.N -eq "AuthenticationMethod" } | Select-Object -ExpandProperty LST | Select-Object -ExpandProperty S
        $l2tpPsk = $vpnConfig.Objs.Obj.Props.S | Where-Object { $_.N -eq "L2tpIPsecAuth" } | Select-Object -ExpandProperty '#text'
        # $napState = $vpnConfig.Objs.Obj.Props.S | Where-Object { $_.N -eq "NapState" } | Select-Object -ExpandProperty '#text'

        # Convert boolean values from text to actual boolean type
        $splitTunneling = [System.Convert]::ToBoolean($splitTunneling)
        $rememberCredential = [System.Convert]::ToBoolean($rememberCredential)
        $useWinlogonCredential = [System.Convert]::ToBoolean($useWinlogonCredential)

        # Validate extracted details
        if ([string]::IsNullOrWhiteSpace($connectionName) -or [string]::IsNullOrWhiteSpace($serverAddress)) {
            Write-EnhancedLog -Message "Connection name or server address could not be found in the XML file." -Level "ERROR"
            return
        }

        # Validate if VPN connection already exists
        if (Test-VPNConnection -ConnectionName $connectionName) {
            Write-EnhancedLog -Message "VPN connection '$connectionName' already exists. Removing it before re-importing." -Level "WARNING"
            Remove-VpnConnection -Name $connectionName -Force -AllUserConnection -ErrorAction SilentlyContinue

            # Verify the VPN connection is removed
            if (Test-VPNConnection -ConnectionName $connectionName) {
                Write-EnhancedLog -Message "Failed to remove existing VPN connection '$connectionName'." -Level "ERROR"
                return
            }

            Start-Sleep -Seconds 5 # Wait to ensure the connection is fully removed
        }

        # Splatting parameters for Add-VpnConnection
        $splatVpnParams = @{
            Name                  = $connectionName
            ServerAddress         = $serverAddress
            TunnelType            = $tunnelType
            EncryptionLevel       = $encryptionLevel
            SplitTunneling        = $splitTunneling
            RememberCredential    = $rememberCredential
            UseWinlogonCredential = $useWinlogonCredential
            IdleDisconnectSeconds = $idleDisconnectSeconds
            AuthenticationMethod  = $authenticationMethod
            L2tpPsk               = $l2tpPsk
            AllUserConnection     = $true
            Force                 = $true
        }

        # Remove L2tpPsk parameter if TunnelType is not L2tp
        if ($tunnelType -ne 'L2tp') {
            $splatVpnParams.Remove('L2tpPsk')
        }

        # Create the VPN connection
        Add-VpnConnection @splatVpnParams
        Write-EnhancedLog -Message "VPN connection '$connectionName' imported. Verifying the import..." -Level "INFO"

        # Introduce a brief pause to ensure the connection is fully created
        Start-Sleep -Seconds 5

        # Verify the VPN connection was created successfully
        if (Test-VPNConnection -ConnectionName $connectionName) {
            Write-EnhancedLog -Message "VPN connection '$connectionName' was successfully imported." -Level "INFO"
        } else {
            Write-EnhancedLog -Message "VPN connection '$connectionName' failed to import." -Level "ERROR"
        }
    }
    catch {
        Handle-Error -ErrorRecord $_
        Write-EnhancedLog -Message "An error occurred while importing VPN connection." -Level "ERROR"
        throw $_
    }
}
#EndRegion '.\Public\Import-VPNConnection.ps1' 94
#Region '.\Public\Initialize-Context.ps1' -1

function Initialize-Context {
    param (
        [Parameter(Mandatory = $true)]
        [PSCustomObject]$Context
    )

    if (-not $Context.UniqueDeviceIds) {
        $Context.UniqueDeviceIds = [System.Collections.Generic.HashSet[string]]::new()
    }
}
#EndRegion '.\Public\Initialize-Context.ps1' 11
#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 -ExecutionMode $ExecutionMode

        # 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 -ExecutionMode $ExecutionMode
        }
    }
}
#EndRegion '.\Public\Initialize-Environment.ps1' 106
#Region '.\Public\Initialize-HyperVServices.ps1' -1

function Initialize-HyperVServices {
    [CmdletBinding()]
    param ()

    Begin {
        Write-EnhancedLog -Message "Starting Initialize-HyperVServices function" -Level "INFO"
    }

    Process {
        try {
            Write-EnhancedLog -Message "Checking for Hyper-V services" -Level "INFO"
            if (Get-Service -DisplayName *hyper*) {
                Write-EnhancedLog -Message "Starting Hyper-V services: vmcompute and vmms" -Level "INFO"
                Start-Service -Name vmcompute -ErrorAction SilentlyContinue
                Start-Service -Name vmms -ErrorAction SilentlyContinue
                Write-EnhancedLog -Message "Hyper-V services started" -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
            } else {
                Write-EnhancedLog -Message "No Hyper-V services found" -Level "WARNING"
            }
        } catch {
            Write-EnhancedLog -Message "An error occurred while starting Hyper-V services: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Initialize-HyperVServices function" -Level "INFO"
    }
}
#EndRegion '.\Public\Initialize-HyperVServices.ps1' 30
#Region '.\Public\Initialize-ScriptAndLogging.ps1' -1

$LoggingDeploymentName = $config.LoggingDeploymentName


function Initialize-ScriptAndLogging {
    $isWindowsOS = $false
    if ($PSVersionTable.PSVersion.Major -ge 6) {
        $isWindowsOS = $isWindowsOS -or ($PSVersionTable.Platform -eq 'Win32NT')
    } else {
        $isWindowsOS = $isWindowsOS -or ($env:OS -eq 'Windows_NT')
    }

    $deploymentName = "$LoggingDeploymentName" # Replace this with your actual deployment name
    $baseScriptPath = if ($isWindowsOS) { "C:\code" } else { "/home/code" }
    $scriptPath_1001 = Join-Path -Path $baseScriptPath -ChildPath $deploymentName
    $computerName = if ($isWindowsOS) { $env:COMPUTERNAME } else { (hostname) }

    try {
        if (-not (Test-Path -Path $scriptPath_1001)) {
            New-Item -ItemType Directory -Path $scriptPath_1001 -Force | Out-Null
            Write-Host "Created directory: $scriptPath_1001" -ForegroundColor Green
        }

        $Filename = "$LoggingDeploymentName"
        $logDir = Join-Path -Path $scriptPath_1001 -ChildPath "exports/Logs/$computerName"
        $logPath = Join-Path -Path $logDir -ChildPath "$(Get-Date -Format 'yyyy-MM-dd-HH-mm-ss')"

        if (-not (Test-Path $logPath)) {
            Write-Host "Did not find log directory at $logPath" -ForegroundColor Yellow
            Write-Host "Creating log directory at $logPath" -ForegroundColor Yellow
            New-Item -ItemType Directory -Path $logPath -Force -ErrorAction Stop | Out-Null
            Write-Host "Created log directory at $logPath" -ForegroundColor Green
        }

        $logFile = Join-Path -Path $logPath -ChildPath "$Filename-Transcript.log"
        Start-Transcript -Path $logFile -ErrorAction Stop | Out-Null

        return @{
            ScriptPath  = $scriptPath_1001
            Filename    = $Filename
            LogPath     = $logPath
            LogFile     = $logFile
        }
    } catch {
        Write-Host "An error occurred while initializing script and logging: $_" -ForegroundColor Red
    }
}

# Example usage of Initialize-ScriptAndLogging
# try {
# $initResult = Initialize-ScriptAndLogging
# Write-Host "Initialization successful. Log file path: $($initResult.LogFile)" -ForegroundColor Green
# } catch {
# Write-Host "Initialization failed: $_" -ForegroundColor Red
# }
#EndRegion '.\Public\Initialize-ScriptAndLogging.ps1' 55
#Region '.\Public\Initialize-ScriptVariables.ps1' -1

function Initialize-ScriptVariables {
    <#
    .SYNOPSIS
    Initializes global script variables and defines the path for storing related files.
 
    .DESCRIPTION
    This function initializes global script variables such as PackageName, PackageUniqueGUID, Version, and ScriptMode. Additionally, it constructs the path where related files will be stored based on the provided parameters.
 
    .PARAMETER PackageName
    The name of the package being processed.
 
    .PARAMETER PackageUniqueGUID
    The unique identifier for the package being processed.
 
    .PARAMETER Version
    The version of the package being processed.
 
    .PARAMETER ScriptMode
    The mode in which the script is being executed (e.g., "Remediation", "PackageName").
 
    .PARAMETER PackageExecutionContext
    The context in which the package is being executed (e.g., User, System).
 
    .PARAMETER RepetitionInterval
    The interval at which the package is executed repeatedly.
 
    .EXAMPLE
    Initialize-ScriptVariables -PackageName "MyPackage" -PackageUniqueGUID "1234-5678" -Version 1 -ScriptMode "Remediation" -PackageExecutionContext "System" -RepetitionInterval "P1D"
 
    This example initializes the script variables with the specified values.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the name of the package.")]
        [string]$PackageName,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the unique identifier of the package.")]
        [string]$PackageUniqueGUID,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the version of the package.")]
        [int]$Version,

        [Parameter(Mandatory = $true, HelpMessage = "Specify the script execution mode.")]
        [string]$ScriptMode,

        [Parameter(Mandatory = $true, HelpMessage = "Specify the execution context (e.g., User, System).")]
        [string]$PackageExecutionContext,

        [Parameter(Mandatory = $true, HelpMessage = "Specify the repetition interval (e.g., 'P1D').")]
        [string]$RepetitionInterval
    )

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

        # Check if the global variable $Path_local is set, otherwise set it based on execution context
        Write-EnhancedLog -Message "Determining the local path based on the execution context..." -Level "INFO"
        try {
            if (-not $Path_local) {
                if (Test-RunningAsSystem) {
                    $Path_local = "C:\_MEM"
                }
                else {
                    $Path_local = "$ENV:LOCALAPPDATA\_MEM"
                }
            }
            Write-EnhancedLog -Message "Local path set to $Path_local" -Level "INFO"
        }
        catch {
            Write-EnhancedLog -Message "Error determining local path: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
    }

    Process {
        try {
            Write-EnhancedLog -Message "Initializing variables based on the provided parameters..." -Level "INFO"

            # Construct paths and task names
            $Path_PR = "$Path_local\Data\$PackageName-$PackageUniqueGUID"
            $schtaskName = "$PackageName - $PackageUniqueGUID"
            $schtaskDescription = "Version $Version"

            Write-EnhancedLog -Message "Variables initialized successfully." -Level "INFO"
        }
        catch {
            Write-EnhancedLog -Message "Error initializing variables: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
    }

    End {
        Write-EnhancedLog -Message "Returning initialized script variables." -Level "INFO"
        try {
            # Return a hashtable containing all important variables
            return @{
                PackageName             = $PackageName
                PackageUniqueGUID       = $PackageUniqueGUID
                Version                 = $Version
                ScriptMode              = $ScriptMode
                Path_local              = $Path_local
                Path_PR                 = $Path_PR
                schtaskName             = $schtaskName
                schtaskDescription      = $schtaskDescription
                PackageExecutionContext = $PackageExecutionContext
                RepetitionInterval      = $RepetitionInterval
            }
        }
        catch {
            Write-EnhancedLog -Message "Error returning initialized script variables: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
        finally {
            Write-EnhancedLog -Message "Exiting Initialize-ScriptVariables function" -Level "Notice"
        }
    }
}

# Example usage
# $params = @{
# PackageName = "MyPackage"
# PackageUniqueGUID = "1234-5678"
# Version = 1
# ScriptMode = "Remediation"
# PackageExecutionContext = "System"
# RepetitionInterval = "P1D"
# }
# Initialize-ScriptVariables @params
#EndRegion '.\Public\Initialize-ScriptVariables.ps1' 134
#Region '.\Public\Initialize-Win32Environment.ps1' -1

function Initialize-Win32Environment {
    <#
    .SYNOPSIS
    Initializes the Win32 environment by setting up platform-specific configurations.
 
    .DESCRIPTION
    This function detects the platform (Windows or Unix) and sets up the environment accordingly. It throws an error if the operating system is unsupported, with proper error handling. The function returns an object containing platform details and the environment setup results.
 
    .PARAMETER scriptpath
    The path to the script being executed, used for logging or module setup purposes.
 
    .EXAMPLE
    $envInitialization = Initialize-Win32Environment -scriptpath "C:\path\to\your\script.ps1"
    Initializes the Win32 environment for the specified script path and returns initialization details.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the path to the script being executed.")]
        [ValidateNotNullOrEmpty()]
        [string]$scriptpath
    )

    Begin {
        Write-EnhancedLog -Message "Starting Initialize-Win32Environment function" -Level "INFO"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            $platform = Get-Platform
            $envDetails = $null

            if ($platform -eq 'Win32NT' -or $platform -eq [System.PlatformID]::Win32NT) {
                Write-EnhancedLog -Message "Detected platform: Windows (Win32NT)" -Level "INFO"
                $envDetails = Setup-WindowsEnvironment -scriptpath $scriptpath
            }
            elseif ($platform -eq 'Unix' -or $platform -eq [System.PlatformID]::Unix) {
                Write-EnhancedLog -Message "Detected platform: Unix" -Level "INFO"
                # Setup-LinuxEnvironment (commented out for now)
                $envDetails = [pscustomobject]@{ Platform = "Unix"; Setup = "Not implemented" }
            }
            else {
                Write-EnhancedLog -Message "Unsupported operating system detected: $platform" -Level "ERROR"
                throw "Unsupported operating system: $platform"
            }

            # Return the platform and environment setup details as an object
            return [pscustomobject]@{
                Platform   = $platform
                EnvDetails = $envDetails
            }
        }
        catch {
            Write-EnhancedLog -Message "Error occurred during environment initialization: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
    }

    End {
        Write-EnhancedLog -Message "Environment initialization completed for script: $scriptpath" -Level "INFO"
    }
}


# Run Initialize-Win32Environment and store the returned object
# $envInitialization = Initialize-Win32Environment -scriptpath "C:\path\to\your\script.ps1"

# Access the properties of the returned object
# Write-Host "Platform: $($envInitialization.Platform)"
# Write-Host "Environment Details: $($envInitialization.EnvDetails)"


# Platform: Win32NT
# Environment Details: @{AOscriptDirectory=C:\path\to\Win32Apps-DropBox; directoryPath=C:\path\to\Win32Apps-DropBox; Repo_Path=C:\path\to; Repo_winget=C:\path\to\Win32Apps-DropBox}



# # Run Initialize-Win32Environment and store the returned object
# $envInitialization = Initialize-Win32Environment -scriptpath "C:\path\to\your\script.ps1"

# # Access the properties of the EnvDetails object
# $AOscriptDirectory = $envInitialization.EnvDetails.AOscriptDirectory
# $directoryPath = $envInitialization.EnvDetails.directoryPath
# $Repo_Path = $envInitialization.EnvDetails.Repo_Path
# $Repo_winget = $envInitialization.EnvDetails.Repo_winget

# # Output the extracted values
# Write-Host "AO Script Directory: $AOscriptDirectory"
# Write-Host "Directory Path: $directoryPath"
# Write-Host "Repository Path: $Repo_Path"
# Write-Host "Winget Path: $Repo_winget"
#EndRegion '.\Public\Initialize-Win32Environment.ps1' 94
#Region '.\Public\Install-ADKFromMSI.ps1' -1

function Install-ADKFromMSI {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$OfflinePath,

        [Parameter(Mandatory = $true)]
        [string]$ICDPath = "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Imaging and Configuration Designer\x86\ICD.exe"
    )

    Begin {
        Write-EnhancedLog -Message "Starting Install-ADKFromMSI function" -Level "INFO"
        Log-Params -Params @{
            OfflinePath = $OfflinePath
            ICDPath = $ICDPath
        }

        # Ensure offline path exists
        if (-not (Test-Path -Path $OfflinePath)) {
            throw "Offline path not found: $OfflinePath"
        }
    }

    Process {
        try {
            # Get all MSI files in the offline path
            $MSIFiles = Get-ChildItem -Path $OfflinePath -Filter *.msi

            if (-not $MSIFiles) {
                throw "No MSI files found in: $OfflinePath"
            }

            # Install each MSI file
            foreach ($MSI in $MSIFiles) {
                Write-EnhancedLog -Message "Installing MSI: $($MSI.FullName)" -Level "INFO"
                Start-Process -FilePath "msiexec.exe" -ArgumentList "/i `"$($MSI.FullName)`" /quiet /norestart" -Wait -NoNewWindow
            }

            # Check if ICD.exe exists
            if (Test-Path -Path $ICDPath) {
                Write-EnhancedLog -Message "ICD.exe found at: $ICDPath" -Level "INFO"
            } else {
                throw "ICD.exe not found at: $ICDPath"
            }

        } catch {
            Write-EnhancedLog -Message "An error occurred while processing the Install-ADKFromMSI function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Install-ADKFromMSI function" -Level "INFO"
    }
}

# # Example usage
# $installParams = @{
# OfflinePath = "$env:TEMP\ADKOffline\Installers"
# ICDPath = "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Imaging and Configuration Designer\x86\ICD.exe"
# }

# Install-ADKFromMSI @installParams
#EndRegion '.\Public\Install-ADKFromMSI.ps1' 64
#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-MSIPackage.ps1' -1

function Install-MsiPackage {
    [CmdletBinding()]
    param (
        [string]$ScriptRoot,
        [string]$MsiFileName,
        [string]$FilePath,
        [string]$ArgumentTemplate
    )

    begin {
        Write-EnhancedLog -Message "Starting Install-MsiPackage function for: $MsiFileName" -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            $installerPath = Join-Path -Path $ScriptRoot -ChildPath $MsiFileName
            
            if (Test-Path $installerPath) {
                Write-EnhancedLog -Message "Found installer file: $installerPath" -Level 'INFO'
                $arguments = $ArgumentTemplate -replace '{InstallerPath}', $installerPath
                Start-Process -FilePath $FilePath -ArgumentList $arguments -Wait
                Write-EnhancedLog -Message "Installation process completed for: $installerPath" -Level 'INFO'
                Write-EnhancedLog -Message "Installation process completed for: $installerPath" -Level 'INFO'
            } else {
                Write-EnhancedLog -Message "Installer file not found at path: $installerPath.. proceeding to extract ZIP files" -Level 'WARNING'

                Write-EnhancedLog -Message "Extracting all ZIP files recursively..."
                $zipFiles = Get-ChildItem -Path $ScriptRoot -Recurse -Include '*.zip.001'
                foreach ($zipFile in $zipFiles) {
                    $destinationFolder = [System.IO.Path]::GetDirectoryName($zipFile.FullName)
                    Write-EnhancedLog -Message "Combining and extracting segmented ZIP files for $($zipFile.BaseName) using 7-Zip..."
                    $sevenZipCommand = "& `"$env:ProgramFiles\7-Zip\7z.exe`" x `"$zipFile`" -o`"$destinationFolder`""
                    Write-EnhancedLog -Message "Executing: $sevenZipCommand"
                    Invoke-Expression $sevenZipCommand
                }
                Write-EnhancedLog -Message "All ZIP files extracted."

                $arguments = $ArgumentTemplate -replace '{InstallerPath}', $installerPath
                Start-Process -FilePath $FilePath -ArgumentList $arguments -Wait


                # Write-Output "Installer file not found at path: $installerPath"
            }
        } catch {
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'Install-MsiPackage function completed' -Level 'INFO'
    }
}

# # Example usage of Install-MsiPackage function with splatting
# $params = @{
# ScriptRoot = $PSScriptRoot
# MsiFileName = 'FortiClient.msi'
# FilePath = 'MsiExec.exe'
# ArgumentTemplate = "/i `{InstallerPath}` /quiet /norestart"
# }
# Install-MsiPackage @params
#EndRegion '.\Public\Install-MSIPackage.ps1' 63
#Region '.\Public\Install-OneDriveSetup.ps1' -1

function Install-OneDriveSetup {
    param (
        [string]$ODSetupPath,
        [string]$SetupArgumentList
    )
    
    Write-Log "Installing OneDrive setup from $ODSetupPath..." -Level "INFO"
    $startProcessParams = @{
        FilePath     = $ODSetupPath
        ArgumentList = $SetupArgumentList
        Wait         = $true
        NoNewWindow  = $true
    }
    
    try {
        Start-Process @startProcessParams
        Write-Log "OneDrive installation completed." -Level "INFO"
    }
    catch {
        Write-Log "An error occurred during OneDrive installation: $($_.Exception.Message)" -Level "ERROR"
        throw $_
    }
}
#EndRegion '.\Public\Install-OneDriveSetup.ps1' 24
#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-PPKG.ps1' -1

function Install-PPKG {
    <#
    .SYNOPSIS
    Installs a provisioning package (PPKG) with validation and logging.
 
    .DESCRIPTION
    The Install-PPKG function installs a provisioning package (PPKG) from a specified path. If the provisioning package is already installed, it will force remove it before reinstalling. It logs the installation process, validates the installation, and handles errors gracefully.
 
    .PARAMETER PPKGPath
    The full path to the provisioning package (PPKG) to be installed.
 
    .EXAMPLE
    $params = @{
        PPKGPath = "C:\ProgramData\AADMigration\MyProvisioningPackage.ppkg"
    }
    Install-PPKG @params
    Installs the specified provisioning package and logs the installation.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the full path to the PPKG file.")]
        [ValidateNotNullOrEmpty()]
        [string]$PPKGPath
    )

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

        # Validate PPKG file exists
        if (-not (Test-Path -Path $PPKGPath)) {
            throw "Provisioning package file not found: $PPKGPath"
        }


        # Extract just the file name from the PPKG path (without extension)
        $ppkgFileName = [System.IO.Path]::GetFileNameWithoutExtension($PPKGPath)

        # Check if the PPKG is already installed
        Write-EnhancedLog -Message "Validating if provisioning package is already installed." -Level "INFO"
        $isInstalled = Validate-PPKGInstallation -PPKGName $ppkgFileName
  

        if ($isInstalled) {
            Remove-InstalledPPKG -PackageName $ppkgFileName
        }
        


        # Wait-Debugger
    }

    Process {
        try {
            Write-EnhancedLog -Message "Installing provisioning package: $PPKGPath" -Level "INFO"

            # Get current timestamp
            $timestamp = (Get-Date).ToString("yyyy-MM-dd_HH-mm-ss")

            # Create the dynamic log directory path based on PPKGPath and timestamp
            $logDir = "C:\logs\PPKG_EJ_Bulk_Enrollment\$ppkgFileName"
            $logFile = "$logDir\InstallLog_$timestamp.etl"

            # Check if the log directory exists, and create it if not
            if (-not (Test-Path $logDir)) {
                Write-EnhancedLog -Message "Log directory does not exist. Creating directory: $logDir" -Level "INFO"
                New-Item -Path $logDir -ItemType Directory -Force
            }

            # Set the Install-ProvisioningPackage parameters with the dynamic log file path
            $InstallProvisioningPackageParams = @{
                PackagePath   = $PPKGPath
                ForceInstall  = $true
                QuietInstall  = $true
                LogsDirectory = $logFile
            }

            # Log the parameters being passed to Install-ProvisioningPackage
            Write-EnhancedLog -Message "Starting installation of provisioning package with the following parameters:" -Level "INFO"
            Log-Params -Params $InstallProvisioningPackageParams

            # Install the provisioning package
            Install-ProvisioningPackage @InstallProvisioningPackageParams
            Write-EnhancedLog -Message "Successfully installed provisioning package: $PPKGPath" -Level "INFO"
        }
        catch {
            Write-EnhancedLog -Message "Error installing provisioning package: $($_.Exception.Message)" -Level "ERROR"
            Write-EnhancedLog -Message "Did you check that the Package_GUID Account in Entra is excluded from All Conditional Access Policies" -Level "INFO"
            Handle-Error -ErrorRecord $_
            throw
        }
        Finally {
            # Always log exit and cleanup actions
            Write-EnhancedLog -Message "Exiting Install-PPKG function" -Level "Notice"
        }
    }

    End {
        Write-EnhancedLog -Message "Validating provisioning package installation" -Level "INFO"
        $isInstalled = Validate-PPKGInstallation -PPKGName $ppkgFileName
        if ($isInstalled) {
            Write-EnhancedLog -Message "Provisioning package $PPKGPath installed successfully" -Level "INFO"
        }
        else {
            Write-EnhancedLog -Message "Provisioning package $PPKGPath installation failed" -Level "ERROR"
            throw "Provisioning package $PPKGPath installation could not be validated"
        }
    }
}

# # Example usage
# $params = @{
# PPKGPath = "C:\ProgramData\AADMigration\Files\ICTC_Project_2_Aug_29_2024\ICTC_Project_2.ppkg"
# }
# Install-PPKG @params
#EndRegion '.\Public\Install-PPKG.ps1' 117
#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\Install-Software.ps1' -1

# Main function to manage the entire OneDrive installation and configuration
function Install-Software {
    <#
    .SYNOPSIS
        Installs a specified software and performs pre- and post-installation validation.
 
    .DESCRIPTION
        This function handles the installation of software by downloading the installer, validating the software before and after installation,
        and performing any necessary post-installation tasks such as syncing or configuring the software.
 
    .PARAMETER MigrationPath
        The base directory path where the setup file will be stored.
 
    .PARAMETER SoftwareName
        The name of the software to be installed, used for validation.
 
    .PARAMETER SetupUri
        The URL from which the setup executable will be downloaded.
 
    .PARAMETER SetupFile
        The name of the setup executable file.
 
    .PARAMETER RegKey
        The registry key path used for validating the installed version.
 
    .PARAMETER MinVersion
        The minimum required version of the software to validate the installation.
 
    .PARAMETER ExePath
        The path to the executable file used for file-based validation.
 
    .PARAMETER ScheduledTaskName
        The name of the scheduled task used for any post-installation tasks.
 
    .PARAMETER ScheduledTaskDescription
        A description for the scheduled task.
 
    .PARAMETER SetupArgumentList
        The arguments passed to the installer executable during installation.
 
    .PARAMETER KFM
        Specifies whether to perform a Known Folder Move (KFM) sync after installation. Default is $false.
 
    .PARAMETER TimestampPrefix
        A prefix used for naming the timestamped folder in the TEMP directory. Default is 'Setup_'.
 
    .EXAMPLE
        $installParams = @{
            MigrationPath = "C:\Migration"
            SoftwareName = "OneDrive"
            SetupUri = "https://go.microsoft.com/fwlink/?linkid=844652"
            SetupFile = "OneDriveSetup.exe"
            RegKey = "HKLM:\SOFTWARE\Microsoft\OneDrive"
            MinVersion = [version]"23.143.0712.0001"
            ExePath = "C:\Program Files\Microsoft OneDrive\OneDrive.exe"
            ScheduledTaskName = "OneDriveRemediation"
            ScheduledTaskDescription= "Restart OneDrive to kick off KFM sync"
            SetupArgumentList = "/allusers"
            KFM = $true
            TimestampPrefix = "OneDriveSetup_"
        }
        Install-Software @installParams
 
    .NOTES
        Author: Abdullah Ollivierre
        Date: 2024-08-15
    #>


    [CmdletBinding()]
    param (
        [string]$MigrationPath,
        [string]$SoftwareName,
        [string]$SetupUri,
        [string]$SetupFile,
        [string]$RegKey,
        [version]$MinVersion,
        [string]$ExePath,
        [string]$ScheduledTaskName,
        [string]$ScheduledTaskDescription,
        [string]$SetupArgumentList,
        [bool]$KFM = $false,
        [string]$TimestampPrefix # Default prefix for the timestamped folder
    )

    Begin {
        Write-EnhancedLog -Message "Starting Install-Software function for $SoftwareName" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Ensure the script is running with elevated privileges
        CheckAndElevate

        # Generate a timestamped folder within the TEMP directory
        $timestamp = (Get-Date).ToString("yyyyMMdd_HHmmss")
        $destinationFolder = [System.IO.Path]::Combine($env:TEMP, "$TimestampPrefix$timestamp")
        $SetupPath = [System.IO.Path]::Combine($destinationFolder, $SetupFile)
    }

    Process {
        # Step 1: Pre-installation validation
        Write-EnhancedLog -Message "Step 1: Performing pre-installation validation for $SoftwareName..." -Level "INFO"
        $preInstallParams = @{
            SoftwareName        = $SoftwareName
            MinVersion          = $MinVersion
            RegistryPath        = $RegKey
            ExePath             = $ExePath
            MaxRetries          = 3
            DelayBetweenRetries = 5
        }
        $preInstallCheck = Validate-SoftwareInstallation @preInstallParams
        if ($preInstallCheck.IsInstalled) {
            Write-EnhancedLog -Message "$SoftwareName version $($preInstallCheck.Version) is already installed. Skipping installation." -Level "INFO"
            return
        }
        Write-EnhancedLog -Message "$SoftwareName is not currently installed or needs an update." -Level "INFO"

        # Step 2: Download the setup file if not already present
        Write-EnhancedLog -Message "Step 2: Downloading $SoftwareName setup..." -Level "INFO"
        if (-not (Test-Path -Path $SetupPath)) {
            Download-OneDriveSetup -ODSetupUri $SetupUri -ODSetupPath $SetupPath
        } else {
            Write-EnhancedLog -Message "$SoftwareName setup already downloaded at $SetupPath" -Level "INFO"
        }

        # Step 3: Install the software
        Write-EnhancedLog -Message "Step 3: Installing $SoftwareName..." -Level "INFO"
        Install-OneDriveSetup -ODSetupPath $SetupPath -SetupArgumentList $SetupArgumentList

        # Step 4: Post-installation validation
        Write-EnhancedLog -Message "Step 4: Performing post-installation validation for $SoftwareName..." -Level "INFO"
        $postInstallCheck = Validate-SoftwareInstallation @preInstallParams
        if ($postInstallCheck.IsInstalled) {
            Write-EnhancedLog -Message "$SoftwareName version $($postInstallCheck.Version) installed successfully." -Level "INFO"
        } else {
            Write-EnhancedLog -Message "$SoftwareName installation failed." -Level "ERROR"
            throw "$SoftwareName installation validation failed."
        }

        # Step 5: Perform KFM sync if enabled
        if ($KFM) {
            Write-EnhancedLog -Message "Step 5: Performing KFM sync for $SoftwareName..." -Level "INFO"
            Perform-KFMSync -OneDriveExePath $ExePath -ScheduledTaskName $ScheduledTaskName -ScheduledTaskDescription $ScheduledTaskDescription
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Install-Software function for $SoftwareName" -Level "NOTICE"
    }
}



# $installParams = @{
# MigrationPath = "C:\ProgramData\AADMigration"
# SoftwareName = "OneDrive"
# SetupUri = "https://go.microsoft.com/fwlink/?linkid=844652"
# SetupFile = "OneDriveSetup.exe"
# RegKey = "HKLM:\SOFTWARE\Microsoft\OneDrive"
# MinVersion = [version]"24.146.0721.0003"
# ExePath = "C:\Program Files\Microsoft OneDrive\OneDrive.exe"
# ScheduledTaskName = "OneDriveRemediation"
# ScheduledTaskDescription = "Restart OneDrive to kick off KFM sync"
# SetupArgumentList = "/allusers"
# KFM = $true
# TimestampPrefix = "OneDriveSetup_"
# }

# Install-Software @installParams
#EndRegion '.\Public\Install-Software.ps1' 168
#Region '.\Public\InstallAndImportModulesPSGallery.ps1' -1

function InstallAndImportModulesPSGallery {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$modulePsd1Path,

        [Parameter(Mandatory = $false)]
        [ValidateSet("series", "parallel")]
        [string]$ExecutionMode
    )

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

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

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

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

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

            # Determine the PowerShell path
            $PwshPath = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"

            # Check if we are in dev or prod mode and handle accordingly
            if ($ExecutionMode -eq "parallel" -and -not (Is-ServerCore)) {
                # Parallel execution in dev mode (outside Server Core)
                Write-EnhancedLog -Message "Running in dev mode with parallel execution" -Level "INFO"
                $processList = [System.Collections.Generic.List[System.Diagnostics.Process]]::new()

                foreach ($moduleName in $requiredModules) {
                    $splatProcessParams = @{
                        FilePath     = $PwshPath
                        ArgumentList = @(
                            "-NoProfile",
                            "-ExecutionPolicy", "Bypass",
                            "-Command", "& { Update-ModuleIfOldOrMissing -ModuleName '$moduleName' }"
                        )
                        NoNewWindow  = $true
                        PassThru     = $true
                    }

                    # Start the process for parallel execution
                    $process = Start-Process @splatProcessParams
                    $processList.Add($process)
                }

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

                Write-EnhancedLog "All module update processes have completed (parallel execution)." -Level "NOTICE"
            } else {
                # Series execution in both prod mode and Server Core in dev mode
                Write-EnhancedLog -Message "Running in $ExecutionMode mode with series execution" -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

                        if ($moduleInfo) {
                            $moduleDetails = [PSCustomObject]@{
                                Name    = $moduleName
                                Version = $moduleInfo.Version
                                Path    = $moduleInfo.ModuleBase
                            }
                            $successModules.Add($moduleDetails)
                            Write-EnhancedLog -Message "Successfully installed/updated module: $moduleName" -Level "INFO"
                            $moduleSuccessCount++
                        }
                    } catch {
                        $moduleDetails = [PSCustomObject]@{
                            Name    = $moduleName
                            Version = "N/A"
                            Path    = "N/A"
                        }
                        $failedModules.Add($moduleDetails)
                        Write-EnhancedLog -Message "Failed to install/update module: $moduleName. Error: $_" -Level "ERROR"
                        $moduleFailCount++
                    }
                }
            }

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

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

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

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

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

        if ($successModules.Count -gt 0) {
            Write-Host "Successful Modules:" -ForegroundColor Green
            $successModules | 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' 192
#Region '.\Public\Invoke-AsSystem.ps1' -1

function Invoke-AsSystem {
    <#
    .SYNOPSIS
    Executes a PowerShell script under the SYSTEM context, similar to Intune's execution context.
 
    .DESCRIPTION
    The Invoke-AsSystem function executes a PowerShell script using PsExec64.exe to run under the SYSTEM context. This method is useful for scenarios requiring elevated privileges beyond the current user's capabilities.
 
    .PARAMETER PsExec64Path
    Specifies the full path to PsExec64.exe. If not provided, it assumes PsExec64.exe is in the same directory as the script.
 
    .PARAMETER ScriptPathAsSYSTEM
    Specifies the path to the PowerShell script you want to run as SYSTEM.
 
    .PARAMETER TargetFolder
    Specifies the target folder where PsExec64.exe and other required files will be stored.
 
    .PARAMETER UsePowerShell5
    Specifies whether to always use PowerShell 5 for launching the process. If set to $true, the script will use the PowerShell 5 executable path.
 
    .EXAMPLE
    Invoke-AsSystem -PsExec64Path "C:\Tools\PsExec64.exe" -ScriptPathAsSYSTEM "C:\Scripts\MyScript.ps1" -TargetFolder "C:\ProgramData\SystemScripts" -UsePowerShell5 $true
 
    Executes PowerShell 5 as SYSTEM using PsExec64.exe located at "C:\Tools\PsExec64.exe".
 
    .NOTES
    Ensure PsExec64.exe is available and the script has the necessary permissions to execute it.
 
    .LINK
    https://docs.microsoft.com/en-us/sysinternals/downloads/psexec
    #>


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

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

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

        [Parameter(Mandatory = $false)]
        [bool]$UsePowerShell5 = $false
    )

    begin {
        CheckAndElevate

        # Get the PowerShell executable path
        $pwshPath = if ($UsePowerShell5) {
            "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
        }
        else {
            Get-PowerShellPath
        }

        # Define the command for running PowerShell
        # $commandToRun = "`"$pwshPath`" -NoExit -ExecutionPolicy Bypass -File `"$ScriptPathAsSYSTEM`""
        # $commandToRun = "`"$pwshPath`" -NoProfile -ExecutionPolicy Bypass -NoExit -WindowStyle Hidden -File `"$ScriptPathAsSYSTEM`""
        $commandToRun = "`"$pwshPath`" -NoProfile -ExecutionPolicy Bypass -NoExit -File `"$ScriptPathAsSYSTEM`""

        # Define the arguments for PsExec64.exe to run PowerShell as SYSTEM with the script
        # Define the PsExec arguments in a readable array format
        $argList = @(
            "-accepteula", # Accept the EULA silently
            "-i", # Run interactively
            "-s", # Run as SYSTEM
            "-d", # Don't wait for process completion
            $commandToRun   # Pass the command to run as SYSTEM
        )

        Write-EnhancedLog -Message "Preparing to execute PowerShell as SYSTEM using PsExec64 with the script: $ScriptPathAsSYSTEM" -Level "INFO"

        # Log parameters using splatting
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Download PsExec to the target folder if necessary
        Download-PsExec -targetFolder $TargetFolder
    }

    process {
        try {
            # Ensure PsExec64Path exists
            if (-not (Test-Path -Path $PsExec64Path)) {
                $errorMessage = "PsExec64.exe not found at path: $PsExec64Path"
                Write-EnhancedLog -Message $errorMessage -Level "ERROR"
                throw $errorMessage
            }

            # Splat parameters for Start-Process
            $processParams = @{
                FilePath     = $PsExec64Path
                ArgumentList = $argList
                Wait         = $true
                NoNewWindow  = $true
            }

            # Run PsExec64.exe with the defined arguments to execute the script as SYSTEM
            Write-EnhancedLog -Message "Executing PsExec64.exe to start PowerShell as SYSTEM running script: $ScriptPathAsSYSTEM" -Level "INFO"
            Start-Process @processParams
                        
            Write-EnhancedLog -Message "SYSTEM session started. Closing elevated session..." -Level "INFO"
            exit
        }
        catch {
            Write-EnhancedLog -Message "An error occurred: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }
}
#EndRegion '.\Public\Invoke-AsSystem.ps1' 114
#Region '.\Public\Invoke-BitlockerEscrow.ps1' -1


function Invoke-BitlockerEscrow {
    <#
    .SYNOPSIS
    Escrows the BitLocker recovery key to Azure AD.
 
    .DESCRIPTION
    The Invoke-BitlockerEscrow function escrows the BitLocker recovery key for the specified drive to Azure AD.
 
    .PARAMETER BitlockerDrive
    The drive letter of the BitLocker protected drive.
 
    .PARAMETER BitlockerKey
    The key protector ID to be escrowed.
 
    .EXAMPLE
    Invoke-BitlockerEscrow -BitlockerDrive "C:" -BitlockerKey "12345678-1234-1234-1234-123456789012"
    Escrows the BitLocker recovery key for drive C: to Azure AD.
    #>


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

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

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

    Process {
        try {
            Write-EnhancedLog -Message "Escrowing the BitLocker recovery key to Azure AD for drive: $BitlockerDrive" -Level "INFO"
            BackupToAAD-BitLockerKeyProtector -MountPoint $BitlockerDrive -KeyProtectorId $BitlockerKey -ErrorAction SilentlyContinue
            Write-EnhancedLog -Message "Attempted to escrow key in Azure AD - Please verify manually!" -Level "INFO"
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while escrowing the BitLocker key to Azure AD" -Level "ERROR"
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Invoke-BitlockerEscrow function" -Level "Notice"
    }
}
#EndRegion '.\Public\Invoke-BitlockerEscrow.ps1' 51
#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-EnhancedGraphAORequest.ps1' -1

function Invoke-EnhancedGraphAORequest {
    param (
        [string]$Method,
        [string]$Uri,
        [string]$AccessToken,
        [string]$Body = $null
    )

    $httpClient = New-Object System.Net.Http.HttpClient
    $httpClient.DefaultRequestHeaders.Authorization = New-Object System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", $AccessToken)

    try {
        if ($Body) {
            $content = New-Object System.Net.Http.StringContent($Body, [System.Text.Encoding]::UTF8, "application/json")
        }

        $response = $null
        switch ($Method) {
            "GET" { $response = $httpClient.GetAsync($Uri).Result }
            "POST" { $response = $httpClient.PostAsync($Uri, $content).Result }
            "PATCH" { $response = $httpClient.PatchAsync($Uri, $content).Result }
            "DELETE" { $response = $httpClient.DeleteAsync($Uri).Result }
        }

        if (-not $response) {
            throw "No response received from the server."
        }

        $responseContent = $response.Content.ReadAsStringAsync().Result
        $responseStatus = $response.IsSuccessStatusCode

        # Define the directory for response JSON files
        $responseDir = Join-Path -Path $PSScriptRoot -ChildPath "responses"
        if (-not (Test-Path -Path $responseDir)) {
            New-Item -ItemType Directory -Path $responseDir
        }

        # Log the full request and response in JSON format
        $logEntry = @{
            RequestUri    = $Uri
            RequestMethod = $Method
            RequestBody   = $Body
            Response      = $responseContent
            IsSuccess     = $responseStatus
            TimeStamp     = Get-Date -Format "yyyyMMddHHmmssfff"
        }

        $logFile = Join-Path -Path $responseDir -ChildPath ("Response_$($logEntry.TimeStamp).json")
        $logEntry | ConvertTo-Json | Set-Content -Path $logFile

        Write-EnhancedLog -Message "Response logged to $logFile" -Level "INFO"

        if ($response.IsSuccessStatusCode) {
            Write-EnhancedLog -Message "Successfully executed $Method request to $Uri." -Level "INFO"
            return $responseContent
        }
        else {
            $errorContent = $responseContent
            Write-EnhancedLog -Message "HTTP request failed with status code: $($response.StatusCode). Error content: $errorContent" -Level "ERROR"
            return $null
        }
    }
    catch {
        Write-EnhancedLog -Message "Failed to execute $Method request to $Uri $_" -Level "ERROR"
        return $null
    }
    finally {
        $httpClient.Dispose()
    }
}
#EndRegion '.\Public\Invoke-EnhancedGraphAORequest.ps1' 71
#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-PrinterInstallation.ps1' -1

function Invoke-PrinterInstallation {

    <#
.SYNOPSIS
Installs or uninstalls printer drivers based on JSON configuration files.
 
.DESCRIPTION
This PowerShell function reads printer installation settings from a specified printer configuration JSON file (printer.json) and application configuration JSON file (config.json). It constructs and optionally executes command lines for installing or uninstalling printer drivers. The function proceeds only if the 'PrinterInstall' attribute in the application configuration is set to true.
 
.PARAMETER PrinterConfigPath
The full file path to the printer configuration JSON file (printer.json). This file contains the printer settings such as PrinterName, PrinterIPAddress, PortName, DriverName, InfPathRelative, InfFileName, and DriverIdentifier.
 
.PARAMETER AppConfigPath
The full file path to the application configuration JSON file (config.json). This file contains application-wide settings including the 'PrinterInstall' flag that controls whether the installation or uninstallation should proceed.
 
.EXAMPLE
.\Invoke-PrinterInstallation -PrinterConfigPath "d:\path\to\printer.json" -AppConfigPath "d:\path\to\config.json"
 
Executes the Invoke-PrinterInstallation function using the specified printer and application configuration files. It constructs and displays the install and uninstall commands based on the configurations.
 
.INPUTS
None. You cannot pipe objects to Invoke-PrinterInstallation.
 
.OUTPUTS
String. Outputs the constructed install and uninstall commands to the console.
 
.NOTES
Version: 1.0
Author: Your Name
Creation Date: The Date
Purpose/Change: Initial function development
 
.LINK
URL to more information if available
 
#>


    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$PrinterConfigPath, # Path to printer.json

        [Parameter(Mandatory = $true)]
        [string]$AppConfigPath  # Path to config.json
    )

    Begin {
        Write-EnhancedLog -Message "Starting Invoke-PrinterInstallation" -Level "INFO" -ForegroundColor Green
    }

    Process {
        try {
            if (-not (Test-Path -Path $PrinterConfigPath)) {
                Write-EnhancedLog -Message "Printer configuration file not found at path: $PrinterConfigPath" -Level "ERROR" -ForegroundColor Red
                throw "Printer configuration file not found."
            }

            if (-not (Test-Path -Path $AppConfigPath)) {
                Write-EnhancedLog -Message "Application configuration file not found at path: $AppConfigPath" -Level "ERROR" -ForegroundColor Red
                throw "Application configuration file not found."
            }

            $appConfig = Get-Content -Path $AppConfigPath -Raw | ConvertFrom-Json

            if ($appConfig.PrinterInstall -eq $true) {
                $printerConfig = Get-Content -Path $PrinterConfigPath -Raw | ConvertFrom-Json

                $InstallCommandLine = "%SystemRoot%\sysnative\WindowsPowerShell\v1.0\powershell.exe -windowstyle hidden -executionpolicy bypass -File ""install.ps1"""
                $UninstallCommandLine = "%SystemRoot%\sysnative\WindowsPowerShell\v1.0\powershell.exe -windowstyle hidden -executionpolicy bypass -File ""uninstall.ps1"""

                $printerConfig.psobject.Properties | ForEach-Object {
                    $InstallCommandLine += " -$($_.Name) `"$($_.Value)`""
                    $UninstallCommandLine += " -$($_.Name) `"$($_.Value)`""
                }

                Write-EnhancedLog -Message "Install and Uninstall command lines constructed successfully" -Level "VERBOSE" -ForegroundColor Cyan

                # Return a custom object containing both commands
                $commands = [PSCustomObject]@{
                    InstallCommand   = $InstallCommandLine
                    UninstallCommand = $UninstallCommandLine
                }

                return $commands

            }
            else {
                Write-EnhancedLog -Message "PrinterInstall is not set to true in the application configuration. No commands will be executed." -Level "WARNING" -ForegroundColor Yellow
            }

        }
        catch {
            Write-EnhancedLog -Message "An error occurred: $_" -Level "ERROR" -ForegroundColor Red
        }
    }

    End {
        Write-EnhancedLog -Message "Invoke-PrinterInstallation completed" -Level "INFO" -ForegroundColor Green
    }
}


# # Define paths to the configuration files
# $printerConfigPath = Join-Path -Path $PSScriptRoot -ChildPath "printer.json"
# $appConfigPath = Join-Path -Path $PSScriptRoot -ChildPath "config.json"

# Invoke-PrinterInstallation -PrinterConfigPath $printerConfigPath -AppConfigPath $appConfigPath
#EndRegion '.\Public\Invoke-PrinterInstallation.ps1' 108
#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-Uninstall.ps1' -1

function Invoke-Uninstall {
    [CmdletBinding()]
    param (
        [string]$ProductId,
        [string]$FilePath,
        [string]$ArgumentTemplate
    )

    begin {
        Write-EnhancedLog -Message 'Starting Invoke-Uninstall function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            Write-EnhancedLog -Message 'Starting uninstallation process.' -Level 'INFO'

            # Ensure the ProductId is wrapped in curly braces
            $wrappedProductId = "{$ProductId}"
            # Construct the argument list using the template
            $arguments = $ArgumentTemplate -replace '{ProductId}', $wrappedProductId

            Write-EnhancedLog -Message "FilePath: $FilePath" -Level 'INFO'
            Write-EnhancedLog -Message "Arguments: $arguments" -Level 'INFO'

            Start-Process -FilePath $FilePath -ArgumentList $arguments -Wait -WindowStyle Hidden

            Write-EnhancedLog -Message "Executed uninstallation with arguments: $arguments" -Level 'INFO'
        } catch {
            Write-EnhancedLog -Message "An error occurred during the uninstallation process: $($_.Exception.Message)" -Level 'ERROR'
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'Invoke-Uninstall function completed' -Level 'INFO'
    }
}

# # Example usage of Invoke-Uninstall function with splatting
# $params = @{
# ProductId = '0DC51760-4FB7-41F3-8967-D3DEC9D320EB'
# FilePath = 'MsiExec.exe'
# ArgumentTemplate = "/X{ProductId} /quiet /norestart"
# }
# Invoke-Uninstall @params
#EndRegion '.\Public\Invoke-Uninstall.ps1' 47
#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\Kill-LockingProcesses.ps1' -1

function Kill-LockingProcesses {
    <#
    .SYNOPSIS
    Kills processes that are locking a specified file.
 
    .DESCRIPTION
    The Kill-LockingProcesses function finds processes that are locking a specified file and terminates them forcefully.
 
    .PARAMETER LockedFile
    The path to the locked file.
 
    .EXAMPLE
    Kill-LockingProcesses -LockedFile "C:\Path\To\LockedFile.txt"
    Finds and kills processes locking the specified file.
 
    .NOTES
    This function relies on the Find-LockingProcesses function to identify locking processes.
    #>


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

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

    Process {
        try {
            Write-EnhancedLog -Message "Finding processes locking file: $LockedFile" -Level "INFO"
            $lockingProcesses = Find-LockingProcesses -LockedFile $LockedFile

            if ($lockingProcesses) {
                foreach ($process in $lockingProcesses) {
                    Write-EnhancedLog -Message "Killing process $($process.ProcessName) (ID: $($process.Id)) locking the file $LockedFile" -Level "INFO"
                    Stop-Process -Id $process.Id -Force -Confirm:$false
                }
            } else {
                Write-EnhancedLog -Message "No processes found locking the file: $LockedFile" -Level "INFO"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while killing locking processes: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Kill-LockingProcesses function" -Level "Notice"
    }
}

# Example usage
# Kill-LockingProcesses -LockedFile "C:\Path\To\LockedFile.txt"
#EndRegion '.\Public\Kill-LockingProcesses.ps1' 58
#Region '.\Public\Leave-Domain.ps1' -1


function Leave-Domain {
    <#
        .SYNOPSIS
            Removes the computer from the domain.
         
        .DESCRIPTION
            This function attempts to remove the computer from the domain using provided credentials. If domain credentials fail, it falls back to using local credentials.
         
        .PARAMETER DomainLeaveUser
            The domain user account to use for leaving the domain.
         
        .PARAMETER DomainLeavePassword
            The password for the domain user account.
         
        .PARAMETER ComputerName
            The name of the computer to remove from the domain.
         
        .PARAMETER TempUser
            The temporary local user to use if domain credentials fail.
         
        .PARAMETER TempUserPassword
            The password for the temporary local user.
         
        .EXAMPLE
            Leave-Domain -DomainLeaveUser "AdminUser" -DomainLeavePassword "P@ssw0rd" -ComputerName "localhost" -TempUser "LocalAdmin" -TempUserPassword "P@ssw0rd"
         
        .NOTES
            This function removes the computer from the domain, using domain credentials if possible.
        #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]$DomainLeaveUser,
        
        [Parameter(Mandatory = $false)]
        [string]$DomainLeavePassword,
        
        [Parameter(Mandatory = $true)]
        [string]$ComputerName,
        
        [Parameter(Mandatory = $true)]
        [string]$TempUser,
        
        [Parameter(Mandatory = $true)]
        [string]$TempUserPassword
    )
        
    Begin {
        Write-EnhancedLog -Message "Starting Leave-Domain function" -Level "NOTICE"
    }
        
    Process {
        if ($DomainLeaveUser) {
            $SecurePassword = ConvertTo-SecureString -String $DomainLeavePassword -AsPlainText -Force
            $Credentials = New-Object System.Management.Automation.PSCredential($DomainLeaveUser, $SecurePassword)
        
            try {
                Remove-Computer -ComputerName $ComputerName -Credential $Credentials -Verbose -Force -ErrorAction Stop
            }
            catch {
                Write-EnhancedLog -Message "Leaving domain with domain credentials failed. Will leave domain with local account." -Level "ERROR"
                # Fallback to local user
                $SecurePassword = ConvertTo-SecureString -String $TempUserPassword -AsPlainText -Force
                $Credentials = New-Object System.Management.Automation.PSCredential($TempUser, $SecurePassword)
                Remove-Computer -ComputerName $ComputerName -Credential $Credentials -Verbose -Force
            }
        }
    }
        
    End {
        Write-EnhancedLog -Message "Exiting Leave-Domain function" -Level "NOTICE"
    }
}
#EndRegion '.\Public\Leave-Domain.ps1' 75
#Region '.\Public\Load-Certificate.ps1' -1

function Load-Certificate {
    param (
        [Parameter(Mandatory = $true)]
        [string]$CertPath,

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

    try {
        Write-EnhancedLog -Message "Attempting to load certificate from path: $CertPath" -Level "INFO"

        # Validate certificate path before loading
        $certExistsBefore = Validate-Certificate -CertPath $CertPath
        if (-not $certExistsBefore) {
            throw "Certificate path does not exist: $CertPath"
        }

        # Check the OS and convert the certificate path if running on Linux
        if ($PSVersionTable.PSVersion.Major -ge 7) {
            if ($PSVersionTable.Platform -eq 'Unix') {
                $CertPath = Convert-WindowsPathToLinuxPath -WindowsPath $CertPath
            }
        } else {
            $os = [System.Environment]::OSVersion.Platform
            if ($os -eq [System.PlatformID]::Unix) {
                $CertPath = Convert-WindowsPathToLinuxPath -WindowsPath $CertPath
            }
        }

        # Load the certificate directly from the file
        $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($CertPath, $CertPassword)
        
        Write-EnhancedLog -Message "Successfully loaded certificate from path: $CertPath" -Level "INFO"

        # Validate certificate path after loading
        $certExistsAfter = Validate-Certificate -CertPath $CertPath
        if ($certExistsAfter) {
            Write-EnhancedLog -Message "Certificate path still exists after loading: $CertPath" -Level "INFO"
        } else {
            Write-EnhancedLog -Message "Certificate path does not exist after loading: $CertPath" -Level "WARNING"
        }

        return $cert
    }
    catch {
        Write-EnhancedLog -Message "Error loading certificate from path: $CertPath. Error: $_" -Level "ERROR"
        throw $_
    }
}
#EndRegion '.\Public\Load-Certificate.ps1' 51
#Region '.\Public\Load-SignInLogs.ps1' -1

function Load-SignInLogs {
    param (
        [Parameter(Mandatory = $true)]
        [string]$JsonFilePath
    )

    $signInLogs = [System.Collections.Generic.List[PSCustomObject]]::new()
    Write-EnhancedLog -Message "Opening file: $JsonFilePath" -Level 'Debug'
    $fileStream = [System.IO.FileStream]::new($JsonFilePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read, 4096, [System.IO.FileOptions]::SequentialScan)

    try {
        $jsonDoc = [System.Text.Json.JsonDocument]::Parse($fileStream)

        foreach ($element in $jsonDoc.RootElement.EnumerateArray()) {
            $deviceDetail = [PSCustomObject]@{
                DeviceId       = $element.GetProperty("deviceDetail").GetProperty("deviceId").GetString()
                DisplayName    = $element.GetProperty("deviceDetail").GetProperty("displayName").GetString()
                OperatingSystem = $element.GetProperty("deviceDetail").GetProperty("operatingSystem").GetString()
                IsCompliant    = $element.GetProperty("deviceDetail").GetProperty("isCompliant").GetBoolean()
                TrustType      = $element.GetProperty("deviceDetail").GetProperty("trustType").GetString()
            }

            $signInLog = [PSCustomObject]@{
                UserDisplayName = $element.GetProperty("userDisplayName").GetString()
                UserId          = $element.GetProperty("userId").GetString()
                DeviceDetail    = $deviceDetail
            }

            $signInLogs.Add($signInLog)
        }

        Write-EnhancedLog -Message "Sign-in logs loaded successfully from $JsonFilePath." -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
    } catch {
        Handle-Error -ErrorRecord $_
    } finally {
        $fileStream.Dispose()
    }

    return $signInLogs
}

# # Example usage
# $jsonFilePath = "path_to_your_json_file.json"
# $signInLogs = Load-SignInLogs -JsonFilePath $jsonFilePath
# Write-Output "Sign-In Logs: $($signInLogs | Format-Table -AutoSize)"

#EndRegion '.\Public\Load-SignInLogs.ps1' 47
#Region '.\Public\Log-And-Execute-Step.ps1' -1

function Log-And-Execute-Step {
    [CmdletBinding()]
    param ()

    Begin {
        Write-EnhancedLog -Message "Starting Log-And-Execute-Step function" -Level "INFO"
    }

    Process {
        try {
            $global:currentStep++
            $totalSteps = $global:steps.Count
            $step = $global:steps[$global:currentStep - 1]
            Write-EnhancedLog -Message "Step [$global:currentStep/$totalSteps]: $($step.Description)" -Level "INFO"
            
            & $step.Action
        } catch {
            Write-EnhancedLog -Message "Error in step: $($step.Description) - $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Log-And-Execute-Step function" -Level "INFO"
    }
}

# Example usage
# Log-And-Execute-Step
#EndRegion '.\Public\Log-And-Execute-Step.ps1' 30
#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\Main-MigrateToAADJOnly.ps1' -1

function Main-MigrateToAADJOnly {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$PPKGPath,
        
        [Parameter(Mandatory = $false)]
        [string]$DomainLeaveUser,
        
        [Parameter(Mandatory = $false)]
        [string]$DomainLeavePassword,
        
        [Parameter(Mandatory = $true)]
        [string]$TempUser,
        
        [Parameter(Mandatory = $true)]
        [string]$TempUserPassword,
        
        [Parameter(Mandatory = $true)]
        [string]$ScriptPath
    )

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

    Process {
        try {
            # Test provisioning package
            # $TestProvisioningPackParams = @{
            # PPKGName = $PPKGName
            # }
            # Test-ProvisioningPack @TestProvisioningPackParams

            # Validate PPKG file exists
            if (-not (Test-Path -Path $PPKGPath)) {
                throw "Provisioning package file not found: $PPKGPath"
            }


            # Extract just the file name from the PPKG path (without extension)
            $ppkgFileName = [System.IO.Path]::GetFileNameWithoutExtension($PPKGPath)

            # Check if the PPKG is already installed
            Write-EnhancedLog -Message "Validating if provisioning package is already installed." -Level "INFO"
            $isInstalled = Validate-PPKGInstallation -PPKGName $ppkgFileName
  

            if ($isInstalled) {
                Remove-InstalledPPKG -PackageName $ppkgFileName
            }

            # Add local user
            $AddLocalUserParams = @{
                TempUser         = $TempUser
                TempUserPassword = $TempUserPassword
                Description      = "account for autologin"
                Group            = "Administrators"
            }
            Add-LocalUser @AddLocalUserParams

            # Wait-Debugger

            # Set autologin
            $SetAutologinParams = @{
                TempUser            = $TempUser
                TempUserPassword    = $TempUserPassword
                RegPath             = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
                AutoAdminLogonName  = 'AutoAdminLogon'
                AutoAdminLogonValue = '1'
                DefaultUsernameName = 'DefaultUsername'
                DefaultPasswordName = 'DefaultPassword'
            }
            Set-Autologin @SetAutologinParams

            # Disable OOBE privacy
            $DisableOOBEPrivacyParams = @{
                OOBERegistryPath      = 'HKLM:\Software\Policies\Microsoft\Windows\OOBE'
                OOBEName              = 'DisablePrivacyExperience'
                OOBEValue             = '1'
                AnimationRegistryPath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
                AnimationName         = 'EnableFirstLogonAnimation'
                AnimationValue        = '0'
                LockRegistryPath      = 'HKLM:\Software\Policies\Microsoft\Windows\Personalization'
                LockName              = 'NoLockScreen'
                LockValue             = '1'
            }
            Disable-OOBEPrivacy @DisableOOBEPrivacyParams

            # Set RunOnce script
            $SetRunOnceParams = @{
                ScriptPath      = $ScriptPath
                RunOnceKey      = "HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce"
                PowershellPath  = "C:\Windows\System32\WindowsPowerShell\v1.0\Powershell.exe"
                ExecutionPolicy = "Unrestricted"
                RunOnceName     = "NextRun"
            }
            Set-RunOnce @SetRunOnceParams

            # Suspend BitLocker with reboot count
            $SuspendBitLockerWithRebootParams = @{
                MountPoint  = "C:"
                RebootCount = 3
            }
            Suspend-BitLockerWithReboot @SuspendBitLockerWithRebootParams

            # Remove Intune management
            $RemoveIntuneMgmtParams = @{
                OMADMPath             = "HKLM:\SOFTWARE\Microsoft\Provisioning\OMADM\Accounts\*"
                EnrollmentBasePath    = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments"
                TrackedBasePath       = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\EnterpriseResourceManager\Tracked"
                PolicyManagerBasePath = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PolicyManager"
                ProvisioningBasePath  = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning"
                CertCurrentUserPath   = "cert:\CurrentUser"
                CertLocalMachinePath  = "cert:\LocalMachine"
                TaskPathBase          = "\Microsoft\Windows\EnterpriseMgmt"
                MSDMProviderID        = "MS DM Server"
                RegistryPathsToRemove = @(
                    "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments",
                    "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments\Status",
                    "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\EnterpriseResourceManager\Tracked",
                    "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PolicyManager\AdmxInstalled",
                    "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PolicyManager\Providers",
                    "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning\OMADM\Accounts",
                    "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning\OMADM\Logger",
                    "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning\OMADM\Sessions"
                )
                UserCertIssuer        = "CN=SC_Online_Issuing"
                DeviceCertIssuers     = @("CN=Microsoft Intune Root Certification Authority", "CN=Microsoft Intune MDM Device CA")
            }
            Remove-IntuneMgmt @RemoveIntuneMgmtParams



            Perform-IntuneCleanup


            # Remove hybrid join
            Remove-Hybrid

            # Remove AD join
            $RemoveADJoinParams = @{
                TempUser         = $TempUser
                TempUserPassword = $TempUserPassword
                ComputerName     = "localhost"
            }
            Remove-ADJoin @RemoveADJoinParams

            # Restart-ComputerIfNeeded

        }
        catch {
            Write-EnhancedLog -Message "An error occurred: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Main-MigrateToAADJOnly function" -Level "Notice"
    }
}


# $MainMigrateParams = @{
# PPKGName = "YourProvisioningPackName"
# DomainLeaveUser = "YourDomainUser"
# DomainLeavePassword = "YourDomainPassword"
# TempUser = "YourTempUser"
# TempUserPassword = "YourTempUserPassword"
# ScriptPath = "C:\ProgramData\AADMigration\Scripts\PostRunOnce.ps1"
# }

# Main-MigrateToAADJOnly @MainMigrateParams
#EndRegion '.\Public\Main-MigrateToAADJOnly.ps1' 175
#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\Manage-LocalUserAccounts.ps1' -1

function Manage-LocalUserAccounts {
    <#
    .SYNOPSIS
    Manages local user accounts by enabling them in Dev mode or disabling them in Prod mode.
   
    .DESCRIPTION
    The Manage-LocalUserAccounts function enables all local user accounts in Dev mode and disables them in Prod mode.
   
    .PARAMETER Mode
    Specifies the mode in which the script should run. Options are "Dev" for development mode or "Prod" for production mode.
   
    .EXAMPLE
    Manage-LocalUserAccounts -Mode Dev
    Enables all local user accounts in Dev mode.
   
    .EXAMPLE
    Manage-LocalUserAccounts -Mode Prod
    Disables all local user accounts in Prod mode.
    #>

  
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateSet("Dev", "Prod")]
        [string]$Mode
    )
  
    Begin {
        Write-EnhancedLog -Message "Starting Manage-LocalUserAccounts function in $Mode mode" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }
  
    Process {
        try {
            if ($Mode -eq "Dev") {
                # Enable local user accounts in Dev mode
                Write-EnhancedLog -Message "Enabling local user accounts in $Mode mode" -Level "INFO"
                Enable-LocalUserAccounts
                Write-EnhancedLog -Message "Local user accounts enabled in $Mode mode" -Level "INFO"
            }
            elseif ($Mode -eq "Prod") {
                # Disable local user accounts in Prod mode
                Write-EnhancedLog -Message "Disabling local user accounts in $Mode mode" -Level "INFO"
                Disable-LocalUserAccounts
                Write-EnhancedLog -Message "Local user accounts disabled in $Mode mode" -Level "INFO"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Manage-LocalUserAccounts function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }
  
    End {
        Write-EnhancedLog -Message "Exiting Manage-LocalUserAccounts function" -Level "Notice"
    }
}

# Example usage
# Manage-LocalUserAccounts -Mode Dev
# Manage-LocalUserAccounts -Mode Prod
#EndRegion '.\Public\Manage-LocalUserAccounts.ps1' 63
#Region '.\Public\Manage-NetworkAdapters.ps1' -1

function Manage-NetworkAdapters {
    <#
                .SYNOPSIS
                    Manages the state of network adapters.
                 
                .DESCRIPTION
                    This function disables or enables all connected network adapters based on the provided parameter.
                 
                .PARAMETER Disable
                    Switch parameter to disable network adapters. If not provided, the function will enable the adapters.
                 
                .EXAMPLE
                    Manage-NetworkAdapters -Disable
                 
                .EXAMPLE
                    Manage-NetworkAdapters
                 
                .NOTES
                    This function is useful for temporarily disabling network connectivity during specific operations.
                #>

    [CmdletBinding()]
    param (
        [switch]$Disable
    )
                
    Begin {
        Write-EnhancedLog -Message "Starting Manage-NetworkAdapters function" -Level "NOTICE"
    }
                
    Process {
        $ConnectedAdapters = Get-NetAdapter | Where-Object { $_.MediaConnectionState -eq "Connected" }
                
        foreach ($Adapter in $ConnectedAdapters) {
            if ($Disable) {
                Write-EnhancedLog -Message "Disabling network adapter $($Adapter.Name)" -Level "INFO"
                Disable-NetAdapter -Name $Adapter.Name -Confirm:$false
            }
            else {
                Write-EnhancedLog -Message "Enabling network adapter $($Adapter.Name)" -Level "INFO"
                Enable-NetAdapter -Name $Adapter.Name -Confirm:$false
            }
        }
    }
                
    End {
        Write-EnhancedLog -Message "Exiting Manage-NetworkAdapters function" -Level "NOTICE"
    }
}
#EndRegion '.\Public\Manage-NetworkAdapters.ps1' 49
#Region '.\Public\New-And-ValidateVPNConnection.ps1' -1

function New-And-ValidateVPNConnection {
    param (
        [Parameter(Mandatory = $true)]
        [string]$VPNConnectionName,
        [Parameter(Mandatory = $true)]
        [string]$VPNServerAddress
    )

    try {
        # Create the VPN connection
        New-VPNConnection -ConnectionName $VPNConnectionName -ServerAddress $VPNServerAddress -TunnelType 'Pptp'

        # Validate VPN connection
        if (Test-VPNConnection -ConnectionName $VPNConnectionName) {
            Write-EnhancedLog -Message "VPN connection '$VPNConnectionName' is ready for use." -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
        }
        else {
            Write-EnhancedLog -Message "VPN connection '$VPNConnectionName' validation failed." -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
        }
    }
    catch {
        Write-EnhancedLog -Message "An error occurred: $_" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
    }
}

# # Example usage
# $vpnConnectionName = "MyVPNConnection"
# $vpnServerAddress = "vpn.example.com"
# New-And-ValidateVPNConnection -VPNConnectionName $vpnConnectionName -VPNServerAddress $vpnServerAddress
#EndRegion '.\Public\New-And-ValidateVPNConnection.ps1' 30
#Region '.\Public\New-CustomVMWithDifferencingDisk.ps1' -1

function New-CustomVMWithDifferencingDisk {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$VMName,

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

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

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

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

        [Parameter(Mandatory = $true)]
        [int64]$MemoryStartupBytes,

        [Parameter(Mandatory = $true)]
        [int64]$MemoryMinimumBytes,

        [Parameter(Mandatory = $true)]
        [int64]$MemoryMaximumBytes,

        [Parameter(Mandatory = $true)]
        [int]$Generation
    )

    Begin {
        Write-EnhancedLog -Message "Starting New-CustomVMWithDifferencingDisk function" -Level "INFO"
        Log-Params -Params @{
            VMName               = $VMName
            VMFullPath           = $VMFullPath
            ParentVHDPath        = $ParentVHDPath
            DifferencingDiskPath = $DifferencingDiskPath
            SwitchName           = $SwitchName
            MemoryStartupBytes   = $MemoryStartupBytes
            MemoryMinimumBytes   = $MemoryMinimumBytes
            MemoryMaximumBytes   = $MemoryMaximumBytes
            Generation           = $Generation
        }
    }

    Process {
        try {
            $NewVMSplat = @{
                Generation         = $Generation
                Path               = $VMFullPath
                Name               = $VMName
                MemoryStartupBytes = $MemoryStartupBytes
                SwitchName         = $SwitchName
                NoVHD              = $true
            }
            New-VM @NewVMSplat
            Write-EnhancedLog -Message "VM $VMName created with specified parameters" -Level "INFO"

            Set-VMMemory -VMName $VMName -DynamicMemoryEnabled $true -MinimumBytes $MemoryMinimumBytes -MaximumBytes $MemoryMaximumBytes -StartupBytes $MemoryStartupBytes
            Write-EnhancedLog -Message "Dynamic memory set for VM $VMName" -Level "INFO"

            New-VHD -Path $DifferencingDiskPath -ParentPath $ParentVHDPath -Differencing
            Write-EnhancedLog -Message "Differencing disk created at $DifferencingDiskPath based on $ParentVHDPath" -Level "INFO"

            Add-VMHardDiskDrive -VMName $VMName -Path $DifferencingDiskPath
            Write-EnhancedLog -Message "Differencing disk added to VM $VMName" -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
        } catch {
            Write-EnhancedLog -Message "An error occurred while creating the VM or its components: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting New-CustomVMWithDifferencingDisk function" -Level "INFO"
    }
}
#EndRegion '.\Public\New-CustomVMWithDifferencingDisk.ps1' 78
#Region '.\Public\New-DeviceDetail.ps1' -1

# Function to create a DeviceDetail object
function New-DeviceDetail {
    param (
        [string] $DeviceId,
        [string] $DisplayName,
        [string] $OperatingSystem,
        [bool] $IsCompliant,
        [string] $TrustType
    )
    [PSCustomObject]@{
        DeviceId        = $DeviceId
        DisplayName     = $DisplayName
        OperatingSystem = $OperatingSystem
        IsCompliant     = $IsCompliant
        TrustType       = $TrustType
    }
}
#EndRegion '.\Public\New-DeviceDetail.ps1' 18
#Region '.\Public\New-DeviceItem.ps1' -1

# Function to create a DeviceItem object
function New-DeviceItem {
    param (
        [string] $DeviceId,
        [string] $UserId,
        [string] $UserDisplayName
    )
    [PSCustomObject]@{
        DeviceId = $DeviceId
        UserId = $UserId
        UserDisplayName = $UserDisplayName
    }
}
#EndRegion '.\Public\New-DeviceItem.ps1' 14
#Region '.\Public\New-ProcessingContext.ps1' -1

# Function to create a ProcessingContext object

function New-ProcessingContext {
    [PSCustomObject]@{
        UniqueDeviceIds = [System.Collections.Generic.HashSet[string]]::new()
        Results = [System.Collections.Generic.List[PSCustomObject]]::new()
        # ProcessedUserDeviceIds = [System.Collections.Generic.Dictionary[string, System.Collections.Generic.HashSet[string]]]::new()
    }
}

#EndRegion '.\Public\New-ProcessingContext.ps1' 11
#Region '.\Public\New-Result.ps1' -1

# Function to create a Result object
function New-Result {
    param (
        [string] $DeviceName,
        [string] $UserName,
        [string] $DeviceEntraID,
        [string] $UserEntraID,
        [string] $DeviceOS,
        [string] $DeviceComplianceStatus,
        [string] $DeviceStateInIntune,
        [string] $TrustType,
        [string] $UserLicense,
        [string] $OSVersion
    )
    [PSCustomObject]@{
        DeviceName             = $DeviceName
        UserName               = $UserName
        DeviceEntraID          = $DeviceEntraID
        UserEntraID            = $UserEntraID
        DeviceOS               = $DeviceOS
        DeviceComplianceStatus = $DeviceComplianceStatus
        DeviceStateInIntune    = $DeviceStateInIntune
        TrustType              = $TrustType
        UserLicense            = $UserLicense
        OSVersion              = $OSVersion
    }
}
#EndRegion '.\Public\New-Result.ps1' 28
#Region '.\Public\New-SharePointFolder.ps1' -1

function New-SharePointFolder {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$DocumentDriveId,

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

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

        [Parameter(Mandatory = $true)]
        [hashtable]$Headers
    )

    try {
        # Check if the folder already exists
        $checkUrl = "https://graph.microsoft.com/v1.0/drives/" + $DocumentDriveId + "/root:/" + $ParentFolderPath + ":/children"
        $existingFolders = Invoke-RestMethod -Headers $Headers -Uri $checkUrl -Method GET
        $existingFolder = $existingFolders.value | Where-Object { $_.name -eq $FolderName -and $_.folder }

        if ($existingFolder) {
            Write-EnhancedLog -Message "Folder '$FolderName' already exists in '$ParentFolderPath'. Skipping folder creation." -Level "INFO"
            return $existingFolder
        }
    }
    catch {
        Write-EnhancedLog -Message "Folder '$FolderName' not found in '$ParentFolderPath'. Proceeding with folder creation." -Level "INFO"
    }

    try {
        # If the folder does not exist, create it
        $url = "https://graph.microsoft.com/v1.0/drives/" + $DocumentDriveId + "/root:/" + $ParentFolderPath + ":/children"
        $body = @{
            "@microsoft.graph.conflictBehavior" = "fail"
            "name"                              = $FolderName
            "folder"                            = @{}
        }

        Write-EnhancedLog -Message "Creating folder '$FolderName' in '$ParentFolderPath'..." -Level "INFO"
        $createdFolder = Invoke-RestMethod -Headers $Headers -Uri $url -Body ($body | ConvertTo-Json) -Method POST
        Write-EnhancedLog -Message "Folder created successfully." -Level "INFO"
        return $createdFolder
    }
    catch {
        Write-EnhancedLog -Message "Failed to create folder '$FolderName' in '$ParentFolderPath': $_" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
        throw $_
    }
}

# # Example usage
# $headers = @{
# "Authorization" = "Bearer YOUR_ACCESS_TOKEN"
# "Content-Type" = "application/json"
# }

# $documentDriveId = "your_document_drive_id"
# $parentFolderPath = "your/parent/folder/path"
# $folderName = "NewFolder"

# New-SharePointFolder -DocumentDriveId $documentDriveId -ParentFolderPath $parentFolderPath -FolderName $folderName -Headers $headers
#EndRegion '.\Public\New-SharePointFolder.ps1' 63
#Region '.\Public\New-SignInLog.ps1' -1

# Function to create a SignInLog object
function New-SignInLog {
    param (
        [string] $UserDisplayName,
        [string] $UserId,
        $DeviceDetail
    )
    [PSCustomObject]@{
        UserDisplayName = $UserDisplayName
        UserId = $UserId
        DeviceDetail = $DeviceDetail
    }
}
#EndRegion '.\Public\New-SignInLog.ps1' 14
#Region '.\Public\New-VPNConnection.ps1' -1

function New-VPNConnection {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$ConnectionName,

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

        [Parameter(Mandatory = $true)]
        # [string]$TunnelType = "Pptp" # Default to Pptp, can be changed to Ikev2, L2tp, etc.
        [string]$TunnelType
    )

    try {
        # Validate if VPN connection already exists
        if (Test-VPNConnection -ConnectionName $ConnectionName) {
            Write-EnhancedLog -Message "VPN connection '$ConnectionName' already exists." -Level "INFO"
            return
        }

        # Create the VPN connection
        Add-VpnConnection -Name $ConnectionName -ServerAddress $ServerAddress -TunnelType $TunnelType -AuthenticationMethod MSChapv2 -EncryptionLevel Optional -Force

        # Validate if VPN connection was created successfully
        if (Test-VPNConnection -ConnectionName $ConnectionName) {
            Write-EnhancedLog -Message "VPN connection '$ConnectionName' created successfully." -Level "INFO"
        } else {
            Write-EnhancedLog -Message "Failed to create VPN connection '$ConnectionName'." -Level "ERROR"
            throw "Failed to create VPN connection '$ConnectionName'."
        }
    }
    catch {
        Write-EnhancedLog -Message "An error occurred while creating VPN connection '$ConnectionName': $_" -Level "ERROR"
        throw $_
    }
}
#EndRegion '.\Public\New-VPNConnection.ps1' 38
#Region '.\Public\Open-CertificateStore.ps1' -1

function Open-CertificateStore {
    if (Is-ServerCore) {
        Write-Output "Running on Windows Server Core. Skipping opening of certificate manager."
    } else {
        # Open the certificate store
        Start-Process certmgr.msc
    }
}
#EndRegion '.\Public\Open-CertificateStore.ps1' 9
#Region '.\Public\Output-secrets.ps1' -1

function Output-Secrets {
    param (
        [Parameter(Mandatory = $false)]
        [string]$AppDisplayName,
        
        [Parameter(Mandatory = $false)]
        [string]$ApplicationID,
        
        [Parameter(Mandatory = $false)]
        [string]$Thumbprint,
        
        [Parameter(Mandatory = $false)]
        [string]$TenantID,
        
        [Parameter(Mandatory = $false)]
        [string]$SecretsFile,
        
        [Parameter(Mandatory = $false)]
        [string]$CertPassword,
        
        [Parameter(Mandatory = $false)]
        [string]$CertName,
        
        [Parameter(Mandatory = $false)]
        [string]$TenantName,
        
        [Parameter(Mandatory = $false)]
        [string]$TenantDomainName,
        
        [Parameter(Mandatory = $false)]
        [string]$OutputPath
    )

    try {
        Write-EnhancedLog -Message "Starting to output secrets." -Level "INFO"
        
        $secrets = @{
            AppDisplayName   = $AppDisplayName
            ClientId         = $ApplicationID
            Thumbprint       = $Thumbprint
            TenantID         = $TenantID
            CertPassword     = $CertPassword
            CertName         = $CertName
            TenantName       = $TenantName
            TenantDomainName = $TenantDomainName
            OutputPath       = $OutputPath
        }

        $secrets | ConvertTo-Json | Set-Content -Path $SecretsFile

        Write-EnhancedLog -Message "Secrets have been written to file: $SecretsFile" -Level "INFO"

        Write-Host "================ Secrets ================"
        Write-Host "`$AppDisplayName = $($AppDisplayName)"
        Write-Host "`$ClientId = $($ApplicationID)"
        Write-Host "`$Thumbprint = $($Thumbprint)"
        Write-Host "`$TenantID = $TenantID"
        Write-Host "`$CertPassword = $CertPassword"
        Write-Host "`$CertName = $CertName"
        Write-Host "`$TenantName = $TenantName"
        Write-Host "`$TenantDomainName = $TenantDomainName"
        Write-Host "`$OutputPath = $OutputPath"
        Write-Host "================ Secrets ================"
        Write-Host " SAVE THESE IN A SECURE LOCATION "

        Write-EnhancedLog -Message "Secrets have been output to the console." -Level "INFO"

    } catch {
        Write-EnhancedLog -Message "An error occurred while outputting secrets." -Level "ERROR"
        Handle-Error -ErrorRecord $_
        throw $_
    }
}

# # Example usage
# $params = @{
# AppDisplayName = $app.DisplayName
# ApplicationID = $app.AppId
# TenantID = $tenantDetails.Id
# SecretsFile = $secretsfile
# CertName = $Certname
# Thumbprint = $thumbprint
# CertPassword = $CertPassword
# TenantName = $tenantDetails.DisplayName
# TenantDomainName = $tenantDetails.DomainName
# OutputPath = $certexportDirectory
# }

# Output-Secrets @params
#EndRegion '.\Public\Output-secrets.ps1' 90
#Region '.\Public\Parse-Size.ps1' -1

function Parse-Size {
    <#
    .SYNOPSIS
    Parses a size string and converts it to bytes.
    #>

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

    Begin {
        Write-EnhancedLog -Message "Starting Parse-Size function" -Level "INFO"
        Log-Params -Params @{ Size = $Size }
    }

    Process {
        try {
            Write-EnhancedLog -Message "Parsing size string: $Size" -Level "INFO"
            switch -regex ($Size) {
                '^(\d+)\s*KB$' {
                    $result = [int64]$matches[1] * 1KB
                    Write-EnhancedLog -Message "Parsed size: $Size to $result bytes" -Level "INFO"
                    return $result
                }
                '^(\d+)\s*MB$' {
                    $result = [int64]$matches[1] * 1MB
                    Write-EnhancedLog -Message "Parsed size: $Size to $result bytes" -Level "INFO"
                    return $result
                }
                '^(\d+)\s*GB$' {
                    $result = [int64]$matches[1] * 1GB
                    Write-EnhancedLog -Message "Parsed size: $Size to $result bytes" -Level "INFO"
                    return $result
                }
                '^(\d+)\s*TB$' {
                    $result = [int64]$matches[1] * 1TB
                    Write-EnhancedLog -Message "Parsed size: $Size to $result bytes" -Level "INFO"
                    return $result
                }
                default {
                    Write-EnhancedLog -Message "Invalid size format: $Size" -Level "ERROR"
                    throw "Invalid size format: $Size"
                }
            }
        } catch {
            Write-EnhancedLog -Message "An error occurred while parsing size: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Parse-Size function" -Level "INFO"
    }
}
#EndRegion '.\Public\Parse-Size.ps1' 56
#Region '.\Public\Perform-KFMSync.ps1' -1

function Perform-KFMSync {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$OneDriveExePath,

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

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

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

    Process {
        try {
            Write-EnhancedLog -Message "Performing KFM sync" -Level "INFO"
            $ODProcess = Get-Process -Name OneDrive -ErrorAction SilentlyContinue

            if ($ODProcess) {
                $ODProcess | Stop-Process -Confirm:$false -Force
                Start-Sleep -Seconds 5

                Unregister-ScheduledTaskWithLogging -TaskName $ScheduledTaskName

                $CreateOneDriveRemediationTaskParams = @{
                    OneDriveExePath           = $OneDriveExePath
                    ScheduledTaskName         = $ScheduledTaskName
                    ScheduledTaskDescription  = $ScheduledTaskDescription
                }
                
                Create-OneDriveRemediationTask @CreateOneDriveRemediationTaskParams
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while performing KFM sync: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Perform-KFMSync function" -Level "Notice"
    }
}
#EndRegion '.\Public\Perform-KFMSync.ps1' 50
#Region '.\Public\PostRunOnce-Phase1EntraJoin.ps1' -1

function PostRunOnce-Phase1EntraJoin {
    <#
    .SYNOPSIS
    Starts the migration process by configuring settings, blocking user input, displaying a progress form, installing a provisioning package, and optionally restarting the computer.
 
    .DESCRIPTION
    The PostRunOnce-Phase1EntraJoin function configures migration settings, blocks user input, displays a migration progress form, sets a RunOnce script for post-reboot tasks, installs a provisioning package, and then optionally restarts the computer. The function includes validation of the provisioning package installation using the Get-ProvisioningPackage cmdlet.
 
    .PARAMETER MigrationConfigPath
    The path to the migration configuration file.
 
    .PARAMETER ImagePath
    The path to the image file to be displayed on the migration progress form.
 
    .PARAMETER RunOnceScriptPath
    The path to the PowerShell script to be executed on the next system startup.
 
    .PARAMETER RunOnceKey
    The registry key path for the RunOnce entry.
 
    .PARAMETER PowershellPath
    The path to the PowerShell executable.
 
    .PARAMETER ExecutionPolicy
    The execution policy for running the PowerShell script.
 
    .PARAMETER RunOnceName
    The name of the RunOnce entry.
 
    .PARAMETER RebootAfterInstallation
    A switch parameter that controls whether the computer should be restarted after the provisioning package installation. If not specified, the computer will be restarted by default.
 
    .PARAMETER Mode
    Specifies the mode in which the script should run. Options are "Dev" for development mode or "Prod" for production mode. In Dev mode, certain features are skipped, while in Prod mode, all features are executed.
 
    .EXAMPLE
    $params = @{
        MigrationConfigPath = "C:\ProgramData\AADMigration\scripts\MigrationConfig.psd1"
        ImagePath = "C:\ProgramData\AADMigration\Files\MigrationInProgress.bmp"
        RunOnceScriptPath = "C:\ProgramData\AADMigration\Scripts\PostRunOnce2.ps1"
        RunOnceKey = "HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce"
        PowershellPath = "C:\Windows\System32\WindowsPowerShell\v1.0\Powershell.exe"
        ExecutionPolicy = "Unrestricted"
        RunOnceName = "NextRun"
        Mode = "Dev"
    }
    PostRunOnce-Phase1EntraJoin @params
    Runs the migration process in Dev mode, skipping user input blocking and reboots.
 
    .EXAMPLE
    $params = @{
        MigrationConfigPath = "C:\ProgramData\AADMigration\scripts\MigrationConfig.psd1"
        ImagePath = "C:\ProgramData\AADMigration\Files\MigrationInProgress.bmp"
        RunOnceScriptPath = "C:\ProgramData\AADMigration\Scripts\PostRunOnce2.ps1"
        RunOnceKey = "HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce"
        PowershellPath = "C:\Windows\System32\WindowsPowerShell\v1.0\Powershell.exe"
        ExecutionPolicy = "Unrestricted"
        RunOnceName = "NextRun"
        RebootAfterInstallation = $true
        Mode = "Prod"
    }
    PostRunOnce-Phase1EntraJoin @params
    Runs the migration process in Prod mode, executing all features, including user input blocking and reboots.
    #>


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

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

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

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

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

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

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

        [Parameter(Mandatory = $false)]
        [switch]$RebootAfterInstallation,

        [Parameter(Mandatory = $false)]
        [ValidateSet("Dev", "Prod")]
        [string]$Mode
    )

    Begin {
        Write-EnhancedLog -Message "Starting PostRunOnce-Phase1EntraJoin function in $Mode mode" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Load the migration configuration
            Write-EnhancedLog -Message "Loading migration configuration from $MigrationConfigPath" -Level "INFO"
            $MigrationConfig = Import-PowerShellDataFile -Path $MigrationConfigPath
            $PPKGPath = $MigrationConfig.ProvisioningPack
            $MigrationPath = $MigrationConfig.MigrationPath
            Write-EnhancedLog -Message "Loaded PPKGPath: $PPKGPath, MigrationPath: $MigrationPath" -Level "INFO"

            # Block user input if in Prod mode
            if ($Mode -eq "Prod") {
                Write-EnhancedLog -Message "Blocking user input" -Level "INFO"
                $blockParams = @{
                    Block = $true
                }
                Block-UserInput @blockParams
                Write-EnhancedLog -Message "User input blocked" -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "Skipping user input blocking in Dev mode" -Level "WARNING"
            }

            # Show migration in progress form
            if ($Mode -eq "Prod") {
                Write-EnhancedLog -Message "Displaying migration in progress form with image $ImagePath" -Level "INFO"
                $formParams = @{
                    ImagePath = $ImagePath
                }
                Show-MigrationInProgressForm @formParams
                Write-EnhancedLog -Message "Migration in progress form displayed" -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "Skipping Displaying Migration in Progress form in Dev mode" -Level "WARNING"
            }

            # Set RunOnce script
            Write-EnhancedLog -Message "Setting RunOnce script at $RunOnceKey with script $RunOnceScriptPath" -Level "INFO"
            $runOnceParams = @{
                ScriptPath      = $RunOnceScriptPath
                RunOnceKey      = $RunOnceKey
                PowershellPath  = $PowershellPath
                ExecutionPolicy = $ExecutionPolicy
                RunOnceName     = $RunOnceName
            }
            Set-RunOnce @runOnceParams
            Write-EnhancedLog -Message "RunOnce script set" -Level "INFO"

            # Install provisioning package
            Write-EnhancedLog -Message "Installing provisioning package $PPKGPath from $MigrationPath" -Level "INFO"
            $installParams = @{
                PPKGPath      = $PPKGPath
                # MigrationPath = $MigrationPath
            }
            Install-PPKG @installParams
            Write-EnhancedLog -Message "Provisioning package installation command executed" -Level "INFO"

            # Unblock user input and close form if in Prod mode
            if ($Mode -eq "Prod") {
                Write-EnhancedLog -Message "Unblocking user input and closing migration progress form" -Level "INFO"
                Block-UserInput -Block $false
            }
            else {
                Write-EnhancedLog -Message "Skipping unblocking of user input in Dev mode" -Level "WARNING"
            }

            # Optionally reboot the machine
            if ($RebootAfterInstallation -and $Mode -eq "Prod") {
                Write-EnhancedLog -Message "Rebooting computer after successful migration" -Level "INFO"
                Restart-Computer
            }
            else {
                Write-EnhancedLog -Message "Skipping reboot as per mode or user request" -Level "INFO"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred during the migration process: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting PostRunOnce-Phase1EntraJoin function" -Level "Notice"
    }
}
#EndRegion '.\Public\PostRunOnce-Phase1EntraJoin.ps1' 186
#Region '.\Public\PostRunOnce-Phase2EscrowBitlocker.ps1' -1

function PostRunOnce-Phase2EscrowBitlocker {
    <#
    .SYNOPSIS
    Executes post-run operations for the second phase of the migration process.
 
    .DESCRIPTION
    The PostRunOnce-Phase2EscrowBitlocker function blocks user input, displays a migration in progress form, creates a scheduled task for post-migration cleanup, escrows the BitLocker recovery key, sets various registry values, and optionally restarts the computer. The actions performed vary depending on the specified mode (Dev or Prod).
 
    .PARAMETER ImagePath
    The path to the image file to be displayed on the migration progress form.
 
    .PARAMETER TaskPath
    The path of the task in Task Scheduler.
 
    .PARAMETER TaskName
    The name of the scheduled task.
 
    .PARAMETER BitlockerDrives
    An array of drive letters for the BitLocker protected drives. The BitLocker recovery key for each drive will be escrowed as part of the process.
 
    .PARAMETER RegistrySettings
    A hashtable of registry settings to be applied. The hashtable should be structured with registry paths as keys, and each path should contain another hashtable with the registry value name, type, and data.
 
    .PARAMETER RebootAfterCompletion
    A switch parameter that controls whether the computer should be restarted after completing all post-run operations. If not specified, the computer will be restarted by default.
 
    .PARAMETER Mode
    Specifies the mode in which the script should run. Options are "Dev" for development mode or "Prod" for production mode. In Dev mode, certain features are skipped, while in Prod mode, all features are executed.
 
    .EXAMPLE
    $params = @{
        ImagePath = "C:\ProgramData\AADMigration\Files\MigrationInProgress.bmp"
        TaskPath = "AAD Migration"
        TaskName = "Run Post-migration cleanup"
        ScriptPath = "C:\ProgramData\AADMigration\Scripts\PostRunOnce3.ps1"
        BitlockerDrives = @("C:", "D:")
        RegistrySettings = @{
            "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" = @{
                "AutoAdminLogon" = @{
                    "Type" = "DWORD"
                    "Data" = "0"
                }
            }
            "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" = @{
                "dontdisplaylastusername" = @{
                    "Type" = "DWORD"
                    "Data" = "1"
                }
                "legalnoticecaption" = @{
                    "Type" = "String"
                    "Data" = "Migration Completed"
                }
                "legalnoticetext" = @{
                    "Type" = "String"
                    "Data" = "This PC has been migrated to Azure Active Directory. Please log in to Windows using your email address and password."
                }
            }
        }
        RebootAfterCompletion = $false
        Mode = "Dev"
    }
    PostRunOnce-Phase2EscrowBitlocker @params
    Executes the post-run operations in Dev mode, skipping user input blocking and reboots.
 
    .EXAMPLE
    $params = @{
        ImagePath = "C:\ProgramData\AADMigration\Files\MigrationInProgress.bmp"
        TaskPath = "AAD Migration"
        TaskName = "Run Post-migration cleanup"
        ScriptPath = "C:\ProgramData\AADMigration\Scripts\PostRunOnce3.ps1"
        BitlockerDrives = @("C:", "D:")
        RegistrySettings = @{
            "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" = @{
                "AutoAdminLogon" = @{
                    "Type" = "DWORD"
                    "Data" = "0"
                }
            }
            "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" = @{
                "dontdisplaylastusername" = @{
                    "Type" = "DWORD"
                    "Data" = "1"
                }
                "legalnoticecaption" = @{
                    "Type" = "String"
                    "Data" = "Migration Completed"
                }
                "legalnoticetext" = @{
                    "Type" = "String"
                    "Data" = "This PC has been migrated to Azure Active Directory. Please log in to Windows using your email address and password."
                }
            }
        }
        Mode = "Prod"
    }
    PostRunOnce-Phase2EscrowBitlocker @params
    Executes the post-run operations in Prod mode, including user input blocking and reboots.
    #>


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

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

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

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

        [Parameter(Mandatory = $true)]
        [hashtable]$RegistrySettings,

        [Parameter(Mandatory = $false)]
        [switch]$RebootAfterCompletion,

        [Parameter(Mandatory = $false)]
        [ValidateSet("Dev", "Prod")]
        [string]$Mode = "Prod"
    )

    Begin {
        Write-EnhancedLog -Message "Starting PostRunOnce-Phase2EscrowBitlocker function in $Mode mode" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Block user input if in Prod mode
            if ($Mode -eq "Prod") {
                Write-EnhancedLog -Message "Blocking user input" -Level "INFO"
                $blockParams = @{
                    Block = $true
                }
                Block-UserInput @blockParams
                Write-EnhancedLog -Message "User input blocked" -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "Skipping user input blocking in Dev mode" -Level "WARNING"
            }

        
            # Show migration in progress form
            if ($Mode -eq "Prod") {
                Write-EnhancedLog -Message "Displaying migration in progress form with image $ImagePath" -Level "INFO"
                $formParams = @{
                    ImagePath = $ImagePath
                }
                Show-MigrationInProgressForm @formParams
                Write-EnhancedLog -Message "Migration in progress form displayed" -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "Skipping Displaying Migration in Progress form in Dev mode" -Level "WARNING"
            }


            # Create scheduled task for post-migration cleanup
            Write-EnhancedLog -Message "Creating scheduled task $TaskName at $TaskPath" -Level "INFO"

            # Define the parameters to be splatted
            $CreatePostMigrationCleanupTaskParams = @{
                TaskPath            = "AAD Migration"
                TaskName            = "Run Post migration cleanup"
                ScriptDirectory     = "C:\ProgramData\AADMigration\Scripts"
                ScriptName          = "ExecuteMigrationCleanupTasks.Task.ps1"
                TaskArguments       = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File `"{ScriptPath}`""
                TaskPrincipalUserId = "NT AUTHORITY\SYSTEM"
                TaskRunLevel        = "Highest"
                PowerShellPath      = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
                TaskDescription     = "Run post AAD Migration cleanup"
                TaskTriggerType     = "AtLogOn"  # Trigger type as a parameter
                Delay               = "PT1M"  # Optional delay before starting, set to 1 minute
            }

            # Call the function with the splatted parameters
            Create-PostMigrationCleanupTask @CreatePostMigrationCleanupTaskParams

            Write-EnhancedLog -Message "Scheduled task $TaskName created" -Level "INFO"

            $DBG

            # Escrow BitLocker recovery key for each drive
            foreach ($drive in $BitlockerDrives) {
                Write-EnhancedLog -Message "Escrowing BitLocker key for drive $drive" -Level "INFO"
                $escrowParams = @{
                    DriveLetter = $drive
                }
                Escrow-BitLockerKey @escrowParams
                Write-EnhancedLog -Message "BitLocker key for drive $drive escrowed" -Level "INFO"
            }

            #Set registry values
            Apply-RegistrySettings -RegistrySettings $RegistrySettings

            # Unblock user input and close form if in Prod mode
            if ($Mode -eq "Prod") {
                Write-EnhancedLog -Message "Unblocking user input and closing migration progress form" -Level "INFO"
                Block-UserInput -Block $false
            }
            else {
                Write-EnhancedLog -Message "Skipping unblocking of user input in Dev mode" -Level "WARNING"
            }

            # Optionally reboot the machine
            if ($RebootAfterCompletion -and $Mode -eq "Prod") {
                Write-EnhancedLog -Message "Rebooting computer after successful completion" -Level "INFO"
                Restart-Computer
            }
            else {
                Write-EnhancedLog -Message "Skipping reboot as per mode or user request" -Level "INFO"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in PostRunOnce-Phase2EscrowBitlocker function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting PostRunOnce-Phase2EscrowBitlocker function" -Level "Notice"
    }
}
#EndRegion '.\Public\PostRunOnce-Phase2EscrowBitlocker.ps1' 226
#Region '.\Public\Prepare-AADMigration.ps1' -1

function Prepare-AADMigration {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$MigrationPath,

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

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

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

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

        [Parameter(Mandatory = $true)]
        [bool]$OneDriveKFM,

        [Parameter(Mandatory = $true)]
        [bool]$InstallOneDrive
    )

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

    Process {
        try {
            # Ensure the target directory exists
            if (-not (Test-Path -Path $MigrationPath)) {
                New-Item -Path $MigrationPath -ItemType Directory -Force | Out-Null
            }

            # Copy the entire content of $PSScriptRoot to $MigrationPath

            Stop-ProcessesUsingOneDriveLib -OneDriveLibPath "C:\ProgramData\AADMigration\Files\OneDriveLib.dll"

            # $DBG

            # Remove the ADD migration
            Remove-ScheduledTaskFilesWithLogging -Path $MigrationPath

            # Copy-FilesToPathWithKill -SourcePath $sourcePath1 -DestinationPath $destinationPath1

            # Ensure the destination directory exists
            # if (Test-Path -Path $MigrationPath) {
            # Write-EnhancedLog -Message "Destination directory already exists. Removing: $MigrationPath" -Level "WARNING"
            # Remove-Item -Path $MigrationPath -Recurse -Force
            # Write-EnhancedLog -Message "Destination directory removed: $MigrationPath" -Level "INFO"
            # }

            # Create a new destination directory
            New-Item -Path $MigrationPath -ItemType Directory | Out-Null
            Write-EnhancedLog -Message "New destination directory created: $MigrationPath" -Level "INFO"


            $params = @{
                Source          = $PSScriptbase
                Destination     = $MigrationPath
                Exclude         = ".git"
                RetryCount      = 2
                WaitTime        = 5
                RequiredSpaceGB = 10
            }


            # Execute the function with splatting
            Copy-FilesWithRobocopy @params

            # Verify the copy operation for $PSScriptRoot
            Verify-CopyOperation -SourcePath $PSScriptbase -DestinationPath $MigrationPath
            ####################################################################################
            # Import migration configuration
            $MigrationConfig = Import-LocalizedData -BaseDirectory $ConfigBaseDirectory -FileName $ConfigFileName
            $TenantID = $MigrationConfig.TenantID
            $OneDriveKFM = $MigrationConfig.UseOneDriveKFM
            $InstallOneDrive = $MigrationConfig.InstallOneDrive

            # $DBG

            # Set OneDrive KFM settings if required
            if ($OneDriveKFM) {

                # $TenantID = "YourTenantID"
                $RegistrySettings = @(
                    @{
                        RegValName = "AllowTenantList"
                        RegValType = "STRING"
                        RegValData = $TenantID
                    },
                    @{
                        RegValName = "SilentAccountConfig"
                        RegValType = "DWORD"
                        RegValData = "1"
                    },
                    @{
                        RegValName = "KFMOptInWithWizard"
                        RegValType = "STRING"
                        RegValData = $TenantID
                    },
                    @{
                        RegValName = "KFMSilentOptIn"
                        RegValType = "STRING"
                        RegValData = $TenantID
                    },
                    @{
                        RegValName = "KFMSilentOptInDesktop"
                        RegValType = "DWORD"
                        RegValData = "1"
                    },
                    @{
                        RegValName = "KFMSilentOptInDocuments"
                        RegValType = "DWORD"
                        RegValData = "1"
                    },
                    @{
                        RegValName = "KFMSilentOptInPictures"
                        RegValType = "DWORD"
                        RegValData = "1"
                    }
                )
                
                $SetODKFMRegistrySettingsParams = @{
                    TenantID         = $TenantID
                    RegKeyPath       = "HKLM:\SOFTWARE\Policies\Microsoft\OneDrive"
                    RegistrySettings = $RegistrySettings
                }
                
                Set-ODKFMRegistrySettings @SetODKFMRegistrySettingsParams

            }

            # Install OneDrive if required
            if ($InstallOneDrive) {
                

                # Example usage
                $installParams = @{
                    MigrationPath            = "C:\ProgramData\AADMigration"
                    SoftwareName             = "OneDrive"
                    SetupUri                 = "https://go.microsoft.com/fwlink/?linkid=844652"
                    SetupFile                = "OneDriveSetup.exe"
                    RegKey                   = "HKLM:\SOFTWARE\Microsoft\OneDrive"
                    MinVersion               = [version]"24.146.0721.0003"
                    ExePath                  = "C:\Program Files\Microsoft OneDrive\OneDrive.exe"
                    ScheduledTaskName        = "OneDriveRemediation"
                    ScheduledTaskDescription = "Restart OneDrive to kick off KFM sync"
                    SetupArgumentList        = "/allusers"
                    KFM                      = $true
                    TimestampPrefix          = "OneDriveSetup_"
                }
                
                Install-Software @installParams
            }

            # # Example usage with splatting
            $CreateOneDriveSyncUtilStatusTask = @{
                TaskPath               = "AAD Migration"
                TaskName               = "AADM Get OneDrive Sync Util Status"
                ScriptDirectory        = "C:\ProgramData\AADMigration\Scripts"
                ScriptName             = "Check-ODSyncUtilStatus.Task.ps1"
                TaskArguments          = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -file `"{ScriptPath}`""
                TaskRepetitionDuration = "P1D"
                TaskRepetitionInterval = "PT30M"
                TaskPrincipalGroupId   = "BUILTIN\Users"
                PowerShellPath         = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
                TaskDescription        = "AADM Get OneDrive Sync Util Status"
                AtLogOn                = $true
            }

            Create-OneDriveSyncUtilStatusTask @CreateOneDriveSyncUtilStatusTask


            $RemoveExistingStatusFilesParams = @{
                LogFolder      = "logs"
                StatusFileName = "ODSyncUtilStatus.json"
        
            }
            # Remove existing status files
            Remove-ExistingStatusFiles @RemoveExistingStatusFilesParams


            $taskParams = @{
                TaskPath = "\AAD Migration"
                TaskName = "AADM Get OneDrive Sync Util Status"
            }

            # Trigger OneDrive Sync Status Scheduled Task
            Trigger-ScheduledTask @taskParams

            # Example usage with try-catch mechanism and Write-EnhancedLog
            $AnalyzeOneDriveSyncUtilStatusParams = @{
                LogFolder      = "logs"
                StatusFileName = "ODSyncUtilStatus.json"
                MaxRetries     = 5
                RetryInterval  = 10
            }

            try {
                $result = Analyze-OneDriveSyncUtilStatus @AnalyzeOneDriveSyncUtilStatusParams

                # Example decision-making based on the result
                if ($result.Status -eq "Healthy") {
                    Write-EnhancedLog -Message "OneDrive is healthy, no further action required." -Level "INFO"
                }
                elseif ($result.Status -eq "InProgress") {
                    Write-EnhancedLog -Message "OneDrive is syncing, please wait..." -Level "INFO"
                }
                elseif ($result.Status -eq "Failed") {
                    Write-EnhancedLog -Message "OneDrive has encountered an error, please investigate." -Level "WARNING"
                }
                else {
                    Write-EnhancedLog -Message "OneDrive status is unknown, further analysis required." -Level "NOTICE"
                }
            }
            catch {
                Write-EnhancedLog -Message "An error occurred while analyzing OneDrive status: $($_.Exception.Message)" -Level "ERROR"
                Write-EnhancedLog -Message "Please check if you are logged in to OneDrive and try again." -Level "ERROR"
                Handle-Error -ErrorRecord $_
    
                # Throw to halt the entire script
                throw $_
            }


            #Todo now we have OneDrive installed and running we need to actually start using our OneDrive for Business location on the local machine to copy user specific files into it as part of our On-prem AD to Entra ID migration prep so we need to copy the following PR4B projects from before

            # 1- copy Outlook Signatures
            # 2- copy Downloads folders
            # any other user specific files

            $CreateUserFileBackupTaskParams = @{
                TaskPath               = "AAD Migration"
                TaskName               = "User File Backup to OneDrive"
                ScriptDirectory        = "C:\ProgramData\AADMigration\Scripts"
                ScriptName             = "BackupUserFiles.Task.ps1"
                TaskArguments          = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -file `"{ScriptPath}`""
                TaskRepetitionDuration = "P1D"
                TaskRepetitionInterval = "PT30M"
                TaskPrincipalGroupId   = "BUILTIN\Users"
                PowerShellPath         = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
                TaskDescription        = "User File Backup to OneDrive"
                AtLogOn                = $true
            }
            
            Create-UserFileBackupTask @CreateUserFileBackupTaskParams


            $RemoveExistingStatusFilesParams = @{
                LogFolder      = "logs"
                StatusFileName = "UserFilesBackupStatus.json"
            }
            # Remove existing status files
            Remove-ExistingStatusFiles @RemoveExistingStatusFilesParams


            $TriggerScheduledTaskParams = @{
                TaskPath = "\AAD Migration"
                TaskName = "User File Backup to OneDrive"
            }

            # Call the function with splatting
            Trigger-ScheduledTask @TriggerScheduledTaskParams

            # Define the parameters for splatting
            $AnalyzeParams = @{
                LogFolder      = "logs"
                StatusFileName = "UserFilesBackupStatus.json"
                MaxRetries     = 5
                RetryInterval  = 10
            }

            # Call the Analyze-CopyOperationStatus function using splatting
            Analyze-CopyOperationStatus @AnalyzeParams

        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Prepare-AADMigration: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Prepare-AADMigration function" -Level "Notice"
    }
}

# # Define parameters
# $PrepareAADMigrationParams = @{
# MigrationPath = "C:\ProgramData\AADMigration"
# PSScriptRoot = "C:\SourcePath"
# ConfigBaseDirectory = "C:\ConfigDirectory\Scripts"
# ConfigFileName = "MigrationConfig.psd1"
# TenantID = "YourTenantID"
# OneDriveKFM = $true
# InstallOneDrive = $true
# }

# # Example usage with splatting
# Prepare-AADMigration @PrepareAADMigrationParams
#EndRegion '.\Public\Prepare-AADMigration.ps1' 306
#Region '.\Public\Prepare-Paths.ps1' -1

function Prepare-Paths {
    <#
    .SYNOPSIS
    Prepares the necessary paths for Win32 app deployment.
 
    .DESCRIPTION
    This function checks for and creates the required directories for a given program. It returns the destination path as part of an object.
 
    .PARAMETER Prg
    The program object containing metadata like the name of the program.
 
    .PARAMETER Prg_Path
    The source path where the program files are located.
 
    .PARAMETER Win32AppsRootPath
    The root path for storing the Win32 apps.
 
    .EXAMPLE
    $paths = Prepare-Paths -Prg $Prg -Prg_Path "C:\Programs\MyApp" -Win32AppsRootPath "C:\Win32AppsRoot"
    #>


    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, HelpMessage = "Provide the program object.")]
        [ValidateNotNullOrEmpty()]
        [pscustomobject]$Prg,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the source path of the program.")]
        [ValidateNotNullOrEmpty()]
        [string]$Prg_Path,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the root path for Win32 apps.")]
        [ValidateNotNullOrEmpty()]
        [string]$Win32AppsRootPath
    )

    # Check if the source path exists, create if it doesn't
    if (-not (Test-Path -Path $Prg_Path)) {
        Write-EnhancedLog -Message "Source path $Prg_Path does not exist. Creating it." -Level "INFO"
        New-Item -Path $Prg_Path -ItemType Directory -Force
    }
    
    # Prepare the destination root path and app-specific destination path
    $destinationRootPath = Join-Path -Path $Win32AppsRootPath -ChildPath "Win32Apps-published"
    if (-not (Test-Path -Path $destinationRootPath)) {
        New-Item -Path $destinationRootPath -ItemType Directory -Force
    }

    $destinationPath = Join-Path -Path $destinationRootPath -ChildPath $Prg.name
    if (-not (Test-Path -Path $destinationPath)) {
        New-Item -Path $destinationPath -ItemType Directory -Force
    }

    Write-EnhancedLog -Message "Destination path created: $destinationPath" -Level "INFO"

    # Return an object with the paths
    return [pscustomobject]@{
        Prg              = $Prg.name
        SourcePath       = $Prg_Path
        DestinationRoot  = $destinationRootPath
        DestinationPath  = $destinationPath
    }
}


# # Prepare paths and store the returned object
# $paths = Prepare-Paths -Prg $Prg -Prg_Path "C:\Programs\MyApp" -Win32AppsRootPath "C:\Win32AppsRoot"

# # Access the properties of the returned object
# Write-Host "Program Name: $($paths.Prg)"
# Write-Host "Source Path: $($paths.SourcePath)"
# Write-Host "Destination Root: $($paths.DestinationRoot)"
# Write-Host "Destination Path: $($paths.DestinationPath)"


# Program Name: MyApp
# Source Path: C:\Programs\MyApp
# Destination Root: C:\Win32AppsRoot\Win32Apps
# Destination Path: C:\Win32AppsRoot\Win32Apps\MyApp

#EndRegion '.\Public\Prepare-Paths.ps1' 81
#Region '.\Public\Prepare-SolutionDirectory.ps1' -1

function Prepare-SolutionDirectory {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$ToolkitFolder,

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

    Begin {
        Write-EnhancedLog -Message "Starting Prepare-SolutionDirectory function" -Level "INFO"
        Log-Params -Params @{
            ToolkitFolder = $ToolkitFolder
            FilesFolder = $FilesFolder
        }
    }

    Process {
        try {
            # Create necessary directories
            New-Item -ItemType Directory -Path $ToolkitFolder -Force
            New-Item -ItemType Directory -Path $FilesFolder -Force
        } catch {
            Write-EnhancedLog -Message "An error occurred while processing the Prepare-SolutionDirectory function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Prepare-SolutionDirectory function" -Level "INFO"
    }
}

# Example usage
# Prepare-SolutionDirectory -ToolkitFolder "C:\path\to\toolkit" -FilesFolder "C:\path\to\files"
#EndRegion '.\Public\Prepare-SolutionDirectory.ps1' 37
#Region '.\Public\Process-DeviceItem-old.ps1' -1

# function Process-DeviceItem {
# [CmdletBinding()]
# param (
# [Parameter(Mandatory = $true)]
# $Item,
# [Parameter(Mandatory = $true)]
# $Context,
# [Parameter(Mandatory = $true)]
# $Headers
# )

# Begin {
# Write-EnhancedLog -Message "Starting Process-DeviceItem function" -Level "INFO"
# Log-Params -Params @{ Item = $Item; Context = $Context }
# if (-not $Context.UniqueDeviceIds) {
# $Context.UniqueDeviceIds = [System.Collections.Generic.HashSet[string]]::new()
# }
# }

# Process {
# # Ensure deviceDetail object and properties exist
# if (-not $Item.deviceDetail) {
# Write-EnhancedLog -Message "Missing deviceDetail for user: $($Item.userDisplayName)" -Level "WARNING"
# return
# }

# $deviceId = $Item.deviceDetail.deviceId
# $userId = $Item.userId
# $os = $Item.deviceDetail.operatingSystem

# if (-not $userId) {
# Write-EnhancedLog -Message "Missing userId for device item" -Level "WARNING"
# return
# }

# # Construct uniqueId based on availability of deviceId and OS for BYOD
# $uniqueId = if ([string]::IsNullOrWhiteSpace($deviceId)) {
# "$userId-$os".ToLowerInvariant()
# } else {
# $deviceId.ToLowerInvariant()
# }

# try {
# # Log the device and user information
# Write-EnhancedLog -Message "Processing device item for user: $($Item.userDisplayName) with unique ID: $uniqueId" -Level "INFO"

# # Handle external Azure AD tenant case
# if ([string]::Equals($deviceId, "{PII Removed}", [System.StringComparison]::OrdinalIgnoreCase)) {
# if ($Context.UniqueDeviceIds.Add($uniqueId)) {
# Write-EnhancedLog -Message "External Azure AD tenant detected for user: $($Item.userDisplayName)" -Level "INFO"
# Add-Result -Context $Context -Item $Item -DeviceId "N/A" -DeviceState "External" -HasPremiumLicense $false -OSVersion $null
# }
# return
# }

# # Process only if the unique ID is not already processed
# if ($Context.UniqueDeviceIds.Add($uniqueId)) {
# # Handle BYOD case
# if ([string]::IsNullOrWhiteSpace($deviceId)) {
# # Check if there are already devices with the same userId and OS
# # $existingBYODs = $Context.UniqueDeviceIds | Where-Object { $_ -like "$userId-*" }

# # if ($existingBYODs.Count -gt 0) {
# # Write-EnhancedLog -Message "User $($Item.userDisplayName) has multiple BYOD devices with the same OS: $os" -Level "WARNING"
# # }

# # Fetch user licenses with retry logic
# $retryCount = 0
# $maxRetries = 3
# do {
# try {
# $userLicenses = Fetch-UserLicense -UserId $userId -Username $Item.userDisplayName -Headers $Headers
# $fetchSuccess = $true
# } catch {
# Write-EnhancedLog -Message "Failed to fetch licenses for user: $($Item.userDisplayName). Attempt $($retryCount + 1) of $maxRetries" -Level "ERROR"
# $fetchSuccess = $false
# $retryCount++
# Start-Sleep -Seconds 2
# }
# } while (-not $fetchSuccess -and $retryCount -lt $maxRetries)

# if (-not $fetchSuccess) {
# Write-EnhancedLog -Message "Failed to fetch licenses for user: $($Item.userDisplayName) after $maxRetries attempts." -Level "ERROR"
# $userLicenses = @()
# }

# $hasPremiumLicense = $userLicenses -and $userLicenses.Count -gt 0 -and $userLicenses.Contains("cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46")
# Write-EnhancedLog -Message "User $($Item.userDisplayName) has the following licenses: $($userLicenses -join ', ')" -Level "INFO"

# Add-Result -Context $Context -Item $Item -DeviceId "N/A" -DeviceState "BYOD" -HasPremiumLicense $hasPremiumLicense -OSVersion $null
# return
# }

# # Handle managed device case with retry logic
# $retryCount = 0
# $maxRetries = 3
# $deviceState = "Unknown"
# do {
# try {
# # Call the method to check device state
# $deviceState = Check-DeviceStateInIntune -entraDeviceId $deviceId -username $Item.userDisplayName -Headers $Headers
# $fetchSuccess = $true
# } catch {
# Write-EnhancedLog -Message "Failed to check device state for device ID: $deviceId. Attempt $($retryCount + 1) of $maxRetries" -Level "ERROR"
# $fetchSuccess = $false
# $retryCount++
# Start-Sleep -Seconds 2
# }
# } while (-not $fetchSuccess -and $retryCount -lt $maxRetries)

# if (-not $fetchSuccess) {
# Write-EnhancedLog -Message "Failed to check device state for device ID: $deviceId after $maxRetries attempts." -Level "ERROR"
# }

# $retryCount = 0
# $osVersion = "Unknown"
# do {
# try {
# # Fetch OS version
# $osVersion = Fetch-OSVersion -DeviceId $deviceId -Headers $Headers
# $fetchSuccess = $true
# } catch {
# Write-EnhancedLog -Message "Failed to fetch OS version for device ID: $deviceId. Attempt $($retryCount + 1) of $maxRetries" -Level "ERROR"
# $fetchSuccess = $false
# $retryCount++
# Start-Sleep -Seconds 2
# }
# } while (-not $fetchSuccess -and $retryCount -lt $maxRetries)

# if (-not $fetchSuccess) {
# Write-EnhancedLog -Message "Failed to fetch OS version for device ID: $deviceId after $maxRetries attempts." -Level "ERROR"
# }

# $retryCount = 0
# $userLicenses = @()
# do {
# try {
# # Fetch user licenses
# $userLicenses = Fetch-UserLicense -UserId $userId -Username $Item.userDisplayName -Headers $Headers
# $fetchSuccess = $true
# } catch {
# Write-EnhancedLog -Message "Failed to fetch licenses for user: $($Item.userDisplayName). Attempt $($retryCount + 1) of $maxRetries" -Level "ERROR"
# $fetchSuccess = $false
# $retryCount++
# Start-Sleep -Seconds 2
# }
# } while (-not $fetchSuccess -and $retryCount -lt $maxRetries)

# if (-not $fetchSuccess) {
# Write-EnhancedLog -Message "Failed to fetch licenses for user: $($Item.userDisplayName) after $maxRetries attempts." -Level "ERROR"
# }

# $hasPremiumLicense = $userLicenses -and $userLicenses.Count -gt 0 -and $userLicenses.Contains("cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46")
# Write-EnhancedLog -Message "User $($Item.userDisplayName) has the following licenses: $($userLicenses -join ', ')" -Level "INFO"

# Add-Result -Context $Context -Item $Item -DeviceId $deviceId -DeviceState $deviceState -HasPremiumLicense $hasPremiumLicense -OSVersion $osVersion
# }
# } catch {
# Write-EnhancedLog -Message "An error occurred while processing the device item for user: $($Item.userDisplayName) - $_" -Level "ERROR"
# Handle-Error -ErrorRecord $_
# }
# }

# End {
# Write-EnhancedLog -Message "Exiting Process-DeviceItem function" -Level "INFO"
# }
# }
#EndRegion '.\Public\Process-DeviceItem-old.ps1' 168
#Region '.\Public\Process-DeviceItem.ps1' -1

function Process-DeviceItem {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        $Item,
        [Parameter(Mandatory = $true)]
        $Context,
        [Parameter(Mandatory = $true)]
        $Headers
    )

    Begin {
        Write-EnhancedLog -Message "Starting Process-DeviceItem function" -Level "INFO"
        Log-Params -Params @{ Item = $Item; Context = $Context }
        Initialize-Context -Context $Context
    }

    Process {
        # Ensure deviceDetail object and properties exist
        if (-not $Item.deviceDetail) {
            Write-EnhancedLog -Message "Missing deviceDetail for user: $($Item.userDisplayName)" -Level "WARNING"
            return
        }

        $deviceId = $Item.deviceDetail.deviceId
        $userId = $Item.userId
        $os = $Item.deviceDetail.operatingSystem

        if (-not $userId) {
            Write-EnhancedLog -Message "Missing userId for device item" -Level "WARNING"
            return
        }

        try {
            # Construct uniqueId based on availability of deviceId and OS for BYOD
            if ([string]::IsNullOrWhiteSpace($deviceId)) {
                $uniqueId = "$userId-$os".ToLowerInvariant()
            } else {
                $uniqueId = $deviceId.ToLowerInvariant()
            }

            # Log the device and user information
            Write-EnhancedLog -Message "Processing device item for user: $($Item.userDisplayName) with unique ID: $uniqueId" -Level "INFO"

            # Handle external Azure AD tenant case
            if (Handle-ExternalAADTenant -Item $Item -Context $Context -UniqueId $uniqueId) {
                return
            }

            # Process only if the unique ID is not already processed
            if ($Context.UniqueDeviceIds.Add($uniqueId)) {
                # Handle BYOD case
                if ([string]::IsNullOrWhiteSpace($deviceId)) {
                    # Fetch user licenses with retry logic
                    $userLicenses = Fetch-UserLicensesWithRetry -UserId $userId -Username $Item.userDisplayName -Headers $Headers
                    $hasPremiumLicense = $userLicenses -and $userLicenses.Count -gt 0 -and $userLicenses.Contains("cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46")
                    Write-EnhancedLog -Message "User $($Item.userDisplayName) has the following licenses: $($userLicenses -join ', ')" -Level "INFO"

                    Add-Result -Context $Context -Item $Item -DeviceId "N/A" -DeviceState "BYOD" -HasPremiumLicense $hasPremiumLicense -OSVersion $null
                    return
                }

                # Handle managed device case with retry logic
                $deviceState = Fetch-DeviceStateWithRetry -DeviceId $deviceId -Username $Item.userDisplayName -Headers $Headers
                $osVersion = Fetch-OSVersionWithRetry -DeviceId $deviceId -Headers $Headers

                $userLicenses = Fetch-UserLicensesWithRetry -UserId $userId -Username $Item.userDisplayName -Headers $Headers
                $hasPremiumLicense = $userLicenses -and $userLicenses.Count -gt 0 -and $userLicenses.Contains("cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46")
                Write-EnhancedLog -Message "User $($Item.userDisplayName) has the following licenses: $($userLicenses -join ', ')" -Level "INFO"

                Add-Result -Context $Context -Item $Item -DeviceId $deviceId -DeviceState $deviceState -HasPremiumLicense $hasPremiumLicense -OSVersion $osVersion
            }
        } catch {
            Write-EnhancedLog -Message "An error occurred while processing the device item for user: $($Item.userDisplayName) - $_" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Process-DeviceItem function" -Level "INFO"
    }
}
#EndRegion '.\Public\Process-DeviceItem.ps1' 83
#Region '.\Public\Process-Folder.ps1' -1

function Process-Folder {
    <#
    .SYNOPSIS
    Processes a folder by handling printer installations and Win32 app configurations.
 
    .DESCRIPTION
    This function processes a folder by checking for the presence of a printer configuration file (`printer.json`) and, if found, processes the printer installation. It also processes Win32 app configurations based on the provided folder and configuration object. The function returns an object with details about the folder processing and prints a summary report at the end with counts and color-coded statuses.
 
    .PARAMETER Folder
    The folder to be processed, which may contain a `printer.json` file for printer installation.
 
    .PARAMETER config
    The configuration object required for Win32 app processing.
 
    .PARAMETER Repo_winget
    The path to the repository (winget) where the program source resides.
 
    .PARAMETER scriptpath
    The path to the script that is being executed.
 
    .EXAMPLE
    $folderDetails = Process-Folder -Folder (Get-Item "C:\Apps\MyApp") -config $config -Repo_winget "C:\Repo\winget" -scriptpath "C:\path\to\script"
    Processes the folder and returns the folder processing details.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the folder to be processed.")]
        [ValidateNotNullOrEmpty()]
        [System.IO.DirectoryInfo]$Folder,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the configuration for the Win32 app.")]
        [ValidateNotNullOrEmpty()]
        [pscustomobject]$config,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the path to the winget repository.")]
        [ValidateNotNullOrEmpty()]
        [string]$Repo_winget,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the path to the script being executed.")]
        [ValidateNotNullOrEmpty()]
        [string]$scriptpath
    )

    Begin {
        Write-EnhancedLog -Message "Starting Process-Folder function for folder: $($Folder.Name)" -Level "INFO"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Initialize counters and app status list
        $totalApps = 0
        $successfulApps = 0
        $failedApps = 0
        $appStatuses = [System.Collections.Generic.List[PSCustomObject]]::new()  # Efficient list initialization
    }

    Process {
        try {
            # Initialize a variable to track if printer installation was processed
            $printerProcessed = $false

            # Construct the path to the printer.json within the current folder
            $printerConfigPath = Join-Path -Path $Folder.FullName -ChildPath "printer.json"

            if (Test-Path -Path $printerConfigPath) {
                Write-EnhancedLog -Message "printer.json found in folder: $($Folder.Name). Processing printer installation." -Level "INFO"
                Process-PrinterInstallation -PrinterConfigPath $printerConfigPath
                Write-EnhancedLog -Message "Processed printer installation for folder: $($Folder.Name)" -Level "INFO"
                $printerProcessed = $true
            }
            else {
                Write-EnhancedLog -Message "printer.json not found in folder: $($Folder.Name)" -Level "WARNING"
            }

            # Process Win32 app and get the details
            Write-EnhancedLog -Message "Processing Win32 app configuration for folder: $($Folder.Name)" -Level "INFO"
            $totalApps++

            try {
                $appDetails = Process-Win32App -Folder $Folder -config $config -Repo_winget $Repo_winget -scriptpath $scriptpath
                Write-EnhancedLog -Message "Successfully processed Win32 app: $($Folder.Name)" -Level "INFO"
                $successfulApps++
                $appStatuses.Add([pscustomobject]@{ AppName = $Folder.Name; Status = "Success" })
            }
            catch {
                Write-EnhancedLog -Message "Failed to process Win32 app: $($Folder.Name)" -Level "ERROR"
                $failedApps++
                $appStatuses.Add([pscustomobject]@{ AppName = $Folder.Name; Status = "Failed" })
            }

            # Build the return object
            $folderDetails = [pscustomobject]@{
                FolderName       = $Folder.Name
                PrinterProcessed = $printerProcessed
                AppDetails       = $appDetails
            }

            return $folderDetails
        }
        catch {
            Write-EnhancedLog -Message "Error occurred during folder processing: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
    }

    End {
        Write-EnhancedLog -Message "Completed Process-Folder function for folder: $($Folder.Name)" -Level "INFO"

        # Print final summary report
        Write-Host "Final Summary Report" -ForegroundColor Green
        Write-Host "---------------------" -ForegroundColor Green
        Write-Host "Total Apps Processed: $totalApps" -ForegroundColor Green
        Write-Host "Successful Apps: $successfulApps" -ForegroundColor Green
        Write-Host "Failed Apps: $failedApps" -ForegroundColor Red

        # Loop through appStatuses for detailed report
        foreach ($appStatus in $appStatuses) {
            if ($appStatus.Status -eq "Success") {
                Write-Host "App: $($appStatus.AppName) - Status: $($appStatus.Status)" -ForegroundColor Green
            }
            else {
                Write-Host "App: $($appStatus.AppName) - Status: $($appStatus.Status)" -ForegroundColor Red
            }
        }
    }
}

# Example Usage:
# $folderDetails = Process-Folder -Folder (Get-Item "C:\Apps\MyApp") -config $config -Repo_winget "C:\Repo\winget" -scriptpath "C:\path\to\script"

#EndRegion '.\Public\Process-Folder.ps1' 131
#Region '.\Public\Process-PrinterInstallation.ps1' -1

function Process-PrinterInstallation {
    param (
        [Parameter(Mandatory = $true)]
        [string]$PrinterConfigPath
    )

    $commands = Invoke-PrinterInstallation -PrinterConfigPath $PrinterConfigPath -AppConfigPath $configPath
    Write-EnhancedLog -Message "Install Command: $($commands.InstallCommand)"
    Write-EnhancedLog -Message "Uninstall Command: $($commands.UninstallCommand)"
    
    $global:InstallCommandLine = $commands.InstallCommand
    $global:UninstallCommandLine = $commands.UninstallCommand
}
#EndRegion '.\Public\Process-PrinterInstallation.ps1' 14
#Region '.\Public\Process-SignInLogs.ps1' -1

function Process-SignInLogs {
    param (
        [Parameter(Mandatory = $true)]
        [System.Collections.Generic.List[PSCustomObject]]$signInLogs,
        [Parameter(Mandatory = $true)]
        [hashtable]$Headers
    )

    # Ensure the signInLogs variable is not null before using it
    if ($null -eq $signInLogs -or $signInLogs.Count -eq 0) {
        Write-Warning "No sign-in logs were loaded."
        exit 1
    }

    # Display the count of loaded sign-in logs
    Write-Host "Loaded $($signInLogs.Count) sign-in logs."

    # Debugging: Print the first sign-in log entry
    if ($signInLogs.Count -gt 0) {
        $firstSignInLog = $signInLogs[0]
        Write-Host "First sign-in log entry:"
        Write-Host "UserDisplayName: $($firstSignInLog.UserDisplayName)"
        Write-Host "UserId: $($firstSignInLog.UserId)"
        Write-Host "DeviceDetail:"
        Write-Host " DeviceId: $($firstSignInLog.DeviceDetail.DeviceId)"
        Write-Host " DisplayName: $($firstSignInLog.DeviceDetail.DisplayName)"
        Write-Host " OperatingSystem: $($firstSignInLog.DeviceDetail.OperatingSystem)"
        Write-Host " IsCompliant: $($firstSignInLog.DeviceDetail.IsCompliant)"
        Write-Host " TrustType: $($firstSignInLog.DeviceDetail.TrustType)"
    }

    $context = New-ProcessingContext

    # Process each log item directly
    foreach ($log in $signInLogs) {
        # Exclude "On-Premises Directory Synchronization Service Account" user
        if ($log.UserDisplayName -ne "On-Premises Directory Synchronization Service Account" -and $null -ne $log) {
            try {
                Process-DeviceItem -Item $log -Context $context -Headers $Headers
            } catch {
                Write-Error "Error processing item: $($_.Exception.Message)"
                Handle-Error -ErrorRecord $_
            }
        }
    }

    # Remove null entries from the results list
    $context.Results = $context.Results | Where-Object { $_ -ne $null }

    # Return the results
    return $context.Results
}
#EndRegion '.\Public\Process-SignInLogs.ps1' 53
#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\Process-Win32App.ps1' -1

function Process-Win32App {
    <#
    .SYNOPSIS
    Processes a Win32 app folder and uploads the app to Intune.
 
    .DESCRIPTION
    This function processes the Win32 app by defining the program's ID, name, and description based on the folder's name. It checks for a valid application image, defines the source path, and uploads the application to Intune using PowerShell 5. The function returns an object with details about the processed Win32 app.
 
    .PARAMETER Folder
    The folder containing the Win32 app to be processed.
 
    .PARAMETER config
    The configuration object required for uploading the Win32 app.
 
    .PARAMETER Repo_winget
    The path to the repository (winget) where the program source resides.
 
    .PARAMETER scriptpath
    The path to the script that is being executed.
 
    .EXAMPLE
    $appDetails = Process-Win32App -Folder (Get-Item "C:\Programs\MyApp") -config $config -Repo_winget "C:\Repo\winget" -scriptpath "C:\path\to\script"
    Processes the Win32 app and returns the app details.
    #>


    [CmdletBinding(ConfirmImpact = 'Medium')]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the folder containing the Win32 app.")]
        [ValidateNotNullOrEmpty()]
        [System.IO.DirectoryInfo]$Folder,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the configuration for the Win32 app.")]
        [ValidateNotNullOrEmpty()]
        [PSCustomObject]$config,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the path to the winget repository.")]
        [ValidateNotNullOrEmpty()]
        [string]$Repo_winget,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the path to the script being executed.")]
        [ValidateNotNullOrEmpty()]
        [string]$scriptpath
    )

    Begin {
        # Create the program object based on the folder's name
        $Prg = [PSCustomObject]@{
            id          = $Folder.Name
            name        = $Folder.Name
            Description = $Folder.Name
        }

        Write-EnhancedLog -Message "Program ID: $($Prg.id)" -Level "INFO"
        Write-EnhancedLog -Message "Program Name: $($Prg.name)" -Level "INFO"
        Write-EnhancedLog -Message "Description: $($Prg.Description)" -Level "INFO"

        # Ensure that Program ID matches Program Name
        if ($Prg.id -ne $Prg.name) {
            throw "Error: Program ID ('$($Prg.id)') does not match Program Name ('$($Prg.name)')."
        }
    }

    Process {
        # Define the source path for the program and check for the application image
        $sourcePathDetails = Define-SourcePath -Repo_winget $Repo_winget -Prg $Prg
        $Prg_Path = $sourcePathDetails.SourcePath
        $imageDetails = Check-ApplicationImage -Prg_Path $Prg_Path -Prg $Prg

        # Define the splatted parameters for Upload-Win32App
        $UploadWin32AppParams = @{

            Prg               = $Prg
            Prg_Path          = $sourcePathDetails.SourcePath
            Prg_img           = $imageDetails.ImagePath
            Win32AppsRootPath = $scriptpath
            config            = $config
        }
        Upload-Win32App @UploadWin32AppParams

        # Build the return object with details of the Win32 app
        $appDetails = [pscustomobject]@{
            ProgramID       = $Prg.id
            ProgramName     = $Prg.name
            SourcePath      = $sourcePathDetails.SourcePath
            Config          = $config
        }

        # Return the app details object
        return $appDetails
    }

    End {
        Write-EnhancedLog -Message "Completed processing of Win32 app: $($Prg.name)" -Level "INFO"
    }
}

# Example Usage:
# $appDetails = Process-Win32App -Folder (Get-Item "C:\Programs\MyApp") -config $config -Repo_winget "C:\Repo\winget" -scriptpath "C:\path\to\script"





# # Run Process-Win32App and store the returned object
# $appDetails = Process-Win32App -Folder (Get-Item "C:\Programs\MyApp") -config $config

# # Access the properties of the returned object
# Write-Host "Program ID: $($appDetails.ProgramID)"
# Write-Host "Program Name: $($appDetails.ProgramName)"
# Write-Host "Source Path: $($appDetails.SourcePath)"

# Program ID: MyApp
# Program Name: MyApp
# Source Path: C:\path\to\Win32Apps-DropBox\MyApp
#EndRegion '.\Public\Process-Win32App.ps1' 115
#Region '.\Public\Remove-ADJoin.ps1' -1

function Remove-ADJoin {
    <#
    .SYNOPSIS
        Removes the computer from the Active Directory domain.
     
    .DESCRIPTION
        This function checks if the computer is part of an Active Directory domain and, if so, removes it from the domain.
        It also disables specified scheduled tasks, manages network adapters, and restarts the computer if necessary.
     
    .PARAMETER DomainLeaveUser
        The domain user account to use for leaving the domain.
     
    .PARAMETER DomainLeavePassword
        The password for the domain user account.
     
    .PARAMETER TempUser
        The temporary local user to use if domain credentials fail.
     
    .PARAMETER TempUserPassword
        The password for the temporary local user.
     
    .PARAMETER ComputerName
        The name of the computer to remove from the domain.
     
    .PARAMETER TaskName
        The name of the scheduled task to be disabled.
     
    .PARAMETER TaskPath
        The path of the scheduled task to be disabled.
     
    .EXAMPLE
        Remove-ADJoin -DomainLeaveUser "AdminUser" -DomainLeavePassword "P@ssw0rd" -TempUser "LocalAdmin" -TempUserPassword "P@ssw0rd" -ComputerName "localhost" -TaskName "TaskName" -TaskPath "\Path\To\Task"
     
    .NOTES
        This function performs multiple actions, including removing the computer from the domain, disabling scheduled tasks, managing network adapters, and restarting the computer.
    #>

    [CmdletBinding()]
    param (
        [string]$DomainLeaveUser,
        [string]$DomainLeavePassword,
        [string]$TempUser,
        [string]$TempUserPassword,
        [string]$ComputerName
    )
    
    Begin {
        Write-EnhancedLog -Message "Starting Remove-ADJoin function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }
    
    Process {
        try {
            $PartOfDomain = Check-DomainMembership
    
            if ($PartOfDomain) {
                Write-EnhancedLog -Message "Computer is domain member, removing domain membership" -Level "INFO"
                $leaveDomainParams = @{
                    DomainLeaveUser     = $DomainLeaveUser
                    DomainLeavePassword = $DomainLeavePassword
                    ComputerName        = $ComputerName
                    TempUser            = $TempUser
                    TempUserPassword    = $TempUserPassword
                }
                
                Leave-Domain @leaveDomainParams
                
    
                # Disable-ScheduledTaskByPath -TaskName $TaskName -TaskPath $TaskPath
    
                Manage-NetworkAdapters -Disable
                Start-Sleep -Seconds 5
                Manage-NetworkAdapters
    
                # Restart-ComputerIfNeeded
            }
            else {
                Write-EnhancedLog -Message "Computer is not a domain member, no domain removal needed." -Level "INFO"
                # Disable-ScheduledTaskByPath -TaskName $TaskName -TaskPath $TaskPath
                # Restart-ComputerIfNeeded
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while removing AD join: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }
    
    End {
        Write-EnhancedLog -Message "Exiting Remove-ADJoin function" -Level "NOTICE"
    }
}
#EndRegion '.\Public\Remove-ADJoin.ps1' 92
#Region '.\Public\Remove-AppListJson.ps1' -1

function Remove-AppListJson {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$JsonPath
    )

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

    process {
        try {
            # Check if the file exists
            if (Test-Path -Path $JsonPath) {
                # Remove the file
                $removeParams = @{
                    Path  = $JsonPath
                    Force = $true
                }
                Remove-Item @removeParams
                Write-EnhancedLog -Message "The applist.json file has been removed successfully." -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "The file at path '$JsonPath' does not exist." -Level "WARNING"
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while removing the file: $_" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    end {
        Write-EnhancedLog -Message "Remove-AppListJson function execution completed." -Level "INFO"
    }
}

# Example usage
# Remove-AppListJson -JsonPath "C:\Path\To\Your\applist.json"
#EndRegion '.\Public\Remove-AppListJson.ps1' 43
#Region '.\Public\Remove-AppRegistrationsAndDeletedItems.ps1' -1

function Remove-AppRegistrationsAndDeletedItems {
    param (
        [Parameter(Mandatory = $true)]
        [string]$AppDisplayNamePattern
    )

    try {
        Write-EnhancedLog -Message "Starting cleanup process for app registrations with display names like: $AppDisplayNamePattern" -Level "INFO"

        # Retrieve all applications with the specified display name pattern
        $apps = Get-MgApplication -Filter "startswith(DisplayName,'graphapp-test')"

        if ($apps.Count -eq 0) {
            Write-EnhancedLog -Message "No applications found with display names like: $AppDisplayNamePattern" -Level "WARNING"
            return
        }

        Write-EnhancedLog -Message "Applications to be deleted: $($apps.DisplayName -join ', ')" -Level "INFO"

        # Remove each application
        foreach ($app in $apps) {
            Remove-MgApplication -ApplicationId $app.Id -Confirm:$false
            Write-EnhancedLog -Message "Deleted application: $($app.DisplayName) with ID: $($app.Id)" -Level "INFO"
        }

        Write-EnhancedLog -Message "Cleanup process completed successfully." -Level "INFO"
    } catch {
        Write-EnhancedLog -Message "An error occurred during the cleanup process." -Level "ERROR"
        Handle-Error -ErrorRecord $_ 
        throw $_
    }
}

# # Example usage
# $scopes = @("Application.ReadWrite.All", "Directory.ReadWrite.All")
# Connect-MgGraph -Scopes $scopes

# Remove-AppRegistrationsAndDeletedItems -AppDisplayNamePattern "*graphapp-test*"
#EndRegion '.\Public\Remove-AppRegistrationsAndDeletedItems.ps1' 39
#Region '.\Public\Remove-Autologin.ps1' -1

function Remove-AutoLogin {
    [CmdletBinding()]

    param ()

    begin {
        Write-EnhancedLog -Message 'Starting Remove-AutoLogin function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            # Check and remove the auto-login registry keys if they exist
            $keysToRemove = @("AutoAdminLogon", "DefaultUserName", "DefaultPassword", "DefaultDomainName")
            foreach ($key in $keysToRemove) {
                $keyPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\$key"
                if (Test-Path -Path $keyPath) {
                    Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name $key -Force
                    Write-EnhancedLog -Message "Removed auto-login key '$key'." -Level 'INFO'
                } else {
                    Write-EnhancedLog -Message "Auto-login key '$key' does not exist." -Level 'INFO'
                }
            }

            Write-EnhancedLog -Message "Auto-login settings removed." -Level 'INFO'
        } catch {
            Write-EnhancedLog -Message "An error occurred while removing auto-login settings: $_" -Level 'ERROR'
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'Remove-AutoLogin function completed' -Level 'INFO'
    }
}

# Example usage:
# Remove-AutoLogin
#EndRegion '.\Public\Remove-Autologin.ps1' 39
#Region '.\Public\Remove-CompanyPortal.ps1' -1

function Remove-CompanyPortal {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$AppxPackageName
    )

    Begin {
        Write-EnhancedLog -Message "Starting Remove-CompanyPortal function" -Level "INFO"
        Log-Params -Params @{ AppxPackageName = $AppxPackageName }
    }

    Process {
        try {
            Write-EnhancedLog -Message "Removing AppxPackage: $AppxPackageName" -Level "INFO"
            Get-AppxPackage -AllUsers -Name $AppxPackageName | Remove-AppxPackage -Confirm:$false
        } catch {
            Write-EnhancedLog -Message "An error occurred while removing AppxPackage: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Remove-CompanyPortal function" -Level "INFO"
    }
}

# $RemoveCompanyPortalParams = @{
# AppxPackageName = "Microsoft.CompanyPortal"
# }

# Remove-CompanyPortal @RemoveCompanyPortalParams
#EndRegion '.\Public\Remove-CompanyPortal.ps1' 33
#Region '.\Public\Remove-DeviceCertificates.ps1' -1

# Function to remove device certificates
function Remove-DeviceCertificates {
    param (
        [string]$CertLocalMachinePath,
        [string[]]$DeviceCertIssuers
    )

    Write-EnhancedLog -Message "Attempting to remove device certificates with issuers: $DeviceCertIssuers" -Level "INFO"
    $DeviceCerts = Get-ChildItem -Path $CertLocalMachinePath -Recurse
    $IntuneCerts = $DeviceCerts | Where-Object { $DeviceCertIssuers -contains $_.Issuer }
    foreach ($Cert in $IntuneCerts) {
        Write-EnhancedLog -Message "Removing device certificate: $($Cert.Subject)" -Level "INFO"
        $Cert | Remove-Item -Force -ErrorAction SilentlyContinue
    }
    Write-EnhancedLog -Message "Device certificates removed successfully" -Level "INFO"

    return [PSCustomObject]@{
        Action = "Remove Device Certificates"
        Path   = $CertLocalMachinePath
        Status = "Success"
    }
}
#EndRegion '.\Public\Remove-DeviceCertificates.ps1' 23
#Region '.\Public\Remove-EnhancedItem.ps1' -1

# function Remove-EnhancedItem {
# [CmdletBinding()]
# param (
# [Parameter(Mandatory = $true)]
# [string]$Path,

# [Parameter(Mandatory = $false)]
# [switch]$ForceKillProcesses, # Option to force kill processes locking the files

# [Parameter(Mandatory = $false)]
# [int]$MaxRetries = 3, # Maximum number of retries

# [Parameter(Mandatory = $false)]
# [int]$RetryInterval = 5 # Interval between retries in seconds
# )

# Begin {
# Write-EnhancedLog -Message "Starting Remove-EnhancedItem function for path: $Path" -Level "Notice"
# Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
# }

# Process {
# # Validate before removal
# $validationResultsBefore = Validate-PathExistsWithLogging -Paths $Path

# if ($validationResultsBefore.TotalValidatedFiles -gt 0) {
# $retryCount = 0
# $removedSuccessfully = $false

# while (-not $removedSuccessfully -and $retryCount -lt $MaxRetries) {
# try {
# Write-EnhancedLog -Message "Attempting to remove item: $Path (Attempt $($retryCount + 1) of $MaxRetries)" -Level "INFO"
# Remove-Item -Path $Path -Recurse -Force
# $removedSuccessfully = $true
# Write-EnhancedLog -Message "Successfully removed item: $Path" -Level "INFO"
# }
# catch {
# Write-EnhancedLog -Message "Error encountered while trying to remove item: $Path - $($_.Exception.Message)" -Level "ERROR"

# # Check if the error is due to the file being locked by another process
# if ($_.Exception.Message -match "being used by another process") {
# Write-EnhancedLog -Message "Identifying processes locking the file or directory..." -Level "WARNING"

# # Identify processes locking the file using Sysinternals Handle or similar tool
# $lockingProcessesParams = @{
# FilePath = $Path
# HandlePath = "C:\ProgramData\SystemTools\handle64.exe"
# }
# $lockingProcesses = Get-LockingProcess @lockingProcessesParams

# if ($lockingProcesses) {
# Write-EnhancedLog -Message "Processes locking the file or directory:" -Level "INFO"
# $lockingProcesses | ForEach-Object {
# Write-EnhancedLog -Message "Process ID: $($_.ProcessId), Process Name: $($_.ProcessName)" -Level "INFO"
# }

# # Optionally force kill processes
# if ($ForceKillProcesses) {
# $lockingProcesses | ForEach-Object {
# try {
# Write-EnhancedLog -Message "Attempting to kill process ID: $($_.ProcessId), Process Name: $($_.ProcessName)" -Level "WARNING"
# Stop-Process -Id $_.ProcessId -Force
# Write-EnhancedLog -Message "Successfully killed process ID: $($_.ProcessId), Process Name: $($_.ProcessName)" -Level "INFO"
# }
# catch {
# Write-EnhancedLog -Message "Failed to kill process ID: $($_.ProcessId), Process Name: $($_.ProcessName) - $($_.Exception.Message)" -Level "ERROR"
# }
# }
# }

# # Add a short delay before retrying to allow processes to terminate
# Start-Sleep -Seconds 2
# }
# else {
# Write-EnhancedLog -Message "No locking processes identified." -Level "WARNING"
# }
# }

# # Increment retry count and wait before retrying
# $retryCount++
# if ($retryCount -lt $MaxRetries) {
# Write-EnhancedLog -Message "Retrying removal in $RetryInterval seconds..." -Level "INFO"
# Start-Sleep -Seconds $RetryInterval
# }
# }
# }

# # Validate after removal
# $validationResultsAfter = Validate-PathExistsWithLogging -Paths $Path

# if ($removedSuccessfully -and $validationResultsAfter.TotalValidatedFiles -eq 0) {
# Write-EnhancedLog -Message "Item $Path successfully removed." -Level "CRITICAL"
# }
# else {
# Write-EnhancedLog -Message "Failed to remove item: $Path after $MaxRetries attempts." -Level "CRITICAL"
# throw "Failed to remove item: $Path after $MaxRetries attempts."
# }
# }
# else {
# Write-EnhancedLog -Message "Item $Path does not exist. No action taken." -Level "WARNING"
# }
# }

# End {
# Write-EnhancedLog -Message "Exiting Remove-EnhancedItem function" -Level "Notice"
# }
# }




function Remove-EnhancedItem {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Path,

        [Parameter(Mandatory = $false)]
        [switch]$ForceKillProcesses, # Option to force kill processes locking the files

        [Parameter(Mandatory = $false)]
        [int]$MaxRetries = 3, # Maximum number of retries

        [Parameter(Mandatory = $false)]
        [int]$RetryInterval = 5         # Interval between retries in seconds
    )

    Begin {
        Write-EnhancedLog -Message "Starting Remove-EnhancedItem function for path: $Path" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        # Validate before removal
        if (-not (Test-Path -Path $Path)) {
            Write-EnhancedLog -Message "Item $Path does not exist. No action taken." -Level "WARNING"
            return
        }

        $retryCount = 0
        $removedSuccessfully = $false

        while (-not $removedSuccessfully -and $retryCount -lt $MaxRetries) {
            try {
                Write-EnhancedLog -Message "Attempting to remove item: $Path (Attempt $($retryCount + 1) of $MaxRetries)" -Level "INFO"
                Remove-Item -Path $Path -Recurse -Force
                $removedSuccessfully = $true
                Write-EnhancedLog -Message "Successfully removed item: $Path" -Level "INFO"
            }
            catch {
                Write-EnhancedLog -Message "Error encountered while trying to remove item: $Path - $($_.Exception.Message)" -Level "ERROR"

                if ($_.Exception.Message -match "being used by another process") {
                    Write-EnhancedLog -Message "Identifying processes locking the file or directory..." -Level "WARNING"

                    # Identify processes locking the file using Sysinternals Handle or similar tool
                    $lockingProcessesParams = @{
                        FilePath   = $Path
                        HandlePath = "C:\ProgramData\SystemTools\handle64.exe"
                    }
                    $lockingProcesses = Get-LockingProcess @lockingProcessesParams

                    if ($lockingProcesses) {
                        Write-EnhancedLog -Message "Processes locking the file or directory:" -Level "INFO"
                        $lockingProcesses | ForEach-Object {
                            Write-EnhancedLog -Message "Process ID: $($_.ProcessId), Process Name: $($_.ProcessName)" -Level "INFO"
                        }

                        if ($ForceKillProcesses) {
                            $lockingProcesses | ForEach-Object {
                                try {
                                    Write-EnhancedLog -Message "Attempting to kill process ID: $($_.ProcessId), Process Name: $($_.ProcessName)" -Level "WARNING"
                                    Stop-Process -Id $_.ProcessId -Force
                                    Write-EnhancedLog -Message "Successfully killed process ID: $($_.ProcessId), Process Name: $($_.ProcessName)" -Level "INFO"
                                }
                                catch {
                                    Write-EnhancedLog -Message "Failed to kill process ID: $($_.ProcessId), Process Name: $($_.ProcessName) - $($_.Exception.Message)" -Level "ERROR"
                                }
                            }
                            Start-Sleep -Seconds 2 # Delay to allow processes to terminate
                        }
                    }
                    else {
                        Write-EnhancedLog -Message "No locking processes identified." -Level "WARNING"
                    }
                }

                $retryCount++
                if ($retryCount -lt $MaxRetries) {
                    Write-EnhancedLog -Message "Retrying removal in $RetryInterval seconds..." -Level "INFO"
                    Start-Sleep -Seconds $RetryInterval
                }
                else {
                    Write-EnhancedLog -Message "Failed to remove item: $Path after $MaxRetries attempts." -Level "CRITICAL"
                    throw "Failed to remove item: $Path after $MaxRetries attempts."
                }
            }
        }

        # Validate after removal
        if ($removedSuccessfully -and -not (Test-Path -Path $Path)) {
            Write-EnhancedLog -Message "Item $Path successfully removed." -Level "CRITICAL"
        }
        else {
            Write-EnhancedLog -Message "Failed to remove item: $Path after $MaxRetries attempts." -Level "CRITICAL"
            throw "Failed to remove item: $Path after $MaxRetries attempts."
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Remove-EnhancedItem function" -Level "Notice"
    }
}
#EndRegion '.\Public\Remove-EnhancedItem.ps1' 214
#Region '.\Public\Remove-ExistingHandle.ps1' -1

function Remove-ExistingHandle {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$TargetFolder
    )

    # Full path for Handle64.exe
    $Handle64Path = Join-Path -Path $TargetFolder -ChildPath "Handle64.exe"

    try {
        # Check if Handle64.exe exists
        if (Test-Path -Path $Handle64Path) {
            Write-EnhancedLog -Message "Removing existing Handle64.exe from: $TargetFolder" -Level "INFO"
            # Remove Handle64.exe
            Remove-Item -Path $Handle64Path -Force
            Write-EnhancedLog -Message "Handle64.exe has been removed from: $TargetFolder" -Level "INFO"
        }
        else {
            Write-EnhancedLog -Message "No Handle64.exe file found in: $TargetFolder" -Level "INFO"
        }
    }
    catch {
        # Handle any errors during the removal
        Write-EnhancedLog -Message "An error occurred while trying to remove Handle64.exe: $($_.Exception.Message)" -Level "ERROR"
        Handle-Error -ErrorRecord $_
    }
}
#EndRegion '.\Public\Remove-ExistingHandle.ps1' 29
#Region '.\Public\Remove-ExistingPsExec.ps1' -1

function Remove-ExistingPsExec {
    [CmdletBinding()]
    param(
        # [string]$TargetFolder = "$PSScriptRoot\private"
        [string]$TargetFolder
    )

    # Full path for PsExec64.exe
    $PsExec64Path = Join-Path -Path $TargetFolder -ChildPath "PsExec64.exe"

    try {
        # Check if PsExec64.exe exists
        if (Test-Path -Path $PsExec64Path) {
            Write-EnhancedLog -Message "Removing existing PsExec64.exe from: $TargetFolder"
            # Remove PsExec64.exe
            Remove-Item -Path $PsExec64Path -Force
            Write-EnhancedLog -Message "PsExec64.exe has been removed from: $TargetFolder"
        }
        else {
            Write-EnhancedLog -Message "No PsExec64.exe file found in: $TargetFolder"
        }
    }
    catch {
        # Handle any errors during the removal
        Write-Error "An error occurred while trying to remove PsExec64.exe: $_"
    }
}

#EndRegion '.\Public\Remove-ExistingPsExec.ps1' 29
#Region '.\Public\Remove-ExistingServiceUI.ps1' -1

function Remove-ExistingServiceUI {
    [CmdletBinding()]
    param(
        [string]$TargetFolder,
        [string]$FileName
    )

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

    process {
        # Full path for ServiceUI.exe
        $serviceUIPathParams = @{
            Path = $TargetFolder
            ChildPath = $FileName
        }
        $ServiceUIPath = Join-Path @serviceUIPathParams

        try {
            # Check if ServiceUI.exe exists
            $testPathParams = @{
                Path = $ServiceUIPath
            }
            if (Test-Path @testPathParams) {
                Write-EnhancedLog -Message "Removing existing ServiceUI.exe from: $TargetFolder" -Level "INFO"

                # Remove ServiceUI.exe
                $removeItemParams = @{
                    Path = $ServiceUIPath
                    Force = $true
                }
                Remove-Item @removeItemParams

                Write-Output "ServiceUI.exe has been removed from: $TargetFolder"
            }
            else {
                Write-EnhancedLog -Message "No ServiceUI.exe file found in: $TargetFolder" -Level "INFO"
            }
        }
        catch {
            # Handle any errors during the removal
            Write-Error "An error occurred while trying to remove ServiceUI.exe: $_"
            Write-EnhancedLog -Message "An error occurred while trying to remove ServiceUI.exe: $_" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message "Remove-ExistingServiceUI function execution completed." -Level "INFO"
    }
}

# # Example usage of Remove-ExistingServiceUI function with splatting
# $params = @{
# TargetFolder = "C:\Path\To\Your\Desired\Folder",
# FileName = "ServiceUI.exe"
# }
# Remove-ExistingServiceUI @params
#EndRegion '.\Public\Remove-ExistingServiceUI.ps1' 61
#Region '.\Public\Remove-ExistingStatusFiles.ps1' -1

function Remove-ExistingStatusFiles {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$LogFolder,

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

    Begin {
        Write-EnhancedLog -Message "Starting Remove-ExistingStatusFiles function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
        $statusFiles = [System.Collections.Generic.List[System.IO.FileInfo]]::new()
        $isSystem = Test-RunningAsSystem
    }

    Process {
        if ($isSystem) {
            Write-EnhancedLog -Message "Running as SYSTEM. Analyzing logs across all user profiles." -Level "INFO"
            $userProfiles = Get-ChildItem 'C:\Users' -Directory | Where-Object { $_.Name -notlike "Public" -and $_.Name -notlike "Default*" }

            foreach ($profile in $userProfiles) {
                $profileLogFolder = Join-Path -Path $profile.FullName -ChildPath $LogFolder
                $profileStatusFile = Join-Path -Path $profileLogFolder -ChildPath $StatusFileName
                if (Test-Path -Path $profileStatusFile) {
                    $statusFiles.Add((Get-Item -Path $profileStatusFile))
                }
            }

            if ($statusFiles.Count -gt 0) {
                foreach ($file in $statusFiles) {
                    if (Test-Path -Path $file.FullName) {
                        $removeParams = @{
                            Path               = $file.FullName
                            ForceKillProcesses = $true
                            MaxRetries         = 5
                            RetryInterval      = 10
                        }
                        Remove-EnhancedItem @removeParams
                        Write-EnhancedLog -Message "Removed existing status file: $($file.FullName)" -Level "INFO"
                    }
                }
            }
            else {
                Write-EnhancedLog -Message "No status files found across user profiles." -Level "WARNING"
            }
        }
        else {
            #Not Running as SYSTEM but running As User so we will scan the current user profile instead of all user profiles
            $logFolder = Join-Path -Path $env:USERPROFILE -ChildPath $LogFolder
            $statusFile = Join-Path -Path $logFolder -ChildPath $StatusFileName

            if (Test-Path -Path $statusFile) {
                $removeParams = @{
                    Path               = $statusFile
                    ForceKillProcesses = $true
                    MaxRetries         = 5
                    RetryInterval      = 10
                }
                Remove-EnhancedItem @removeParams
                Write-EnhancedLog -Message "Removed existing status file: $statusFile" -Level "INFO"
            }
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Remove-ExistingStatusFiles function" -Level "Notice"
    }
}
#EndRegion '.\Public\Remove-ExistingStatusFiles.ps1' 71
#Region '.\Public\Remove-FortiSoftware.ps1' -1

function Remove-FortiSoftware {
    [CmdletBinding()]
    param (
        [string]$ScriptRoot,
        [string]$SoftwareName,
        [string]$MsiZapFileName,
        [string]$ArgumentTemplate
    )

    begin {
        Write-EnhancedLog -Message 'Starting Remove-FortiSoftware function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            $msiZapPath = Join-Path -Path $ScriptRoot -ChildPath $MsiZapFileName

            if (Test-Path $msiZapPath) {
                $identifyingNumber = Get-CimInstance -ClassName Win32_Product | Where-Object { $_.Name -like $SoftwareName } | Select-Object -ExpandProperty IdentifyingNumber

                if ($identifyingNumber) {
                    Write-EnhancedLog -Message "Found software with IdentifyingNumber: $identifyingNumber" -Level 'INFO'
                    Write-EnhancedLog -Message "Executing MsiZap with IdentifyingNumber: $identifyingNumber" -Level 'INFO'

                    $argumentList = $ArgumentTemplate -replace '{IdentifyingNumber}', $identifyingNumber
                    Start-Process -FilePath $msiZapPath -ArgumentList $argumentList -Verb RunAs -Wait

                    Write-EnhancedLog -Message 'MsiZap process completed' -Level 'INFO'
                } else {
                    Write-EnhancedLog -Message 'No matching software found' -Level 'WARNING'
                }
            } else {
                Write-EnhancedLog -Message "MsiZap.exe not found at path: $msiZapPath" -Level 'ERROR'
            }
        } catch {
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'Remove-FortiSoftware function completed' -Level 'INFO'
    }
}

# # Example usage of Remove-FortiSoftware function with splatting
# $params = @{
# ScriptRoot = $PSScriptRoot
# SoftwareName = '*forti*'
# MsiZapFileName = 'MsiZap.Exe'
# ArgumentTemplate= 'TW! {IdentifyingNumber}'
# }
# Remove-FortiSoftware @params
#EndRegion '.\Public\Remove-FortiSoftware.ps1' 54
#Region '.\Public\Remove-Hybrid.ps1' -1

function Remove-Hybrid {
    [CmdletBinding()]
    param ()

    Begin {
        Write-EnhancedLog -Message "Starting Remove-Hybrid function" -Level "INFO"
    }

    Process {
        try {
          
            # $Dsregcmd = New-Object PSObject
            # Dsregcmd /status | Where-Object { $_ -match ' : ' } | ForEach-Object {
            # $Item = $_.Trim() -split '\s:\s'
            # $Dsregcmd | Add-Member -MemberType NoteProperty -Name $($Item[0] -replace '[:\s]', '') -Value $Item[1] -ErrorAction SilentlyContinue
            # }

            # $AzureADJoined = $Dsregcmd.AzureAdJoined

            Write-EnhancedLog -Message "Checking if device is Hyrbid Azure AD joined" -Level "INFO"

            # Main script execution block
            $dsregStatus = Get-DSRegStatus

            # Determine and output the join status
            if ($dsregStatus.IsWorkgroup) {
                Write-EnhancedLog -Message "Device is Workgroup joined (not Azure AD, Hybrid, or On-prem Joined)."
            }
            elseif ($dsregStatus.IsAzureADJoined -and -not $dsregStatus.IsHybridJoined) {
                Write-EnhancedLog -Message "Device is Azure AD Joined."
            }
            elseif ($dsregStatus.IsHybridJoined) {
                Write-EnhancedLog -Message "Device is Hybrid Joined (both On-prem and Azure AD Joined)."
            }
            elseif ($dsregStatus.IsOnPremJoined) {
                Write-EnhancedLog -Message "Device is On-prem Joined only."
            }

            # Determine and output the MDM enrollment status
            if ($dsregStatus.IsMDMEnrolled) {
                Write-EnhancedLog -Message "Device is Intune Enrolled."
            }
            else {
                Write-EnhancedLog -Message "Device is NOT Intune Enrolled."
            }

            # Exit code based on Azure AD and MDM status
            if ($dsregStatus.IsAzureADJoined -and -not $dsregStatus.IsHybridJoined -and $dsregStatus.IsMDMEnrolled) {
                Write-EnhancedLog -Message "Device is Azure AD Joined and Intune Enrolled. No migration needed." -Level "INFO"
                # exit 0 # Do not migrate: Device is Azure AD Joined and Intune Enrolled
            }
            else {
                # Migrate: All other cases where the device is not 100% Azure AD joined or is hybrid/on-prem joined
                # exit 1
            }

            if ($dsregStatus.IsHybridJoined) {
                Write-EnhancedLog -Message "Device is Hybrid Azure AD joined. Removing hybrid join." -Level "INFO"
                & "C:\Windows\System32\dsregcmd.exe" /leave
            }

        
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while removing hybrid join: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Remove-Hybrid function" -Level "INFO"
    }
}

# Example usage
# Remove-Hybrid
#EndRegion '.\Public\Remove-Hybrid.ps1' 77
#Region '.\Public\Remove-InstalledPPKG.ps1' -1

function Remove-InstalledPPKG {
    <#
    .SYNOPSIS
    Removes an installed provisioning package by its PackageName.
 
    .DESCRIPTION
    This function checks if a provisioning package is installed based on the provided package name.
    If the package is installed, it retrieves the PackageId and removes it from the system, with detailed
    logging and error handling.
 
    .PARAMETER PackageName
    The name of the provisioning package to remove.
 
    .EXAMPLE
    Remove-InstalledPPKG -PackageName "MyProvisioningPackage"
    # Removes the provisioning package with the name "MyProvisioningPackage".
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Specify the name of the provisioning package to remove.")]
        [ValidateNotNullOrEmpty()]
        [string]$PackageName
    )

    Begin {
        Write-EnhancedLog -Message "Starting Remove-InstalledPPKG function for package: $PackageName" -Level "INFO"
    }

    Process {
        try {
            # Write-EnhancedLog -Message "Provisioning package $PackageName is already installed, attempting to remove it." -Level "WARNING"

            # Log the attempt to fetch installed package information
            Write-EnhancedLog -Message "Retrieving installed package information for package: $PackageName" -Level "INFO"

            # Retrieve the correct PackageId from the installed package information
            $installedPackage = Get-ProvisioningPackage -AllInstalledPackages | Where-Object { $_.PackageName -like "*$PackageName*" }

            # Check if the installed package information is found
            if ($installedPackage) {
                $installedPackageId = $installedPackage.PackageId
                Write-EnhancedLog -Message "Found installed package ID: $installedPackageId" -Level "INFO"

                try {
                    # Log the attempt to remove the package
                    Write-EnhancedLog -Message "Attempting to remove provisioning package with ID: $installedPackageId" -Level "INFO"

                    # Remove the installed provisioning package using PackageId
                    Remove-ProvisioningPackage -PackageID $installedPackageId

                    # Log success of removal
                    Write-EnhancedLog -Message "Provisioning package removed successfully with ID: $installedPackageId" -Level "INFO"
                }
                catch {
                    # Log error during package removal
                    Write-EnhancedLog -Message "Error during provisioning package removal using ID $installedPackageId $($_.Exception.Message)" -Level "ERROR"
                    Handle-Error -ErrorRecord $_
                    throw $_
                }
            }
            else {
                # Log if the installed package information could not be found
                Write-EnhancedLog -Message "Error: Unable to find the installed provisioning package with name: $PackageName" -Level "ERROR"
                throw "Provisioning package information not found for removal."
            }
        }
        catch {
            # General error handling for the entire removal process
            Write-EnhancedLog -Message "An error occurred while trying to remove the installed provisioning package: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Remove-InstalledPPKG function for package: $PackageName" -Level "INFO"
    }
}

# Example usage:
# Remove-InstalledPPKG -PackageName "ICTC_Project_2"
#EndRegion '.\Public\Remove-InstalledPPKG.ps1' 83
#Region '.\Public\Remove-IntuneMgmt.ps1' -1

# Main function to remove Intune management
function Remove-IntuneMgmt {
    [CmdletBinding()]
    param (
        [string]$OMADMPath,
        [string]$EnrollmentBasePath,
        [string]$TrackedBasePath,
        [string]$PolicyManagerBasePath,
        [string]$ProvisioningBasePath,
        [string]$CertCurrentUserPath,
        [string]$CertLocalMachinePath,
        [string]$TaskPathBase,
        [string]$MSDMProviderID,
        [string[]]$RegistryPathsToRemove,
        [string]$UserCertIssuer,
        [string[]]$DeviceCertIssuers
    )

    Begin {
        # Initialize counters and summary table
        $successCount = 0
        $warningCount = 0
        $errorCount = 0
        $summaryTable = [System.Collections.Generic.List[PSCustomObject]]::new()

        Write-EnhancedLog -Message "Starting Remove-IntuneMgmt function" -Level "INFO"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Check Intune enrollment
            $Account = Check-IntuneEnrollment -OMADMPath $OMADMPath -EnrollmentBasePath $EnrollmentBasePath -MSDMProviderID $MSDMProviderID
            if ($null -eq $Account) {
                Write-EnhancedLog -Message "Device is not enrolled in Intune. No further action required." -Level "INFO"
                $warningCount++
                return
            }

            # Perform cleanup tasks
            $summaryTable.Add((Remove-ScheduledTasks -TaskPathBase $TaskPathBase -Account $Account))
            $summaryTable.Add((Remove-RegistryKeys -RegistryPathsToRemove $RegistryPathsToRemove -Account $Account))
            $summaryTable.Add((Remove-UserCertificates -CertCurrentUserPath $CertCurrentUserPath -UserCertIssuer $UserCertIssuer))
            $summaryTable.Add((Remove-DeviceCertificates -CertLocalMachinePath $CertLocalMachinePath -DeviceCertIssuers $DeviceCertIssuers))
            $successCount++
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while removing Intune management: $($_.Exception.Message)" -Level "ERROR"
            $errorCount++
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        # Generate final summary report
        # Define a hashtable for splatting
        $reportParams = @{
            successCount = $successCount
            warningCount = $warningCount
            errorCount   = $errorCount
            summaryTable = $summaryTable
        }

        # Call the function using the splat
        Generate-RemoveIntuneMgmtSummaryReport @reportParams


        Write-EnhancedLog -Message "Exiting Remove-IntuneMgmt function" -Level "INFO"
    }
}


# $RemoveIntuneMgmtParams = @{
# OMADMPath = "HKLM:\SOFTWARE\Microsoft\Provisioning\OMADM\Accounts\*"
# EnrollmentBasePath = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments"
# TrackedBasePath = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\EnterpriseResourceManager\Tracked"
# PolicyManagerBasePath = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PolicyManager"
# ProvisioningBasePath = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning"
# CertCurrentUserPath = "cert:\CurrentUser"
# CertLocalMachinePath = "cert:\LocalMachine"
# TaskPathBase = "\Microsoft\Windows\EnterpriseMgmt"
# MSDMProviderID = "MS DM Server"
# RegistryPathsToRemove = @(
# "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments",
# "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments\Status",
# "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\EnterpriseResourceManager\Tracked",
# "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PolicyManager\AdmxInstalled",
# "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PolicyManager\Providers",
# "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning\OMADM\Accounts",
# "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning\OMADM\Logger",
# "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning\OMADM\Sessions"
# )
# UserCertIssuer = "CN=SC_Online_Issuing"
# DeviceCertIssuers = @("CN=Microsoft Intune Root Certification Authority", "CN=Microsoft Intune MDM Device CA")
# }

# Remove-IntuneMgmt @RemoveIntuneMgmtParams
#EndRegion '.\Public\Remove-IntuneMgmt.ps1' 98
#Region '.\Public\Remove-IntuneWinFiles.ps1' -1

function Remove-IntuneWinFiles {
    <#
    .SYNOPSIS
    Removes all *.intuneWin files from a specified directory.
 
    .DESCRIPTION
    This function searches for all files with the .intuneWin extension
    in the specified directory and removes them. It logs actions taken
    and any errors encountered using the Write-EnhancedLog function.
 
    .PARAMETER DirectoryPath
    The path to the directory from which *.intuneWin files will be removed.
 
    .EXAMPLE
    Remove-IntuneWinFiles -DirectoryPath "d:\Users\aollivierre\AppData\Local\Intune-Win32-Deployer\apps-winget"
    Removes all *.intuneWin files from the specified directory and logs the actions.
 
    .NOTES
    Ensure you have the necessary permissions to delete files in the specified directory.
    #>


    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the directory path from which *.intuneWin files will be removed.")]
        [ValidateNotNullOrEmpty()]
        [string]$DirectoryPath
    )

    Begin {
        Write-EnhancedLog -Message "Starting to remove *.intuneWin files from $DirectoryPath recursively." -Level "INFO"

        # Validate directory existence
        if (-not (Test-Path -Path $DirectoryPath)) {
            Write-EnhancedLog -Message "Directory not found: $DirectoryPath" -Level "ERROR"
            throw "Directory not found: $DirectoryPath"
        }
    }

    Process {
        try {
            # Get the list of *.intuneWin files recursively
            Write-EnhancedLog -Message "Searching for *.intuneWin files in $DirectoryPath..." -Level "INFO"
            $files = Get-ChildItem -Path $DirectoryPath -Filter "*.intuneWin" -Recurse -ErrorAction Stop

            if ($files.Count -eq 0) {
                Write-EnhancedLog -Message "No *.intuneWin files found in $DirectoryPath." -Level "INFO"
            }
            else {
                foreach ($file in $files) {
                    if ($PSCmdlet.ShouldProcess($file.FullName, "Remove *.intuneWin file")) {
                        Remove-Item -Path $file.FullName -Force -ErrorAction Stop
                        Write-EnhancedLog -Message "Removed file: $($file.FullName)" -Level "INFO"
                    }
                }
            }
        }
        catch {
            Write-EnhancedLog -Message "Error removing *.intuneWin files: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_  # Re-throwing the error for further handling
        }
    }

    End {
        Write-EnhancedLog -Message "Completed removal of *.intuneWin files from $DirectoryPath." -Level "INFO"
    }
}
#EndRegion '.\Public\Remove-IntuneWinFiles.ps1' 68
#Region '.\Public\Remove-LocalUserAccount.ps1' -1

function Remove-LocalUserAccount {
    <#
    .SYNOPSIS
    Removes a specified local user account.
 
    .DESCRIPTION
    The Remove-LocalUserAccount function removes a specified local user account if it exists.
 
    .PARAMETER UserName
    The name of the local user account to be removed.
 
    .EXAMPLE
    $params = @{
        UserName = "MigrationInProgress"
    }
    Remove-LocalUserAccount @params
    Removes the local user account named "MigrationInProgress".
    #>


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

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

        # Check if running in PowerShell 7 or later and import LocalAccounts
        if ($PSVersionTable.PSVersion.Major -ge 7) {
            Write-EnhancedLog -Message "Running in PowerShell 7. Importing LocalAccounts module using Windows PowerShell..." -Level "INFO"
            try {
                Import-Module -Name Microsoft.PowerShell.LocalAccounts -UseWindowsPowerShell -ErrorAction Stop
            }
            catch {
                Write-EnhancedLog -Message "Failed to import LocalAccounts module: $($_.Exception.Message)" -Level "ERROR"
                throw
            }
        }
    }

    Process {
        try {
            # Check if the user account exists
            $user = Get-LocalUser -Name $UserName -ErrorAction SilentlyContinue

            if ($null -eq $user) {
                Write-EnhancedLog -Message "User account '$UserName' does not exist. No action taken." -Level "WARNING"
            }
            else {
                if ($PSCmdlet.ShouldProcess("User Account", "Removing user account '$UserName'")) {
                    Write-EnhancedLog -Message "Removing local user account: $UserName" -Level "INFO"
                    Remove-LocalUser -Name $UserName -ErrorAction Stop
                    Write-EnhancedLog -Message "Successfully removed local user account: $UserName" -Level "INFO"
                }
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Remove-LocalUserAccount function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Remove-LocalUserAccount function" -Level "Notice"
    }
}

# # Example usage
# $params = @{
# UserName = "MigrationInProgress"
# }
# Remove-LocalUserAccount @params
#EndRegion '.\Public\Remove-LocalUserAccount.ps1' 76
#Region '.\Public\Remove-LogsFolder.ps1' -1

function Remove-LogsFolder {
    <#
    .SYNOPSIS
    Removes the logs folder located at C:\Logs and validates the removal.
 
    .DESCRIPTION
    The Remove-LogsFolder function removes the logs folder located at C:\Logs using the Remove-EnhancedItem function. It validates the existence of the folder before and after removal to ensure successful deletion.
 
    .PARAMETER LogFolderPath
    The path of the logs folder to be removed. Default is C:\Logs.
 
    .PARAMETER MaxRetries
    The maximum number of retries to attempt for removal. Default is 5.
 
    .PARAMETER RetryInterval
    The interval in seconds between retries. Default is 10 seconds.
 
    .EXAMPLE
    Remove-LogsFolder
    Removes the logs folder at C:\Logs.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]$LogFolderPath = "C:\Logs",

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

        [Parameter(Mandatory = $false)]
        [int]$RetryInterval = 10
    )

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

    Process {
        try {
            # Validate before removal
            $validationResultsBefore = Validate-PathExistsWithLogging -Paths $LogFolderPath

            if ($validationResultsBefore.TotalValidatedFiles -gt 0) {
                Write-EnhancedLog -Message "Attempting to remove logs folder: $LogFolderPath" -Level "INFO"
                
                $removeParams = @{
                    Path               = $LogFolderPath
                    ForceKillProcesses = $true
                    MaxRetries         = $MaxRetries
                    RetryInterval      = $RetryInterval
                }
                Remove-EnhancedItem @removeParams

                # Validate after removal
                $validationResultsAfter = Validate-PathExistsWithLogging -Paths $LogFolderPath

                if ($validationResultsAfter.TotalValidatedFiles -gt 0) {
                    Write-EnhancedLog -Message "Logs folder $LogFolderPath still exists after attempting to remove. Manual intervention may be required." -Level "ERROR"
                }
                else {
                    Write-EnhancedLog -Message "Logs folder $LogFolderPath successfully removed." -Level "CRITICAL"
                }
            }
            else {
                Write-EnhancedLog -Message "Logs folder $LogFolderPath does not exist. No action taken." -Level "WARNING"
            }
        }
        catch {
            Write-EnhancedLog -Message "Error during removal of logs folder at path: $LogFolderPath. Error: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Remove-LogsFolder function" -Level "Notice"
    }
}

# Example usage
# Remove-LogsFolder
#EndRegion '.\Public\Remove-LogsFolder.ps1' 83
#Region '.\Public\Remove-MGApplication-Run-InterActivefromConsole.ps1' -1

# # Import the module
# Import-Module Microsoft.Graph.Applications

# # Connect to Microsoft Graph
# Connect-MgGraph -Scopes "Application.ReadWrite.All"

# # Get and remove applications starting with 'GraphApp-Test001'
# Get-MgApplication -Filter "startswith(displayName, 'GraphApp-Test001')" | ForEach-Object {
# Remove-MgApplication -ApplicationId $_.Id -Confirm:$false
# }

# # Disconnect the session
# Disconnect-MgGraph
#EndRegion '.\Public\Remove-MGApplication-Run-InterActivefromConsole.ps1' 14
#Region '.\Public\Remove-MigrationFiles.ps1' -1

function Remove-MigrationFiles {
    <#
    .SYNOPSIS
    Removes specified directories used during the migration process.
   
    .DESCRIPTION
    The Remove-MigrationFiles function deletes specified directories used during the migration process, leaving the log folder intact.
   
    .PARAMETER Directories
    An array of directories to be removed.
   
    .EXAMPLE
    $params = @{
        Directories = @(
            "C:\ProgramData\AADMigration\Files",
            "C:\ProgramData\AADMigration\Scripts",
            "C:\ProgramData\AADMigration\Toolkit"
        )
    }
    Remove-MigrationFiles @params
    Removes the specified directories.
    #>

  
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string[]]$Directories
    )
  
    Begin {
        Write-EnhancedLog -Message "Starting Remove-MigrationFiles function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }
  
    Process {
        try {
            foreach ($directory in $Directories) {
                if (Test-Path -Path $directory) {
                    Write-EnhancedLog -Message "Removing directory: $directory" -Level "INFO"
                    $removeParams = @{
                        Path               = $directory
                        ForceKillProcesses = $true
                        MaxRetries         = 5
                        RetryInterval      = 10
                    }
                    Remove-EnhancedItem @removeParams
                    Write-EnhancedLog -Message "Successfully removed directory: $directory" -Level "INFO"
                }
                else {
                    Write-EnhancedLog -Message "Directory not found: $directory" -Level "WARNING"
                }
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Remove-MigrationFiles function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }
  
    End {
        Write-EnhancedLog -Message "Exiting Remove-MigrationFiles function" -Level "Notice"
    }
}


# Example usage
# $params = @{
# Directories = @(
# "C:\ProgramData\AADMigration\Files",
# "C:\ProgramData\AADMigration\Scripts",
# "C:\ProgramData\AADMigration\Toolkit"
# )
# }
# Remove-MigrationFiles @params
#EndRegion '.\Public\Remove-MigrationFiles.ps1' 77
#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\Remove-OrphanedSIDs-Archive.ps1' -1


# function Remove-OrphanedSIDs {
# <#
# .SYNOPSIS
# Removes orphaned SIDs from the specified group.

# .DESCRIPTION
# The Remove-OrphanedSIDs function checks for orphaned SIDs in a specified group, typically 'Administrators'. It attempts to remove any orphaned SIDs found and logs the process.

# .PARAMETER GroupName
# The name of the group from which orphaned SIDs should be removed. Defaults to "Administrators".

# .EXAMPLE
# Remove-OrphanedSIDs -GroupName "Administrators"
# Removes orphaned SIDs from the Administrators group.
# #>

# [CmdletBinding()]
# param (
# [Parameter(Mandatory = $false, HelpMessage = "Provide the group name to remove orphaned SIDs from.")]
# [string]$GroupName = "Administrators"
# )

# Begin {
# Write-EnhancedLog -Message "Starting Remove-OrphanedSIDs function" -Level "Notice"
# Write-EnhancedLog -Message "Checking for orphaned SIDs in the '$GroupName' group." -Level "INFO"

# # Get orphaned SIDs using the previous function
# $orphanedSIDs = Get-OrphanedSIDs -GroupName $GroupName

# # Check if any orphaned SIDs were found
# if ($orphanedSIDs.Count -eq 0) {
# Write-EnhancedLog -Message "No orphaned SIDs found in the '$GroupName' group." -Level "INFO"
# } else {
# Write-EnhancedLog -Message "Found $($orphanedSIDs.Count) orphaned SIDs in the '$GroupName' group." -Level "INFO"
# }
# }

# Process {
# # Proceed if orphaned SIDs were found
# if ($orphanedSIDs.Count -gt 0) {
# foreach ($orphanedSID in $orphanedSIDs) {
# try {
# # Log the attempt to remove the SID
# Write-EnhancedLog -Message "Attempting to remove orphaned SID: $($orphanedSID.AccountName) from '$GroupName'." -Level "INFO"

# # Remove the orphaned SID from the group
# Remove-LocalGroupMember -Group $GroupName -Member $orphanedSID.AccountName -ErrorAction Stop

# # Log successful removal
# Write-EnhancedLog -Message "Successfully removed orphaned SID: $($orphanedSID.AccountName)." -Level "INFO"
# } catch {
# # Log any errors during removal
# Write-EnhancedLog -Message "Failed to remove orphaned SID: $($orphanedSID.AccountName). Error: $($_.Exception.Message)" -Level "ERROR"
# Handle-Error -ErrorRecord $_
# }
# }
# }
# }

# End {
# Write-EnhancedLog -Message "Exiting Remove-OrphanedSIDs function" -Level "Notice"
# }
# }

# # Example usage
# # Remove-OrphanedSIDs -GroupName "Administrators"
#EndRegion '.\Public\Remove-OrphanedSIDs-Archive.ps1' 68
#Region '.\Public\Remove-OrphanedSIDsFromAdministratorsGroup-Archive.ps1' -1

# function Remove-OrphanedSIDsFromAdministratorsGroup {
# <#
# .SYNOPSIS
# Removes orphaned SIDs from the local "Administrators" group.

# .DESCRIPTION
# This function iterates through the members of the local "Administrators" group and removes any orphaned SIDs, which are typically leftover accounts no longer associated with a user.

# .EXAMPLE
# Remove-OrphanedSIDsFromAdministratorsGroup

# This will check the local "Administrators" group for any orphaned SIDs and remove them.

# .NOTES
# Author: Abdullah Ollivierre
# Date: 2024-09-06

# #>

# # Access the local "Administrators" group using ADSI
# $group = [ADSI]"WinNT://./Administrators,group"

# # Retrieve the members of the group
# $members = $group.psbase.Invoke("Members")

# # Iterate through each member of the group
# foreach ($member in $members) {
# try {
# # Try to resolve the member (if this works, the member is valid)
# $memberPath = $member.GetType().InvokeMember('AdsPath', 'GetProperty', $null, $member, $null)

# # Check if the member is an orphaned SID by checking the format of the AdsPath
# if ($memberPath -like "*S-1-*") {
# Write-Host "Orphaned SID detected: $memberPath" -ForegroundColor Yellow

# # Extract just the SID from the path and remove it
# $orphanedSID = $memberPath -replace "^.*?S-1-", "S-1-"
# try {
# $group.Remove("WinNT://$orphanedSID")
# Write-Host "Successfully removed orphaned SID: $orphanedSID" -ForegroundColor Green
# }
# catch {
# Write-Host "Failed to remove orphaned SID: $($_.Exception.Message)" -ForegroundColor Red
# }
# }
# else {
# Write-Host "Valid member: $memberPath"
# }
# }
# catch {
# # If we cannot resolve the member, it is an orphaned SID
# Write-Host "Failed to resolve member, skipping." -ForegroundColor Red
# }
# }
# }


# # Remove-OrphanedSIDsFromAdministratorsGroup
#EndRegion '.\Public\Remove-OrphanedSIDsFromAdministratorsGroup-Archive.ps1' 59
#Region '.\Public\Remove-RegistryKeys.ps1' -1


# Function to remove registry keys
function Remove-RegistryKeys {
    param (
        [string[]]$RegistryPathsToRemove,
        [string]$Account
    )

    Write-EnhancedLog -Message "Attempting to remove registry keys associated with enrollment" -Level "INFO"
    foreach ($RegistryPath in $RegistryPathsToRemove) {
        Write-EnhancedLog -Message "Removing registry path: $RegistryPath\$Account" -Level "INFO"
        Remove-Item -Path "$RegistryPath\$Account" -Recurse -Force -ErrorAction SilentlyContinue
    }
    Write-EnhancedLog -Message "Registry keys removed successfully (if any existed)" -Level "INFO"

    return [PSCustomObject]@{
        Action = "Remove Registry Keys"
        Path   = "$RegistryPath\$Account"
        Status = "Success"
    }
}
#EndRegion '.\Public\Remove-RegistryKeys.ps1' 22
#Region '.\Public\Remove-RegistryPath.ps1' -1

function Remove-RegistryPath {
    param (
        [string]$RegistryPath
    )

    Write-EnhancedLog -Message "Starting Remove-RegistryPath function for: $RegistryPath" -Level 'INFO'

    try {
        if (Test-Path -Path "Registry::$RegistryPath") {
            Remove-Item -Path "Registry::$RegistryPath" -Recurse -Force
            Write-EnhancedLog -Message "Successfully removed registry path: $RegistryPath" -Level 'INFO'
            Write-Output "Successfully removed registry path: $RegistryPath"
        } else {
            Write-EnhancedLog -Message "Registry path not found: $RegistryPath" -Level 'WARNING'
            Write-Output "Registry path not found: $RegistryPath"
        }
    } catch {
        Handle-Error -ErrorRecord $_
    } finally {
        Write-EnhancedLog -Message 'Remove-RegistryPath function completed' -Level 'INFO'
    }
}
#EndRegion '.\Public\Remove-RegistryPath.ps1' 23
#Region '.\Public\Remove-ScheduledTaskFilesWithLogging.ps1' -1

function Remove-ScheduledTaskFilesWithLogging {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Path
    )

    Begin {
        Write-EnhancedLog -Message "Starting Remove-ScheduledTaskFilesWithLogging function" -Level "INFO"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Validate before removal
            $validationResultsBefore = Validate-PathExistsWithLogging -Paths $Path

            if ($validationResultsBefore.TotalValidatedFiles -gt 0) {
                Write-EnhancedLog -Message "Calling Remove-Item for path: $Path" -Level "INFO"
                # Remove-Item -Path $Path -Recurse -Force

                if (Test-Path $Path) {
                    $removeParams = @{
                        Path               = $Path
                        ForceKillProcesses = $true
                        MaxRetries         = 5
                        RetryInterval      = 10
                    }
                    Remove-EnhancedItem @removeParams
                    Write-EnhancedLog -Message "Removed existing destination path: $finalDestinationPath" -Level "INFO"
                }
    


                # Validate after removal
                $validationResultsAfter = Validate-PathExistsWithLogging -Paths $Path

                if ($validationResultsAfter.TotalValidatedFiles -gt 0) {
                    Write-EnhancedLog -Message "Path $Path still exists after attempting to remove. Manual intervention may be required." -Level "ERROR"
                }
                else {
                    Write-EnhancedLog -Message "All files within $Path successfully removed." -Level "CRITICAL"
                }
            }
            else {
                Write-EnhancedLog -Message "Path $Path does not exist. No action taken." -Level "WARNING"
            }
        }
        catch {
            Write-EnhancedLog -Message "Error during Remove-Item for path: $Path. Error: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Remove-ScheduledTaskFilesWithLogging function" -Level "INFO"
    }
}

# Example usage
# Remove-ScheduledTaskFilesWithLogging -Path "C:\Path\To\ScheduledTaskFiles"
#EndRegion '.\Public\Remove-ScheduledTaskFilesWithLogging.ps1' 62
#Region '.\Public\Remove-ScheduledTasks.ps1' -1


# Function to remove scheduled tasks
function Remove-ScheduledTasks {
    param (
        [string]$TaskPathBase,
        [string]$Account
    )

    Write-EnhancedLog -Message "Attempting to remove scheduled tasks at path $TaskPathBase\$Account" -Level "INFO"
    Get-ScheduledTask -TaskPath "$TaskPathBase\$Account\*" | Unregister-ScheduledTask -Confirm:$false -ErrorAction SilentlyContinue
    Write-EnhancedLog -Message "Scheduled tasks removed successfully (if any existed)" -Level "INFO"

    return [PSCustomObject]@{
        Action = "Remove Scheduled Tasks"
        Path   = "$TaskPathBase\$Account"
        Status = "Success"
    }
}
#EndRegion '.\Public\Remove-ScheduledTasks.ps1' 19
#Region '.\Public\Remove-UserCertificates.ps1' -1


# Function to remove user certificates
function Remove-UserCertificates {
    param (
        [string]$CertCurrentUserPath,
        [string]$UserCertIssuer
    )

    Write-EnhancedLog -Message "Attempting to remove user certificates with issuer: $UserCertIssuer" -Level "INFO"
    $UserCerts = Get-ChildItem -Path $CertCurrentUserPath -Recurse
    $IntuneCerts = $UserCerts | Where-Object { $_.Issuer -eq $UserCertIssuer }
    foreach ($Cert in $IntuneCerts) {
        Write-EnhancedLog -Message "Removing certificate: $($Cert.Subject)" -Level "INFO"
        $Cert | Remove-Item -Force
    }
    Write-EnhancedLog -Message "User certificates removed successfully" -Level "INFO"

    return [PSCustomObject]@{
        Action = "Remove User Certificates"
        Path   = $CertCurrentUserPath
        Status = "Success"
    }
}
#EndRegion '.\Public\Remove-UserCertificates.ps1' 24
#Region '.\Public\Rename-PSFLogFilesWithUsername.ps1' -1

function Rename-PSFLogFilesWithUsername {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$LogDirectoryPath,

        [Parameter(Mandatory = $true, Position = 1)]
        [string]$ParentScriptName,

        [Parameter(Mandatory = $true, Position = 2)]
        [string]$JobName
    )

    Begin {
        Write-EnhancedLog -Message "Starting Rename-PSFLogFilesWithUsername function..." -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        Write-EnhancedLog -Message "Starting the renaming process for log files in directory: $LogDirectoryPath" -Level "INFO"

        # Get all log files in the specified directory
        $logFiles = Get-ChildItem -Path $LogDirectoryPath -Filter "*.log"

        if ($logFiles.Count -eq 0) {
            Write-EnhancedLog -Message "No log files found in the directory." -Level "WARNING"
            return
        }

        Write-EnhancedLog -Message "Found $($logFiles.Count) log files to process." -Level "INFO"

        foreach ($logFile in $logFiles) {
            try {
                Write-EnhancedLog -Message "Processing file: $($logFile.FullName)" -Level "INFO"

                # Import the log file as CSV
                $logEntries = Import-Csv -Path $logFile.FullName

                # Extract the username, timestamp, and computer name from the first entry in the CSV
                $firstEntry = $logEntries | Select-Object -First 1
                $username = $firstEntry.Username
                $timestamp = $firstEntry.Timestamp
                $computerName = $firstEntry.ComputerName

                if (-not $username) {
                    Write-EnhancedLog -Message "No username found in $($logFile.Name). Skipping file." -Level "WARNING"
                    continue
                }

                if (-not $timestamp) {
                    $timestamp = "NoTimestamp"
                    Write-EnhancedLog -Message "No timestamp found in $($logFile.Name). Defaulting to 'NoTimestamp'." -Level "WARNING"
                }

                if (-not $computerName) {
                    $computerName = "UnknownComputer"
                    Write-EnhancedLog -Message "No computer name found in $($logFile.Name). Defaulting to 'UnknownComputer'." -Level "WARNING"
                }

                Write-EnhancedLog -Message "Username found in file: $username" -Level "INFO"
                Write-EnhancedLog -Message "Timestamp found in file: $timestamp" -Level "INFO"
                Write-EnhancedLog -Message "Computer name found in file: $computerName" -Level "INFO"

                # Remove the domain part if present in the username
                if ($username -match '^[^\\]+\\(.+)$') {
                    $username = $matches[1]
                }

                Write-EnhancedLog -Message "Processed username: $username" -Level "INFO"

                # Sanitize the username, timestamp, and computer name
                $safeUsername = $username -replace '[\\/:*?"<>|]', '_'
                $safeTimestamp = $timestamp -replace '[\\/:*?"<>|]', '_'
                $safeComputerName = $computerName -replace '[\\/:*?"<>|]', '_'

                Write-EnhancedLog -Message "Sanitized username: $safeUsername" -Level "INFO"
                Write-EnhancedLog -Message "Sanitized timestamp: $safeTimestamp" -Level "INFO"
                Write-EnhancedLog -Message "Sanitized computer name: $safeComputerName" -Level "INFO"

                # Generate a unique identifier
                $uniqueId = [guid]::NewGuid().ToString()

                # Construct the new file name with the GUID
                $fileExtension = [System.IO.Path]::GetExtension($logFile.FullName)
                $newFileName = "$JobName-$safeUsername-$safeComputerName-$ParentScriptName-$safeTimestamp-$uniqueId$fileExtension"
                $newFilePath = [System.IO.Path]::Combine($logFile.DirectoryName, $newFileName)
                Write-EnhancedLog -Message "Attempting to rename to: $newFilePath" -Level "INFO"

                # Rename the file
                Rename-Item -Path $logFile.FullName -NewName $newFileName -Force
                Write-EnhancedLog -Message "Successfully renamed $($logFile.FullName) to $newFileName" -Level "INFO"
            }
            catch {
                Write-EnhancedLog -Message "Failed to process $($logFile.FullName): $_" -Level "ERROR"
                Write-EnhancedLog -Message "Possible cause: The file name or path may contain invalid characters or the file might be in use." -Level "ERROR"
            }
        }

        Write-EnhancedLog -Message "Finished processing all log files." -Level "INFO"
    }

    End {
        Write-EnhancedLog -Message "Exiting Rename-PSFLogFilesWithUsername function" -Level "NOTICE"
    }
}
#EndRegion '.\Public\Rename-PSFLogFilesWithUsername.ps1' 106
#Region '.\Public\Replace-BannerImage.ps1' -1

function Replace-BannerImage {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Source,

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

    Begin {
        Write-EnhancedLog -Message "Starting Replace-BannerImage function" -Level "INFO"
        Log-Params -Params @{
            Source = $Source
            Destination = $Destination
        }
    }

    Process {
        try {
            # Replace the banner image in the toolkit folder
            Copy-Item -Path $Source -Destination $Destination -Force
        } catch {
            Write-EnhancedLog -Message "An error occurred while processing the Replace-BannerImage function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Replace-BannerImage function" -Level "INFO"
    }
}

# Example usage
# Replace-BannerImage -Source 'C:\YourPath\YourBannerImage.png' -Destination 'C:\YourPath\Toolkit\AppDeployToolkit\AppDeployToolkitBanner.png'
#EndRegion '.\Public\Replace-BannerImage.ps1' 36
#Region '.\Public\Replace-DeployApplicationPS1.ps1' -1

function Replace-DeployApplicationPS1 {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Source,

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

    Begin {
        Write-EnhancedLog -Message "Starting Replace-DeployApplicationPS1 function" -Level "INFO"
        Log-Params -Params @{
            Source = $Source
            Destination = $Destination
        }
    }

    Process {
        try {
            # Replace Deploy-Application.ps1 in the toolkit folder
            Copy-Item -Path $Source -Destination $Destination -Force
        } catch {
            Write-EnhancedLog -Message "An error occurred while processing the Replace-DeployApplicationPS1 function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Replace-DeployApplicationPS1 function" -Level "INFO"
    }
}

# Example usage
# Replace-DeployApplicationPS1 -Source 'C:\YourPath\Scripts\Deploy-Application.ps1' -Destination 'C:\YourPath\Toolkit\Deploy-Application.ps1'
#EndRegion '.\Public\Replace-DeployApplicationPS1.ps1' 36
#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\Resolve-SID-Archive.ps1' -1

# function Resolve-SID {
# param (
# [string]$AccountName
# )

# try {
# $account = Get-WmiObject -Class Win32_UserAccount -Filter "Name='$AccountName'"
# if ($account) {
# # Use the SID directly
# $sid = New-Object System.Security.Principal.SecurityIdentifier($account.SID)
# return $sid
# } else {
# Write-EnhancedLog -Message "Unable to resolve SID for $AccountName." -Level "WARNING"
# return $null
# }
# }
# catch {
# Write-EnhancedLog -Message "Error resolving SID for $AccountName $_" -Level "ERROR"
# return $null
# }
# }
#EndRegion '.\Public\Resolve-SID-Archive.ps1' 22
#Region '.\Public\Restart-ComputerIfNeeded.ps1' -1

function Restart-ComputerIfNeeded {
    <#
    .SYNOPSIS
    Restarts the computer if necessary.
 
    .DESCRIPTION
    This function forces a restart of the computer and logs the process. It is typically used after significant system changes that require a reboot.
 
    .EXAMPLE
    Restart-ComputerIfNeeded
 
    Forces a computer restart and logs the process.
 
    .NOTES
    This function should be used with caution as it forces an immediate restart.
    #>


    [CmdletBinding()]
    param ()

    Begin {
        Write-EnhancedLog -Message "Starting Restart-ComputerIfNeeded function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            Write-EnhancedLog -Message "Restarting computer..." -Level "INFO"
            Restart-Computer -Force
        }
        catch {
            Write-EnhancedLog -Message "An error occurred during the restart process: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
        finally {
            Write-EnhancedLog -Message "Exiting Restart-ComputerIfNeeded function" -Level "NOTICE"
        }
    }

    End {
        Write-EnhancedLog -Message "Restart-ComputerIfNeeded function completed" -Level "INFO"
    }
}
#EndRegion '.\Public\Restart-ComputerIfNeeded.ps1' 45
#Region '.\Public\Sanitize-LogFilePath.ps1' -1

function Sanitize-LogFilePath {
    [CmdletBinding()]
    param (
        [string]$LogFilePath
    )

    try {
        Write-EnhancedLog -Message "Starting Sanitize-LogFilePath function..." -Level "NOTICE"
        Write-EnhancedLog -Message "Original LogFilePath: $LogFilePath" -Level "INFO"

        # Trim leading and trailing whitespace
        $LogFilePath = $LogFilePath.Trim()
        Write-EnhancedLog -Message "LogFilePath after trim: $LogFilePath" -Level "INFO"

        # Replace multiple spaces with a single space
        $LogFilePath = $LogFilePath -replace '\s+', ' '
        Write-EnhancedLog -Message "LogFilePath after removing multiple spaces: $LogFilePath" -Level "INFO"

        # Replace illegal characters (preserve drive letter and colon)
        if ($LogFilePath -match '^([a-zA-Z]):\\') {
            $drive = $matches[1]
            $LogFilePath = $LogFilePath -replace '[<>:"|?*]', '_'
            $LogFilePath = "$drive`:$($LogFilePath.Substring(2))"
        }
        else {
            # Handle cases where the path doesn't start with a drive letter
            $LogFilePath = $LogFilePath -replace '[<>:"|?*]', '_'
        }
        Write-EnhancedLog -Message "LogFilePath after replacing invalid characters: $LogFilePath" -Level "INFO"

        # Replace multiple backslashes with a single backslash
        $LogFilePath = [System.Text.RegularExpressions.Regex]::Replace($LogFilePath, '\\+', '\')
        Write-EnhancedLog -Message "LogFilePath after replacing multiple slashes: $LogFilePath" -Level "INFO"

        # Ensure the path is still rooted
        if (-not [System.IO.Path]::IsPathRooted($LogFilePath)) {
            throw "The LogFilePath is not rooted: $LogFilePath"
        }

        Write-EnhancedLog -Message "Sanitized LogFilePath: $LogFilePath" -Level "INFO"
        Write-EnhancedLog -Message "Exiting Sanitize-LogFilePath function" -Level "NOTICE"
        return $LogFilePath
    }
    catch {
        Write-EnhancedLog -Message "An error occurred in Sanitize-LogFilePath: $_" -Level "ERROR"
        Handle-Error -ErrorRecord $_
        throw $_  # Re-throw the error after logging it
    }
}
#EndRegion '.\Public\Sanitize-LogFilePath.ps1' 50
#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\Set-AppIcon.ps1' -1


function Set-AppIcon {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Prg_img
    )

    $Icon = New-IntuneWin32AppIcon -FilePath $Prg_img
    Write-EnhancedLog -Message "App icon set - done" -Level "INFO"

    return $Icon
}
#EndRegion '.\Public\Set-AppIcon.ps1' 13
#Region '.\Public\Set-Autologin.ps1' -1

function Set-Autologin {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$TempUser,

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

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

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

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

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

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

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

    Begin {
        Write-EnhancedLog -Message "Starting Set-Autologin function" -Level "INFO"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            Write-EnhancedLog -Message "Setting user account $TempUser to Auto Login" -Level "INFO"

            $autoLoginParams = @{
                Path  = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
                Name  = "AutoAdminLogon"
                Value = "1"
            }

            if (Test-Path -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\AutoAdminLogon") {
                Remove-ItemProperty @autoLoginParams
            }

            # Set AutoAdminLogon
            Set-ItemProperty -Path $RegPath -Name $AutoAdminLogonName -Value $AutoAdminLogonValue -Type String -Verbose

            # Set DefaultUserName
            Set-ItemProperty -Path $RegPath -Name $DefaultUsernameName -Value $TempUser -Type String -Verbose

            # Set DefaultPassword
            Set-ItemProperty -Path $RegPath -Name $DefaultPasswordName -Value $TempUserPassword -Type String -Verbose

            # Set DefaultDomainName if provided
            if ($PSBoundParameters.ContainsKey('DefaultDomainName')) {
                Set-ItemProperty -Path $RegPath -Name 'DefaultDomainName' -Value $DefaultDomainName -Type String -Verbose
            }

            # Create UserList key if it doesn't exist and add the user
            $userListPath = "$RegPath\SpecialAccounts\UserList"
            if (-not (Test-Path -Path $userListPath)) {
                Write-EnhancedLog -Message "Creating UserList registry path: $userListPath" -Level "INFO"
                New-Item -Path $userListPath -Force
            }
            New-ItemProperty -Path $userListPath -Name $TempUser -Value 0 -PropertyType DWord -Force -Verbose

            Write-EnhancedLog -Message "Auto-login set for user '$TempUser'." -Level 'INFO'
        } catch {
            Write-EnhancedLog -Message "An error occurred while setting autologin: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Set-Autologin function" -Level "INFO"
    }
}

# # Example usage with splatting
# $SetAutologinParams = @{
# TempUser = 'YourTempUser'
# TempUserPassword = 'YourTempUserPassword'
# RegPath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
# AutoAdminLogonName = 'AutoAdminLogon'
# AutoAdminLogonValue = '1'
# DefaultUsernameName = 'DefaultUserName'
# DefaultPasswordName = 'DefaultPassword'
# DefaultDomainName = $env:COMPUTERNAME
# }

# Set-Autologin @SetAutologinParams
#EndRegion '.\Public\Set-Autologin.ps1' 95
#Region '.\Public\Set-InstallCommandLine.ps1' -1

function Set-InstallCommandLine {
    param(
        [Parameter(Mandatory = $true)]
        [pscustomobject]$config
    )

    if ($config.serviceUIPSADT -eq $true) {
        $InstallCommandLine = "ServiceUI.exe -process:explorer.exe Deploy-Application.exe -DeploymentType install -Deploymode Interactive"
        $UninstallCommandLine = "ServiceUI.exe -process:explorer.exe Deploy-Application.exe -DeploymentType Uninstall -Deploymode Interactive"
    }
    elseif ($config.PSADT -eq $true) {
        $InstallCommandLine = "Deploy-Application.exe -DeploymentType install -DeployMode Interactive"
        $UninstallCommandLine = "Deploy-Application.exe -DeploymentType Uninstall -DeployMode Interactive"
    }
    else {
        $InstallCommandLine = "%SystemRoot%\sysnative\WindowsPowerShell\v1.0\powershell.exe -windowstyle hidden -executionpolicy bypass -command .\install.ps1"
        $UninstallCommandLine = "%SystemRoot%\sysnative\WindowsPowerShell\v1.0\powershell.exe -windowstyle hidden -executionpolicy bypass -command .\uninstall.ps1"
    }

    return @{
        InstallCommandLine   = $InstallCommandLine
        UninstallCommandLine = $UninstallCommandLine
    }
}
#EndRegion '.\Public\Set-InstallCommandLine.ps1' 25
#Region '.\Public\Set-LocalPathBasedOnContext.ps1' -1

function Set-LocalPathBasedOnContext {
    Write-EnhancedLog -Message "Checking running context..." -Level "INFO" -ForegroundColor ([System.ConsoleColor]::Cyan)
    if (Test-RunningAsSystem) {
        Write-EnhancedLog -Message "Running as system, setting path to Program Files" -Level "INFO" -ForegroundColor ([System.ConsoleColor]::Yellow)
        # return "$ENV:Programfiles\_MEM"
        return "C:\_MEM"
    }
    else {
        Write-EnhancedLog -Message "Running as user, setting path to Local AppData" -Level "INFO" -ForegroundColor ([System.ConsoleColor]::Yellow)
        return "$ENV:LOCALAPPDATA\_MEM"
    }
}
#EndRegion '.\Public\Set-LocalPathBasedOnContext.ps1' 13
#Region '.\Public\Set-ODKFMRegistrySettings.ps1' -1

function Set-ODKFMRegistrySettings {
    <#
    .SYNOPSIS
    Sets OneDrive Known Folder Move (KFM) registry settings.
 
    .DESCRIPTION
    The Set-ODKFMRegistrySettings function sets specified registry values for OneDrive Known Folder Move (KFM) based on provided tenant ID, registry key path, and an array of registry settings.
 
    .PARAMETER TenantID
    The tenant ID for OneDrive.
 
    .PARAMETER RegKeyPath
    The path to the registry key.
 
    .PARAMETER RegistrySettings
    An array of registry settings to be applied. Each setting should include RegValName, RegValType, and RegValData.
 
    .EXAMPLE
    $settings = @(
        @{
            RegValName = "KFMValue1"
            RegValType = "String"
            RegValData = "Value1"
        },
        @{
            RegValName = "KFMValue2"
            RegValType = "DWORD"
            RegValData = "1"
        }
    )
    $params = @{
        TenantID = "your-tenant-id"
        RegKeyPath = "HKLM:\Software\Policies\Microsoft\OneDrive"
        RegistrySettings = $settings
    }
    Set-ODKFMRegistrySettings @params
    Sets the specified OneDrive KFM registry values.
    #>


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

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

        [Parameter(Mandatory = $true)]
        [array]$RegistrySettings
    )

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

    Process {
        try {

            # Apply the registry settings using the defined hash table
            Apply-RegistrySettings -RegistrySettings $RegistrySettings

        }
        catch {
            Write-EnhancedLog -Message "An error occurred while setting OneDrive KFM registry values: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Set-ODKFMRegistrySettings function" -Level "Notice"
    }
}


# # Example usage
# $settings = @(
# @{
# RegValueName = "KFMValue1"
# RegValType = "String"
# RegValData = "Value1"
# },
# @{
# RegValueName = "KFMValue2"
# RegValType = "DWORD"
# RegValData = "1"
# }
# )
# $params = @{
# TenantID = "your-tenant-id"
# RegKeyPath = "HKLM:\Software\Policies\Microsoft\OneDrive"
# RegistrySettings = $settings
# }
# Set-ODKFMRegistrySettings @params


# $TenantID = "YourTenantID"

# $RegistrySettings = @(
# @{
# RegValueName = "AllowTenantList"
# RegValType = "STRING"
# RegValData = $TenantID
# },
# @{
# RegValueName = "SilentAccountConfig"
# RegValType = "DWORD"
# RegValData = "1"
# },
# @{
# RegValueName = "KFMOptInWithWizard"
# RegValType = "STRING"
# RegValData = $TenantID
# },
# @{
# RegValueName = "KFMSilentOptIn"
# RegValType = "STRING"
# RegValData = $TenantID
# },
# @{
# RegValueName = "KFMSilentOptInDesktop"
# RegValType = "DWORD"
# RegValData = "1"
# },
# @{
# RegValueName = "KFMSilentOptInDocuments"
# RegValType = "DWORD"
# RegValData = "1"
# },
# @{
# RegValueName = "KFMSilentOptInPictures"
# RegValType = "DWORD"
# RegValData = "1"
# }
# )

# $SetODKFMRegistrySettingsParams = @{
# TenantID = $TenantID
# RegKeyPath = "HKLM:\SOFTWARE\Policies\Microsoft\OneDrive"
# RegistrySettings = $RegistrySettings
# }

# Set-ODKFMRegistrySettings @SetODKFMRegistrySettingsParams


#optionally you can create an event source here using Create-EventLogSource.ps1
#EndRegion '.\Public\Set-ODKFMRegistrySettings.ps1' 147
#Region '.\Public\Set-RegistryValue.ps1' -1

function Set-RegistryValue {
    <#
    .SYNOPSIS
    Sets a registry value with validation before and after setting the value.
 
    .DESCRIPTION
    The Set-RegistryValue function sets a registry value at a specified registry path. It validates the value before setting it and ensures it is set correctly after the operation.
 
    .PARAMETER RegKeyPath
    The path to the registry key.
 
    .PARAMETER RegValName
    The name of the registry value.
 
    .PARAMETER RegValType
    The type of the registry value (e.g., String, DWORD).
 
    .PARAMETER RegValData
    The data to be set for the registry value. It can be a string, an integer, or even an empty string.
    #>


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

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

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

        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [string]$RegValData
    )

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

        CheckAndElevate
    }

    Process {
        try {
            # Validate before setting the value
            Write-EnhancedLog -Message "Validating registry value before setting" -Level "INFO"
            $validationBefore = Validate-RegistryValue -RegKeyPath $RegKeyPath -RegValName $RegValName -ExpectedValData $RegValData

            if ($validationBefore) {
                Write-EnhancedLog -Message "Registry value $RegValName is already correctly set. No action taken." -Level "INFO"
                return $true
            }

            # Test to see if registry key exists, if it does not exist create it
            if (-not (Test-Path -Path $RegKeyPath)) {
                New-Item -Path $RegKeyPath -Force | Out-Null
                Write-EnhancedLog -Message "Created registry key: $RegKeyPath" -Level "INFO"
            }

            # Check if value exists and if it needs updating
            try {
                $CurrentValue = Get-ItemPropertyValue -Path $RegKeyPath -Name $RegValName
            }
            catch {
                # If value does not exist, create it
                Set-ItemProperty -Path $RegKeyPath -Name $RegValName -Type $RegValType -Value $RegValData -Force
                Write-EnhancedLog -Message "Created registry value: $RegValName with data: $RegValData" -Level "INFO"
                
                # Validate after setting the value
                $validationAfter = Validate-RegistryValue -RegKeyPath $RegKeyPath -RegValName $RegValName -ExpectedValData $RegValData
                if ($validationAfter) {
                    return $true
                }
                else {
                    return $false
                }
            }

            if ($CurrentValue -ne $RegValData) {
                # If value exists but data is wrong, update the value
                Set-ItemProperty -Path $RegKeyPath -Name $RegValName -Type $RegValType -Value $RegValData -Force
                Write-EnhancedLog -Message "Updated registry value: $RegValName with data: $RegValData" -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "Registry value: $RegValName already has the correct data: $RegValData" -Level "INFO"
            }

            # Validate after setting the value
            $validationAfter = Validate-RegistryValue -RegKeyPath $RegKeyPath -RegValName $RegValName -ExpectedValData $RegValData
            if ($validationAfter) {
                return $true
            }
            else {
                return $false
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Set-RegistryValue function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            return $false
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Set-RegistryValue function" -Level "Notice"
    }
}




# # Define your parameters
# $regKeyPath = "HKCU:\Software\MyApp"
# $regValName = "Setting"
# $regValType = "String"
# $regValData = "Enabled"

# # Call the Set-RegistryValue function and capture the result
# $setRegistryResult = Set-RegistryValue -RegKeyPath $regKeyPath -RegValName $regValName -RegValType $regValType -RegValData $regValData

# # Build decision-making logic based on the result
# if ($setRegistryResult -eq $true) {
# Write-EnhancedLog -Message "Successfully set the registry value: $regValName at $regKeyPath" -Level "INFO"
# } else {
# Write-EnhancedLog -Message "Failed to set the registry value: $regValName at $regKeyPath" -Level "ERROR"
# }


#EndRegion '.\Public\Set-RegistryValue.ps1' 131
#Region '.\Public\Set-RunOnce.ps1' -1

# function Set-RunOnce {
# [CmdletBinding()]
# param (
# [Parameter(Mandatory = $true)]
# [string]$ScriptPath,
        
# [Parameter(Mandatory = $true)]
# [string]$RunOnceKey,
        
# [Parameter(Mandatory = $true)]
# [string]$PowershellPath,
        
# [Parameter(Mandatory = $true)]
# [string]$ExecutionPolicy,
        
# [Parameter(Mandatory = $true)]
# [string]$RunOnceName
# )

# Begin {
# Write-EnhancedLog -Message "Starting Set-RunOnce function" -Level "INFO"
# Log-Params -Params @{
# ScriptPath = $ScriptPath
# RunOnceKey = $RunOnceKey
# PowershellPath = $PowershellPath
# ExecutionPolicy = $ExecutionPolicy
# RunOnceName = $RunOnceName
# }
# }

# Process {
# try {
# Write-EnhancedLog -Message "Setting RunOnce script" -Level "INFO"
# $RunOnceValue = "$PowershellPath -executionPolicy $ExecutionPolicy -File $ScriptPath"
# Set-ItemProperty -Path $RunOnceKey -Name $RunOnceName -Value $RunOnceValue -Verbose
# } catch {
# Write-EnhancedLog -Message "An error occurred while setting RunOnce script: $($_.Exception.Message)" -Level "ERROR"
# Handle-Error -ErrorRecord $_
# }
# }

# End {
# Write-EnhancedLog -Message "Exiting Set-RunOnce function" -Level "INFO"
# }
# }

# # # Example usage with splatting
# # $SetRunOnceParams = @{
# # ScriptPath = "C:\YourScriptPath.ps1"
# # RunOnceKey = "HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce"
# # PowershellPath = "C:\Windows\System32\WindowsPowerShell\v1.0\Powershell.exe"
# # ExecutionPolicy = "Unrestricted"
# # RunOnceName = "NextRun"
# # }

# # Set-RunOnce @SetRunOnceParams







function Set-RunOnce {
    <#
    .SYNOPSIS
    Sets a RunOnce registry key to execute a specified script on the next system startup.
 
    .DESCRIPTION
    The Set-RunOnce function sets a RunOnce registry key to execute a specified PowerShell script on the next system startup. This can be useful for scheduling post-reboot tasks.
 
    .PARAMETER ScriptPath
    The path to the PowerShell script to be executed on the next system startup.
 
    .PARAMETER RunOnceKey
    The registry key path for the RunOnce entry.
 
    .PARAMETER PowershellPath
    The path to the PowerShell executable.
 
    .PARAMETER ExecutionPolicy
    The execution policy for running the PowerShell script.
 
    .PARAMETER RunOnceName
    The name of the RunOnce entry.
 
    .EXAMPLE
    $params = @{
        ScriptPath = "C:\ProgramData\AADMigration\Scripts\PostRunOnce2.ps1"
        RunOnceKey = "HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce"
        PowershellPath = "C:\Windows\System32\WindowsPowerShell\v1.0\Powershell.exe"
        ExecutionPolicy = "Unrestricted"
        RunOnceName = "NextRun"
    }
    Set-RunOnce @params
    Sets the RunOnce registry key to execute the specified script on the next system startup.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$ScriptPath,
        
        [Parameter(Mandatory = $true)]
        [string]$RunOnceKey,
        
        [Parameter(Mandatory = $true)]
        [string]$PowershellPath,
        
        [Parameter(Mandatory = $true)]
        [string]$ExecutionPolicy,
        
        [Parameter(Mandatory = $true)]
        [string]$RunOnceName
    )

    Begin {
        Write-EnhancedLog -Message "Starting Set-RunOnce function" -Level "Notice"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
        # Log-Params -Params @{
        # ScriptPath = $ScriptPath
        # RunOnceKey = $RunOnceKey
        # PowershellPath = $PowershellPath
        # ExecutionPolicy = $ExecutionPolicy
        # RunOnceName = $RunOnceName
        # }
    }

    Process {
        try {
            # Validate script path
            if (-not (Test-Path -Path $ScriptPath)) {
                Throw "Script file not found: $ScriptPath"
            }

            Write-EnhancedLog -Message "Setting RunOnce registry key for script: $ScriptPath" -Level "INFO"
            $RunOnceValue = "$PowershellPath -executionPolicy $ExecutionPolicy -File $ScriptPath"

            $params = @{
                Path  = $RunOnceKey
                Name  = $RunOnceName
                Value = $RunOnceValue
            }

            Set-ItemProperty @params
            Write-EnhancedLog -Message "RunOnce registry key set successfully." -Level "INFO"
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Set-RunOnce function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Set-RunOnce function" -Level "Notice"
    }
}

# # Example usage with splatting
# $params = @{
# ScriptPath = "C:\ProgramData\AADMigration\Scripts\PostRunOnce2.ps1"
# RunOnceKey = "HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce"
# PowershellPath = "C:\Windows\System32\WindowsPowerShell\v1.0\Powershell.exe"
# ExecutionPolicy = "Unrestricted"
# RunOnceName = "NextRun"
# }
# Set-RunOnce @params
#EndRegion '.\Public\Set-RunOnce.ps1' 169
#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\Setup-LinuxEnvironment.ps1' -1

# function Setup-LinuxEnvironment {
# # Get the base paths from the global variables
# Setup-Win32GlobalPaths

# # Import the module using the Linux path
# Import-Module $LinuxModulePath -Verbose

# # Convert paths from Windows to Linux format
# # $global:AOscriptDirectory = Convert-WindowsPathToLinuxPath -WindowsPath "$PSscriptroot"
# # $global:directoryPath = Convert-WindowsPathToLinuxPath -WindowsPath "$PSscriptroot\Win32Apps-DropBox"
# # $global:Repo_Path = Convert-WindowsPathToLinuxPath -WindowsPath "$PSscriptroot"
# $global:IntuneWin32App = Convert-WindowsPathToLinuxPath -WindowsPath "C:\Code\IntuneWin32App\IntuneWin32App.psm1"

# Import-Module $global:IntuneWin32App -Verbose -Global


# $global:AOscriptDirectory = "$PSscriptroot"
# $global:directoryPath = "$PSscriptroot/Win32Apps-DropBox"
# $global:Repo_Path = "$PSscriptroot"
# $global:Repo_winget = "$global:Repo_Path/Win32Apps-DropBox"
# }
#EndRegion '.\Public\Setup-LinuxEnvironment.ps1' 22
#Region '.\Public\Setup-Win32GlobalPaths.ps1' -1

function Setup-Win32GlobalPaths {
    <#
    .SYNOPSIS
    Sets up the global paths based on the environment.
 
    .DESCRIPTION
    This function sets the global paths dynamically based on whether the environment is running inside Docker or not. It uses environment variables when in Docker, otherwise defaults to the script root path or a provided script path.
 
    .PARAMETER scriptpath
    The path to the script that is being executed, used to determine the base paths.
 
    .EXAMPLE
    Setup-Win32GlobalPaths -scriptpath "C:\path\to\script.ps1"
    Sets up the global paths using the provided script path.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false, HelpMessage = "Provide the path to the script being executed.")]
        [ValidateNotNullOrEmpty()]
        [string]$scriptpath
    )

    Begin {
        Write-EnhancedLog -Message "Starting Setup-Win32GlobalPaths function" -Level "INFO"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            if ($env:DOCKER_ENV -eq $true) {
                Write-EnhancedLog -Message "Docker environment detected." -Level "INFO"
                $global:scriptBasePath = $env:SCRIPT_BASE_PATH
                Write-EnhancedLog -Message "Docker script base path: $global:scriptBasePath" -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "Non-Docker environment detected." -Level "INFO"

                # Use provided scriptpath if available, otherwise default to $PSScriptRoot
                if ($scriptpath) {
                    $global:scriptBasePath = Split-Path -Path $scriptpath -Parent
                    Write-EnhancedLog -Message "Using provided script path: $global:scriptBasePath" -Level "INFO"
                }
                else {
                    $global:scriptBasePath = $PSScriptRoot
                    Write-EnhancedLog -Message "Using PSScriptRoot as script base path: $global:scriptBasePath" -Level "INFO"
                }
            }
        }
        catch {
            Write-EnhancedLog -Message "Error occurred during Setup-Win32GlobalPaths: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
    }

    End {
        Write-EnhancedLog -Message "Completed Setup-Win32GlobalPaths function." -Level "INFO"
    }
}
#EndRegion '.\Public\Setup-Win32GlobalPaths.ps1' 61
#Region '.\Public\Setup-WindowsEnvironment.ps1' -1

function Setup-WindowsEnvironment {
    <#
    .SYNOPSIS
    Sets up the Windows environment by configuring necessary paths and logging the setup process.
 
    .DESCRIPTION
    This function dynamically constructs the necessary paths for the Windows environment using the provided script path. It logs the setup process and returns an object with the relevant paths.
 
    .PARAMETER scriptpath
    The path to the script that is being executed, used to determine the base paths for the setup.
 
    .EXAMPLE
    $envDetails = Setup-WindowsEnvironment -scriptpath "C:\path\to\script.ps1"
    Initializes the Windows environment using the provided script path and returns the paths.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the path to the script being executed.")]
        [ValidateNotNullOrEmpty()]
        [string]$scriptpath
    )

    Begin {
        Write-EnhancedLog -Message "Starting Setup-WindowsEnvironment function" -Level "INFO"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        try {
            # Set base paths from script path
            Write-EnhancedLog -Message "Script base path: $scriptpath" -Level "INFO"

            # Construct the paths dynamically using the base path
            $AOscriptDirectory = Join-Path -Path $scriptpath -ChildPath "Win32Apps-Source"
            $directoryPath = Join-Path -Path $scriptpath -ChildPath "Win32Apps-Source"
            $Repo_winget = Join-Path -Path $scriptpath -ChildPath "Win32Apps-Source"

            # Log the dynamically constructed paths
            Log-Params -Params @{

                AOscriptDirectory = $AOscriptDirectory
                directoryPath     = $directoryPath
                Repo_Path         = $scriptpath
                Repo_winget       = $Repo_winget
            }

            Write-EnhancedLog -Message "Paths set up successfully." -Level "INFO"

            # Return the constructed paths as an object
            return [pscustomobject]@{
                AOscriptDirectory = $AOscriptDirectory
                directoryPath     = $directoryPath
                Repo_Path         = $scriptpath
                Repo_winget       = $Repo_winget
            }
        }
        catch {
            Write-EnhancedLog -Message "Error occurred during Setup-WindowsEnvironment: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
    }

    End {
        Write-EnhancedLog -Message "Completed Setup-WindowsEnvironment function." -Level "INFO"
    }
}


# # Run Setup-WindowsEnvironment and store the returned object
# $envDetails = Setup-WindowsEnvironment -scriptpath "C:\path\to\script.ps1"

# # Access the properties of the returned object
# Write-Host "AO Script Directory: $($envDetails.AOscriptDirectory)"
# Write-Host "Directory Path: $($envDetails.directoryPath)"
# Write-Host "Repository Path: $($envDetails.Repo_Path)"
# Write-Host "Winget Path: $($envDetails.Repo_winget)"

# AO Script Directory: C:\path\to\Win32Apps-Source
# Directory Path: C:\path\to\Win32Apps-Source
# Repository Path: C:\path\to\script.ps1
# Winget Path: C:\path\to\Win32Apps-Source
#EndRegion '.\Public\Setup-WindowsEnvironment.ps1' 82
#Region '.\Public\Show-MigrationInProgressForm.ps1' -1

function Show-MigrationInProgressForm {
    <#
    .SYNOPSIS
    Displays a migration in progress form.
 
    .DESCRIPTION
    The Show-MigrationInProgressForm function displays a form with a "Migration in Progress" message and an image to indicate that a migration process is ongoing. The form is displayed in full-screen mode and prevents user interaction with other windows.
 
    .PARAMETER ImagePath
    The path to the image file to be displayed on the form.
 
    .EXAMPLE
    $params = @{
        ImagePath = "C:\ProgramData\AADMigration\Files\MigrationInProgress.bmp"
    }
    Show-MigrationInProgressForm @params
    Displays the migration in progress form with the specified image.
    #>


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

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

    Process {
        try {
            if (-not (Test-Path -Path $ImagePath)) {
                Throw "Image file not found: $ImagePath"
            }

            [void][reflection.assembly]::LoadWithPartialName("System.Drawing")
            [void][reflection.assembly]::LoadWithPartialName("System.Windows.Forms")
            $img = [System.Drawing.Image]::FromFile($ImagePath)

            [System.Windows.Forms.Application]::EnableVisualStyles()
            $form = New-Object Windows.Forms.Form
            $form.Text = "Migration in Progress"
            $form.WindowState = 'Maximized'
            $form.BackColor = "#000000"
            $form.TopMost = $true

            $pictureBox = New-Object Windows.Forms.PictureBox
            $pictureBox.Width = $img.Size.Width
            $pictureBox.Height = $img.Size.Height
            $pictureBox.Dock = "Fill"
            $pictureBox.SizeMode = "StretchImage"
            $pictureBox.Image = $img
            $form.Controls.Add($pictureBox)
            $form.Add_Shown({ $form.Activate() })
            $form.Show()
            Write-EnhancedLog -Message "Displayed migration in progress form." -Level "INFO"

            # Keep the form open
            # while ($form.Visible) {
            # [System.Windows.Forms.Application]::DoEvents()
            # }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Show-MigrationInProgressForm function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Show-MigrationInProgressForm function" -Level "Notice"
    }
}

# # Example usage
# $params = @{
# ImagePath = "C:\ProgramData\AADMigration\Files\MigrationInProgress.bmp"
# }
# Show-MigrationInProgressForm @params
#EndRegion '.\Public\Show-MigrationInProgressForm.ps1' 81
#Region '.\Public\Shutdown-DependentVMs.ps1' -1

# function Shutdown-DependentVMs {
# [CmdletBinding()]
# param (
# [Parameter(Mandatory = $true)]
# [string]$VHDXPath
# )

# Process {
# try {
# $dependentVMs = Get-DependentVMs -VHDXPath $VHDXPath
# foreach ($vm in $dependentVMs) {
# Write-EnhancedLog -Message "Shutting down VM: $($vm.Name)" -Level "INFO"
# Stop-VM -Name $vm.Name -Force -ErrorAction Stop
# }
# } catch {
# Write-EnhancedLog -Message "An error occurred while shutting down dependent VMs: $($_.Exception.Message)" -Level "ERROR"
# Handle-Error -ErrorRecord $_
# }
# }
# }
#EndRegion '.\Public\Shutdown-DependentVMs.ps1' 21
#Region '.\Public\Start-FileDownloadWithRetry.ps1' -1

function Start-FileDownloadWithRetry {

    <#
    .SYNOPSIS
        Downloads a file from a specified URL with retry logic. Falls back to using WebClient if BITS transfer fails.
 
    .DESCRIPTION
        This function attempts to download a file from a specified source URL to a destination path using BITS (Background Intelligent Transfer Service).
        If BITS fails after a specified number of retries, the function falls back to using the .NET WebClient class for the download.
 
    .PARAMETER Source
        The URL of the file to download.
 
    .PARAMETER Destination
        The file path where the downloaded file will be saved.
 
    .PARAMETER MaxRetries
        The maximum number of retry attempts if the download fails. Default is 3.
 
    .EXAMPLE
        Start-FileDownloadWithRetry -Source "https://example.com/file.zip" -Destination "C:\Temp\file.zip"
 
    .NOTES
        Author: Abdullah Ollivierre
        Date: 2024-08-15
    #>


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

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

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

    Begin {
        Write-EnhancedLog -Message "Starting Start-FileDownloadWithRetry function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Ensure the destination folder exists, create it if necessary
        $destinationFolder = Split-Path -Path $Destination -Parent
        if (-not (Test-Path -Path $destinationFolder)) {
            Write-EnhancedLog -Message "Destination folder does not exist. Creating folder: $destinationFolder" -Level "INFO"
            New-Item -Path $destinationFolder -ItemType Directory | Out-Null
        }
    }

    Process {
        $attempt = 0
        $success = $false

        while ($attempt -lt $MaxRetries -and -not $success) {
            try {
                $attempt++
                Write-EnhancedLog -Message "Attempt $attempt to download from $Source to $Destination" -Level "INFO"

                if (-not (Test-Path -Path $destinationFolder)) {
                    throw "Destination folder does not exist: $destinationFolder"
                }

                # Attempt download using BITS
                $bitsTransferParams = @{
                    Source      = $Source
                    Destination = $Destination
                    ErrorAction = "Stop"
                }
                Start-BitsTransfer @bitsTransferParams

                # Validate file existence and size after download
                if (Test-Path $Destination) {
                    $fileInfo = Get-Item $Destination
                    if ($fileInfo.Length -gt 0) {
                        Write-EnhancedLog -Message "Download successful using BITS on attempt $attempt. File size: $($fileInfo.Length) bytes" -Level "INFO"
                        $success = $true
                    }
                    else {
                        Write-EnhancedLog -Message "Download failed: File is empty after BITS transfer." -Level "ERROR"
                        throw "Download failed due to empty file after BITS transfer."
                    }
                }
                else {
                    Write-EnhancedLog -Message "Download failed: File not found after BITS transfer." -Level "ERROR"
                    throw "Download failed due to missing file after BITS transfer."
                }

            }
            catch {
                Write-EnhancedLog -Message "BITS transfer failed on attempt $attempt $($_.Exception.Message)" -Level "ERROR"
                if ($attempt -eq $MaxRetries) {
                    Write-EnhancedLog -Message "Maximum retry attempts reached. Falling back to WebClient for download." -Level "WARNING"
                    try {
                        $webClient = [System.Net.WebClient]::new()
                        $webClient.DownloadFile($Source, $Destination)
                    
                        # Validate file existence and size after download
                        if (Test-Path $Destination) {
                            $fileInfo = Get-Item $Destination
                            if ($fileInfo.Length -gt 0) {
                                Write-EnhancedLog -Message "Download successful using WebClient. File size: $($fileInfo.Length) bytes" -Level "INFO"
                                $success = $true
                            }
                            else {
                                Write-EnhancedLog -Message "Download failed: File is empty after WebClient download." -Level "ERROR"
                                throw "Download failed due to empty file after WebClient download."
                            }
                        }
                        else {
                            Write-EnhancedLog -Message "Download failed: File not found after WebClient download." -Level "ERROR"
                            throw "Download failed due to missing file after WebClient download."
                        }
                    }
                    catch {
                        Write-EnhancedLog -Message "WebClient download failed: $($_.Exception.Message)" -Level "ERROR"
                        throw "Download failed after multiple attempts using both BITS and WebClient."
                    }
                    
                }
                else {
                    Start-Sleep -Seconds 5
                }
            }
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Start-FileDownloadWithRetry function" -Level "NOTICE"
    }
}

# # Generate a timestamped folder within the TEMP directory
# $timestamp = (Get-Date).ToString("yyyyMMdd_HHmmss")
# $destinationFolder = [System.IO.Path]::Combine($env:TEMP, "OneDriveSetup_$timestamp")

# # Set up the parameters for downloading OneDrive Setup
# $downloadParams = @{
# Source = "https://go.microsoft.com/fwlink/?linkid=844652" # OneDrive Setup URL
# Destination = [System.IO.Path]::Combine($destinationFolder, "OneDriveSetup.exe") # Local destination path in the timestamped folder
# MaxRetries = 3 # Number of retry attempts
# }

# # Call the Start-FileDownloadWithRetry function with splatted parameters
# Start-FileDownloadWithRetry @downloadParams
#EndRegion '.\Public\Start-FileDownloadWithRetry.ps1' 147
#Region '.\Public\Start-ServiceUIWithAppDeploy.ps1' -1

function Start-ServiceUIWithAppDeploy {
    [CmdletBinding()]
    param (
        [string]$PSADTExecutable = "$PSScriptRoot\Private\PSAppDeployToolkit\Toolkit\Deploy-Application.exe",
        [string]$ServiceUIExecutable = "$PSScriptRoot\Private\ServiceUI.exe",
        [string]$DeploymentType = "Install",
        [string]$DeployMode = "Interactive"
    )

    try {
        # Verify if the ServiceUI executable exists
        if (-not (Test-Path -Path $ServiceUIExecutable)) {
            throw "ServiceUI executable not found at path: $ServiceUIExecutable"
        }

        # Verify if the PSAppDeployToolkit executable exists
        if (-not (Test-Path -Path $PSADTExecutable)) {
            throw "PSAppDeployToolkit executable not found at path: $PSADTExecutable"
        }

        # Log the start of the process
        Write-EnhancedLog -Message "Starting ServiceUI.exe with Deploy-Application.exe" -Level "INFO"

        # Define the arguments to pass to ServiceUI.exe
        $arguments = "-process:explorer.exe `"$PSADTExecutable`" -DeploymentType $DeploymentType -Deploymode $Deploymode"

        # Start the ServiceUI.exe process with the specified arguments
        Start-Process -FilePath $ServiceUIExecutable -ArgumentList $arguments -Wait -WindowStyle Hidden

        # Log successful completion
        Write-EnhancedLog -Message "ServiceUI.exe started successfully with Deploy-Application.exe" -Level "INFO"
    }
    catch {
        # Handle any errors during the process
        Write-Error "An error occurred: $_"
        Write-EnhancedLog -Message "An error occurred: $_" -Level "ERROR"
    }
}
#EndRegion '.\Public\Start-ServiceUIWithAppDeploy.ps1' 39
#Region '.\Public\Start-VMEnhanced.ps1' -1

function Start-VMEnhanced {
    <#
    .SYNOPSIS
    Starts the specified VM if it exists and is not already running.
    #>

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

    Begin {
        Write-EnhancedLog -Message "Starting Start-VMEnhanced function" -Level "INFO"
        Log-Params -Params @{ VMName = $VMName }
    }

    Process {
        try {
            Write-EnhancedLog -Message "Validating if VM $VMName exists" -Level "INFO"
            if (-not (Validate-VMExists -VMName $VMName)) {
                Write-EnhancedLog -Message "VM $VMName does not exist. Exiting function." -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
                return
            }

            Write-EnhancedLog -Message "Checking if VM $VMName is already running" -Level "INFO"
            if (Validate-VMStarted -VMName $VMName) {
                Write-EnhancedLog -Message "VM $VMName is already running." -Level "INFO" -ForegroundColor ([ConsoleColor]::Yellow)
            } else {
                Write-EnhancedLog -Message "Starting VM $VMName" -Level "INFO"
                Start-VM -Name $VMName -ErrorAction Stop
                Write-EnhancedLog -Message "VM $VMName has been started successfully." -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
            }
        } catch {
            Write-EnhancedLog -Message "An error occurred while starting the VM $VMName. $($_.Exception.Message)" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Start-VMEnhanced function" -Level "INFO"
    }
}
#EndRegion '.\Public\Start-VMEnhanced.ps1' 43
#Region '.\Public\Stop-ProcessesUsingOneDriveLib.ps1' -1

function Stop-ProcessesUsingOneDriveLib {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$OneDriveLibPath
    )

    Begin {
        Write-EnhancedLog -Message "Starting Stop-ProcessesUsingOneDriveLib function" -Level "INFO"
        Log-Params -Params @{ OneDriveLibPath = $OneDriveLibPath }
    }

    Process {
        try {
            # Validate before removal
            $initialProcesses = Validate-OneDriveLibUsage -OneDriveLibPath $OneDriveLibPath
            if ($initialProcesses.Count -eq 0) {
                Write-EnhancedLog -Message "No processes found using OneDriveLib.dll before attempting termination." -Level "INFO"
            }

            # Terminate processes
            foreach ($process in $initialProcesses) {
                Write-EnhancedLog -Message "Found process using OneDriveLib.dll: $($process.ProcessName) (ID: $($process.ProcessId)). Attempting to terminate." -Level "WARNING"
                Stop-Process -Id $process.ProcessId -Force -ErrorAction Stop
            }

            # Validate after removal
            $remainingProcesses = Validate-OneDriveLibUsage -OneDriveLibPath $OneDriveLibPath
            if ($remainingProcesses.Count -eq 0) {
                Write-EnhancedLog -Message "Successfully terminated all processes using OneDriveLib.dll." -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "Some processes could not be terminated. Manual intervention may be required." -Level "ERROR"
                foreach ($process in $remainingProcesses) {
                    Write-EnhancedLog -Message "Process still running: $($process.ProcessName) (ID: $($process.ProcessId))." -Level "ERROR"
                }
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Stop-ProcessesUsingOneDriveLib function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Stop-ProcessesUsingOneDriveLib function" -Level "INFO"
    }
}

# Example usage
# Stop-ProcessesUsingOneDriveLib -OneDriveLibPath "C:\ProgramData\AADMigration\Files\OneDriveLib.dll"
#EndRegion '.\Public\Stop-ProcessesUsingOneDriveLib.ps1' 52
#Region '.\Public\Suspend-BitLockerForDrives.ps1' -1

function Suspend-BitLockerForDrives {
    [CmdletBinding()]
    param (
        [string[]]$DriveLetters
    )

    begin {
        Write-EnhancedLog -Message 'Starting Suspend-BitLockerForDrives function' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        # Detect BitLocker status for the provided drives
        $bitLockerStatusResults = Detect-BitLockerStatus -DriveLetters $DriveLetters

        foreach ($status in $bitLockerStatusResults) {
            $drive = $status.MountPoint
            $protectionStatus = $status.ProtectionStatus

            if ($protectionStatus -eq "On") {
                try {
                    Write-EnhancedLog -Message "Suspending BitLocker on drive $drive" -Level 'INFO'
                    Suspend-BitLocker -MountPoint $drive -RebootCount 0

                    Write-EnhancedLog -Message "BitLocker suspended on drive $drive" -Level 'INFO'
                } catch {
                    Handle-Error -ErrorRecord $_
                }
            } else {
                Write-EnhancedLog -Message "BitLocker is not enabled on drive $drive" -Level 'INFO'
            }
        }
    }

    end {
        Write-EnhancedLog -Message 'Suspend-BitLockerForDrives function completed' -Level 'INFO'
    }
}

# # Example usage of Suspend-BitLockerForDrives function with splatting
# $params = @{
# DriveLetters = @("C:", "D:")
# }

# # Call the Suspend-BitLockerForDrives function using splatting
# Suspend-BitLockerForDrives @params
#EndRegion '.\Public\Suspend-BitLockerForDrives.ps1' 47
#Region '.\Public\Suspend-BitLockerWithReboot.ps1' -1

function Suspend-BitLockerWithReboot {
    <#
    .SYNOPSIS
    Suspends BitLocker and configures the system to reboot a specified number of times.
 
    .DESCRIPTION
    The Suspend-BitLockerWithReboot function suspends BitLocker protection on the specified drive and configures the system to reboot a specified number of times.
 
    .PARAMETER MountPoint
    The drive letter of the BitLocker protected drive.
 
    .PARAMETER RebootCount
    The number of reboots to suspend BitLocker protection for.
 
    .EXAMPLE
    $params = @{
        MountPoint = "C:"
        RebootCount = 2
    }
    Suspend-BitLockerWithReboot @params
    Suspends BitLocker on drive C: for 2 reboots.
    #>


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

        [Parameter(Mandatory = $true)]
        [int]$RebootCount
    )

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

    Process {
        try {
            Write-EnhancedLog -Message "Suspending BitLocker on drive $MountPoint for $RebootCount reboots" -Level "INFO"
            Suspend-BitLocker -MountPoint $MountPoint -RebootCount $RebootCount -Verbose
            Write-EnhancedLog -Message "Successfully suspended BitLocker on drive $MountPoint" -Level "INFO"
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while suspending BitLocker: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Suspend-BitLockerWithReboot function" -Level "Notice"
    }
}

# # Example usage
# $params = @{
# MountPoint = "C:"
# RebootCount = 2
# }
# Suspend-BitLockerWithReboot @params
#EndRegion '.\Public\Suspend-BitLockerWithReboot.ps1' 62
#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-Bitlocker.ps1' -1

function Test-Bitlocker {
    <#
    .SYNOPSIS
    Tests if BitLocker is enabled on the specified drive.
 
    .DESCRIPTION
    The Test-Bitlocker function tests if BitLocker is enabled on the specified drive.
 
    .PARAMETER BitlockerDrive
    The drive letter of the BitLocker protected drive.
 
    .EXAMPLE
    Test-Bitlocker -BitlockerDrive "C:"
    Tests if BitLocker is enabled on drive C:.
    #>


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

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

    Process {
        try {
            $bitlockerVolume = Get-BitLockerVolume -MountPoint $BitlockerDrive -ErrorAction Stop
            Write-EnhancedLog -Message "BitLocker is enabled on drive: $BitlockerDrive" -Level "INFO"
            return $bitlockerVolume
        }
        catch {
            Write-EnhancedLog -Message "BitLocker is not enabled on drive: $BitlockerDrive. Terminating script!" -Level "ERROR"
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Test-Bitlocker function" -Level "Notice"
    }
}
#EndRegion '.\Public\Test-Bitlocker.ps1' 44
#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-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\Test-VPNConnection.ps1' -1

function Test-VPNConnection {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$ConnectionName
    )

    try {
        # Check if the VPN connection exists
        $vpnConnection = Get-VpnConnection -Name $ConnectionName -AllUserConnection -ErrorAction SilentlyContinue
        if ($null -ne $vpnConnection) {
            Write-EnhancedLog -Message "VPN connection '$ConnectionName' exists." -Level "INFO"
            return $true
        } else {
            Write-EnhancedLog -Message "VPN connection '$ConnectionName' does not exist." -Level "INFO"
            return $false
        }
    }
    catch {
        Handle-Error -ErrorRecord $_
        Write-EnhancedLog -Message "An error occurred while checking VPN connection '$ConnectionName'." -Level "ERROR"
        throw $_
    }
}



# Test-VPNConnection -ConnectionName "ICTC VPN"
#EndRegion '.\Public\Test-VPNConnection.ps1' 29
#Region '.\Public\Trigger-ScheduledTask.ps1' -1

function Trigger-ScheduledTask {
    [CmdletBinding()]
    param (
        [string]$TaskPath,
        [string]$TaskName
    )

    Begin {
        Write-EnhancedLog -Message "Starting Trigger-ScheduledTask function" -Level "NOTICE"
        CheckAndElevate -ElevateIfNotAdmin $true
    }

    Process {
        try {
            Write-EnhancedLog -Message "Triggering the scheduled task '$TaskName' under the '$TaskPath' folder..." -Level "INFO"

            $startTaskParams = @{
                TaskPath = $TaskPath
                TaskName = $TaskName
            }

            Start-ScheduledTask @startTaskParams

            Write-EnhancedLog -Message "Scheduled task triggered successfully." -Level "INFO"
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while triggering the scheduled task: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Trigger-ScheduledTask function" -Level "NOTICE"
    }
}

# # Example usage
# # Define parameters using a hashtable
# $taskParams = @{
# TaskPath = "\AAD Migration"
# TaskName = "AADM Get OneDrive Sync Status"
# }

# # Call the function with splatting
# Trigger-ScheduledTask @taskParams
#EndRegion '.\Public\Trigger-ScheduledTask.ps1' 47
#Region '.\Public\Uninstall-FortiClientEMSAgentApplication.ps1' -1

function Uninstall-FortiClientEMSAgentApplication {
    [CmdletBinding()]
    param (
        [string[]]$UninstallKeys,
        [string]$ApplicationName,
        [string]$FilePath,
        [string]$ArgumentTemplate
    )

    begin {
        Write-EnhancedLog -Message 'Starting the Uninstall-FortiClientEMSAgentApplication function...' -Level 'INFO'
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    process {
        try {
            $findParams = @{
                UninstallKeys  = $UninstallKeys
                ApplicationName = $ApplicationName
            }
            $productId = Find-UninstallString @findParams

            if ($null -ne $productId) {
                Write-EnhancedLog -Message "Found product ID: $productId" -Level 'INFO'
                
                # Prepare parameters for Invoke-Uninstall
                $invokeParams = @{
                    ProductId        = $productId
                    FilePath         = $FilePath
                    ArgumentTemplate = $ArgumentTemplate
                }
                Invoke-Uninstall @invokeParams
                #wait a bit before going into detection/validation
                Start-Sleep -Seconds 30
            } else {
                Write-EnhancedLog -Message 'Product ID not found for FortiClientEMSAgent application.' -Level 'WARNING'
            }
        } catch {
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        Write-EnhancedLog -Message 'Uninstall process completed.' -Level 'INFO'
    }
}

# # Example usage of Uninstall-FortiClientEMSAgentApplication function with splatting
# $params = @{
# UninstallKeys = @(
# 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
# 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
# )
# ApplicationName = '*Forti*'
# FilePath = 'MsiExec.exe'
# ArgumentTemplate = "/X{ProductId} /quiet /norestart"
# }
# Uninstall-FortiClientEMSAgentApplication @params
#EndRegion '.\Public\Uninstall-FortiClientEMSAgentApplication.ps1' 59
#Region '.\Public\Unregister-ScheduledTaskWithLogging.ps1' -1

function Unregister-ScheduledTaskWithLogging {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$TaskName
    )

    Begin {
        Write-EnhancedLog -Message "Starting Unregister-ScheduledTaskWithLogging function" -Level "Notice"
        Log-Params -Params @{ TaskName = $TaskName }
    }

    Process {
        try {
            Write-EnhancedLog -Message "Checking if task '$TaskName' exists before attempting to unregister." -Level "INFO"
            $taskExistsBefore = Check-ExistingTask -taskName $TaskName
            
            if ($taskExistsBefore) {
                Write-EnhancedLog -Message "Task '$TaskName' found. Proceeding to unregister." -Level "INFO"
                Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false
                Write-EnhancedLog -Message "Unregister-ScheduledTask done for task: $TaskName" -Level "INFO"
            } else {
                Write-EnhancedLog -Message "Task '$TaskName' not found. No action taken." -Level "INFO"
            }

            Write-EnhancedLog -Message "Checking if task '$TaskName' exists after attempting to unregister." -Level "INFO"
            $taskExistsAfter = Check-ExistingTask -taskName $TaskName
            
            if ($taskExistsAfter) {
                Write-EnhancedLog -Message "Task '$TaskName' still exists after attempting to unregister. Manual intervention may be required." -Level "ERROR"
            } else {
                Write-EnhancedLog -Message "Task '$TaskName' successfully unregistered." -Level "INFO"
            }
        } catch {
            Write-EnhancedLog -Message "Error during Unregister-ScheduledTask for task: $TaskName. Error: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Unregister-ScheduledTaskWithLogging function" -Level "Notice"
    }
}


# Unregister-ScheduledTaskWithLogging -TaskName "YourScheduledTaskName"
#EndRegion '.\Public\Unregister-ScheduledTaskWithLogging.ps1' 47
#Region '.\Public\Update-ApplicationPermissions.ps1' -1

# function Update-ApplicationPermissions {
# param (
# [string]$appId,
# [string]$permissionsFile
# )

# $resourceAppId = "00000003-0000-0000-c000-000000000000" # Microsoft Graph

# # Load permissions from the JSON file
# if (Test-Path -Path $permissionsFile) {
# $permissions = Get-Content -Path $permissionsFile | ConvertFrom-Json
# }
# else {
# Write-EnhancedLog -Message "Permissions file not found: $permissionsFile" -Level "ERROR"
# throw "Permissions file not found: $permissionsFile"
# }

# # Retrieve the existing application (optional, uncomment if needed)
# # $app = Get-MgApplication -ApplicationId $appId

# # Prepare the required resource access
# $requiredResourceAccess = @(
# @{
# ResourceAppId = $resourceAppId
# ResourceAccess = $permissions
# }
# )

# # Update the application
# try {
# Update-MgApplication -ApplicationId $appId -RequiredResourceAccess $requiredResourceAccess
# Write-EnhancedLog -Message "Successfully updated application permissions for appId: $appId" -Level "INFO"
# }
# catch {
# Write-EnhancedLog -Message "Failed to update application permissions for appId: $appId. Error: $_" -Level "ERROR"
# throw $_
# }
# }



# function Update-ApplicationPermissions {
# param (
# [string]$appId,
# [string]$permissionsFile
# )

# $resourceAppId = "00000003-0000-0000-c000-000000000000" # Microsoft Graph

# # Load permissions from the JSON file
# if (Test-Path -Path $permissionsFile) {
# $permissions = Get-Content -Path $permissionsFile | ConvertFrom-Json
# }
# else {
# Write-EnhancedLog -Message "Permissions file not found: $permissionsFile" -Level "ERROR"
# throw "Permissions file not found: $permissionsFile"
# }

# # Convert permissions to the required type
# $resourceAccess = @()
# foreach ($permission in $permissions) {
# $resourceAccess += [Microsoft.Graph.PowerShell.Models.IMicrosoftGraphResourceAccess]@{
# Id = [Guid]$permission.Id
# Type = $permission.Type
# }
# }

# # Prepare the required resource access
# $requiredResourceAccess = @(
# [Microsoft.Graph.PowerShell.Models.IMicrosoftGraphRequiredResourceAccess]@{
# ResourceAppId = [Guid]$resourceAppId
# ResourceAccess = $resourceAccess
# }
# )

# # Update the application
# try {
# Update-MgApplication -ApplicationId $appId -RequiredResourceAccess $requiredResourceAccess
# Write-EnhancedLog -Message "Successfully updated application permissions for appId: $appId" -Level "INFO"
# }
# catch {
# Write-EnhancedLog -Message "Failed to update application permissions for appId: $appId. Error: $_" -Level "ERROR"
# throw $_
# Handle-Error -ErrorRecord $_
# }
# }


# function Update-ApplicationPermissions {
# param (
# [string]$appId,
# [string]$permissionsFile
# )

# $resourceAppId = "00000003-0000-0000-c000-000000000000" # Microsoft Graph

# # Load permissions from the JSON file
# if (Test-Path -Path $permissionsFile) {
# $permissions = Get-Content -Path $permissionsFile | ConvertFrom-Json
# }
# else {
# Write-EnhancedLog -Message "Permissions file not found: $permissionsFile" -Level "ERROR"
# throw "Permissions file not found: $permissionsFile"
# }

# # Convert permissions to the required type
# $resourceAccess = @()
# foreach ($permission in $permissions) {
# if ($null -eq $permission.Id) {
# Write-EnhancedLog -Message "Permission Id is null. Skipping this entry." -Level "WARNING"
# continue
# }

# try {
# $resourceAccess += [Microsoft.Graph.PowerShell.Models.IMicrosoftGraphResourceAccess]@{
# Id = [Guid]$permission.Id
# Type = $permission.Type
# }
# }
# catch {
# Write-EnhancedLog -Message "Failed to convert permission Id: $($permission.Id). Error: $_" -Level "ERROR"
# throw $_
# }
# }

# if ($resourceAccess.Count -eq 0) {
# Write-EnhancedLog -Message "No valid permissions found to update the application." -Level "ERROR"
# throw "No valid permissions found to update the application."
# }

# # Prepare the required resource access
# $requiredResourceAccess = @(
# [Microsoft.Graph.PowerShell.Models.IMicrosoftGraphRequiredResourceAccess]@{
# ResourceAppId = [Guid]$resourceAppId
# ResourceAccess = $resourceAccess
# }
# )

# # Update the application
# try {
# Update-MgApplication -ApplicationId $appId -RequiredResourceAccess $requiredResourceAccess
# Write-EnhancedLog -Message "Successfully updated application permissions for appId: $appId" -Level "INFO"
# }
# catch {
# Write-EnhancedLog -Message "Failed to update application permissions for appId: $appId. Error: $_" -Level "ERROR"
# throw $_
# Handle-Error -ErrorRecord $_
# }
# }
#EndRegion '.\Public\Update-ApplicationPermissions.ps1' 150
#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\Upload-FileToSharePoint.ps1' -1

function Upload-FileToSharePoint {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$DocumentDriveId,

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

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

        [Parameter(Mandatory = $true)]
        [hashtable]$Headers
    )

    try {
        # Validate file existence before upload
        Validate-FileExists -FilePath $FilePath

        # Read the file content
        $content = Get-Content -Path $FilePath -Raw
        $filename = (Get-Item -Path $FilePath).Name

        # Construct the PUT URL
        $putUrl = "https://graph.microsoft.com/v1.0/drives/$DocumentDriveId/root:/$FolderName/$($filename):/content"

        # Upload the file
        Write-EnhancedLog -Message "Uploading file '$filename' to folder '$FolderName'..." -Level "INFO"
        $uploadResponse = Invoke-RestMethod -Headers $Headers -Uri $putUrl -Body $content -Method PUT
        Write-EnhancedLog -Message "File '$filename' uploaded successfully." -Level "INFO"

        # Validate file existence after upload
        Validate-FileUpload -DocumentDriveId $DocumentDriveId -FolderName $FolderName -FileName $filename -Headers $Headers

        return $uploadResponse
    }
    catch {
        Write-EnhancedLog -Message "Failed to upload file '$filename' to folder '$FolderName': $_" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
        throw $_
    }
}
#EndRegion '.\Public\Upload-FileToSharePoint.ps1' 43
#Region '.\Public\Upload-IntuneWinPackage.ps1' -1

function Upload-IntuneWinPackage {
    <#
    .SYNOPSIS
    Uploads the Win32 Intune package and assigns it to all users.
 
    .DESCRIPTION
    This function uploads a specified Win32 Intune package, logs the process, and assigns the app to all users. It handles errors and logs all parameters excluding sensitive data like the app icon.
 
    .PARAMETER Prg
    The program object containing metadata like the name of the program.
 
    .PARAMETER Prg_Path
    The path where the program resides.
 
    .PARAMETER Prg_img
    The path to the program's image.
 
    .PARAMETER config
    The configuration object required for uploading the Win32 Intune package.
 
    .PARAMETER IntuneWinFile
    The path to the .intunewin file.
 
    .PARAMETER InstallCommandLine
    The command line to install the app.
 
    .PARAMETER UninstallCommandLine
    The command line to uninstall the app.
 
    .EXAMPLE
    Upload-IntuneWinPackage -Prg $Prg -Prg_Path "C:\Path\To\App" -Prg_img "C:\Path\To\Image.png" -config $config -IntuneWinFile "C:\Path\To\Package.intunewin" -InstallCommandLine "install.cmd" -UninstallCommandLine "uninstall.cmd"
    Uploads the Win32 Intune package and assigns it to all users.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the program object.")]
        [ValidateNotNullOrEmpty()]
        [pscustomobject]$Prg,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the program path.")]
        [ValidateNotNullOrEmpty()]
        [string]$Prg_Path,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the program image.")]
        [ValidateNotNullOrEmpty()]
        [string]$Prg_img,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the configuration object.")]
        [ValidateNotNullOrEmpty()]
        [pscustomobject]$config,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the IntuneWin file path.")]
        [ValidateNotNullOrEmpty()]
        [string]$IntuneWinFile,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the install command line.")]
        [ValidateNotNullOrEmpty()]
        [string]$InstallCommandLine,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the uninstall command line.")]
        [ValidateNotNullOrEmpty()]
        [string]$UninstallCommandLine
    )

    Begin {
        Write-EnhancedLog -Message "Starting Upload-IntuneWinPackage function for program: $($Prg.Name)" -Level "INFO"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Set display name
            $DisplayName = "$($Prg.Name)"
            Write-EnhancedLog -Message "DisplayName set: $DisplayName" -Level "INFO"

            # Create detection rule, requirement rule, and set app icon
            $DetectionRule = Create-DetectionRule -Prg_Path $Prg_Path
            $RequirementRule = Create-RequirementRule
            $Icon = Set-AppIcon -Prg_img $Prg_img

            # Splatting for Intune app parameters
            $IntuneAppParams = @{
                FilePath                 = $IntuneWinFile
                Icon                     = $Icon
                DisplayName              = "$DisplayName ($($config.InstallExperience))"
                Description              = "$DisplayName ($($config.InstallExperience))"
                Publisher                = $config.Publisher
                AppVersion               = $config.AppVersion
                Developer                = $config.Developer
                Owner                    = $config.Owner
                CompanyPortalFeaturedApp = [System.Convert]::ToBoolean($config.CompanyPortalFeaturedApp)
                InstallCommandLine       = $InstallCommandLine
                UninstallCommandLine     = $UninstallCommandLine
                InstallExperience        = $config.InstallExperience
                RestartBehavior          = $config.RestartBehavior
                DetectionRule            = $DetectionRule
                RequirementRule          = $RequirementRule
                InformationURL           = $config.InformationURL
                PrivacyURL               = $config.PrivacyURL
                Verbose                  = $true
            }

            # Log the parameters excluding the Icon
            $IntuneAppParamsForLogging = [ordered]@{}
            foreach ($key in $IntuneAppParams.Keys) {
                if ($key -ne 'Icon') {
                    $IntuneAppParamsForLogging[$key] = $IntuneAppParams[$key]
                }
            }
            Log-Params -Params $IntuneAppParamsForLogging

            Write-EnhancedLog -Message "Calling Add-IntuneWin32App with IntuneAppParams - in progress" -Level "WARNING"
            $Win32App = Add-IntuneWin32App @IntuneAppParams
            #TODO add more logging here to check if Running in PS7 when PS5 is launched from PS7 because it giving a null value error which usually happens when you are running from PS7 as Add-IntuneWin32App uses star-process to start a new session so we might need to call Reset-ModulePaths within a modified version of the Add-IntuneWin32App script and then build a script to copy the modified version of this into the Windows PowerShell Modules folder just like how we are doing with the other function Copy-InvokeAzureStorageBlobUploadFinalize
            Write-EnhancedLog -Message "Win32 app added successfully. App ID: $($Win32App.id)" -Level "INFO"

            Write-EnhancedLog -Message "Assigning Win32 app to all users..." -Level "WARNING"
            Add-IntuneWin32AppAssignmentAllUsers -ID $Win32App.id -Intent "available" -Notification "showAll" -Verbose
            Write-EnhancedLog -Message "Assignment completed successfully." -Level "INFO"
        }
        catch {
            Write-EnhancedLog -Message "Error during IntuneWin32 app process: $($_.Exception.Message)" -Level "ERROR"
            Write-Host "Error during IntuneWin32 app process: $($_.Exception.Message)" -ForegroundColor Red
            exit
        }
    }

    End {
        Write-EnhancedLog -Message "Completed Upload-IntuneWinPackage function for program: $($Prg.Name)" -Level "INFO"
    }
}
#EndRegion '.\Public\Upload-IntuneWinPackage.ps1' 133
#Region '.\Public\Upload-Win32App.ps1' -1

function Upload-Win32App {
    <#
    .SYNOPSIS
    Uploads a Win32 application to Intune.
 
    .DESCRIPTION
    The Upload-Win32App function uploads a Win32 application package to Intune, prepares paths, creates the IntuneWin package, and uploads it along with the install and uninstall command lines. It validates that the script is running in PowerShell 5 before proceeding.
 
    .PARAMETER Prg
    The application object with necessary details for the upload.
 
    .PARAMETER Prg_Path
    The path to the application being uploaded.
 
    .PARAMETER Prg_img
    The image associated with the application (optional).
 
    .PARAMETER Win32AppsRootPath
    The root path where the Win32 apps are stored (optional).
 
    .PARAMETER linetoadd
    Additional lines to add (optional).
 
    .PARAMETER config
    Configuration object containing necessary details for installation commands.
 
    .EXAMPLE
    $params = @{
        Prg = [pscustomobject]@{ name = 'ExampleApp'; id = 'exampleApp' }
        Prg_Path = "C:\Programs\ExampleApp"
        config = [pscustomobject]@{ InstallCommand = 'install.ps1' }
    }
    Upload-Win32App @params
    Uploads the specified Win32 app to Intune.
    #>


    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param(
        [Parameter(Mandatory = $true, HelpMessage = "Provide the application object containing necessary details.")]
        [ValidateNotNullOrEmpty()]
        [pscustomobject]$Prg,
    
        [Parameter(Mandatory = $true, HelpMessage = "Specify the path to the program.")]
        [ValidateNotNullOrEmpty()]
        [string]$Prg_Path,
    
        [Parameter(HelpMessage = "Specify the image associated with the application (optional).")]
        [string]$Prg_img,
    
        [Parameter(HelpMessage = "Specify the root path where the Win32 apps are stored (optional).")]
        [string]$Win32AppsRootPath,
    
        [Parameter(HelpMessage = "Provide any additional lines to add (optional).")]
        [string]$linetoadd,
    
        [Parameter(Mandatory = $true, HelpMessage = "Provide the configuration object for command lines.")]
        [ValidateNotNullOrEmpty()]
        [pscustomobject]$config
    )
    

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

        # Check if running in PowerShell 5
        if ($PSVersionTable.PSVersion.Major -ne 5) {
            Write-EnhancedLog -Message "This script must be run in PowerShell 5. Please switch to PowerShell 5 and rerun the script." -Level "ERROR"
            throw "PowerShell 5 is required for this operation. Halting script execution."
        }

        Write-EnhancedLog -Message "Validated PowerShell 5 environment" -Level "INFO"
    }

    Process {
        try {
            if ($PSCmdlet.ShouldProcess("Win32 app: $($Prg.name)", "Upload to Intune")) {
                Write-EnhancedLog -Message "Uploading: $($Prg.name)" -Level "WARNING"
    
                # Set the install and uninstall command lines
                $InstallCommandLines = Set-InstallCommandLine -config $config
    
                # Log parameters
                Log-Params -Params @{
                    Prg      = $Prg
                    Prg_Path = $Prg_Path
                    Prg_img  = $Prg_img
                }
    
                # Prepare paths for the application
                $paths = Prepare-Paths -Prg $Prg -Prg_Path $Prg_Path -Win32AppsRootPath $Win32AppsRootPath
    
                # Splatting for Create-IntuneWinPackage
                $createIntuneWinParams = @{
                    Prg             = $Prg
                    Prg_Path        = $Prg_Path
                    destinationPath = $paths.destinationPath
                }
                $IntuneWinFile = Create-IntuneWinPackage @createIntuneWinParams
    
                # Splatting for Upload-IntuneWinPackage
                $uploadParams = @{
                    Prg                  = $Prg
                    Prg_Path             = $Prg_Path
                    Prg_img              = $Prg_img
                    config               = $config
                    IntuneWinFile        = $IntuneWinFile
                    InstallCommandLine   = $InstallCommandLines.InstallCommandLine
                    UninstallCommandLine = $InstallCommandLines.UninstallCommandLine
                }
                Upload-IntuneWinPackage @uploadParams
            }
            else {
                Write-EnhancedLog -Message "Operation skipped due to WhatIf or confirmation." -Level "INFO"
            }
        }
        catch {
            Write-EnhancedLog -Message "Error during Upload-Win32App: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
        finally {
            Write-EnhancedLog -Message "Exiting Upload-Win32App function" -Level "Notice"
        }
    }
    

    End {
        Write-EnhancedLog -Message "Upload-Win32App completed successfully for $($Prg.name)" -Level "INFO"
    }
}
#EndRegion '.\Public\Upload-Win32App.ps1' 132
#Region '.\Public\Validate-AppCreation.ps1' -1

function Validate-AppCreation {
    param (
        [string]$AppName,
        [string]$JsonPath
    )

    # Call the function to run the script in its own instance of pwsh
    

    # Example usage
    # $jsonPath = "C:\path\to\your\jsonfile.json"
    # $appInfo = Get-AppInfoFromJson -jsonPath $jsonPath

   
    Write-EnhancedLog -Message "validating AppName $AppName from $JsonPath"

    try {
        # Import application objects from JSON using Get-AppInfoFromJson function
        $allApps = Get-AppInfoFromJson -jsonPath $JsonPath

         # Output the extracted data
        # $allApps | Format-Table -AutoSize

        # # List all applications
        # Write-EnhancedLog -Message "Listing all applications:"
        # $allApps | Format-Table Id, DisplayName, AppId, SignInAudience, PublisherDomain -AutoSize

        # Filter the applications to find the one with the specified display name
        $app = $allApps | Where-Object { $_.DisplayName -eq $AppName }

        # Debug output
        # Write-EnhancedLog -Message "Filtered applications count: $($app.Count)"
        if ($app.Count -eq 0) {
            Write-EnhancedLog -Message "No applications found with the name $AppName"
        }
        else {
            # Write-EnhancedLog -Message "Filtered applications details:"
            # $app | Format-Table Id, DisplayName, AppId, SignInAudience, PublisherDomain -AutoSize
        }

        # Log the parameters and the retrieved application object
        $params = @{
            AppName    = $AppName
            AppCount   = ($app | Measure-Object).Count
            AppDetails = $app
        }
        Log-Params -Params $params

        # Check if the application object is not null and has items
        if ($null -ne $app -and ($app | Measure-Object).Count -gt 0) {
            Write-EnhancedLog -Message "Application found."
            return $true
        }
        Write-EnhancedLog -Message "Application not found."
        return $false
    }
    catch {
        Write-EnhancedLog -Message "An error occurred: $_"
        throw $_
    }
}
#EndRegion '.\Public\Validate-AppCreation.ps1' 62
#Region '.\Public\Validate-AppCreationWithRetry.ps1' -1

function Validate-AppCreationWithRetry {
    param (
        [Parameter(Mandatory = $true)]
        [string]$AppName,
        [Parameter(Mandatory = $true)]
        [string]$JsonPath
    )

    $maxDuration = 120  # Maximum duration in seconds (2 minutes)
    $interval = 2       # Interval in seconds
    $elapsed = 0        # Elapsed time counter

    while ($elapsed -lt $maxDuration) {
        try {
            # Validate the app creation
            Write-EnhancedLog -Message 'second validation'
            Remove-AppListJson -jsonPath $jsonPath
            # Start-Sleep -Seconds 30
            Run-DumpAppListToJSON -JsonPath $JsonPath
            $appExists = Validate-AppCreation -AppName $AppName -JsonPath $JsonPath
            if (-not $appExists) {
                Write-EnhancedLog -Message "App creation validation failed" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
                throw "App creation validation failed"
            }

            # If the app validation passes, exit the loop
            break
        }
        catch {
            Write-EnhancedLog -Message "An error occurred during app creation validation: $_" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
            Start-Sleep -Seconds $interval
            $elapsed += $interval
        }
    }

    if ($elapsed -ge $maxDuration) {
        Write-EnhancedLog -Message "App creation validation failed after multiple retries" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
        throw "App creation validation failed after multiple retries"
    }
}
#EndRegion '.\Public\Validate-AppCreationWithRetry.ps1' 41
#Region '.\Public\Validate-CertCreation.ps1' -1

function Validate-CertCreation {
    param (
        [string]$Thumbprint,
        [string[]]$StoreLocations = @("Cert:\LocalMachine", "Cert:\CurrentUser")
    )

    foreach ($storeLocation in $StoreLocations) {
        $cert = Get-ChildItem -Path "$storeLocation\My" | Where-Object { $_.Thumbprint -eq $Thumbprint }
        if ($null -ne $cert) {
            Write-EnhancedLog -Message "Certificate validated successfully in $storeLocation" -Level "INFO" -ForegroundColor ([ConsoleColor]::Cyan)
            return $cert
        }
    }

    Write-EnhancedLog -Message "Certificate validation failed" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
    throw "Certificate not found"
    Handle-Error -ErrorRecord $_
}
#EndRegion '.\Public\Validate-CertCreation.ps1' 19
#Region '.\Public\Validate-Certificate.ps1' -1

function Validate-Certificate {
    param (
        [Parameter(Mandatory = $true)]
        [string]$CertPath
    )

    try {
        if (Test-Path -Path $CertPath) {
            Write-EnhancedLog -Message "Certificate path exists: $CertPath" -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
            return $true
        } else {
            Write-EnhancedLog -Message "Certificate path does not exist: $CertPath" -Level "WARNING" -ForegroundColor ([ConsoleColor]::Yellow)
            return $false
        }
    }
    catch {
        Write-EnhancedLog -Message "Error validating certificate path: $CertPath. Error: $_" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
        throw $_
    }
}
#EndRegion '.\Public\Validate-Certificate.ps1' 21
#Region '.\Public\Validate-FileExists.ps1' -1

function Validate-FileExists {
    param (
        [Parameter(Mandatory = $true)]
        [string]$FilePath
    )

    if (-Not (Test-Path -Path $FilePath)) {
        Write-EnhancedLog -Message "File '$FilePath' does not exist." -Level "ERROR"
        throw "File '$FilePath' does not exist."
    }
}


#EndRegion '.\Public\Validate-FileExists.ps1' 14
#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-ISOAdded.ps1' -1

function Validate-ISOAdded {
    <#
    .SYNOPSIS
    Validates if the specified ISO file is added to the VM.
    #>

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

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

    Begin {
        Write-EnhancedLog -Message "Starting Validate-ISOAdded function" -Level "INFO"
        Log-Params -Params @{ VMName = $VMName; InstallMediaPath = $InstallMediaPath }
    }

    Process {
        try {
            Write-EnhancedLog -Message "Retrieving DVD drive information for VM: $VMName" -Level "INFO"
            $dvdDrive = Get-VMDvdDrive -VMName $VMName -ErrorAction SilentlyContinue

            if ($dvdDrive -and ($dvdDrive.Path -eq $InstallMediaPath)) {
                Write-EnhancedLog -Message "ISO is correctly added to VM: $VMName" -Level "INFO" -ForegroundColor Green
                return $true
            } else {
                Write-EnhancedLog -Message "ISO is not added to VM: $VMName" -Level "WARNING" -ForegroundColor Red
                return $false
            }
        } catch {
            Write-EnhancedLog -Message "An error occurred while validating ISO addition: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            return $false
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Validate-ISOAdded function" -Level "INFO"
    }
}
#EndRegion '.\Public\Validate-ISOAdded.ps1' 43
#Region '.\Public\Validate-LogFilePath.ps1' -1

function Validate-LogFilePath {
    [CmdletBinding()]
    param (
        [string]$LogFilePath
    )

    try {
        Write-EnhancedLog -Message "Starting Validate-LogFilePath function..." -Level "NOTICE"
        Write-EnhancedLog -Message "Validating LogFilePath: $LogFilePath" -Level "INFO"

        # Check for invalid characters in the file path
        if ($LogFilePath -match "[<>""|?*]") {
            Write-EnhancedLog -Message "Warning: The LogFilePath contains invalid characters." -Level "WARNING"
        }

        # Check for double backslashes which may indicate an error in path generation
        if ($LogFilePath -match "\\\\") {
            Write-EnhancedLog -Message "Warning: The LogFilePath contains double backslashes." -Level "WARNING"
        }

        Write-EnhancedLog -Message "Validation complete for LogFilePath: $LogFilePath" -Level "INFO"
        Write-EnhancedLog -Message "Exiting Validate-LogFilePath function" -Level "NOTICE"
    }
    catch {
        Write-EnhancedLog -Message "An error occurred in Validate-LogFilePath: $_" -Level "ERROR"
        Handle-Error -ErrorRecord $_
        throw $_  # Re-throw the error after logging it
    }
}
#EndRegion '.\Public\Validate-LogFilePath.ps1' 30
#Region '.\Public\Validate-OneDriveLibUsage.ps1' -1

function Validate-OneDriveLibUsage {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$OneDriveLibPath
    )

    $processesUsingLib = [System.Collections.Generic.List[PSCustomObject]]::new()

    try {
        # Get all processes
        $processes = Get-Process

        # Iterate over each process and check if it has loaded OneDriveLib.dll
        foreach ($process in $processes) {
            try {
                $modules = $process.Modules | Where-Object { $_.FileName -eq $OneDriveLibPath }
                if ($modules) {
                    $processesUsingLib.Add([PSCustomObject]@{
                        ProcessName = $process.ProcessName
                        ProcessId   = $process.Id
                    })
                }
            }
            catch {
                # Handle any errors encountered while accessing process modules
                Write-EnhancedLog -Message "Could not access modules for process: $($process.ProcessName) (ID: $($process.Id)). Error: $($_.Exception.Message)" -Level "WARNING"
            }
        }
    }
    catch {
        Write-EnhancedLog -Message "An error occurred in Validate-OneDriveLibUsage function: $($_.Exception.Message)" -Level "ERROR"
        Handle-Error -ErrorRecord $_
    }

    return $processesUsingLib
}
#EndRegion '.\Public\Validate-OneDriveLibUsage.ps1' 38
#Region '.\Public\Validate-PathExistsWithLogging.ps1' -1

function Validate-PathExistsWithLogging {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string[]]$Paths
    )

    Begin {
        Write-EnhancedLog -Message "Starting Validate-PathExistsWithLogging function" -Level "INFO"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters

        # Initialize counters and lists
        $totalExpectedFiles = [System.Collections.Generic.List[int]]::new()
        $totalValidatedFiles = [System.Collections.Generic.List[int]]::new()
        $missingFiles = [System.Collections.Generic.List[string]]::new()
    }

    Process {
        foreach ($Path in $Paths) {
            try {
                if ([string]::IsNullOrWhiteSpace($Path)) {
                    Write-EnhancedLog -Message "Invalid Path: Path is null or empty." -Level "ERROR"
                    throw "Invalid Path: Path is null or empty."
                }

                Write-EnhancedLog -Message "Validating path: $Path" -Level "INFO"
                $exists = Test-Path -Path $Path

                if ($exists) {
                    Write-EnhancedLog -Message "Path exists: $Path" -Level "INFO"

                    try {
                        $filesInPath = Get-ChildItem -Path $Path -Recurse -File
                        $fileCount = $filesInPath.Count

                        # Update counters
                        $totalExpectedFiles.Add($fileCount)
                        $totalValidatedFiles.Add($fileCount)

                        Write-EnhancedLog -Message "Total files found in $Path $fileCount" -Level "INFO"
                    }
                    catch {
                        Write-EnhancedLog -Message "Error retrieving files in path: $Path. Error: $($_.Exception.Message)" -Level "ERROR"
                        throw "Error retrieving files in path: $Path. Error: $($_.Exception.Message)"
                    }
                }
                else {
                    Write-EnhancedLog -Message "Path does not exist: $Path" -Level "WARNING"
                    $missingFiles.Add($Path)
                }
            }
            catch {
                Write-EnhancedLog -Message "Error during path validation for: $Path. Error: $($_.Exception.Message)" -Level "ERROR"
                Handle-Error -ErrorRecord $_
            }
        }
    }

    End {
        # Sum up the total counts
        $sumExpectedFiles = $totalExpectedFiles | Measure-Object -Sum | Select-Object -ExpandProperty Sum
        $sumValidatedFiles = $totalValidatedFiles | Measure-Object -Sum | Select-Object -ExpandProperty Sum

        if ($sumExpectedFiles -eq 0) {
            Write-EnhancedLog -Message "No files expected. Ensure the paths provided are correct and contain files." -Level "WARNING"
        }

        if ($sumValidatedFiles -lt $sumExpectedFiles) {
            $missingCount = $sumExpectedFiles - $sumValidatedFiles
            Write-EnhancedLog -Message "Validation incomplete: $missingCount files are missing." -Level "ERROR"
            $missingFiles | ForEach-Object {
                Write-EnhancedLog -Message "Missing file: $_" -Level "ERROR"
            }
        }
        else {
            Write-EnhancedLog -Message "Validation complete: All files accounted for." -Level "INFO"
        }

        # Log summary and results
        Write-EnhancedLog -Message "Validation Summary: Total Files Expected: $sumExpectedFiles, Total Files Validated: $sumValidatedFiles" -Level "INFO"
        Write-EnhancedLog -Message "Exiting Validate-PathExistsWithLogging function" -Level "INFO"

        # Return result summary
        return @{
            TotalExpectedFiles  = $sumExpectedFiles
            TotalValidatedFiles = $sumValidatedFiles
            MissingFiles        = $missingFiles
        }
    }
}

# Example usage
# $results = Validate-PathExistsWithLogging -Paths "C:\Path\To\Check", "C:\Another\Path"
#EndRegion '.\Public\Validate-PathExistsWithLogging.ps1' 94
#Region '.\Public\Validate-PPKGInstallation.ps1' -1


function Validate-PPKGInstallation {
    <#
    .SYNOPSIS
    Validates whether a provisioning package (PPKG) is installed on the system.
 
    .DESCRIPTION
    The Validate-PPKGInstallation function checks if a specified provisioning package (PPKG) is installed on the system by comparing its name with installed packages and validating the `IsInstalled` property. If found and installed, the function returns `$true`, otherwise `$false`.
 
    .PARAMETER PPKGName
    The name (or partial name) of the provisioning package (PPKG) to validate.
 
    .EXAMPLE
    # Example: Validate if the "ICTC_Project_2" PPKG is installed
    $ppkgInstalled = Validate-PPKGInstallation -PPKGName "ICTC_Project_2"
    if ($ppkgInstalled) {
        Write-Host "Provisioning package is installed."
    } else {
        Write-Host "Provisioning package is not installed."
    }
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Specify the name of the provisioning package (PPKG) to validate.")]
        [ValidateNotNullOrEmpty()]
        [string]$PPKGName
    )

    Begin {
        Write-EnhancedLog -Message "Starting Validate-PPKGInstallation function for $PPKGName" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Fetch all installed provisioning packages
            Write-EnhancedLog -Message "Fetching all installed provisioning packages..." -Level "INFO"
            $installedPackages = Get-ProvisioningPackage -AllInstalledPackages
    
            # Log the number of installed packages found
            $packageCount = $installedPackages.Count
            Write-EnhancedLog -Message "Found $packageCount installed provisioning packages." -Level "INFO"

            # Check if no packages are found and exit the script
            if ($packageCount -eq 0) {
                Write-EnhancedLog -Message "No provisioning packages found. Exiting script." -Level "WARNING"
                return
            }

    
            # Filter the packages based on the provided PPKG name
            Write-EnhancedLog -Message "Searching for a package matching: *$PPKGName*" -Level "INFO"
    
            $ppkgInfo = $null
            foreach ($package in $installedPackages) {
                Write-EnhancedLog -Message "Checking package: `nPackage ID: $($package.PackageId)`nPackage Name: $($package.PackageName)`nPackage Path: $($package.PackagePath)" -Level "INFO"
    
                # Match on PackageName, but also validate IsInstalled
                if ($package.PackageName -like "*$PPKGName*" -and $package.IsInstalled) {
                    Write-EnhancedLog -Message "Match found and installed: `nPackage ID: $($package.PackageId)`nPackage Name: $($package.PackageName)`nPackage Path: $($package.PackagePath)" -Level "INFO"
                    $ppkgInfo = $package
                    break
                }
                else {
                    Write-EnhancedLog -Message "No match or package not installed: $($package.PackageName)" -Level "INFO"
                }
            }
    
            # Log if a matching package was found or not
            if ($ppkgInfo) {
                Write-EnhancedLog -Message "Provisioning package $($ppkgInfo.PackageName) is installed." -Level "INFO"
                return $true
            }
            else {
                Write-EnhancedLog -Message "No matching installed package found for: *$PPKGName*" -Level "ERROR"
                return $false
            }
        }
        catch {
            # Log and handle any errors encountered during validation
            Write-EnhancedLog -Message "An error occurred during PPKG validation: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Validate-PPKGInstallation function for $PPKGName" -Level "NOTICE"
    }
}

# # Example: Validate if a specific provisioning package is installed
# $ppkgInstalled = Validate-PPKGInstallation -PPKGName "MyProvisioningPackage"
# if ($ppkgInstalled) {
# Write-Host "Provisioning package is installed."
# }
# else {
# Write-Host "Provisioning package is not installed."
# }
#EndRegion '.\Public\Validate-PPKGInstallation.ps1' 101
#Region '.\Public\Validate-PSADTFiles.ps1' -1

function Validate-PSADTFiles {
    <#
    .SYNOPSIS
    Validates the presence and non-emptiness of required PSADT files and folders.
 
    .DESCRIPTION
    This function checks if the necessary PSADT files (`Deploy-Application.exe`, `Deploy-Application.exe.config`, `Deploy-Application.ps1`)
    are present in the `Toolkit` folder. It also checks if the `AppDeployToolkit` folder exists and is not empty.
 
    .PARAMETER ScriptRoot
    The root path of the PowerShell script where the `Toolkit` and `AppDeployToolkit` folders are located.
 
    .EXAMPLE
    $params = @{
        ScriptRoot = "C:\Path\To\Your\ScriptRoot"
    }
    Validate-PSADTFiles @params
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Specify the root path of the PowerShell script.")]
        [ValidateNotNullOrEmpty()]
        [string]$ScriptRoot
    )

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

        # Define the required files in the Toolkit folder
        $requiredFiles = @(
            "Deploy-Application.exe",
            "Deploy-Application.exe.config",
            "Deploy-Application.ps1"
        )

        # Define the paths
        $appDeployToolkitPath = Join-Path -Path $ScriptRoot -ChildPath "AppDeployToolkit"
        # $toolkitPath = Join-Path -Path $ScriptRoot -ChildPath "Toolkit"
        $toolkitPath = $ScriptRoot

        # Check if the AppDeployToolkit folder exists and is not empty
        if (-not (Test-Path -Path $appDeployToolkitPath)) {
            throw "AppDeployToolkit folder not found at: $appDeployToolkitPath"
        } elseif ((Get-ChildItem -Path $appDeployToolkitPath | Measure-Object).Count -eq 0) {
            throw "AppDeployToolkit folder is empty: $appDeployToolkitPath"
        } else {
            Write-EnhancedLog -Message "AppDeployToolkit folder exists and is not empty: $appDeployToolkitPath" -Level "INFO"
        }
    }

    Process {
        try {
            # Validate each file's presence and non-emptiness in the Toolkit folder
            $validationPassed = $true

            foreach ($file in $requiredFiles) {
                $filePath = Join-Path -Path $toolkitPath -ChildPath $file
                
                if (-not (Test-Path -Path $filePath)) {
                    Write-EnhancedLog -Message "File missing: $filePath" -Level "ERROR"
                    $validationPassed = $false
                } elseif ((Get-Item $filePath).Length -eq 0) {
                    Write-EnhancedLog -Message "File is empty: $filePath" -Level "ERROR"
                    $validationPassed = $false
                } else {
                    Write-EnhancedLog -Message "File exists and is valid: $filePath" -Level "INFO"
                }
            }

            # Throw an error if validation failed
            if (-not $validationPassed) {
                throw "One or more PSADT files in the Toolkit folder are missing or empty."
            }

        } catch {
            Write-EnhancedLog -Message "An error occurred during file validation: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Validate-PSADTFiles function" -Level "Notice"
    }
}

# Example usage
# $params = @{
# ScriptRoot = "C:\Path\To\Your\ScriptRoot"
# }
# Validate-PSADTFiles @params
#EndRegion '.\Public\Validate-PSADTFiles.ps1' 94
#Region '.\Public\Validate-RegistryKeys.ps1' -1

function Validate-RegistryKeys {
    param (
        [string]$RegistryFilePath
    )

    Write-EnhancedLog -Message "Starting Validate-RegistryKeys function for: $RegistryFilePath" -Level 'INFO'

    try {
        $importedKeys = Get-Content -Path $RegistryFilePath | Where-Object { $_ -match '^\[.*\]$' } | ForEach-Object { $_ -replace '^\[|\]$', '' }
        $importSuccess = $true

        foreach ($key in $importedKeys) {
            if (Test-Path -Path "Registry::$key") {
                Write-EnhancedLog -Message "Validated registry key: $key" -Level 'INFO'
                Write-EnhancedLog "Validated registry key: $key" -Level 'INFO'
            }
            else {
                Write-EnhancedLog -Message "Failed to validate registry key: $key" -Level 'ERROR'
                Write-EnhancedLog "Failed to validate registry key: $key" -Level 'ERROR'
                $importSuccess = $false
            }
        }

        if ($importSuccess) {
            Write-EnhancedLog -Message "Successfully validated all registry keys for: $RegistryFilePath" -Level 'INFO'
        }
        else {
            Write-EnhancedLog -Message "Some registry keys failed to validate for: $RegistryFilePath" -Level 'ERROR'
        }
    }
    catch {
        Handle-Error -ErrorRecord $_
    }
    finally {
        Write-EnhancedLog -Message 'Validate-RegistryKeys function completed' -Level 'INFO'
    }
}
#EndRegion '.\Public\Validate-RegistryKeys.ps1' 38
#Region '.\Public\Validate-RegistryRemoval.ps1' -1

function Validate-RegistryRemoval {
    param (
        [string]$RegistryPath
    )

    Write-EnhancedLog -Message "Starting Validate-RegistryRemoval function for: $RegistryPath" -Level 'INFO'

    try {
        if (Test-Path -Path "Registry::$RegistryPath") {
            Write-EnhancedLog -Message "Registry path still exists: $RegistryPath" -Level 'ERROR'
            Write-Output "Registry path still exists: $RegistryPath"
        } else {
            Write-EnhancedLog -Message "Registry path successfully removed: $RegistryPath" -Level 'INFO'
            Write-Output "Registry path successfully removed: $RegistryPath"
        }
    } catch {
        Handle-Error -ErrorRecord $_
    } finally {
        Write-EnhancedLog -Message 'Validate-RegistryRemoval function completed' -Level 'INFO'
    }
}
#EndRegion '.\Public\Validate-RegistryRemoval.ps1' 22
#Region '.\Public\Validate-RegistryValue.ps1' -1

function Validate-RegistryValue {
    <#
    .SYNOPSIS
    Validates that a registry value is set correctly.
 
    .DESCRIPTION
    The Validate-RegistryValue function checks whether a registry value matches the expected data.
 
    .PARAMETER RegKeyPath
    The path to the registry key.
 
    .PARAMETER RegValName
    The name of the registry value.
 
    .PARAMETER ExpectedValData
    The expected data of the registry value.
    #>


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

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

        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [string]$ExpectedValData
    )

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

    Process {
        try {
            $CurrentValue = Get-ItemPropertyValue -Path $RegKeyPath -Name $RegValName
            if ($CurrentValue -eq $ExpectedValData) {
                Write-EnhancedLog -Message "Registry value: $RegValName is set correctly with data: $ExpectedValData" -Level "INFO"
                return $true
            }
            else {
                Write-EnhancedLog -Message "Registry value: $RegValName is not set correctly. Expected: $ExpectedValData, Found: $CurrentValue" -Level "WARNING"
                return $false
            }
        }
        catch {
            Write-EnhancedLog -Message "Registry value: $RegValName not found at $RegKeyPath" -Level "WARNING"
            return $false
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Validate-RegistryValue function" -Level "Notice"
    }
}

# Example usage:
# $params = @{
# RegKeyPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
# RegValName = "AutoAdminLogon"
# RegValType = "DWORD"
# RegValData = "0"
# }
# Set-RegistryValue @params
#EndRegion '.\Public\Validate-RegistryValue.ps1' 68
#Region '.\Public\Validate-ScheduledTask.ps1' -1

function Validate-ScheduledTask {
    <#
    .SYNOPSIS
    Validates whether a scheduled task exists and meets the expected criteria.
 
    .DESCRIPTION
    The Validate-ScheduledTask function checks if a scheduled task exists at the specified task path and validates its properties.
 
    .PARAMETER TaskPath
    The path of the task in Task Scheduler.
 
    .PARAMETER TaskName
    The name of the scheduled task.
 
    .EXAMPLE
    Validate-ScheduledTask -TaskPath "AAD Migration" -TaskName "Run Post-migration cleanup"
    Validates if the scheduled task exists and meets the expected criteria.
    #>


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

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

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

    Process {
        try {
            $taskExists = Get-ScheduledTask -TaskPath "\$TaskPath\" -TaskName $TaskName -ErrorAction SilentlyContinue
            if ($taskExists) {
                Write-EnhancedLog -Message "Scheduled task '$TaskName' exists at '$TaskPath'." -Level "INFO"
                return $true
            } else {
                Write-EnhancedLog -Message "Scheduled task '$TaskName' does not exist at '$TaskPath'." -Level "WARNING"
                return $false
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Validate-ScheduledTask function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Validate-ScheduledTask function" -Level "Notice"
    }
}
#EndRegion '.\Public\Validate-ScheduledTask.ps1' 56
#Region '.\Public\Validate-SoftwareInstallation.ps1' -1

function Validate-SoftwareInstallation {
    <#
    .SYNOPSIS
    Validates the installation of a specified software by checking registry or file-based versions.
 
    .DESCRIPTION
    The function checks if the specified software is installed, either through registry validation or file-based validation, and ensures that the installed version meets a specified minimum version. Supports retries in case of transient issues.
 
    .PARAMETER SoftwareName
    The name of the software to validate.
 
    .PARAMETER MinVersion
    The minimum version of the software required for validation.
 
    .PARAMETER RegistryPath
    The specific registry path to validate the software installation.
 
    .PARAMETER ExePath
    The path to the executable file of the software for file-based validation.
 
    .PARAMETER MaxRetries
    The maximum number of retry attempts for validation.
 
    .PARAMETER DelayBetweenRetries
    The delay in seconds between retry attempts.
 
    .EXAMPLE
    $params = @{
        SoftwareName = 'MySoftware'
        MinVersion = '1.0.0.0'
        RegistryPath = 'HKLM:\SOFTWARE\MySoftware'
        ExePath = 'C:\Program Files\MySoftware\mysoftware.exe'
        MaxRetries = 3
        DelayBetweenRetries = 5
    }
    Validate-SoftwareInstallation @params
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the software name.")]
        [ValidateNotNullOrEmpty()]
        [string]$SoftwareName,

        [Parameter(Mandatory = $false, HelpMessage = "Provide the minimum software version required.")]
        [ValidateNotNullOrEmpty()]
        [version]$MinVersion = [version]"0.0.0.0",

        [Parameter(Mandatory = $false, HelpMessage = "Specify a registry path for validation.")]
        [ValidateNotNullOrEmpty()]
        [string]$RegistryPath = "",

        [Parameter(Mandatory = $false, HelpMessage = "Specify the path to the software executable for validation.")]
        [ValidateNotNullOrEmpty()]
        [string]$ExePath = "",

        [Parameter(Mandatory = $false, HelpMessage = "Maximum number of retries for validation.")]
        [ValidateRange(1, 10)]
        [int]$MaxRetries = 3,

        [Parameter(Mandatory = $false, HelpMessage = "Delay in seconds between retries.")]
        [ValidateRange(1, 60)]
        [int]$DelayBetweenRetries = 5
    )

    Begin {
        Write-EnhancedLog -Message "Starting Validate-SoftwareInstallation function for $SoftwareName" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        $retryCount = 0
        $validationSucceeded = $false
        $foundVersion = $null

        while ($retryCount -lt $MaxRetries -and -not $validationSucceeded) {
            # Registry-based validation
            if ($RegistryPath -or $SoftwareName) {
                Write-EnhancedLog -Message "Starting registry-based validation for $SoftwareName." -Level "INFO"

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

                if ($RegistryPath) {
                    Write-EnhancedLog -Message "Checking specific registry path: $RegistryPath." -Level "INFO"
                    if (Test-Path $RegistryPath) {
                        $app = Get-ItemProperty -Path $RegistryPath -ErrorAction SilentlyContinue
                        if ($app -and $app.DisplayName -like "*$SoftwareName*") {
                            $installedVersion = Sanitize-VersionString -versionString $app.DisplayVersion
                            $foundVersion = $installedVersion
                            if ($installedVersion -ge $MinVersion) {
                                Write-EnhancedLog -Message "Registry validation succeeded: $SoftwareName version $installedVersion found." -Level "INFO"
                                return @{
                                    IsInstalled = $true
                                    Version     = $installedVersion
                                    ProductCode = $app.PSChildName
                                }
                            }
                        }
                    }
                } else {
                    Write-EnhancedLog -Message "Checking common uninstall registry paths for $SoftwareName." -Level "INFO"
                    foreach ($path in $registryPaths) {
                        $items = Get-ChildItem -Path $path -ErrorAction SilentlyContinue
                        foreach ($item in $items) {
                            $app = Get-ItemProperty -Path $item.PsPath -ErrorAction SilentlyContinue
                            if ($app.DisplayName -like "*$SoftwareName*") {
                                $installedVersion = Sanitize-VersionString -versionString $app.DisplayVersion
                                $foundVersion = $installedVersion
                                if ($installedVersion -ge $MinVersion) {
                                    Write-EnhancedLog -Message "Registry validation succeeded: $SoftwareName version $installedVersion found." -Level "INFO"
                                    return @{
                                        IsInstalled = $true
                                        Version     = $installedVersion
                                        ProductCode = $app.PSChildName
                                    }
                                }
                            }
                        }
                    }
                }
            }

            # File-based validation
            if ($ExePath) {
                Write-EnhancedLog -Message "Starting file-based validation for $SoftwareName at $ExePath." -Level "INFO"
                if (Test-Path $ExePath) {
                    $appVersionString = (Get-ItemProperty -Path $ExePath).VersionInfo.ProductVersion.Split(" ")[0]
                    $appVersion = Sanitize-VersionString -versionString $appVersionString
                    $foundVersion = $appVersion

                    if ($appVersion -ge $MinVersion) {
                        Write-EnhancedLog -Message "File-based validation succeeded: $SoftwareName version $appVersion found." -Level "INFO"
                        return @{
                            IsInstalled = $true
                            Version     = $appVersion
                            Path        = $ExePath
                        }
                    } else {
                        Write-EnhancedLog -Message "File-based validation failed: $SoftwareName version $appVersion does not meet the minimum requirement ($MinVersion)." -Level "ERROR"
                    }
                } else {
                    Write-EnhancedLog -Message "File-based validation failed: $SoftwareName executable not found at $ExePath." -Level "ERROR"
                }
            }

            # Retry logic
            if ($foundVersion) {
                Write-EnhancedLog -Message "$SoftwareName version $foundVersion was found, but does not meet the minimum version requirement ($MinVersion)." -Level "ERROR"
            } else {
                Write-EnhancedLog -Message "Validation attempt $retryCount failed: $SoftwareName not found or does not meet the version requirement. Retrying in $DelayBetweenRetries seconds..." -Level "WARNING"
            }

            $retryCount++
            Start-Sleep -Seconds $DelayBetweenRetries
        }

        Write-EnhancedLog -Message "Validation failed after $MaxRetries retries: $SoftwareName not found or version did not meet the minimum requirement." -Level "ERROR"
        return @{ IsInstalled = $false; Version = $foundVersion }
    }

    End {
        Write-EnhancedLog -Message "Exiting Validate-SoftwareInstallation function for $SoftwareName." -Level "NOTICE"
    }
}
#EndRegion '.\Public\Validate-SoftwareInstallation.ps1' 169
#Region '.\Public\Validate-UriAccess.ps1' -1

function Validate-UriAccess {
    param (
        [string]$uri,
        [hashtable]$headers
    )

    Write-EnhancedLog -Message "Validating access to URI: $uri" -Level "INFO" -ForegroundColor ([ConsoleColor]::Cyan)
    try {
        $response = Invoke-WebRequest -Uri $uri -Headers $headers -Method Get
        if ($response.StatusCode -eq 200) {
            Write-EnhancedLog -Message "Access to $uri PASS" -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
            return $true
        } else {
            Write-EnhancedLog -Message "Access to $uri FAIL" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
            return $false
        }
    } catch {
        Write-EnhancedLog -Message "Access to $uri FAIL - $_" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
        return $false
    }
}
#EndRegion '.\Public\Validate-UriAccess.ps1' 22
#Region '.\Public\Validate-VHDMount.ps1' -1

function Validate-VHDMount {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$VHDXPath
    )

    Begin {
        Write-EnhancedLog -Message "Starting Validate-VHDMount function" -Level "INFO"
        Log-Params -Params @{ VHDXPath = $VHDXPath }
    }

    Process {
        try {
            Write-EnhancedLog -Message "Checking if the VHDX is mounted: $VHDXPath" -Level "INFO"
            $vhd = Get-VHD -Path $VHDXPath -ErrorAction SilentlyContinue
            
            if ($null -eq $vhd) {
                Write-EnhancedLog -Message "Get-VHD did not return any information for the path: $VHDXPath" -Level "INFO" -ForegroundColor Red
                return $false
            }

            Write-EnhancedLog -Message "Get-VHD output: $($vhd | Format-List | Out-String)" -Level "DEBUG"

            if ($vhd.Attached) {
                Write-EnhancedLog -Message "VHDX is mounted: $VHDXPath" -Level "INFO" -ForegroundColor Green
                return $true
            } else {
                Write-EnhancedLog -Message "VHDX is not mounted: $VHDXPath" -Level "INFO" -ForegroundColor Red
                return $false
            }
        } catch {
            Write-EnhancedLog -Message "An error occurred while validating VHD mount status: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            return $false
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Validate-VHDMount function" -Level "INFO"
    }
}
#EndRegion '.\Public\Validate-VHDMount.ps1' 43
#Region '.\Public\Validate-VMExists.ps1' -1

function Validate-VMExists {
    <#
    .SYNOPSIS
    Validates if a VM with the specified name exists.
    #>

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

    Begin {
        Write-EnhancedLog -Message "Starting Validate-VMExists function" -Level "INFO"
        Log-Params -Params @{ VMName = $VMName }
    }

    Process {
        try {
            Write-EnhancedLog -Message "Checking existence of VM: $VMName" -Level "INFO"
            $vm = Get-VM -Name $VMName -ErrorAction Stop
            Write-EnhancedLog -Message "VM $VMName exists." -Level "INFO" -ForegroundColor ([ConsoleColor]::Green)
            return $true
        } catch {
            Write-EnhancedLog -Message "VM $VMName does not exist. $($_.Exception.Message)" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red)
            Handle-Error -ErrorRecord $_
            return $false
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Validate-VMExists function" -Level "INFO"
    }
}
#EndRegion '.\Public\Validate-VMExists.ps1' 34
#Region '.\Public\Validate-VMStarted.ps1' -1

function Validate-VMStarted {
    <#
    .SYNOPSIS
    Validates if the specified VM is started (running).
    #>

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

    Begin {
        Write-EnhancedLog -Message "Starting Validate-VMStarted function" -Level "INFO"
        Log-Params -Params @{ VMName = $VMName }
    }

    Process {
        try {
            Write-EnhancedLog -Message "Checking state of VM: $VMName" -Level "INFO"
            $vm = Get-VM -Name $VMName -ErrorAction Stop

            if ($vm.State -eq 'Running') {
                Write-EnhancedLog -Message "VM $VMName is running." -Level "INFO"
                return $true
            }
            else {
                Write-EnhancedLog -Message "VM $VMName is not running." -Level "WARNING"
                return $false
            }
        }
        catch {
            Write-EnhancedLog -Message "Failed to check the state of VM $VMName. $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Validate-VMStarted function" -Level "INFO"
    }
}
#EndRegion '.\Public\Validate-VMStarted.ps1' 42
#Region '.\Public\Verify-CopyOperation.ps1' -1

function Verify-CopyOperation {
    <#
    .SYNOPSIS
    Verifies that files have been correctly copied between a source and destination directory.
 
    .DESCRIPTION
    The Verify-CopyOperation function compares the contents of a source directory with a destination directory to ensure that all files and directories have been successfully copied.
    It reports missing files, extra files, and provides detailed information about any discrepancies found during the verification process.
 
    .PARAMETER SourcePath
    The path to the source directory whose contents are being copied and need verification.
 
    .PARAMETER DestinationPath
    The path to the destination directory where the files from the source have been copied.
 
    .EXAMPLE
    Verify-CopyOperation -SourcePath "C:\Source" -DestinationPath "C:\Destination"
    Verifies the copied contents between C:\Source and C:\Destination, checking for any discrepancies such as missing or extra files.
 
    .OUTPUTS
    Custom objects detailing missing, extra, or mismatched files between the source and destination.
 
    .NOTES
    The function uses recursion to verify subdirectories and outputs the results to the console.
    Any discrepancies between the source and destination directories are logged for analysis.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the source directory path.")]
        [string]$SourcePath,

        [Parameter(Mandatory = $true, HelpMessage = "Provide the destination directory path.")]
        [string]$DestinationPath
    )

    begin {
        Write-EnhancedLog -Message "Verifying copy operation..." -Level "Notice"
        Log-Params -Params @{
            SourcePath = $SourcePath
            DestinationPath = $DestinationPath
        }

        $sourceItems = Get-ChildItem -Path $SourcePath -Recurse -File
        $destinationItems = Get-ChildItem -Path $DestinationPath -Recurse -File

        # Use a generic list for better performance compared to using an array with +=
        $verificationResults = [System.Collections.Generic.List[PSCustomObject]]::new()
    }

    process {
        try {
            foreach ($item in $sourceItems) {
                $relativePath = $item.FullName.Substring($SourcePath.Length)
                $correspondingPath = Join-Path -Path $DestinationPath -ChildPath $relativePath

                if (-not (Test-Path -Path $correspondingPath)) {
                    $verificationResults.Add([PSCustomObject]@{
                            Status       = "Missing"
                            SourcePath   = $item.FullName
                            ExpectedPath = $correspondingPath
                            FileSize     = $item.Length
                            LastModified = $item.LastWriteTime
                        })
                }
                else {
                    # Compare file sizes and timestamps
                    $destItem = Get-Item -Path $correspondingPath
                    if ($item.Length -ne $destItem.Length -or $item.LastWriteTime -ne $destItem.LastWriteTime) {
                        $verificationResults.Add([PSCustomObject]@{
                                Status       = "Mismatch"
                                SourcePath   = $item.FullName
                                ExpectedPath = $correspondingPath
                                SourceSize   = $item.Length
                                DestinationSize = $destItem.Length
                                SourceModified  = $item.LastWriteTime
                                DestinationModified = $destItem.LastWriteTime
                            })
                    }
                }
            }

            foreach ($item in $destinationItems) {
                $relativePath = $item.FullName.Substring($DestinationPath.Length)
                $correspondingPath = Join-Path -Path $SourcePath -ChildPath $relativePath

                if (-not (Test-Path -Path $correspondingPath)) {
                    $verificationResults.Add([PSCustomObject]@{
                            Status       = "Extra"
                            ActualPath   = $item.FullName
                            FileSize     = $item.Length
                            LastModified = $item.LastWriteTime
                        })
                }
            }
        }
        catch {
            Write-EnhancedLog -Message "Error during verification process: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
        }
    }

    end {
        if ($verificationResults.Count -gt 0) {
            Write-EnhancedLog -Message "Discrepancies found. See detailed log." -Level "WARNING"
            $verificationResults | Format-Table -AutoSize | Out-String | ForEach-Object { 
                Write-EnhancedLog -Message $_ -Level "INFO" 
            }

            # Uncomment when troubleshooting
            # $verificationResults | Out-GridView
        }
        else {
            Write-EnhancedLog -Message "All items verified successfully. No discrepancies found." -Level "Notice"
        }

        Write-EnhancedLog -Message ("Total items in source: $SourcePath " + $sourceItems.Count) -Level "INFO"
        Write-EnhancedLog -Message ("Total items in destination: $DestinationPath " + $destinationItems.Count) -Level "INFO"

        # Return the verification results for further processing
        return $verificationResults
    }
}
#EndRegion '.\Public\Verify-CopyOperation.ps1' 124
#Region '.\Public\Verify-GroupMembers-Archive.ps1' -1

# function Verify-GroupMembers {
# param (
# [string]$GroupName = 'Administrators'
# )

# $groupMembers = Get-GroupMembers -GroupName $GroupName
# foreach ($member in $groupMembers) {
# Write-EnhancedLog -Message "Processing member: $($member.Name)" -Level "INFO"

# $sid = Resolve-SID -AccountName $member.Name
# if ($sid) {
# Write-EnhancedLog -Message "Resolved SID for member $($member.Name): $($sid.Value)" -Level "INFO"
# } else {
# Write-EnhancedLog -Message "Skipping member $($member.Name) due to invalid or unresolved SID." -Level "WARNING"
# }
# }
# }
#EndRegion '.\Public\Verify-GroupMembers-Archive.ps1' 18
#Region '.\Public\WaitForRegistryKey.ps1' -1

function WaitForRegistryKey {
    param (
        [string[]]$RegistryPaths,
        [string]$SoftwareName,
        [version]$MinimumVersion,
        [int]$TimeoutSeconds = 120
    )


    Write-EnhancedLog -Message "Starting WaitForRegistryKey function" -Level "INFO"
    Write-EnhancedLog -Message "Checking for $SoftwareName version $MinimumVersion or later" -Level "INFO"

    $elapsedSeconds = 0

    try {
        while ($elapsedSeconds -lt $TimeoutSeconds) {
            foreach ($path in $RegistryPaths) {
                $items = Get-ChildItem -Path $path -ErrorAction SilentlyContinue

                foreach ($item in $items) {
                    $app = Get-ItemProperty -Path $item.PsPath -ErrorAction SilentlyContinue
                    if ($app.DisplayName -like "*$SoftwareName*") {
                        $installedVersion = New-Object Version $app.DisplayVersion
                        if ($installedVersion -ge $MinimumVersion) {
                            Write-EnhancedLog -Message "Found $SoftwareName version $installedVersion at $item.PsPath" -Level "INFO"
                            return @{
                                IsInstalled = $true
                                Version     = $app.DisplayVersion
                                ProductCode = $app.PSChildName
                            }
                        }
                    }
                }
            }

            Start-Sleep -Seconds 1
            $elapsedSeconds++
        }

        Write-EnhancedLog -Message "Timeout reached. $SoftwareName version $MinimumVersion or later not found." -Level "WARNING"
        return @{ IsInstalled = $false }
    }
    catch {
        Handle-Error -ErrorRecord $_
    }
    finally {
        Write-EnhancedLog -Message "WaitForRegistryKey function completed" -Level "INFO"
    }
}
#EndRegion '.\Public\WaitForRegistryKey.ps1' 50
#Region '.\Public\Write-EnhancedLog.ps1' -1

function Write-EnhancedLog {
    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 = "[$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' }
    }

    # Log the message using PSFramework with the actual calling function name
    Write-PSFMessage -Level $psfLevel -Message $formattedMessage -FunctionName "$parentScriptName.$callerFunction"
}
#EndRegion '.\Public\Write-EnhancedLog.ps1' 39
#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
#Region '.\Public\Write-LogMessage.ps1' -1

function Generate-UniqueBase {
    <#
    .SYNOPSIS
    Generates a unique base identifier using the current timestamp, process ID, and a random number.
 
    .DESCRIPTION
    The Generate-UniqueBase function creates a unique identifier composed of parts of the current timestamp, the process ID, and a random number. This ensures uniqueness for various applications that require a simple, unique identifier.
 
    .PARAMETER TimestampFormat
    Specifies the format of the timestamp to use in the identifier.
 
    .PARAMETER ProcessIdLength
    Specifies the number of digits to use from the process ID.
 
    .PARAMETER RandomPartMin
    Specifies the minimum value for the random number part.
 
    .PARAMETER RandomPartMax
    Specifies the maximum value for the random number part.
 
    .EXAMPLE
    Generate-UniqueBase -TimestampFormat "yyMMddHHmm" -ProcessIdLength 4 -RandomPartMin 10 -RandomPartMax 99
    Generates a unique identifier using the specified timestamp format, process ID length, and random number range.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]$TimestampFormat = "yyMMddHHmm",

        [Parameter(Mandatory = $false)]
        [int]$ProcessIdLength = 4,

        [Parameter(Mandatory = $false)]
        [int]$RandomPartMin = 10,

        [Parameter(Mandatory = $false)]
        [int]$RandomPartMax = 99
    )

    Begin {
        Write-EnhancedLog -Message "Starting Generate-UniqueBase function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Generate the components
            $timestamp = (Get-Date).ToString($TimestampFormat)
            $processid = $PID.ToString("D$ProcessIdLength")
            $randomPart = (Get-Random -Minimum $RandomPartMin -Maximum $RandomPartMax).ToString("D2")

            Write-EnhancedLog -Message "Generated timestamp: '$timestamp', Process ID: '$processid', Random Part: '$randomPart'" -Level "INFO"

            # Adjust the components to ensure the length is 8 characters
            Write-EnhancedLog -Message "Original lengths -> Timestamp: $($timestamp.Length), Process ID: $($processid.Length), Random Part: $($randomPart.Length)" -Level "INFO"

            $uniqueBase = $timestamp.Substring($timestamp.Length - 4, 4) + $processid.Substring(0, 2) + $randomPart

            Write-EnhancedLog -Message "Generated Unique Base (before validation): '$uniqueBase', Length: $($uniqueBase.Length)" -Level "INFO"

            # Validate that the unique base is exactly 8 characters long
            if ($uniqueBase.Length -ne 8) {
                $errorMessage = "The generated unique base '$uniqueBase' is not 8 characters long. It is $($uniqueBase.Length) characters. Halting script execution."
                Write-EnhancedLog -Message $errorMessage -Level "CRITICAL" -ForegroundColor ([ConsoleColor]::Red)
                throw $errorMessage
            }

            Write-EnhancedLog -Message "Unique base successfully generated: $uniqueBase" -Level "INFO"
            return $uniqueBase
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Generate-UniqueBase function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Generate-UniqueBase function" -Level "NOTICE"
    }
}

function Validate-EventLog {
    <#
    .SYNOPSIS
    Validates whether an event log and its associated source exist and are correctly configured.
    #>


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

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

        [Parameter(Mandatory = $false)]
        [switch]$PostValidation
    )

    Begin {
        Write-EnhancedLog -Message "Starting Validate-EventLog function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Validate if the log exists
            Write-EnhancedLog -Message "Validating if event log '$LogName' exists." -Level "INFO"
            $logExists = @(Get-WinEvent -ListLog * | Where-Object { $_.LogName -eq $LogName }).Count -gt 0
            Write-EnhancedLog -Message "Event log '$LogName' existence: $logExists" -Level "INFO"

            # Validate if the source exists
            Write-EnhancedLog -Message "Validating if event source '$SourceName' exists." -Level "INFO"
            $sourceExists = [System.Diagnostics.EventLog]::SourceExists($SourceName)
            Write-EnhancedLog -Message "Event source '$SourceName' existence: $sourceExists" -Level "INFO"

            $sourceLogName = if ($sourceExists) { 
                [System.Diagnostics.EventLog]::LogNameFromSourceName($SourceName, ".") 
            }
            else { 
                Write-EnhancedLog -Message "Event source '$SourceName' is not associated with any log." -Level "WARNING"
                $null 
            }

            Write-EnhancedLog -Message "Event source '$SourceName' is associated with log: $sourceLogName" -Level "INFO"

            # Return the validation results as a hashtable
            $result = @{
                LogExists     = $logExists
                SourceExists  = $sourceExists
                SourceLogName = $sourceLogName
            }

            # Ensure the result is correctly returned as a hashtable
            if ($result -is [System.Collections.Hashtable]) {
                Write-EnhancedLog -Message "Returning validation result as a hashtable: LogExists = $($result['LogExists']), SourceExists = $($result['SourceExists']), SourceLogName = $($result['SourceLogName'])" -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "Unexpected data type for validation result. Expected a hashtable." -Level "ERROR"
                throw "Unexpected data type for validation result."
            }

            return $result
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Validate-EventLog function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Validate-EventLog function" -Level "NOTICE"
    }
}

function Manage-EventLogSource {
    <#
    .SYNOPSIS
    Manages the event log source, ensuring it is correctly associated with the specified event log.
 
    .DESCRIPTION
    The Manage-EventLogSource function checks the current state of the event log and source. It handles scenarios where the log or source might not exist, or where the source is associated with a different log, and ensures that the event log and source are properly created or reassigned.
 
    .PARAMETER LogName
    The name of the event log.
 
    .PARAMETER SourceName
    The name of the event source.
 
    .PARAMETER ValidationResult
    A hashtable containing the results of the pre-validation process, including whether the log and source exist and their associations.
 
    .EXAMPLE
    Manage-EventLogSource -LogName "MyLog" -SourceName "MySource" -ValidationResult $validationResult
    Ensures that the event log "MyLog" is correctly associated with the source "MySource."
    #>


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

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

        [Parameter(Mandatory = $true)]
        [hashtable]$ValidationResult
    )

    Begin {
        Write-EnhancedLog -Message "Starting Manage-EventLogSource function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            if (-not $ValidationResult.LogExists -and -not $ValidationResult.SourceExists) {
                # Neither the log nor the source exist, create both
                Write-EnhancedLog -Message "Neither log nor source exists. Creating both." -Level "INFO"
                New-EventLog -LogName $LogName -Source $SourceName
                Write-EnhancedLog -Message "Created event log '$LogName' and source '$SourceName'." -Level "INFO"
            }
            elseif ($ValidationResult.LogExists -and $ValidationResult.SourceExists -and $ValidationResult.SourceLogName -ne $LogName) {
                # Both log and source exist but the source is associated with a different log
                Write-EnhancedLog -Message "Source exists but is associated with a different log. Recreating source." -Level "WARNING"
                Remove-EventLog -Source $SourceName
                New-EventLog -LogName $LogName -Source $SourceName
                Write-EnhancedLog -Message "Source '$SourceName' was associated with a different log ('$ValidationResult.SourceLogName'). Recreated event log '$LogName' with source '$SourceName'." -Level "WARNING"
            }
            elseif (-not $ValidationResult.LogExists -and $ValidationResult.SourceExists) {
                # Source exists but is associated with a different log, so remove the source and create the correct log and source
                Write-EnhancedLog -Message "Log does not exist, but source exists. Removing source and creating log." -Level "WARNING"
                Remove-EventLog -Source $SourceName
                New-EventLog -LogName $LogName -Source $SourceName
                Write-EnhancedLog -Message "Event source '$SourceName' was associated with a different log. Recreated event log '$LogName' with source '$SourceName'." -Level "WARNING"
            }
            elseif ($ValidationResult.LogExists -and -not $ValidationResult.SourceExists) {
                # Log exists but the source does not, so create the source
                Write-EnhancedLog -Message "Log exists, but source does not. Creating source." -Level "INFO"
                New-EventLog -LogName $LogName -Source $SourceName
                Write-EnhancedLog -Message "Added source '$SourceName' to existing event log '$LogName'." -Level "INFO"
            }
            else {
                # Both log and source exist and are correctly associated
                Write-EnhancedLog -Message "Event log '$LogName' and source '$SourceName' already exist and are correctly associated." -Level "INFO"
            }

            $DBG
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Manage-EventLogSource function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Manage-EventLogSource function" -Level "NOTICE"
    }
}

function Sanitize-ParentScriptName {
    <#
    .SYNOPSIS
    Sanitizes the parent script name by removing spaces and any special characters.
 
    .DESCRIPTION
    The Sanitize-ParentScriptName function ensures that the parent script name is sanitized by removing spaces, special characters, and ensuring it conforms to naming conventions suitable for log names.
 
    .PARAMETER ParentScriptName
    The parent script name to sanitize.
 
    .EXAMPLE
    $sanitizedParentScriptName = Sanitize-ParentScriptName -ParentScriptName "My Script Name 2024"
    #>


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

    Begin {
        Write-EnhancedLog -Message "Starting Sanitize-ParentScriptName function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Remove any spaces and special characters
            Write-EnhancedLog -Message "Sanitizing the parent script name: '$ParentScriptName'." -Level "INFO"
            $sanitizedParentScriptName = $ParentScriptName -replace '\s+', '' -replace '[^a-zA-Z0-9]', ''
            Write-EnhancedLog -Message "Sanitized parent script name: '$sanitizedParentScriptName'." -Level "INFO"

            return $sanitizedParentScriptName
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Sanitize-ParentScriptName function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Sanitize-ParentScriptName function" -Level "NOTICE"
    }
}

function Sanitize-LogName {
    <#
    .SYNOPSIS
    Sanitizes the log name to ensure it is a valid string.
 
    .DESCRIPTION
    The Sanitize-LogName function checks if the log name is a valid string. If the log name is not a string, the function attempts to convert it. If it contains invalid characters or if the conversion is not possible, an error is thrown.
 
    .PARAMETER LogName
    The log name to sanitize.
 
    .EXAMPLE
    $sanitizedLogName = Sanitize-LogName -LogName $logName
    Sanitizes the provided log name and returns a clean string.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [AllowNull()]
        [AllowEmptyString()]
        [System.Object]$LogName
    )

    Begin {
        Write-EnhancedLog -Message "Starting Sanitize-LogName function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Ensure the LogName is a single string, not an array or hashtable
            if ($LogName -is [System.Collections.IEnumerable] -and $LogName -notlike [string]) {
                Write-EnhancedLog -Message "Log name is an array or collection. Attempting to extract the first valid string." -Level "WARNING"
                $LogName = $LogName | Where-Object { $_ -is [string] } | Select-Object -First 1
            }

            # If LogName is still not a string, throw an error
            if ($LogName -isnot [string]) {
                Write-EnhancedLog -Message "Log name is not a valid string. Actual type: $($LogName.GetType().Name)" -Level "ERROR"
                throw "Log name must be a string. Received type: $($LogName.GetType().Name)"
            }

            # Sanitize the log name by trimming and removing any unnecessary characters
            $sanitizedLogName = $LogName.Trim() -replace '[^\w-]', ''
            Write-EnhancedLog -Message "Sanitized log name: '$sanitizedLogName'" -Level "INFO"

            return $sanitizedLogName
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Sanitize-LogName function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Sanitize-LogName function" -Level "NOTICE"
    }
}

function Sanitize-SourceName {
    <#
    .SYNOPSIS
    Sanitizes the source name by removing spaces and special characters.
 
    .DESCRIPTION
    The Sanitize-SourceName function ensures that the source name is sanitized by removing spaces, special characters, and ensuring it conforms to naming conventions suitable for log sources.
 
    .PARAMETER SourceName
    The source name to sanitize.
 
    .EXAMPLE
    $sanitizedSourceName = Sanitize-SourceName -SourceName "My Source Name 2024"
    #>


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

    Begin {
        Write-EnhancedLog -Message "Starting Sanitize-SourceName function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Remove any spaces and special characters
            Write-EnhancedLog -Message "Sanitizing the source name: '$SourceName'." -Level "INFO"
            $sanitizedSourceName = $SourceName -replace '\s+', '' -replace '[^a-zA-Z0-9]', ''
            Write-EnhancedLog -Message "Sanitized source name: '$sanitizedSourceName'." -Level "INFO"

            return $sanitizedSourceName
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Sanitize-SourceName function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Sanitize-SourceName function" -Level "NOTICE"
    }
}

function Manage-EventLogs {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$ParentScriptName
    )

    Begin {
        Write-EnhancedLog -Message "Starting Manage-EventLogs function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Step 1: Sanitize the parent script name
            Write-EnhancedLog -Message "Sanitizing the parent script name." -Level "INFO"
            $sanitizedParentScriptName = Sanitize-ParentScriptName -ParentScriptName $ParentScriptName
            Write-EnhancedLog -Message "Sanitized parent script name: '$sanitizedParentScriptName'." -Level "INFO"

            # Step 2: Generate the unique base for the log name
            Write-EnhancedLog -Message "Generating unique base for log name." -Level "INFO"
            $uniqueBase = Generate-UniqueBase
            Write-EnhancedLog -Message "Unique base generated: $uniqueBase" -Level "INFO"

            # Step 3: Combine the unique base with the sanitized parent script name
            $logName = "$uniqueBase-$sanitizedParentScriptName"
            Write-EnhancedLog -Message "Constructed log name: $logName" -Level "INFO"

            # Step 4: Sanitize the log name
            Write-EnhancedLog -Message "Sanitizing the log name." -Level "INFO"
            $global:sanitizedlogName = Sanitize-LogName -LogName $logName
            Write-EnhancedLog -Message "Sanitized log name: '$global:sanitizedlogName'." -Level "INFO"

            # Step 5: Retrieve existing logs matching the sanitized parent script name
            Write-EnhancedLog -Message "Retrieving existing logs that match the sanitized parent script name: '$sanitizedParentScriptName'." -Level "INFO"
            $logs = @()
            try {
                $logs = @(Get-WinEvent -ListLog * | Where-Object { $_.LogName -like "*$sanitizedParentScriptName*" })
                Write-EnhancedLog -Message "Retrieved $($logs.Count) logs that match the sanitized parent script name." -Level "INFO"
            }
            catch {
                Write-EnhancedLog -Message "Failed to retrieve logs due to an error: $($_.Exception.Message)" -Level "ERROR"
                throw $_
            }

            # Step 6: Check if an existing log should be used
            if ($logs.Count -gt 0) {
                $existingLog = $logs | Select-Object -First 1
                if ($existingLog) {
                    $logName = $existingLog.LogName
                    Write-EnhancedLog -Message "Existing log found and will be used: '$logName'." -Level "INFO"
                }
            }
            else {
                Write-EnhancedLog -Message "No existing log found for sanitized parent script name: '$sanitizedParentScriptName'. Creating a new log: '$logName'." -Level "INFO"
            }

            # Return the sanitized and validated log name
            return $logName
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Manage-EventLogs function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Manage-EventLogs function" -Level "NOTICE"
    }
}

function Construct-SourceName {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$LogName,

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

    Begin {
        Write-EnhancedLog -Message "Starting Construct-SourceName function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Step 1: Construct the initial source name
            Write-EnhancedLog -Message "Constructing source name using LogName '$LogName' and CallerFunction '$CallerFunction'." -Level "INFO"
            $sourceName = "$LogName-$CallerFunction"
            Write-EnhancedLog -Message "Constructed source name: '$sourceName'" -Level "INFO"

            # Step 2: Sanitize the source name
            Write-EnhancedLog -Message "Sanitizing the constructed source name." -Level "INFO"
            $sanitizedSourceName = Sanitize-SourceName -SourceName $sourceName
            Write-EnhancedLog -Message "Sanitized source name: '$sanitizedSourceName'." -Level "INFO"

            return $sanitizedSourceName
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Construct-SourceName function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Construct-SourceName function" -Level "NOTICE"
    }
}
function Initialize-EventLog {
    <#
    .SYNOPSIS
    Initializes the event log by managing logs, validating before and after operations, and managing sources.
 
    .DESCRIPTION
    The Initialize-EventLog function handles the entire process of setting up an event log. It manages the creation or retrieval of logs, validates them before and after operations, and ensures the correct association of event sources.
 
    .PARAMETER SourceName
    The name of the event source to be used.
 
    .PARAMETER ParentScriptName
    The name of the parent script to be used as part of the event log name.
 
    .EXAMPLE
    Initialize-EventLog -SourceName "MySource" -ParentScriptName "MyScript"
    Initializes an event log with the specified source and parent script name.
    #>


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

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

    Begin {
        Write-EnhancedLog -Message "Starting Initialize-EventLog function" -Level "WARNING"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Step 1: Manage Logs
            Write-EnhancedLog -Message "Managing event logs." -Level "INFO"
            try {
                Write-EnhancedLog -Message "Calling Manage-EventLogs function with ParentScriptName: '$ParentScriptName'." -Level "DEBUG"
                $global:sanitizedlogName = Manage-EventLogs -ParentScriptName $ParentScriptName
                Write-EnhancedLog -Message "Manage-EventLogs function returned log name: '$global:sanitizedlogName'." -Level "INFO"
            }
            catch {
                Write-EnhancedLog -Message "An error occurred while managing event logs: $($_.Exception.Message)" -Level "ERROR"
                throw $_
            }

            $DBG

            # Step 2: Sanitize the Log Name
            try {
                Write-EnhancedLog -Message "Sanitizing the log name returned by Manage-EventLogs." -Level "INFO"
                $global:sanitizedlogName = Sanitize-LogName -LogName $global:sanitizedlogName
                Write-EnhancedLog -Message "Sanitized log name: '$global:sanitizedlogName'." -Level "INFO"
            }
            catch {
                Write-EnhancedLog -Message "An error occurred while sanitizing the log name: $($_.Exception.Message)" -Level "ERROR"
                throw $_
            }

            # Step 3: Construct the source name using the sanitized log name and the caller function
            try {
                Write-EnhancedLog -Message "Constructing the source name using the sanitized log name and caller function." -Level "INFO"
                $global:sanitizedsourceName = Construct-SourceName -LogName $global:sanitizedlogName -CallerFunction $SourceName
                Write-EnhancedLog -Message "Constructed source name: '$global:sanitizedsourceName'." -Level "INFO"
            }
            catch {
                Write-EnhancedLog -Message "An error occurred while constructing the source name: $($_.Exception.Message)" -Level "ERROR"
                throw $_
            }

            $DBG




            # Step 4: Pre-Validation
            Write-EnhancedLog -Message "Performing pre-validation for log '$global:sanitizedlogName' and source '$global:sanitizedsourceName'." -Level "INFO"
            try {
                $validationResult = Validate-EventLog -LogName $global:sanitizedlogName -SourceName $global:sanitizedsourceName
                Write-EnhancedLog -Message "Pre-validation completed: Log exists = $($validationResult.LogExists), Source exists = $($validationResult.SourceExists)." -Level "INFO"
            }
            catch {
                Write-EnhancedLog -Message "An error occurred during pre-validation: $($_.Exception.Message)" -Level "ERROR"
                throw $_
            }

            $DBG

            # Step 5: Manage Sources
            Write-EnhancedLog -Message "Managing event log sources for log '$global:sanitizedlogName' and source '$global:sanitizedsourceName'." -Level "INFO"
            try {
                Manage-EventLogSource -LogName $global:sanitizedlogName -SourceName $global:sanitizedsourceName -ValidationResult $validationResult
                Write-EnhancedLog -Message "Event log sources managed successfully." -Level "INFO"
            }
            catch {
                Write-EnhancedLog -Message "An error occurred while managing event log sources: $($_.Exception.Message)" -Level "ERROR"
                throw $_
            }

            $DBG

            # Step 6: Post-Validation (Optional)
            Write-EnhancedLog -Message "Performing post-validation for log '$global:sanitizedlogName' and source '$global:sanitizedsourceName'." -Level "INFO"
            try {
                Validate-EventLog -LogName $global:sanitizedlogName -SourceName $global:sanitizedsourceName -PostValidation
                Write-EnhancedLog -Message "Post-validation completed successfully." -Level "INFO"
            }
            catch {
                Write-EnhancedLog -Message "An error occurred during post-validation: $($_.Exception.Message)" -Level "ERROR"
                throw $_
            }
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Initialize-EventLog function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }

        $DBG
    }

    End {
        Write-EnhancedLog -Message "Preparing to exit Initialize-EventLog function. Returning log name: '$global:sanitizedlogName'." -Level "INFO"

        # Ensure that only the log name string is returned
        try {
            if (-not $global:sanitizedlogName) {
                throw "Log name is null or empty. Cannot return a valid log name."
            }

            # Log the type of logName to ensure it's a string
            if ($global:sanitizedlogName -isnot [string]) {
                Write-EnhancedLog -Message "Log name is not a string. Actual type: $($global:sanitizedlogName.GetType().Name)" -Level "ERROR"
                throw "Unexpected type for log name. Expected string."
            }

            Write-EnhancedLog -Message "Log name '$global:sanitizedlogName' is valid and will be returned." -Level "INFO"
        }
        catch {
            Write-EnhancedLog -Message "An error occurred while finalizing the log name: $($_.Exception.Message)" -Level "ERROR"
            throw $_
        }

        $DBG

        Write-EnhancedLog -Message "Exiting Initialize-EventLog function" -Level "WARNING"
        return $global:sanitizedlogName
    }
}
function Write-LogMessage {
    <#
    .SYNOPSIS
    Writes a log message to the event log with a specified level and event ID.
 
    .DESCRIPTION
    The Write-LogMessage function writes a message to the event log. It maps custom levels to event log entry types and event IDs, determines the calling function, and ensures the event log and source are properly initialized.
 
    .PARAMETER Message
    The message to write to the event log.
 
    .PARAMETER Level
    The level of the message, which determines the event log entry type. Defaults to 'INFO'.
 
    .PARAMETER EventID
    The event ID for the log entry. Defaults to 1000.
 
    .EXAMPLE
    Write-LogMessage -Message "This is a test log message" -Level "ERROR" -EventID 3001
    Writes an error message to the event log with event ID 3001.
    #>


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

        [Parameter(Mandatory = $false)]
        [string]$Level = 'INFO',

        [Parameter(Mandatory = $false)]
        [int]$EventID = 1000
    )

    Begin {
        Write-EnhancedLog -Message "Starting Write-LogMessage function" -Level "NOTICE"
        Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters
    }

    Process {
        try {
            # Get the PowerShell call stack to determine the actual calling function
            Write-EnhancedLog -Message "Retrieving the PowerShell call stack to determine the caller function." -Level "DEBUG"
            $callStack = Get-PSCallStack
            $callerFunction = if ($callStack.Count -ge 2) { $callStack[1].Command } else { '<Unknown>' }
            Write-EnhancedLog -Message "Caller function identified as '$callerFunction'." -Level "INFO"

            # Map custom levels to event log entry types and event IDs
            Write-EnhancedLog -Message "Mapping log level '$Level' to entry type and event ID." -Level "INFO"
            $entryType, $mappedEventID = switch ($Level.ToUpper()) {
                'DEBUG' { 'Information', 1001 }
                'INFO' { 'Information', 1002 }
                'NOTICE' { 'Information', 1003 }
                'WARNING' { 'Warning', 2001 }
                'ERROR' { 'Error', 3001 }
                'CRITICAL' { 'Error', 3002 }
                'IMPORTANT' { 'Information', 1004 }
                'OUTPUT' { 'Information', 1005 }
                'SIGNIFICANT' { 'Information', 1006 }
                'VERYVERBOSE' { 'Information', 1007 }
                'VERBOSE' { 'Information', 1008 }
                'SOMEWHATVERBOSE' { 'Information', 1009 }
                'SYSTEM' { 'Information', 1010 }
                'INTERNALCOMMENT' { 'Information', 1011 }
                default { 'Information', 9999 }
            }
            Write-EnhancedLog -Message "Log level '$Level' mapped to entry type '$entryType' and event ID '$mappedEventID'." -Level "INFO"

            # Use provided EventID if specified, otherwise use mapped EventID
            $eventID = if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey('EventID')) { $EventID } else { $mappedEventID }
            Write-EnhancedLog -Message "Using Event ID: $eventID" -Level "INFO"

            # Ensure the event log and source are initialized
            Write-EnhancedLog -Message "Initializing event log for source '$callerFunction'." -Level "INFO"

            $DBG
            try {
                Write-EnhancedLog -Message "Calling Initialize-EventLog function with SourceName: '$callerFunction'." -Level "INFO"
                $global:sanitizedlogName = Initialize-EventLog -SourceName $callerFunction -ParentScriptName $ParentScriptName
                Write-EnhancedLog -Message "Event log initialized with name '$global:sanitizedlogName'." -Level "INFO"
            
                # Sanitize the log name
                Write-EnhancedLog -Message "Sanitizing the log name for source '$callerFunction'." -Level "INFO"
                $global:sanitizedlogName = Sanitize-LogName -LogName $global:sanitizedlogName
                Write-EnhancedLog -Message "Sanitized log name: '$global:sanitizedlogName'." -Level "INFO"
            
                $DBG
            }
            catch {
                Write-EnhancedLog -Message "Failed to initialize or sanitize event log for source '$callerFunction': $($_.Exception.Message)" -Level "ERROR"
                Handle-Error -ErrorRecord $_
                throw $_
            }
            
            $DBG

            # Validate the log name before writing to the event log
            if (-not $global:sanitizedlogName -or $global:sanitizedlogName -is [System.Collections.Hashtable]) {
                Write-EnhancedLog -Message "The log name '$global:sanitizedlogName' is invalid or is a hashtable. Halting operation." -Level "CRITICAL"
                throw "Invalid log name: '$global:sanitizedlogName'"
            }

            $DBG

            # Write the message to the specified event log
            try {
                Write-EnhancedLog -Message "Validating if the event log source '$global:sanitizedsourceName' exists in log '$global:sanitizedlogName'." -Level "INFO"
                
                # Check if the source exists and count occurrences
          
            
                try {
                    # Check if the source exists and count occurrences
                    Write-EnhancedLog -Message "Checking if event log source '$global:sanitizedsourceName' exists in log '$global:sanitizedlogName'." -Level "INFO"
                    
                    $sourceExists = [System.Diagnostics.EventLog]::SourceExists("$global:sanitizedsourceName")
                    
                    if ($sourceExists) {
                        Write-EnhancedLog -Message "Event log source '$global:sanitizedsourceName' exists. Retrieving log name associated with the source." -Level "INFO"
                        
                        try {
                            $sourceLogName = [System.Diagnostics.EventLog]::LogNameFromSourceName("$global:sanitizedsourceName", ".")
                            Write-EnhancedLog -Message "Associated log name retrieved: '$sourceLogName'." -Level "INFO"
                            
                            try {
                                # Count occurrences of the source in the log
                                $sourceCount = @(Get-EventLog -LogName $sourceLogName -Source "$global:sanitizedsourceName").Count
                            
                                if ($sourceCount -eq 0) {
                                    Write-EnhancedLog -Message "No previous events found for source '$global:sanitizedsourceName' in log '$sourceLogName'. This is the first event being logged for this source." -Level "INFO"
                                }
                                else {
                                    Write-EnhancedLog -Message "Number of occurrences for source '$global:sanitizedsourceName' in log '$sourceLogName': $sourceCount." -Level "INFO"
                                }
                            }
                            catch {
                                Write-EnhancedLog -Message "Failed to count occurrences of source '$global:sanitizedsourceName' in log '$sourceLogName': $($_.Exception.Message)" -Level "ERROR"
                            
                                # Additional enhanced error handling for specific scenarios
                                if ($_.Exception.Message -match "No matches found") {
                                    Write-EnhancedLog -Message "This is expected behavior if the log source '$global:sanitizedsourceName' is being used for the first time. No prior events are logged under this source." -Level "NOTICE"
                                }
                                else {
                                    throw $_  # Re-throw the error for further handling upstream if it's not an expected case.
                                }
                            }
                            
                            
                        }
                        catch {
                            Write-EnhancedLog -Message "Failed to retrieve the log name associated with the source '$global:sanitizedsourceName': $($_.Exception.Message)" -Level "ERROR"
                            throw $_
                        }
                    }
                    else {
                        Write-EnhancedLog -Message "Event log source '$global:sanitizedsourceName' does not exist in log '$global:sanitizedlogName'. Proceeding to create it." -Level "WARNING"
                    }
                }
                catch {
                    Write-EnhancedLog -Message "An error occurred while checking or counting occurrences of the event log source '$global:sanitizedsourceName': $($_.Exception.Message)" -Level "ERROR"
                    Handle-Error -ErrorRecord $_
                    throw $_
                }
                

                $DBG
                Write-EnhancedLog -Message "Writing log message to event log '$global:sanitizedlogName' for source '$global:sanitizedsourceName'." -Level "INFO"
                Write-EventLog -LogName $global:sanitizedlogName -Source "$global:sanitizedsourceName" -EntryType $entryType -EventId $eventID -Message $Message
                Write-Host "Logged message to '$global:sanitizedlogName' from source '$global:sanitizedsourceName'"
            }
            catch {
                Write-EnhancedLog -Message "Failed to write log message to event log '$global:sanitizedlogName' for source '$global:sanitizedsourceName': $($_.Exception.Message)" -Level "ERROR"
                throw $_
            }
            

            $DBG
        }
        catch {
            Write-EnhancedLog -Message "An error occurred in Write-LogMessage function: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw $_
        }
    }

    End {
        Write-EnhancedLog -Message "Exiting Write-LogMessage function" -Level "NOTICE"
    }
}
#EndRegion '.\Public\Write-LogMessage.ps1' 851