EnhancedSchedTaskAO.psm1

#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\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\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-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-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' 436
#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-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-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-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 '.\Public\Download-PsExec.ps1' 99
#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\Ensure-TargetFolderExists.ps1' -1

function Ensure-TargetFolderExists {
    <#
    .SYNOPSIS
    Ensures the target folder exists, creating it if necessary.
 
    .DESCRIPTION
    The Ensure-TargetFolderExists function checks if a specified folder exists. If it does not exist, it creates the folder and logs the action. Errors during the creation process are handled gracefully with logging and error handling.
 
    .PARAMETER TargetFolder
    The full path to the target folder that needs to be checked or created.
 
    .EXAMPLE
    $params = @{
        TargetFolder = "C:\ProgramData\AADMigration\MyFolder"
    }
    Ensure-TargetFolderExists @params
    Ensures the target folder exists or creates it.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Provide the full path to the target folder.")]
        [ValidateNotNullOrEmpty()]
        [string]$TargetFolder
    )

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

        # Validate the target folder path is provided
        if (-not $TargetFolder) {
            throw "Target folder path not provided."
        }
    }

    Process {
        try {
            # Check if the target folder exists
            if (-Not (Test-Path -Path $TargetFolder)) {
                Write-EnhancedLog -Message "Target folder does not exist. Creating folder: $TargetFolder" -Level "INFO"
                
                # Create the target folder
                New-Item -Path $TargetFolder -ItemType Directory -Force
                Write-EnhancedLog -Message "Target folder created: $TargetFolder" -Level "INFO"
            }
            else {
                Write-EnhancedLog -Message "Target folder already exists: $TargetFolder" -Level "INFO"
            }
        }
        catch {
            # Log and handle any errors encountered during the folder creation process
            Write-EnhancedLog -Message "An error occurred while ensuring the target folder exists: $($_.Exception.Message)" -Level "ERROR"
            Handle-Error -ErrorRecord $_
            throw
        }
        finally {
            Write-EnhancedLog -Message "Exiting Ensure-TargetFolderExists function" -Level "Notice"
        }
    }

    End {
        # Final validation or actions could be placed here if needed
        Write-EnhancedLog -Message "Completed Ensure-TargetFolderExists function" -Level "INFO"
    }
}
#EndRegion '.\Public\Ensure-TargetFolderExists.ps1' 67
#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\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\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\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-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\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\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\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\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