Running PSADT Interactive with ServiceUI and PsExec (Dynamically)

Instructions for use:

Used on PSADT version: 3.9.3.

Folder Structure:
A folder called “SupportFiles” is required in the PSADT root folder and should contain the files: “PsExec64.exe” & “ServiceUI_x64.exe”.

Command Lines:
To run via SCCM or Manual Install with Invoke function use command line: “Deploy-Application.exe Install -Invoke” or “Deploy-Application.exe Uninstall -Invoke”.
Otherwise, to run via SCCM or Manual Install without Invoke function use command line: “Deploy-Application.exe Install” or “Deploy-Application.exe Uninstall”.

SCCM Deployment Settings:
Installation behaviour: Install for System.
Login requirement: Whether or not a user is logged on.
Installation program visibility: hidden
Untick - "Allow users to view and interact with the program installation”.

It integrates the Invoke functionality into “Deploy-Application.ps1” script using script blocks located in “AppDeployToolkitExtensions.ps1”.
Invoke is used to determine if a user is logged in or not and run the appropriate type of install. No user logged in: (non-interactive)Silent install / User logged in: (interactive)Full UI.

The invoke works by first running “Deploy-Application.ps1” with the -Invoke switch, this means it goes into the “Invoke_Type” script block where it determines if a user is logged on or not.
“Deploy-Application.ps1” is then run again (in parallel) without the -Invoke switch using the method determined in the “Invoke_Type” Script block. As -Invoke is not specified the script runs as normal.

This is a slightly simplified version of what we run in our environment.
@PaulH has designed a more in-depth version that we run which detects RDP and Disconnected RDP sessions. This is to make sure it runs interactively on these occasions.
This allows us to have one deployment type that we can use for both builds and for existing clients, as it can run whether or not a user is logged on!

I understand that this will be helpful for Intune deployments, as a PSADT will run interactively when a user is logged on.

#Add to Param in Deploy-Application.ps1:

	[Parameter(Mandatory = $false)]

#Add after the .Source is done in Deploy-Application.ps1:

	## CUSTOM: If $Invoke, Invoke Run through.
	If ($Invoke) { & $Invoke_Type }

#Add Script Blocks below to AppDeployToolkitExtensions.ps1:

	## Determine whether a user is logged on or not, then invoke the appropriate script block
    $Invoke_Type = {

        ## Settings/Variables:  
        $configShowBalloonNotifications = $false        <# Override the setting in the XML, turning balloon notifications OFF #>
        $writelogSource = 'Invoke-Deploy-Applcation'    <# Write-Log Component name #>
        $AllowRebootPassThru = $true                    <# Allow the script to exit with a reboot code, if one has been passed from Deploy-Application.ps1 #>
        Write-Log 'PSADT is running with the "-Invoke" switch.' -Source $writelogSource -Severity 2
        ## Invoke the script block.
        If ($usersLoggedOn)
            Write-Log 'A user is logged on to the Console session. PSADT will run interactively with ServiceUI.' -Source $writelogSource -Severity 2
            & $Invoke_ServiceUI_PsExec
        } else {
            Write-Log 'No one is logged on. PSADT will run non-interactive by default.' -Source $writelogSource -Severity 2
            & $Invoke_DeployApplication
	## When no users are logged on, launch 'Deploy-Application.exe'. PSADT will run non-interactive. 
	$Invoke_DeployApplication = {

		[psobject]$ExecuteProcessResult = Execute-Process -Path "$scriptParentPath\Deploy-Application.exe" -Parameters $DeploymentType -PassThru
		Write-Log -Message "The Invoke scriptblock completed with exit code [$($ExecuteProcessResult.ExitCode)]" -Source $writelogSource -Severity 2
		## Exit the script
		[Int32]$mainExitCode = $ExecuteProcessResult.ExitCode 
		Exit-Script -ExitCode $mainExitCode

	## When a user is logged on, launch PSADT 'interactive' via PsExec and ServiceUI.
	$Invoke_ServiceUI_PsExec = {

		[string]$PsExecParameters = "-accepteula -s -w `"$dirSupportFiles`" `"$dirSupportFiles\ServiceUI_x64.exe`" -process:explorer.exe ..\Deploy-Application.exe $DeploymentType"
		[psobject]$ExecuteProcessResult = Execute-Process -Path "$dirSupportFiles\PsExec64.exe" -Parameters $PsExecParameters -PassThru
		Write-Log -Message "The Invoke scriptblock with PsExec completed with exit code [$($ExecuteProcessResult.ExitCode)]" -Source $writelogSource -Severity 2
		## Exit the script
		[Int32]$mainExitCode = $ExecuteProcessResult.ExitCode
		Exit-Script -ExitCode $mainExitCode

Additionally, if you cannot use PsExec ( - or don’t want to) in your environment here is a script block that runs as SYSTEM using Task Scheduler. It’s a solution we created and used for a few deployments before our PsExec method was finalised.

Add the script block to: AppDeployToolkitExtensions.ps1

All you then need to do is change the line:
“& $Invoke_ServiceUI_PsExec” to “& $Invoke_ServiceUI_TaskScheduler”.

We currently use the Task Scheduler as a backup if PsExec has been deleted for any reason so the package still works, but you will have to add your own code for this to be a feature.

## Launch PSADT 'interactive' with ServiceUI via Task Scheduler
$Invoke_ServiceUI_TaskScheduler = {
	# Scheduled Task variables
	$taskName = 'PSADT Invoke-Deploy-Application'
	$process = 'ServiceUI_x64.exe'
	$ServiceUIProcess = $process -replace '\.exe'
	$taskProcess = "$dirSupportFiles\$process"
	$argument = "-process:explorer.exe $scriptParentPath\Deploy-Application.exe $DeploymentType"	
	$ServiceUIProcess_TimeOut = 6840			## 1 hour 54 mins (1 min before PSADT time-out limit)
		# Configure the task
		$action = New-ScheduledTaskAction -Execute $taskProcess -Argument $argument -WorkingDirectory $dirSupportFiles
		$principal = New-ScheduledTaskPrincipal -UserId 'NT AUTHORITY\SYSTEM' -RunLevel Highest
		$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
		$task = New-ScheduledTask -Action $action -Principal $principal -Settings $settings

		# Delete the task if it already exists
		Unregister-ScheduledTask -TaskName $taskName -TaskPath '\' -Confirm:$false -ErrorAction SilentlyContinue
		# Register the new task
		Write-Log "Registering the Scheduled Task `"$taskName`"..." -Source $writelogSource -Severity 2
		Register-ScheduledTask -TaskName $taskName -TaskPath '\' -InputObject $task

		If ((Get-ScheduledTask -TaskName $taskName).State -eq 'Ready')
			# Start the task
			Write-Log "Starting the Scheduled Task `"$taskName`"..." -Source $writelogSource -Severity 2
			Start-ScheduledTask -TaskName $taskName
			## Now wait for ServiceUI_x64 
			Write-Log "Waiting for $ServiceUIProcess..." -Source $writelogSource -Severity 2
			Wait-Process $ServiceUIProcess -Timeout $ServiceUIProcess_TimeOut			
			Write-Log "The Scheduled Task `"$taskName`" is no longer running" -Source $writelogSource -Severity 2

			# Get the task information
			$taskInfo = Get-ScheduledTaskInfo -TaskName $taskName

			# Capture the exit code and Exit
			$taskExitCode = $taskInfo.LastTaskResult			
			Write-Log "The Scheduled Task `"$taskName`" completed with exit code [$taskExitCode]" $writelogSource -Severity 2
			[Int32]$mainExitCode = $taskExitCode
		Write-Log 'Uh oh. Something went wrong in the Scheduled Task stage.' -Source $writelogSource -Severity 3 
		[Int32]$mainExitCode = 69001
		# Delete the task after completion
		Write-Log "Deleting the Scheduled Task `"$taskName`"" -Source $writelogSource -Severity 2
		Unregister-ScheduledTask -TaskName $taskName -TaskPath '\' -Confirm:$false
		## Exit the script
		Exit-Script -ExitCode $mainExitCode