EnhancedSchedTaskAO.psm1
#Region '.\Private\Download-PsExec.ps1' -1 function Download-PsExec { <# .SYNOPSIS Downloads and extracts PsExec64.exe from the official Sysinternals PSTools package. .DESCRIPTION The Download-PsExec function downloads the PSTools package from the official Sysinternals website, extracts PsExec64.exe, and places it in the specified target folder. .PARAMETER TargetFolder The target folder where PsExec64.exe will be stored. .EXAMPLE $params = @{ TargetFolder = "C:\ProgramData\SystemTools" } Download-PsExec @params Downloads PsExec64.exe to the specified target folder. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$TargetFolder ) Begin { Write-EnhancedLog -Message "Starting Download-PsExec function" -Level "Notice" Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters # Ensure the target folder exists Ensure-TargetFolderExists -TargetFolder $TargetFolder Write-EnhancedLog -Message "Removing existing PsExec from target folder: $TargetFolder" -Level "INFO" Remove-ExistingPsExec -TargetFolder $TargetFolder } Process { try { # Define the URL for PsExec download $url = "https://download.sysinternals.com/files/PSTools.zip" # Full path for the downloaded file $timestamp = Get-Date -Format "yyyyMMdd_HHmmss" $zipPath = Join-Path -Path $TargetFolder -ChildPath "PSTools_$timestamp.zip" # Download the PSTools.zip file containing PsExec with retry logic Write-EnhancedLog -Message "Downloading PSTools.zip from: $url to: $zipPath" -Level "INFO" $downloadParams = @{ Source = $url Destination = $zipPath MaxRetries = 3 } Start-FileDownloadWithRetry @downloadParams # Extract PsExec64.exe from the zip file Write-EnhancedLog -Message "Extracting PSTools.zip to: $TargetFolder\PStools" -Level "INFO" Expand-Archive -Path $zipPath -DestinationPath "$TargetFolder\PStools" -Force # Specific extraction of PsExec64.exe $extractedFolderPath = Join-Path -Path $TargetFolder -ChildPath "PSTools" $PsExec64Path = Join-Path -Path $extractedFolderPath -ChildPath "PsExec64.exe" $finalPath = Join-Path -Path $TargetFolder -ChildPath "PsExec64.exe" # Move PsExec64.exe to the desired location if (Test-Path -Path $PsExec64Path) { Write-EnhancedLog -Message "Moving PsExec64.exe from: $PsExec64Path to: $finalPath" -Level "INFO" Move-Item -Path $PsExec64Path -Destination $finalPath # Remove the downloaded zip file and extracted folder Write-EnhancedLog -Message "Removing downloaded zip file and extracted folder" -Level "INFO" Remove-Item -Path $zipPath -Force Remove-Item -Path $extractedFolderPath -Recurse -Force Write-EnhancedLog -Message "PsExec64.exe has been successfully downloaded and moved to: $finalPath" -Level "INFO" } else { Write-EnhancedLog -Message "PsExec64.exe not found in the extracted files." -Level "ERROR" throw "PsExec64.exe not found after extraction." } } catch { Write-EnhancedLog -Message "An error occurred in Download-PsExec function: $($_.Exception.Message)" -Level "ERROR" Handle-Error -ErrorRecord $_ throw $_ } } End { Write-EnhancedLog -Message "Exiting Download-PsExec function" -Level "Notice" } } # Example usage # $params = @{ # TargetFolder = "C:\ProgramData\SystemTools" # } # Download-PsExec @params #EndRegion '.\Private\Download-PsExec.ps1' 99 #Region '.\Private\Ensure-TargetFolderExists.ps1' -1 function Ensure-TargetFolderExists { param ( [string]$TargetFolder ) try { if (-Not (Test-Path -Path $TargetFolder)) { Write-EnhancedLog -Message "Target folder does not exist. Creating folder: $TargetFolder" -Level "INFO" -ForegroundColor ([ConsoleColor]::Cyan) New-Item -Path $TargetFolder -ItemType Directory -Force Write-EnhancedLog -Message "Target folder created: $TargetFolder" -Level "INFO" -ForegroundColor ([ConsoleColor]::Green) } else { Write-EnhancedLog -Message "Target folder already exists: $TargetFolder" -Level "INFO" -ForegroundColor ([ConsoleColor]::Green) } } catch { Write-EnhancedLog -Message "An error occurred while ensuring the target folder exists: $($_.Exception.Message)" -Level "ERROR" -ForegroundColor ([ConsoleColor]::Red) Handle-Error -ErrorRecord $_ } } #EndRegion '.\Private\Ensure-TargetFolderExists.ps1' 19 #Region '.\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\CheckAndElevate.ps1' -1 function CheckAndElevate { <# .SYNOPSIS Checks if the script is running with administrative privileges and optionally elevates it if not. .DESCRIPTION The CheckAndElevate function checks whether the current PowerShell session is running with administrative privileges. It can either return the administrative status or attempt to elevate the script if it is not running as an administrator. .PARAMETER ElevateIfNotAdmin If set to $true, the function will attempt to elevate the script if it is not running with administrative privileges. If set to $false, the function will simply return the administrative status without taking any action. .EXAMPLE CheckAndElevate -ElevateIfNotAdmin $true Checks the current session for administrative privileges and elevates if necessary. .EXAMPLE $isAdmin = CheckAndElevate -ElevateIfNotAdmin $false if (-not $isAdmin) { Write-Host "The script is not running with administrative privileges." } Checks the current session for administrative privileges and returns the status without elevating. .NOTES If the script is elevated, it will restart with administrative privileges. Ensure that any state or data required after elevation is managed appropriately. #> [CmdletBinding()] param ( [bool]$ElevateIfNotAdmin = $true ) Begin { Write-EnhancedLog -Message "Starting CheckAndElevate function" -Level "NOTICE" # Use .NET classes for efficiency try { $isAdmin = [System.Security.Principal.WindowsPrincipal]::new([System.Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) Write-EnhancedLog -Message "Checking for administrative privileges..." -Level "INFO" } catch { Write-EnhancedLog -Message "Error determining administrative status: $($_.Exception.Message)" -Level "ERROR" Handle-Error -ErrorRecord $_ throw $_ } } Process { if (-not $isAdmin) { if ($ElevateIfNotAdmin) { try { Write-EnhancedLog -Message "The script is not running with administrative privileges. Attempting to elevate..." -Level "WARNING" $powerShellPath = Get-PowerShellPath $startProcessParams = @{ FilePath = $powerShellPath ArgumentList = @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "`"$PSCommandPath`"") Verb = "RunAs" } Start-Process @startProcessParams Write-EnhancedLog -Message "Script re-launched with administrative privileges. Exiting current session." -Level "INFO" exit } catch { Write-EnhancedLog -Message "Failed to elevate privileges: $($_.Exception.Message)" -Level "ERROR" Handle-Error -ErrorRecord $_ throw $_ } } else { Write-EnhancedLog -Message "The script is not running with administrative privileges and will continue without elevation." -Level "INFO" } } else { Write-EnhancedLog -Message "Script is already running with administrative privileges." -Level "INFO" } } End { Write-EnhancedLog -Message "Exiting CheckAndElevate function" -Level "NOTICE" return $isAdmin } } # Example usage to check and optionally elevate: # CheckAndElevate -ElevateIfNotAdmin $true # Example usage to just check and return status without elevating: # $isAdmin = CheckAndElevate -ElevateIfNotAdmin $false #EndRegion '.\Public\CheckAndElevate.ps1' 93 #Region '.\Public\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-ScheduledTask.ps1' -1 function Create-ScheduledTask { <# .SYNOPSIS Creates a scheduled task. .DESCRIPTION The Create-ScheduledTask function creates a scheduled task that runs a specified PowerShell script at logon with the highest privileges. .PARAMETER TaskPath The path of the task in Task Scheduler. .PARAMETER TaskName The name of the scheduled task. .PARAMETER ScriptPath The path to the PowerShell script to be executed. .EXAMPLE $params = @{ TaskPath = "AAD Migration" TaskName = "Run Post-migration cleanup" ScriptPath = "C:\ProgramData\AADMigration\Scripts\PostRunOnce3.ps1" } Create-ScheduledTask @params Creates the scheduled task to run the specified script. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$TaskPath, [Parameter(Mandatory = $true)] [string]$TaskName, [Parameter(Mandatory = $true)] [string]$ScriptPath ) Begin { Write-EnhancedLog -Message "Starting Create-ScheduledTask function" -Level "Notice" Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters } Process { try { # Unregister the task if it exists Unregister-ScheduledTaskWithLogging -TaskName $TaskName # Create the new scheduled task $arguments = "-executionpolicy Bypass -file $ScriptPath" Write-EnhancedLog -Message "Creating scheduled task: $TaskName at $TaskPath to run script: $ScriptPath" -Level "INFO" $params = @{ Execute = 'PowerShell.exe' Argument = $arguments } $action = New-ScheduledTaskAction @params $params = @{ AtLogOn = $true } $trigger = New-ScheduledTaskTrigger @params $params = @{ UserId = "SYSTEM" RunLevel = "Highest" } $principal = New-ScheduledTaskPrincipal @params $params = @{ Principal = $principal Action = $action Trigger = $trigger TaskName = $TaskName Description = "Run post AAD Migration cleanup" TaskPath = $TaskPath } $Task = Register-ScheduledTask @params Write-EnhancedLog -Message "Scheduled task created successfully." -Level "INFO" } catch { Write-EnhancedLog -Message "An error occurred in Create-ScheduledTask function: $($_.Exception.Message)" -Level "ERROR" Handle-Error -ErrorRecord $_ throw $_ } } End { Write-EnhancedLog -Message "Exiting Create-ScheduledTask function" -Level "Notice" } } # # Example usage # $params = @{ # TaskPath = "AAD Migration" # TaskName = "Run Post-migration cleanup" # ScriptPath = "C:\ProgramData\AADMigration\Scripts\PostRunOnce3.ps1" # } # Create-ScheduledTask @params #EndRegion '.\Public\Create-ScheduledTask.ps1' 104 #Region '.\Public\Create-VBShiddenPS.ps1' -1 function Create-VBShiddenPS { <# .SYNOPSIS Creates a VBScript file to run a PowerShell script hidden from the user interface. .DESCRIPTION This function generates a VBScript (.vbs) file designed to execute a PowerShell script without displaying the PowerShell window. It's particularly useful for running background tasks or scripts that do not require user interaction. The path to the PowerShell script is taken as an argument, and the VBScript is created in a specified directory within the global path variable. .EXAMPLE $Path_VBShiddenPS = Create-VBShiddenPS This example creates the VBScript file and returns its path. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$Path_local, [string]$DataFolder = "Data", [string]$FileName = "run-ps-hidden.vbs" ) try { # Construct the full path for DataFolder and validate it manually $fullDataFolderPath = Join-Path -Path $Path_local -ChildPath $DataFolder if (-not (Test-Path -Path $fullDataFolderPath -PathType Container)) { throw "DataFolder does not exist or is not a directory: $fullDataFolderPath" } # Log message about creating VBScript Write-EnhancedLog -Message "Creating VBScript to hide PowerShell window..." -Level "INFO" -ForegroundColor Magenta $scriptBlock = @" Dim shell,fso,file Set shell=CreateObject("WScript.Shell") Set fso=CreateObject("Scripting.FileSystemObject") strPath=WScript.Arguments.Item(0) If fso.FileExists(strPath) Then set file=fso.GetFile(strPath) strCMD="powershell -nologo -executionpolicy ByPass -command " & Chr(34) & "&{" & file.ShortPath & "}" & Chr(34) shell.Run strCMD,0 End If "@ # Combine paths to construct the full path for the VBScript $folderPath = $fullDataFolderPath $Path_VBShiddenPS = Join-Path -Path $folderPath -ChildPath $FileName # Write the script block to the VBScript file $scriptBlock | Out-File -FilePath (New-Item -Path $Path_VBShiddenPS -Force) -Force # Validate the VBScript file creation if (Test-Path -Path $Path_VBShiddenPS) { Write-EnhancedLog -Message "VBScript created successfully at $Path_VBShiddenPS" -Level "INFO" -ForegroundColor Green } else { throw "Failed to create VBScript at $Path_VBShiddenPS" } return $Path_VBShiddenPS } catch { Write-EnhancedLog -Message "An error occurred while creating VBScript: $_" -Level "ERROR" -ForegroundColor Red throw $_ } } #EndRegion '.\Public\Create-VBShiddenPS.ps1' 76 #Region '.\Public\CreateAndRegisterScheduledTask.ps1' -1 function CreateAndRegisterScheduledTask { <# .SYNOPSIS Creates and registers a scheduled task based on the provided configuration, and executes it if necessary. .DESCRIPTION This function initializes variables, ensures necessary paths exist, copies files, creates a VBScript for hidden execution, and manages the execution of detection and remediation scripts. If the task does not exist, it sets up a new task environment and registers it. .PARAMETER ConfigPath The path to the JSON configuration file. .PARAMETER FileName The name of the file to be used for the VBScript. .PARAMETER Scriptroot The root directory where the scripts are located. .EXAMPLE CreateAndRegisterScheduledTask -ConfigPath "C:\Tasks\Config.json" -FileName "HiddenScript.vbs" This example creates and registers a scheduled task based on the provided configuration file and VBScript file name. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$ConfigPath, [Parameter(Mandatory = $true)] [string]$FileName, [Parameter(Mandatory = $true)] [string]$Scriptroot ) begin { Write-EnhancedLog -Message 'Starting CreateAndRegisterScheduledTask function' -Level 'NOTICE' Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters # Ensure the script runs with administrative privileges CheckAndElevate -ElevateIfNotAdmin $true } process { try { # Load configuration from PSD1 file $config = Import-PowerShellDataFile -Path $ConfigPath # Initialize variables directly from the config $PackageName = $config.PackageName $PackageUniqueGUID = $config.PackageUniqueGUID $Version = $config.Version $ScriptMode = $config.ScriptMode $PackageExecutionContext = $config.PackageExecutionContext $RepetitionInterval = $config.RepetitionInterval $DataFolder = $config.DataFolder # Determine local path based on execution context if not provided if (-not $Path_local) { if (Test-RunningAsSystem) { Write-EnhancedLog -Message "Detected SYSTEM context. Setting Path_local to $($config.PathLocalSystem)" -Level "CRITICAL" $Path_local = $config.PathLocalSystem } else { Write-EnhancedLog -Message "Not running as SYSTEM. Setting Path_local to $($config.PathLocalUser)" -Level "CRITICAL" $Path_local = $config.PathLocalUser } } else { Write-EnhancedLog -Message "Path_local is already set to $Path_local" -Level "INFO" } $Path_PR = Join-Path -Path $Path_local -ChildPath "Data\$PackageName-$PackageUniqueGUID" $schtaskName = [string]::Format($config.TaskNameFormat, $PackageName, $PackageUniqueGUID) $schtaskDescription = [string]::Format($config.TaskDescriptionFormat, $Version) # Unregister the task if it exists Unregister-ScheduledTaskWithLogging -TaskName $schtaskName # Ensure script paths exist if (-not (Test-Path -Path $Path_local)) { New-Item -Path $Path_local -ItemType Directory -Force | Out-Null Write-EnhancedLog -Message "Created directory: $Path_local" -Level "CRITICAL" } if (-not (Test-Path -Path $Path_PR)) { New-Item -Path $Path_PR -ItemType Directory -Force | Out-Null Write-EnhancedLog -Message "Created directory: $Path_PR" -Level "CRITICAL" } # Copy files to path $CopyFilesToPathParams = @{ SourcePath = $Scriptroot DestinationPath = $Path_PR } Copy-FilesToPath @CopyFilesToPathParams # Verify copy operation $VerifyCopyOperationParams = @{ SourcePath = $Scriptroot DestinationPath = $Path_PR } Verify-CopyOperation @VerifyCopyOperationParams # Ensure the Data folder exists $DataFolderPath = Join-Path -Path $Path_local -ChildPath $DataFolder if (-not (Test-Path -Path $DataFolderPath -PathType Container)) { New-Item -ItemType Directory -Path $DataFolderPath -Force | Out-Null Write-EnhancedLog -Message "Data folder created at $DataFolderPath" -Level "INFO" } # Create the VBScript to run PowerShell script hidden try { $CreateVBShiddenPSParams = @{ Path_local = $Path_local DataFolder = $DataFolder FileName = $FileName } $Path_VBShiddenPS = Create-VBShiddenPS @CreateVBShiddenPSParams # Validation of the VBScript file creation if (Test-Path -Path $Path_VBShiddenPS) { Write-EnhancedLog -Message "Validation successful: VBScript file exists at $Path_VBShiddenPS" -Level "INFO" } else { Write-EnhancedLog -Message "Validation failed: VBScript file does not exist at $Path_VBShiddenPS. Check script execution and permissions." -Level "WARNING" } } catch { Write-EnhancedLog -Message "An error occurred while creating VBScript: $_" -Level "ERROR" } # Check if the task exists and execute or create it accordingly $checkTaskParams = @{ taskName = $schtaskName } $taskExists = Check-ExistingTask @checkTaskParams if ($taskExists) { Write-EnhancedLog -Message "Existing task found. Executing detection and remediation scripts." -Level "INFO" $executeParams = @{ Path_PR = $Path_PR } if ($config.ScheduleOnly -eq $true) { Write-EnhancedLog -Message "Registering task with ScheduleOnly set to $($config.ScheduleOnly)" -Level "INFO" } else { Write-EnhancedLog -Message "Executing detection and remediation scripts." -Level "INFO" Execute-DetectionAndRemediation @executeParams } } else { Write-EnhancedLog -Message "No existing task found. Setting up new task environment." -Level "INFO" # Setup new task environment $Path_PSscript = switch ($ScriptMode) { "Remediation" { Join-Path $Path_PR $config.ScriptPaths.Remediation } "PackageName" { Join-Path $Path_PR $config.ScriptPaths.PackageName } Default { throw "Invalid ScriptMode: $ScriptMode. Expected 'Remediation' or 'PackageName'." } } # Register the scheduled task $startTime = (Get-Date).AddMinutes($config.StartTimeOffsetMinutes).ToString("HH:mm") if ($config.UsePSADT) { Write-EnhancedLog -Message "Setting up Schedule Task action for Service UI and PSADT" -Level "INFO" # Define the path to the PowerShell Application Deployment Toolkit executable $ToolkitExecutable = Join-Path -Path $Path_PR -ChildPath $config.ToolkitExecutablePath Write-EnhancedLog -Message "ToolkitExecutable set to: $ToolkitExecutable" -Level "INFO" # Define the path to the ServiceUI executable $ServiceUIExecutable = Join-Path -Path $Path_PR -ChildPath $config.ServiceUIExecutablePath Write-EnhancedLog -Message "ServiceUIExecutable set to: $ServiceUIExecutable" -Level "INFO" # Define the deployment type from the config $DeploymentType = $config.DeploymentType Write-EnhancedLog -Message "DeploymentType set to: $DeploymentType" -Level "INFO" # Define the arguments for ServiceUI.exe $argList = "-process:$($config.ProcessName) `"$ToolkitExecutable`" -DeploymentType $DeploymentType" Write-EnhancedLog -Message "ServiceUI arguments: $argList" -Level "CRITICAL" # Create the scheduled task action $action = New-ScheduledTaskAction -Execute $ServiceUIExecutable -Argument $argList Write-EnhancedLog -Message "Scheduled Task action $action for Service UI and PSADT created." -Level "INFO" } else { Write-EnhancedLog -Message "Setting up Scheduled Task action for wscript and VBS" -Level "INFO" # Define the arguments for wscript.exe $argList = "`"$Path_VBShiddenPS`" `"$Path_PSscript`"" Write-EnhancedLog -Message "wscript arguments: $argList" -Level "INFO" # Define the path to wscript.exe from the config $WscriptPath = $config.WscriptPath Write-EnhancedLog -Message "WscriptPath set to: $WscriptPath" -Level "INFO" # Create the scheduled task action using the path from the config $action = New-ScheduledTaskAction -Execute $WscriptPath -Argument $argList Write-EnhancedLog -Message "Scheduled Task action for wscript and VBS created." -Level "INFO" } # Define the trigger based on the TriggerType $trigger = switch ($config.TriggerType) { "Daily" { Write-EnhancedLog -Message "Trigger set to Daily at $startTime" -Level "INFO" $trigger = New-ScheduledTaskTrigger -Daily -At $startTime # Apply StartBoundary and Delay if provided if ($config.StartBoundary) { $trigger.StartBoundary = $config.StartBoundary Write-EnhancedLog -Message "StartBoundary set to $($config.StartBoundary)" -Level "INFO" } if ($config.Delay) { $trigger.Delay = $config.Delay Write-EnhancedLog -Message "Delay set to $($config.Delay)" -Level "INFO" } $trigger } "Logon" { if (-not $config.LogonUserId) { throw "LogonUserId must be specified for Logon trigger type." } Write-EnhancedLog -Message "Trigger set to logon of user $($config.LogonUserId)" -Level "INFO" $trigger = New-ScheduledTaskTrigger -AtLogOn # Only apply StartBoundary if provided if ($config.StartBoundary) { $trigger.StartBoundary = $config.StartBoundary Write-EnhancedLog -Message "StartBoundary set to $($config.StartBoundary)" -Level "INFO" } $trigger } "AtStartup" { Write-EnhancedLog -Message "Trigger set at startup" -Level "INFO" $trigger = New-ScheduledTaskTrigger -AtStartup # Apply StartBoundary and Delay if provided if ($config.StartBoundary) { $trigger.StartBoundary = $config.StartBoundary Write-EnhancedLog -Message "StartBoundary set to $($config.StartBoundary)" -Level "INFO" } if ($config.Delay) { $trigger.Delay = $config.Delay Write-EnhancedLog -Message "Delay set to $($config.Delay)" -Level "INFO" } $trigger } default { throw "Invalid TriggerType specified in the configuration." } } $principal = New-ScheduledTaskPrincipal -UserId $config.PrincipalUserId -LogonType $config.LogonType -RunLevel $config.RunLevel # Ensure the task path is set, use the default root "\" if not specified $taskPath = if ($config.TaskFolderPath) { $config.TaskFolderPath } else { "\" } # Create a hashtable for common parameters $registerTaskParams = @{ TaskName = $schtaskName Action = $action Principal = $principal Description = $schtaskDescription TaskPath = $taskPath Force = $true } # Check if RunOnDemand is true and modify the hashtable accordingly if ($config.RunOnDemand -eq $true) { Write-EnhancedLog -Message "Registering task with RunOnDemand set to $($config.RunOnDemand) at path $taskPath" -Level "CRITICAL" # Register the task without a trigger (Run on demand) $task = Register-ScheduledTask @registerTaskParams } else { Write-EnhancedLog -Message "Registering task with RunOnDemand set to $($config.RunOnDemand) at path $taskPath" -Level "CRITICAL" # Add the Trigger to the hashtable $registerTaskParams.Trigger = $trigger # Register the task with the trigger $task = Register-ScheduledTask @registerTaskParams } $task = Get-ScheduledTask -TaskName $schtaskName if ($config.Repeat -eq $true) { Write-EnhancedLog -Message "Registering task with Repeat set to $($config.Repeat)" -Level "INFO" $task.Triggers[0].Repetition.Interval = $RepetitionInterval } $task | Set-ScheduledTask if ($PackageExecutionContext -eq $config.TaskExecutionContext) { $ShedService = New-Object -ComObject 'Schedule.Service' $ShedService.Connect() $taskFolder = $ShedService.GetFolder($config.TaskFolderPath) $Task = $taskFolder.GetTask("$schtaskName") $taskFolder.RegisterTaskDefinition( "$schtaskName", $Task.Definition, $config.TaskRegistrationFlags, $config.TaskUserGroup, $null, $config.TaskLogonType ) } Write-EnhancedLog -Message "Scheduled task $schtaskName registered successfully." -Level "INFO" } } catch { Write-EnhancedLog -Message "An error occurred: $_" -Level "ERROR" Handle-Error -ErrorRecord $_ } } end { Write-EnhancedLog -Message 'CreateAndRegisterScheduledTask function completed' -Level 'NOTICE' } } # CreateAndRegisterScheduledTask -ConfigPath "C:\code\IntuneDeviceMigration\DeviceMigration\config.psd1" -FileName "HiddenScript.vbs" # # Define the parameters using a hashtable # $taskParams = @{ # ConfigPath = "C:\code\IntuneDeviceMigration\DeviceMigration\config.psd1" # FileName = "HiddenScript.vbs" # Scriptroot = "$PSScriptroot" # } # # Call the function with the splatted parameters # CreateAndRegisterScheduledTask @taskParams # # ################################################################################################################################ # # ############### CALLING AS SYSTEM to simulate Intune deployment as SYSTEM (Uncomment for debugging) ############################ # # ################################################################################################################################ # # Example usage # $ensureRunningAsSystemParams = @{ # PsExec64Path = Join-Path -Path $PSScriptRoot -ChildPath "private\PsExec64.exe" # ScriptPath = $MyInvocation.MyCommand.Path # TargetFolder = Join-Path -Path $PSScriptRoot -ChildPath "private" # } # Ensure-RunningAsSystem @ensureRunningAsSystemParams # # ################################################################################################################################ # # ############### END CALLING AS SYSTEM to simulate Intune deployment as SYSTEM (Uncomment for debugging) ######################## # # ################################################################################################################################ # # Example usage of Download-And-Install-ServiceUI function with splatting # $DownloadAndInstallServiceUIparams = @{ # TargetFolder = "$PSScriptRoot" # DownloadUrl = "https://download.microsoft.com/download/3/3/9/339BE62D-B4B8-4956-B58D-73C4685FC492/MicrosoftDeploymentToolkit_x64.msi" # MsiFileName = "MicrosoftDeploymentToolkit_x64.msi" # InstalledServiceUIPath = "C:\Program Files\Microsoft Deployment Toolkit\Templates\Distribution\Tools\x64\ServiceUI.exe" # } # Download-And-Install-ServiceUI @DownloadAndInstallServiceUIparams # # Example usage # $DownloadPSAppDeployToolkitparams = @{ # GithubRepository = 'PSAppDeployToolkit/PSAppDeployToolkit' # FilenamePatternMatch = '*.zip' # ScriptDirectory = $PSScriptRoot # } # Download-PSAppDeployToolkit @DownloadPSAppDeployToolkitparams # #right before rebooting we will schedule our install script (which is our script2 or our post-reboot script to run automatically at startup under the SYSTEM account) # # here I need to pass these in the config file (JSON or PSD1) or here in the splat but I need to have it outside of the function # # $schedulerconfigPath = Join-Path -Path $PSScriptRoot -ChildPath "config.psd1" # $schedulerconfigPath = "C:\code\IntuneDeviceMigration\DeviceMigration\Interactive-Migration-Task-config.psd1" # $taskParams = @{ # ConfigPath = $schedulerconfigPath # FileName = "run-ps-hidden.vbs" # Scriptroot = $PSScriptRoot # } # CreateAndRegisterScheduledTask @taskParams # Yes, there is a difference between `StartBoundary` and `StartTime` in the context of scheduled tasks in PowerShell. # ### 1. **StartBoundary:** # - **Definition:** `StartBoundary` is a property of a `ScheduledTaskTrigger` that defines the earliest time that the trigger can activate. It sets the date and time at which the task is eligible to run for the first time. # - **Format:** `StartBoundary` is typically set in an ISO 8601 date and time format, such as `"2024-08-16T12:00:00"`. # - **Purpose:** It's used to specify when the task becomes active. It doesn't specify a specific time the task should run every day, but rather when the task is allowed to start running, especially for triggers like `Daily`, `AtLogOn`, or `AtStartup`. # - **Example Use Case:** If you want a task to only be eligible to run after a certain date and time, you would set `StartBoundary`. # ### 2. **StartTime:** # - **Definition:** `StartTime` is a property specifically associated with `Daily` or `Weekly` triggers and defines the exact time of day the task should start. # - **Format:** `StartTime` is generally a time-only value, like `"09:00:00"`. # - **Purpose:** It is used to specify a time of day for tasks that need to run on a recurring schedule (e.g., daily or weekly). It indicates when the task should be triggered every day (or on specified days of the week). # - **Example Use Case:** If you want a task to run every day at 9:00 AM, you would set the `StartTime` to `"09:00:00"`. # ### Practical Differences: # - **StartBoundary:** Controls when the task becomes eligible to run. It’s a one-time setting that dictates when the task can first start, often used with non-recurring tasks or as a gate for when recurring tasks can start. # - **StartTime:** Controls the exact time on a daily or weekly basis when the task should be executed. It’s used for recurring tasks that need to start at the same time every day or on specific days of the week. # ### Example Scenario: # If you want a task to start running every day at 9:00 AM but only start doing so from September 1, 2024, you would set: # - `StartBoundary = "2024-09-01T00:00:00"` (the task won’t run before this date). # - `StartTime = "09:00:00"` (the task will run at 9:00 AM daily after September 1, 2024). # ### Conclusion: # - **Use `StartBoundary`** to define when the task becomes eligible to start (based on date and time). # - **Use `StartTime`** to define the time of day for recurring tasks (daily or weekly schedules). #EndRegion '.\Public\CreateAndRegisterScheduledTask.ps1' 435 #Region '.\Public\Download-And-Install-ServiceUI.ps1' -1 function Download-And-Install-ServiceUI { <# .SYNOPSIS Downloads and installs the ServiceUI.exe utility from MDT MSI package. .DESCRIPTION The Download-And-Install-ServiceUI function downloads the Microsoft Deployment Toolkit (MDT) MSI package, extracts the ServiceUI.exe utility, and installs it in a specified target folder. .PARAMETER TargetFolder The directory where ServiceUI.exe will be installed. .PARAMETER DownloadUrl The URL to download the MDT MSI package. .PARAMETER MsiFileName The name of the MSI file to be downloaded. .PARAMETER InstalledServiceUIPath The path where ServiceUI.exe is located after installing the MDT MSI package. .EXAMPLE $params = @{ TargetFolder = "C:\Path\To\Your\Desired\Folder"; 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 Downloads the MDT MSI package, extracts ServiceUI.exe, and installs it in the target folder. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$TargetFolder, [Parameter(Mandatory = $true)] [string]$DownloadUrl, [Parameter(Mandatory = $true)] [string]$MsiFileName, [Parameter(Mandatory = $true)] [string]$InstalledServiceUIPath ) begin { Write-EnhancedLog -Message "Starting Download-And-Install-ServiceUI function" -Level "INFO" Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters try { $removeParams = @{ TargetFolder = $TargetFolder FileName = "ServiceUI.exe" } Remove-ExistingServiceUI @removeParams } catch { Write-EnhancedLog -Message "Error during Remove-ExistingServiceUI: $_" -Level "ERROR" Handle-Error -ErrorRecord $_ throw $_ } } process { $msiPath = Join-Path -Path $([System.IO.Path]::GetTempPath()) -ChildPath $MsiFileName $finalPath = Join-Path -Path $TargetFolder -ChildPath "ServiceUI.exe" try { # Download the MSI file using Start-FileDownloadWithRetry $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 if (Test-Path -Path $InstalledServiceUIPath) { $sourceDir = "`"$(Split-Path -Parent $InstalledServiceUIPath)`"" $destDir = "`"$TargetFolder`"" $fileName = "ServiceUI.exe" $robocopyCommand = "robocopy.exe $sourceDir $destDir $fileName" Write-EnhancedLog -Message "Executing command: $robocopyCommand" -Level "INFO" Invoke-Expression $robocopyCommand Write-EnhancedLog -Message "ServiceUI.exe has been successfully copied to: $finalPath" -Level "INFO" } else { throw "ServiceUI.exe not found at: $InstalledServiceUIPath" } $removeMsiParams = @{ Path = $msiPath Force = $true } Write-EnhancedLog -Message "Removing downloaded MSI file: $msiPath" -Level "INFO" Remove-Item @removeMsiParams } catch { Write-EnhancedLog -Message "An error occurred: $_" -Level "ERROR" Handle-Error -ErrorRecord $_ throw $_ } } end { Write-EnhancedLog -Message "Download-And-Install-ServiceUI function execution completed." -Level "INFO" } } # # Example usage of Download-And-Install-ServiceUI function with splatting # $params = @{ # TargetFolder = "C:\Path\To\Your\Desired\Folder"; # 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' 128 #Region '.\Public\Download-PSAppDeployToolkit.ps1' -1 function Download-PSAppDeployToolkit { <# .SYNOPSIS Downloads and extracts the latest PSAppDeployToolkit from a GitHub repository. .DESCRIPTION The Download-PSAppDeployToolkit function fetches the latest release of the PSAppDeployToolkit from a specified GitHub repository, downloads the release, and extracts its contents to a specified directory named `PSAppDeployToolkit`. .PARAMETER GithubRepository The GitHub repository from which to download the PSAppDeployToolkit (e.g., 'PSAppDeployToolkit/PSAppDeployToolkit'). .PARAMETER FilenamePatternMatch The filename pattern to match the release asset (e.g., '*.zip'). .PARAMETER DestinationDirectory The parent directory where the `PSAppDeployToolkit` folder will be created. .EXAMPLE $params = @{ GithubRepository = 'PSAppDeployToolkit/PSAppDeployToolkit'; FilenamePatternMatch = '*.zip'; DestinationDirectory = 'C:\YourScriptDirectory' } Download-PSAppDeployToolkit @params Downloads and extracts the latest PSAppDeployToolkit to a `PSAppDeployToolkit` directory within the specified parent directory. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$GithubRepository, [Parameter(Mandatory = $true)] [string]$FilenamePatternMatch, [Parameter(Mandatory = $true)] [string]$DestinationDirectory ) Begin { Write-EnhancedLog -Message "Starting Download-PSAppDeployToolkit function" -Level "Notice" Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters try { # Set the URI to get the latest release information from the GitHub repository $psadtReleaseUri = "https://api.github.com/repos/$GithubRepository/releases/latest" Write-EnhancedLog -Message "GitHub release URI: $psadtReleaseUri" -Level "INFO" } catch { Write-EnhancedLog -Message "Error in Begin block: $($_.Exception.Message)" -Level "ERROR" Handle-Error -ErrorRecord $_ throw $_ } } Process { try { # Fetch the latest release information 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" # Set the path for the temporary download location with a timestamp $timestamp = Get-Date -Format "yyyyMMddHHmmss" $zipTempDownloadPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "PSAppDeployToolkit_$timestamp.zip" Write-EnhancedLog -Message "Temporary download path: $zipTempDownloadPath" -Level "INFO" # Download the file with retry mechanism using Start-FileDownloadWithRetry $downloadParams = @{ Source = $psadtDownloadUri Destination = $zipTempDownloadPath MaxRetries = 3 } Start-FileDownloadWithRetry @downloadParams # Unblock the downloaded file if necessary Write-EnhancedLog -Message "Unblocking file at $zipTempDownloadPath" -Level "INFO" Unblock-File -Path $zipTempDownloadPath # Create a timestamped temporary folder for extraction $tempExtractionPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "PSAppDeployToolkit_$timestamp" if (-not (Test-Path $tempExtractionPath)) { New-Item -Path $tempExtractionPath -ItemType Directory | Out-Null } # Extract the contents of the zip file to the temporary extraction path Write-EnhancedLog -Message "Extracting file from $zipTempDownloadPath to $tempExtractionPath" -Level "INFO" Expand-Archive -Path $zipTempDownloadPath -DestinationPath $tempExtractionPath -Force # Define the PSAppDeployToolkit directory path $psAppDeployToolkitPath = Join-Path -Path $DestinationDirectory -ChildPath "PSAppDeployToolkit" if (-not (Test-Path $psAppDeployToolkitPath)) { New-Item -Path $psAppDeployToolkitPath -ItemType Directory | Out-Null Write-EnhancedLog -Message "Created directory: $psAppDeployToolkitPath" -Level "INFO" } # Use Copy-Item to copy the entire extracted folder structure, excluding Toolkit\Deploy-Application.ps1 and AppDeployToolkitBanner.png Write-EnhancedLog -Message "Copying files from $tempExtractionPath to $psAppDeployToolkitPath, excluding Toolkit\Deploy-Application.ps1 and AppDeployToolkitBanner.png" -Level "INFO" #ToDO fix the following as it is creating duplicate empty folders within each other so either fix it with copy-item or bring back robocopy but maintain exclusions or use a hybrid approach of using robocopy to maintain the original folder structure and then use copy-item to copy specific items from source to destination Get-ChildItem -Path $tempExtractionPath -Recurse | Where-Object { $_.FullName -notmatch '\\Toolkit\\Deploy-Application\.ps1$' -and $_.Name -ne 'AppDeployToolkitBanner.png' } | ForEach-Object { $destinationPath = $_.FullName.Replace($tempExtractionPath, $psAppDeployToolkitPath) if (-not (Test-Path -Path (Split-Path -Path $destinationPath -Parent))) { New-Item -Path (Split-Path -Path $destinationPath -Parent) -ItemType Directory | Out-Null } Copy-Item -Path $_.FullName -Destination $destinationPath -Force } Write-EnhancedLog -Message "Files copied successfully from Source: $tempExtractionPath to Destination: $psAppDeployToolkitPath" -Level "INFO" # Verify the copy operation Write-EnhancedLog -Message "Verifying the copy operation..." -Level "INFO" $verificationResults = Verify-CopyOperation -SourcePath $tempExtractionPath -DestinationPath $psAppDeployToolkitPath # Handle the verification results if necessary if ($verificationResults.Count -gt 0) { Write-EnhancedLog -Message "Discrepancies found during copy verification." -Level "ERROR" } else { Write-EnhancedLog -Message "Copy verification completed successfully with no discrepancies." -Level "INFO" } # Clean up temporary files Write-EnhancedLog -Message "Removing temporary download file: $zipTempDownloadPath" -Level "INFO" Remove-Item -Path $zipTempDownloadPath -Force Write-EnhancedLog -Message "Removing temporary extraction folder: $tempExtractionPath" -Level "INFO" Remove-Item -Path $tempExtractionPath -Recurse -Force } catch { Write-EnhancedLog -Message "Error in Process block: $($_.Exception.Message)" -Level "ERROR" Handle-Error -ErrorRecord $_ throw $_ } } End { try { Write-EnhancedLog -Message "File extracted and copied successfully to $psAppDeployToolkitPath" -Level "INFO" } catch { Write-EnhancedLog -Message "Error in End block: $($_.Exception.Message)" -Level "ERROR" Handle-Error -ErrorRecord $_ } Write-EnhancedLog -Message "Exiting Download-PSAppDeployToolkit function" -Level "Notice" } } # # Example usage # $params = @{ # GithubRepository = 'PSAppDeployToolkit/PSAppDeployToolkit'; # FilenamePatternMatch = '*.zip'; # DestinationDirectory = 'C:\temp\psadt-temp6' # } # Download-PSAppDeployToolkit @params #EndRegion '.\Public\Download-PSAppDeployToolkit.ps1' 171 #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 ScriptPath = $ScriptPath TargetFolder = $TargetFolder } 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' 91 #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\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"). .EXAMPLE Initialize-ScriptVariables -PackageName "MyPackage" -PackageUniqueGUID "1234-5678" -Version 1 -ScriptMode "Remediation" This example initializes the script variables with the specified values. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$PackageName, [Parameter(Mandatory = $true)] [string]$PackageUniqueGUID, [Parameter(Mandatory = $true)] [int]$Version, [Parameter(Mandatory = $true)] [string]$ScriptMode, [Parameter(Mandatory = $true)] [string]$PackageExecutionContext, [Parameter(Mandatory = $true)] [string]$RepetitionInterval ) # Assuming Set-LocalPathBasedOnContext and Test-RunningAsSystem are defined elsewhere # $global:Path_local = Set-LocalPathBasedOnContext # Default logic for $Path_local if not set by Set-LocalPathBasedOnContext if (-not $Path_local) { if (Test-RunningAsSystem) { # $Path_local = "$ENV:ProgramFiles\_MEM" $Path_local = "c:\_MEM" } else { $Path_local = "$ENV:LOCALAPPDATA\_MEM" } } $Path_PR = "$Path_local\Data\$PackageName-$PackageUniqueGUID" $schtaskName = "$PackageName - $PackageUniqueGUID" $schtaskDescription = "Version $Version" try { # Assuming Write-EnhancedLog is defined elsewhere Write-EnhancedLog -Message "Initializing script variables..." -Level "INFO" -ForegroundColor ([System.ConsoleColor]::Green) # Returning a hashtable of all the 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-Error "An error occurred while initializing script variables: $_" } } #EndRegion '.\Public\Initialize-ScriptVariables.ps1' 93 #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`"" # Define the arguments for PsExec64.exe to run PowerShell as SYSTEM with the script $argList = @( "-accepteula", "-i", "-s", "-d", $commandToRun ) Write-EnhancedLog -Message "Preparing to execute PowerShell as SYSTEM using PsExec64 with the script: $ScriptPathAsSYSTEM" -Level "INFO" # Log parameters using splatting $logParams = @{ PsExec64Path = $PsExec64Path ScriptPathAsSYSTEM = $ScriptPathAsSYSTEM TargetFolder = $TargetFolder UsePowerShell5 = $UsePowerShell5 } Log-Params -Params $logParams # 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' 116 #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' 30 #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 # 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' 46 #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\Test-RunningAsSystem.ps1' -1 function Test-RunningAsSystem { <# .SYNOPSIS Checks if the current session is running under the SYSTEM account. .DESCRIPTION The Test-RunningAsSystem function checks whether the current PowerShell session is running under the Windows SYSTEM account. This is determined by comparing the security identifier (SID) of the current user with the SID of the SYSTEM account. .EXAMPLE $isSystem = Test-RunningAsSystem if ($isSystem) { Write-Host "The script is running under the SYSTEM account." } else { Write-Host "The script is not running under the SYSTEM account." } Checks if the current session is running under the SYSTEM account and returns the status. .NOTES This function is useful when determining if the script is being executed by a service or task running under the SYSTEM account. #> [CmdletBinding()] param () Begin { Write-EnhancedLog -Message "Starting Test-RunningAsSystem function" -Level "NOTICE" # Initialize variables $systemSid = [System.Security.Principal.SecurityIdentifier]::new("S-1-5-18") } Process { try { Write-EnhancedLog -Message "Checking if the script is running under the SYSTEM account..." -Level "INFO" $currentSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User if ($currentSid -eq $systemSid) { Write-EnhancedLog -Message "The script is running under the SYSTEM account." -Level "INFO" } else { Write-EnhancedLog -Message "The script is not running under the SYSTEM account." -Level "WARNING" } } catch { Write-EnhancedLog -Message "Error determining if running as SYSTEM: $($_.Exception.Message)" -Level "ERROR" Handle-Error -ErrorRecord $_ throw $_ } } End { Write-EnhancedLog -Message "Exiting Test-RunningAsSystem function" -Level "NOTICE" return $currentSid -eq $systemSid } } # Usage Example # $isSystem = Test-RunningAsSystem # if ($isSystem) { # Write-Host "The script is running under the SYSTEM account." # } else { # Write-Host "The script is not running under the SYSTEM account." # } #EndRegion '.\Public\Test-RunningAsSystem.ps1' 66 #Region '.\Public\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' 48 #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 { $exists = Test-Path -Path $Path if ($exists) { Write-EnhancedLog -Message "Path exists: $Path" -Level "INFO" $filesInPath = Get-ChildItem -Path $Path -Recurse -File $totalExpectedFiles.Add($filesInPath.Count) $totalValidatedFiles.Add($filesInPath.Count) Write-EnhancedLog -Message "Total files found in $Path $($filesInPath.Count)" -Level "INFO" } 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 ($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' 77 |