Here’s my custom built Installation Prompt with a ‘Defer’ option. Includes optional countdown timer. Updated in the 3.9.3 version of PSADT.
Deferral returns exit code ‘60012’
Selectable Time for the Defer:
Warning if they select a time in the past:
Creates a scheduled task to run at the selected time or at logon:
Update the Deploy-Application.ps1 with:
## <Enter the processes you need closed below separated by a comma.
# Example: Get-DeploymentProcess -Name "notepad,chrome,winword"
#=================================================================================>
Get-DeploymentProcess -Name "notepad"
#=================================================================================>
# In some cases you will need to have a custom description because the vendor sucks.
# Use both lines below to do this. You will also have to specify individually in the 'Show-InstallationWelcome' line.
#[switch]$UseCustomNames = $true
#$CustomNames = "Microsoft Belly Button" # Example: "Microsoft Belly Button,Microsoft Excel"
#=================================================================================>
## <Show the 'Defer' prompt for the installation>
# Example: Show-DeferPrompt -DeferLimit 2[default] -DeferTimeOptions ShowBoth[default]/IncrementalOnly/SelectTimeOnly -DeferTimeIncrements '15m,30m,1h,4h,8h' -DeferCountdownTimer '300' -DeferDefaultTimeOption Incremental[default]/SelectTime -NetworkRequired 'google.com' -ShowBalloon $True[default]/$False -TopMostDefer $True[default]/$False -ShowMinimizeButton $True/$False[default] (Parameters are not required)
Show-DeferPrompt
## Show Welcome Message, close open apps if required
# For use with 'Show-DeferPrompt'
Show-InstallationWelcome -CloseApps "$ProcessNames" -MinimizeWindows $false -TopMost $true -BlockExecution #-Silent #-CheckDiskSpace -PersistPrompt
# Use the standard version if not using 'Show-DeferPrompt'
#Show-InstallationWelcome -CloseApps "Notepad" -MinimizeWindows $false -TopMost $true -BlockExecution #-CheckDiskSpace -PersistPrompt
## Show Progress Message (with the default message)
Show-InstallationProgress -StatusMessage "Installing $appName`n`nPlease Wait...."
.
.
Add Extensions Get-DeploymentProcess, Show-DeferPrompt, and Show-DeferInstallationPrompt into the ‘AppDeployToolkitExtensions.ps1’ file.
#region Function Get-DeploymentProcess
Function Get-DeploymentProcess {
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true)]
[string]$Name = ''
)
$script:ProcessNames = @("$Name")
#in script .ps1 method uses all below code:
#$script:ProcessNames = @("notepad,iexplore,excel")
# Gets the Full descriptive name for each process to show in the Defer screen
foreach ($p in $ProcessNames -split ',') {
$script:DeferProcesses += @(Get-Process $p -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Description | Sort-Object | Get-Unique)
}
# DeferAppNames shows the apps list in the Defer screen
$script:DeferAppNames = $DeferProcesses
# The ActiveProcess variable is used for determining if the app is already open when the script begins. If not the welcome screen and defer screen won't show at all
$script:ActiveProcesses = $ProcessNames -split ','
foreach($process in $ActiveProcesses) {
Write-Log "Searching for running process: $process"
}
$script:ActiveProcess = Get-Process $ActiveProcesses -ErrorAction SilentlyContinue | Sort-Object | Get-Unique
if($ActiveProcess -ne $null) {
foreach($process in $ActiveProcess) {
Write-Log "Found running process: $process"
}
}
else { Write-Log "No specified running processes were found." }
}
#endregion
#region Function Show-Defer
Function Show-DeferPrompt {
<#
.SYNOPSIS
Show the Application Defer prompt window if a required app needs to be closed.
.DESCRIPTION
Show the Application Defer prompt window if a required app needs to be closed.
.PARAMETER DeferLimit
The number of times a user is allowed to defer the installation.
.PARAMETER DeferTimeIncrements
The time units that a user is allowed to defer the installation.
.PARAMETER NetworkRequired
For installations that require connection to the network to run. This is used in situations where a user defers and then reboots prior to the defer time.
.PARAMETER ShowBalloon
Show balloon for the amount of time deferred.
.PARAMETER TopMostDefer
Keep the defer prompt window as topmost.
.PARAMETER ShowMinimizeButton
Show the MinimizeBox on the defer window.
.PARAMETER DeferCountdownTimer
Show timer countdown for the deferral window. Once countdown is reached, open apps will auto close.
.PARAMETER DeferTimeOptions
'ShowBoth', 'IncrementalOnly' or 'SelectTimeOnly' are the options. Chooses which defer time options are available when the form is opened.
.PARAMETER DeferDefaultTimeOption
'Incremental' or 'SelectTime' are the options. Chooses which radio button as default when the form is opened.
.EXAMPLE
Show-DeferPrompt -DeferLimit 3 -ShowMinimizeButton $true -NetworkRequired 'google.com' -DeferTimeIncrements '30m,1h,2h,4h,2d'
.EXAMPLE
Show-DeferPrompt -DeferLimit 5 -DeferTimeOptions SelectTimeOnly -DeferTimeIncrements '15m,30m,1h,4h,8h' -DeferCountdownTimer '300' -DeferDefaultTimeOption Incremental[default]/SelectTime -NetworkRequired 'google.com' -ShowBalloon $True[default]/$False -TopMostDefer $True[default]/$False -ShowMinimizeButton $True/$False[default] (Parameters are not required)
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory=$false)]
[string]$DeferLimit = '2',
[Parameter(Mandatory=$false)]
[string[]]$DeferTimeIncrements,
[Parameter(Mandatory=$false)]
[string]$NetworkRequired,
[Parameter(Mandatory=$false)]
[boolean]$ShowBalloon = $true,
[Parameter(Mandatory=$false)]
[boolean]$TopMostDefer=$true,
[Parameter(Mandatory=$false)]
[boolean]$ShowMinimizeButton = $false,
[Parameter(Mandatory=$false)]
[int]$DeferCountdownTimer,
[Parameter(Mandatory=$false)]
[ValidateSet('Incremental', 'SelectTime')]
[String]$DeferDefaultTimeOption = 'Incremental',
[Parameter(Mandatory=$false)]
[ValidateSet('IncrementalOnly', 'SelectTimeOnly', 'ShowBoth')]
[String]$DeferTimeOptions = 'ShowBoth'
)
##<===================================================================================================================>##
######<In most, if not all cases you will not need to modify anything between these lines =======================>########
##<===================================================================================================================>##
$script:ShowDeferTime = $false
## Test connection to domain if required
If($NetworkRequired) {
If(-not(Test-Connection -ComputerName "$NetworkRequired" -Count 1)) {
$timeout = new-timespan -Minutes 115
$sw = [diagnostics.stopwatch]::StartNew()
While ($sw.elapsed -lt $timeout) {
If (Test-Connection -ComputerName "$NetworkRequired" -Count 1) {
Write-Host "Found domain connection to $NetworkRequired. Start the install!"
Write-Log "Found domain connection to $NetworkRequired. Start the install!"
$NetCheck = $null
Return
}
Write-Host "$NetworkRequired not found... Retrying."
Write-Log "$NetworkRequired not found... Retrying."
if ($appVendor) { $AppTaskName = "$appVendor - $appName" }
else { $AppTaskName = "$appName" }
$RegPath = "HKLM:\SOFTWARE\Deferral\AppDefer"
$RegPathRunOnce = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce"
$name = "DeferCount-$appName"
$DeferAppNames = foreach($App in $DeferAppNames) { "$app`n" }
# Delete the scheduled task & runonce key if it already exists
Remove-ItemProperty -Path $RegPathRunOnce -Name $AppTaskName -Force -ErrorAction SilentlyContinue
Remove-ItemProperty -Path $RegPath -Name $Name -Force -ErrorAction SilentlyContinue
Write-Host "==============Removing Scheduled Task: $AppTaskName"
Write-Log "==============Removing Scheduled Task: $AppTaskName"
If(Get-ScheduledTask -TaskName $AppTaskName -ErrorAction SilentlyContinue ) {
Write-Host "Unregister Scheduled Task: $AppTaskName"
Write-Log "Unregister Scheduled Task: $AppTaskName"
Unregister-ScheduledTask -TaskName $AppTaskName -Confirm:$false -ErrorAction SilentlyContinue
}
$NetCheck = $true
Show-DeferInstallationPrompt -Message "You must be connected to the domain in order to continue the installation.`n`nConnect to VPN or an office network and click 'OK' to continue.`n`n`n" -buttonrighttext "OK" -PersistPrompt
Start-Sleep -Seconds 2
}
Write-Host "Timed out"
Exit-Script -ExitCode 1618 #Installation failed due to timeout
}
Else { Write-Host "Found domain connection to $NetworkRequired"; Write-Log "Found domain connection to $NetworkRequired" }
}
Else { Write-Host "Network connection not required."; Write-Log "Network connection not required." }
## End domain connection test
if ($appVendor) { $AppTaskName = "$appVendor - $appName" }
else { $AppTaskName = "$appName" }
$RegPath = "HKLM:\SOFTWARE\Deferral\AppDefer"
$RegPathRunOnce = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce"
$name = "DeferCount-$appName"
$DeferAppNames = foreach($App in $DeferAppNames) { "$app`n" }
Write-Host "$appVendor`n$appName`n$AppTaskName" -ForegroundColor Cyan
# Delete the scheduled task & runonce key if it already exists
If(Get-ScheduledTask -TaskName $AppTaskName -ErrorAction SilentlyContinue) {
Write-Host "Removing the previous scheduled task." -ForegroundColor Cyan
Unregister-ScheduledTask -TaskName $AppTaskName -Confirm:$false #-ErrorAction SilentlyContinue
}
$localvalue = Get-ItemProperty -Path $RegPath -Name $name -ErrorAction SilentlyContinue | Select-Object -ExpandProperty $name
if($localvalue -ne 0) {
if($localvalue -eq $null) {$localvalue = $DeferLimit}
If ($activeProcess -ne $null) {
$ShowDeferTime = $True #<Do show the defer time dropdown list>
$ShowListBoxApp = $True #<Do show the apps listbox>
$ShowBoldButtonRight = $True #<Do show the buttonright in bold text>
#$userResponse = Show-InstallationPrompt -Message "The following application is about to be installed:`n$appVendor $appNameDefer`n`n`nYou will be required to close out of the following applications for the installation to begin:`n`n`n`n`n`nNOTE: You can choose to defer the installation until the deferral expires. Choosing 'defer' will re-run the installation at the selected time or at the next reboot/logon.`n`nRemaining Deferrals: $localvalue`n`n`n`n" -ButtonRightText "Install Now" -ButtonLeftText "Defer" -PersistPrompt
$userResponse = Show-DeferInstallationPrompt -Message "You will be required to close out of the following applications for the installation to begin:`n`n`n`n`n`nNOTE: You can choose to defer the installation until the deferral expires. Choosing 'defer' will re-run the installation at the selected time or at the next reboot/logon.`n`n`n`n`n`n`n`n" -ButtonRightText "Install Now" -ButtonLeftText "Defer"
$ShowDeferTime = $False #<Don't show the defer time dropdown list>
$ShowListBoxApp = $False #<Don't show the apps listbox>
$ShowBoldButtonRight = $False #<Don't show the buttonright in bold text>
$DeferCountdownTimer = $null
If($userResponse -eq 'Defer') {
Write-Host '---------User Selected to Defer the Installation---------' -ForegroundColor Cyan
Write-Log "---------User Selected to Defer the Installation---------"
## Grabbed from function 'Show-InstallationPrompt', Combobox Dropdown List in the 'Main' toolkit file
if($RadioButtonSelect.Checked) {
#$DeferExactTime = $selectedTime
$currentdate2 = Get-Date -Format "yyyy/MM/dd"
$currentdate3 = Get-Date -Format "MM/dd/yyyy"
$DeferExactTime = $selectedTime
$Run = "$currentdate3 $DeferExactTime"
}
else {
$DeferTime = $listBox.SelectedItem
Write-Host "Selected defer time item: $DeferTime" -ForegroundColor Red
Write-Log "Selected defer time item: $DeferTime"
if($DeferTime -like '*minutes') { $HrMin = 'minutes'; $DeferTime = $DeferTime -replace ' minutes','' }
elseif($DeferTime -like '*hours') { $HrMin = 'hours'; $DeferTime = $DeferTime -replace ' hours','' }
elseif($DeferTime -like '*hour') { $HrMin = 'hour'; $DeferTime = $DeferTime -replace ' hour','' }
elseif($DeferTime -like '*days*') { $HrMin = 'days'; $DeferTime = $DeferTime -replace ' days','' }
#elseif($DeferTime -like '*day') { $HrMin = 'day'; $DeferTime = $DeferTime -replace ' day','' }
Write-Host "Selected defer time is $DeferTime $HrMin" -ForegroundColor Red
Write-Log "User selected to defer for $DeferTime $HrMin"
}
if($ShowBalloon -eq $true) {
if($RadioButtonSelect.Checked) {
$timeCheck = $12selectedtime.StartsWith('0')
if($timeCheck -eq $true) {
Write-Host "=== time leads with a zero ==="
$fTime = $12selectedtime.Substring(1)
}
else { $fTime = $12selectedtime }
Write-Host "Selected time: $12selectedtime" -ForegroundColor Green
Show-BalloonTip -BalloonTipText "Installation deferred until $fTime"
}
else { Show-BalloonTip -BalloonTipText "Installation deferred for $DeferTime $HrMin" }
}
# Add new registry key and value for defer count
$RegRoot = "HKLM:\SOFTWARE\Deferral"
$RegPath = "HKLM:\SOFTWARE\Deferral\AppDefer"
$name = "DeferCount-$appName"
$newvalue = $DeferLimit
$type = "String"
$localvalue = Get-ItemProperty -Path $RegPath -Name $name -ErrorAction SilentlyContinue | Select -ExpandProperty $name
# If the deferral reg key exists and it's not equal to '0', decrement the value by 1
if(($localvalue -ne $null) -and ($localvalue -ne 0)) {
Write-Host 'found value'
$localvalue = $localvalue -1
Set-ItemProperty -Path $RegPath -Name $name -Value $localvalue
}
# If the If the deferral reg key is equal to '0', start the standard installation script section
elseif($localvalue -eq 0) {}
# If the deferral reg key doesn't exist, create it with the default value and decrement by 1
else {
New-Item -Path $RegRoot -ErrorAction SilentlyContinue
New-Item -Path $RegPath -ErrorAction SilentlyContinue
Set-ItemProperty -Path $RegPath -Name $name -Value $newvalue
$localvalue = (Get-ItemProperty -Path $RegPath -Name $name).$name
$localvalue = $localvalue -1
Set-ItemProperty -Path $RegPath -Name $name -Value $localvalue
}
## Scheduled task creation
# Set the task name
if(($appVendor) -and ($appName)) { $AppTaskName = "$appVendor - $appName" }
else { $AppTaskName = "$appName" }
$TaskDescription = 'Starts the PowerShell ADTK executable.'
# Set the deferral time for minutes or hours
$Now = Get-Date
if($RadioButtonSelect.Checked) {
Write-Host "Selected Exact: $Run" -ForegroundColor Yellow
Write-Host "Rerun selected exact at: $Run" -ForegroundColor Cyan
Write-Log "Rerun selected exact at: $Run"
$Expire = $Now.AddDays('1').AddHours('1').ToString('s')
Write-Host "$Expire" -ForegroundColor Yellow
}
else {
# Set the deferral time for minutes or hours
if($HrMin -eq 'minutes') {
$Run = $Now.AddMinutes($DeferTime)
}
elseif(($HrMin -eq 'hours') -or ($HrMin -eq 'hour')) {
$Run = $Now.AddHours($DeferTime)
}
elseif($HrMin -eq 'days') {
$Run = $Now.AddDays($DeferTime)
}
Write-Host "Setting to: $HrMin" -ForegroundColor Cyan
Write-Host "Rerun at: $Run" -ForegroundColor Cyan
Write-Log "Rerun at: $Run"
# Create the scheduled task expiration
#$Expire = $Run.AddSeconds(1).ToString('s')
#$Expire = $Run.AddHours(5).ToString('s')
$Expire = $Run.AddDays(1).ToString('s')
Write-Host "$Expire" -ForegroundColor Yellow
}
# Set up action to run
$TaskPathRoot = $PSScriptRoot -replace ('\\AppDeployToolkit','')
$Action = New-ScheduledTaskAction -Execute "$TaskPathRoot\Deploy-Application.exe"
# Setup trigger(s)
#$Triggers = @()
#$Triggers += New-ScheduledTaskTrigger -At $Run -Once
#$Triggers += New-ScheduledTaskTrigger -AtLogOn
$Triggers = @(
$(New-ScheduledTaskTrigger -At $Run -Once),
$(New-ScheduledTaskTrigger -AtLogOn)
)
# Setup initial task settings - NOTE: Win8 is used for Windows 10
$Settings = New-ScheduledTaskSettingsSet
$Settings.DeleteExpiredTaskAfter = "PT0S" #Immediately
$Settings.ExecutionTimeLimit = 'PT2H' #2 hours
$Settings.Priority = 5
$Settings.StartWhenAvailable = $true
$Settings.Compatibility = 'Win8'
# Setup the Principal and Runlevel
$Principal = New-ScheduledTaskPrincipal -UserId "$env:USERNAME" -RunLevel Highest
# Setup the scheduled task
$Task = New-ScheduledTask -Action $Action -Trigger $Triggers -Settings $Settings -Description "$TaskDescription" -Principal $Principal
# Set additional desired tweaks
$Task.Triggers[0].EndBoundary = $Expire
$Task.Author = 'PowerShell ADT'
$Task.Settings.RunOnlyIfNetworkAvailable = $true
$Task.Settings.DisallowStartIfOnBatteries = $false
$Task.Principal
# Create the scheduled task and RunOnce key
Register-ScheduledTask -TaskName "$AppTaskName" -TaskPath "\" -InputObject $Task
Set-ItemProperty -Path $RegPathRunOnce -Name $AppTaskName -Value "$TaskPathRoot\Deploy-Application.exe"
# Exit the script
Clear-Variable listBox -Force -Scope Global -Confirm:$false -ErrorAction SilentlyContinue
Clear-Variable ActiveProcess -Force -Scope Script -Confirm:$false -ErrorAction SilentlyContinue
Clear-Variable DeferProcesses -Force -Scope Script -Confirm:$false -ErrorAction SilentlyContinue
#Clear-Variable DeferCountdownTimer -Force -Scope Script -Confirm:$false -ErrorAction SilentlyContinue
Write-Host "Exiting with code 60012"
exit 60012
}
}
}
# Remove the deferral limit and RunOnce registry keys
if ($appVendor) { $AppTaskName = "$appVendor - $appName" }
else { $AppTaskName = "$appName" }
Remove-ItemProperty -Path $RegPath -Name $name -Force -ErrorAction SilentlyContinue
Clear-Variable DeferProcesses -Force -Scope Script -Confirm:$false -ErrorAction SilentlyContinue
##<===================================================================================================================>##
}
#endregion
See comments for the additional extensions: