Skip to content

Instantly share code, notes, and snippets.

@Pollewops
Last active June 14, 2024 09:23
Show Gist options
  • Save Pollewops/900e16e72629cf1dd863c433ecb2d0a7 to your computer and use it in GitHub Desktop.
Save Pollewops/900e16e72629cf1dd863c433ecb2d0a7 to your computer and use it in GitHub Desktop.
AppDeployToolkitExtensions.ps1
<#
.SYNOPSIS
PSAppDeployToolkit - Provides the ability to extend and customise the toolkit by adding your own functions that can be re-used.
.DESCRIPTION
This script is a template that allows you to extend the toolkit with your own custom functions.
This script is dot-sourced by the AppDeployToolkitMain.ps1 script which contains the logic and functions required to install or uninstall an application.
PSApppDeployToolkit is licensed under the GNU LGPLv3 License - (C) 2024 PSAppDeployToolkit Team (Sean Lillis, Dan Cunningham and Muhammad Mashwani).
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
.EXAMPLE
powershell.exe -File .\AppDeployToolkitHelp.ps1
.INPUTS
None
You cannot pipe objects to this script.
.OUTPUTS
None
This script does not generate any output.
.NOTES
.LINK
https://psappdeploytoolkit.com
#>
[CmdletBinding()]
Param (
)
##*===============================================
##* VARIABLE DECLARATION
##*===============================================
# Variables: Script
[string]$appDeployToolkitExtName = 'PSAppDeployToolkitExt'
[string]$appDeployExtScriptFriendlyName = 'App Deploy Toolkit Extensions'
[version]$appDeployExtScriptVersion = [version]'3.10.1'
[string]$appDeployExtScriptDate = '05/03/2024'
[hashtable]$appDeployExtScriptParameters = $PSBoundParameters
##*===============================================
##* FUNCTION LISTINGS
##*===============================================
# <Your custom functions go here>
#region Function Show-InstallationWelcomeIntune
Function Show-InstallationWelcomeIntune {
<#
.SYNOPSIS
Show a welcome dialog prompting the user with information about the installation and actions to be performed before the installation can begin.
.DESCRIPTION
The following prompts can be included in the welcome dialog:
a) Close the specified running applications, or optionally close the applications without showing a prompt (using the -Silent switch).
b) Defer the installation a certain number of times, for a certain number of days or until a deadline is reached.
c) Countdown until applications are automatically closed.
d) Prevent users from launching the specified applications while the installation is in progress.
Notes:
The process descriptions are retrieved from WMI, with a fall back on the process name if no description is available. Alternatively, you can specify the description yourself with a '=' symbol - see examples.
The dialog box will timeout after the timeout specified in the XML configuration file (default 1 hour and 55 minutes) to prevent SCCM installations from timing out and returning a failure code to SCCM. When the dialog times out, the script will exit and return a 1618 code (SCCM fast retry code).
.PARAMETER CloseApps
Name of the process to stop (do not include the .exe). Specify multiple processes separated by a comma. Specify custom descriptions like this: "winword=Microsoft Office Word,excel=Microsoft Office Excel"
.PARAMETER Silent
Stop processes without prompting the user.
.PARAMETER CloseAppsCountdown
Option to provide a countdown in seconds until the specified applications are automatically closed. This only takes effect if deferral is not allowed or has expired.
.PARAMETER ForceCloseAppsCountdown
Option to provide a countdown in seconds until the specified applications are automatically closed regardless of whether deferral is allowed.
.PARAMETER PromptToSave
Specify whether to prompt to save working documents when the user chooses to close applications by selecting the "Close Programs" button. Option does not work in SYSTEM context unless toolkit launched with "psexec.exe -s -i" to run it as an interactive process under the SYSTEM account.
.PARAMETER PersistPrompt
Specify whether to make the Show-InstallationWelcome prompt persist in the center of the screen every couple of seconds, specified in the AppDeployToolkitConfig.xml. The user will have no option but to respond to the prompt. This only takes effect if deferral is not allowed or has expired.
.PARAMETER BlockExecution
Option to prevent the user from launching processes/applications, specified in -CloseApps, during the installation.
.PARAMETER AllowDefer !!! DO NOT USE DEFER WITH INTUNE. THAT DOES NOT WORK !!!
Enables an optional defer button to allow the user to defer the installation.
.PARAMETER AllowDeferCloseApps !!! DO NOT USE DEFER WITH INTUNE. THAT DOES NOT WORK !!!
Enables an optional defer button to allow the user to defer the installation only if there are running applications that need to be closed. This parameter automatically enables -AllowDefer
.PARAMETER DeferTimes !!! DO NOT USE DEFER WITH INTUNE. THAT DOES NOT WORK !!!
Specify the number of times the installation can be deferred.
.PARAMETER DeferDays !!! DO NOT USE DEFER WITH INTUNE. THAT DOES NOT WORK !!!
Specify the number of days since first run that the installation can be deferred. This is converted to a deadline.
.PARAMETER DeferDeadline !!! DO NOT USE DEFER WITH INTUNE. THAT DOES NOT WORK !!!
Specify the deadline date until which the installation can be deferred.
Specify the date in the local culture if the script is intended for that same culture.
If the script is intended to run on EN-US machines, specify the date in the format: "08/25/2013" or "08-25-2013" or "08-25-2013 18:00:00"
If the script is intended for multiple cultures, specify the date in the universal sortable date/time format: "2013-08-22 11:51:52Z"
The deadline date will be displayed to the user in the format of their culture.
.PARAMETER CheckDiskSpace
Specify whether to check if there is enough disk space for the installation to proceed.
If this parameter is specified without the RequiredDiskSpace parameter, the required disk space is calculated automatically based on the size of the script source and associated files.
.PARAMETER RequiredDiskSpace
Specify required disk space in MB, used in combination with CheckDiskSpace.
.PARAMETER MinimizeWindows
Specifies whether to minimize other windows when displaying prompt. Default: $true.
.PARAMETER TopMost
Specifies whether the windows is the topmost window. Default: $true.
.PARAMETER ForceCountdown
Specify a countdown to display before automatically proceeding with the installation when a deferral is enabled.
.PARAMETER CustomText
Specify whether to display a custom message specified in the XML file. Custom message must be populated for each language section in the XML.
.INPUTS
None
You cannot pipe objects to this function.
.OUTPUTS
None
This function does not return objects.
.EXAMPLE
Show-InstallationWelcomeIntune -CloseApps 'iexplore,winword,excel'
Prompt the user to close Internet Explorer, Word and Excel.
.EXAMPLE
Show-InstallationWelcomeIntune -CloseApps 'winword,excel' -Silent
Close Word and Excel without prompting the user.
.EXAMPLE
Show-InstallationWelcomeIntune -CloseApps 'winword,excel' -BlockExecution
Close Word and Excel and prevent the user from launching the applications while the installation is in progress.
.EXAMPLE
Show-InstallationWelcomeIntune -CloseApps 'winword=Microsoft Office Word,excel=Microsoft Office Excel' -CloseAppsCountdown 600
Prompt the user to close Word and Excel, with customized descriptions for the applications and automatically close the applications after 10 minutes.
.EXAMPLE
Show-InstallationWelcomeIntune -CloseApps 'winword,msaccess,excel' -PersistPrompt
Prompt the user to close Word, MSAccess and Excel.
By using the PersistPrompt switch, the dialog will return to the center of the screen every couple of seconds, specified in the AppDeployToolkitConfig.xml, so the user cannot ignore it by dragging it aside.
.EXAMPLE
Show-InstallationWelcomeIntune -CloseApps 'winword,excel' -BlockExecution -CloseAppsCountdown 600
Close Word and Excel and prevent the user from launching the applications while the installation is in progress.
Allow the user to defer the installation a maximum of 10 times or until the deadline is reached, whichever happens first.
When deferral expires, prompt the user to close the applications and automatically close them after 10 minutes.
.NOTES
.LINK
https://psappdeploytoolkit.com
#>
[CmdletBinding(DefaultParametersetName = 'None')]
Param (
## Specify process names separated by commas. Optionally specify a process description with an equals symbol, e.g. "winword=Microsoft Office Word"
[Parameter(Mandatory = $false)]
[ValidateNotNullorEmpty()]
[String]$CloseApps,
## Specify whether to prompt user or force close the applications
[Parameter(Mandatory = $false)]
[Switch]$Silent = $false,
## Specify a countdown to display before automatically closing applications where deferral is not allowed or has expired
[Parameter(Mandatory = $false)]
[ValidateNotNullorEmpty()]
[Int32]$CloseAppsCountdown = 0,
## Specify a countdown to display before automatically closing applications whether or not deferral is allowed
[Parameter(Mandatory = $false)]
[ValidateNotNullorEmpty()]
[Int32]$ForceCloseAppsCountdown = 0,
## Specify whether to prompt to save working documents when the user chooses to close applications by selecting the "Close Programs" button
[Parameter(Mandatory = $false)]
[Switch]$PromptToSave = $false,
## Specify whether to make the prompt persist in the center of the screen every couple of seconds, specified in the AppDeployToolkitConfig.xml.
[Parameter(Mandatory = $false)]
[Switch]$PersistPrompt = $false,
## Specify whether to block execution of the processes during installation
[Parameter(Mandatory = $false)]
[Switch]$BlockExecution = $false,
## Specify whether to enable the optional defer button on the dialog box
[Parameter(Mandatory = $false)]
[Switch]$AllowDefer = $false,
## Specify whether to enable the optional defer button on the dialog box only if an app needs to be closed
[Parameter(Mandatory = $false)]
[Switch]$AllowDeferCloseApps = $false,
## Specify the number of times the deferral is allowed
[Parameter(Mandatory = $false)]
[ValidateNotNullorEmpty()]
[Int32]$DeferTimes = 0,
## Specify the number of days since first run that the deferral is allowed
[Parameter(Mandatory = $false)]
[ValidateNotNullorEmpty()]
[Int32]$DeferDays = 0,
## Specify the deadline (in format dd/mm/yyyy) for which deferral will expire as an option
[Parameter(Mandatory = $false)]
[String]$DeferDeadline = '',
## Specify whether to check if there is enough disk space for the installation to proceed. If this parameter is specified without the RequiredDiskSpace parameter, the required disk space is calculated automatically based on the size of the script source and associated files.
[Parameter(ParameterSetName = 'CheckDiskSpaceParameterSet', Mandatory = $true)]
[ValidateScript({ $_.IsPresent -eq ($true -or $false) })]
[Switch]$CheckDiskSpace,
## Specify required disk space in MB, used in combination with $CheckDiskSpace.
[Parameter(ParameterSetName = 'CheckDiskSpaceParameterSet', Mandatory = $false)]
[ValidateNotNullorEmpty()]
[Int32]$RequiredDiskSpace = 0,
## Specify whether to minimize other windows when displaying prompt
[Parameter(Mandatory = $false)]
[ValidateNotNullorEmpty()]
[Boolean]$MinimizeWindows = $true,
## Specifies whether the window is the topmost window
[Parameter(Mandatory = $false)]
[ValidateNotNullorEmpty()]
[Boolean]$TopMost = $true,
## Specify a countdown to display before automatically proceeding with the installation when a deferral is enabled
[Parameter(Mandatory = $false)]
[ValidateNotNullorEmpty()]
[Int32]$ForceCountdown = 0,
## Specify whether to display a custom message specified in the XML file. Custom message must be populated for each language section in the XML.
[Parameter(Mandatory = $false)]
[Switch]$CustomText = $false
)
Begin {
## Get the name of this function and write header
[String]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header
}
Process {
## If running in NonInteractive mode, force the processes to close silently
If ($deployModeNonInteractive) {
$Silent = $true
}
## If using Zero-Config MSI Deployment, append any executables found in the MSI to the CloseApps list
If ($useDefaultMsi) {
$CloseApps = "$CloseApps,$defaultMsiExecutablesList"
}
## Check disk space requirements if specified
If ($CheckDiskSpace) {
Write-Log -Message 'Evaluating disk space requirements.' -Source ${CmdletName}
[Double]$freeDiskSpace = Get-FreeDiskSpace
If ($RequiredDiskSpace -eq 0) {
Try {
# Determine the size of the Files folder
$fso = New-Object -ComObject 'Scripting.FileSystemObject' -ErrorAction 'Stop'
$RequiredDiskSpace = [Math]::Round((($fso.GetFolder($scriptParentPath).Size) / 1MB))
}
Catch {
Write-Log -Message "Failed to calculate disk space requirement from source files. `r`n$(Resolve-Error)" -Severity 3 -Source ${CmdletName}
}
Finally {
Try {
$null = [Runtime.Interopservices.Marshal]::ReleaseComObject($fso)
}
Catch {
}
}
}
If ($freeDiskSpace -lt $RequiredDiskSpace) {
Write-Log -Message "Failed to meet minimum disk space requirement. Space Required [$RequiredDiskSpace MB], Space Available [$freeDiskSpace MB]." -Severity 3 -Source ${CmdletName}
If (-not $Silent) {
Show-InstallationPromptIntune -Message ($configDiskSpaceMessage -f $installTitle, $RequiredDiskSpace, ($freeDiskSpace)) -ButtonRightText 'OK' -Icon 'Error'
}
Exit-Script -ExitCode $configInstallationUIExitCode
}
Else {
Write-Log -Message 'Successfully passed minimum disk space requirement check.' -Source ${CmdletName}
}
}
## Create a Process object with custom descriptions where they are provided (split on an '=' sign)
[PSObject[]]$processObjects = If ($CloseApps) {
# Split multiple processes on a comma, then split on equal sign, then create custom object with process name and description
ForEach ($process in ($CloseApps -split ',' | Where-Object { $_ })) {
If ($process.Contains('=')) {
[String[]]$ProcessSplit = $process -split '='
New-Object -TypeName 'PSObject' -Property @{
ProcessName = $ProcessSplit[0]
ProcessDescription = $ProcessSplit[1]
}
}
Else {
[String]$ProcessInfo = $process
New-Object -TypeName 'PSObject' -Property @{
ProcessName = $process
ProcessDescription = ''
}
}
}
}
## Check Deferral history and calculate remaining deferrals
[String]$deferDeadlineUniversal = $null
If (($allowDefer) -or ($AllowDeferCloseApps)) {
# Set $allowDefer to true if $AllowDeferCloseApps is true
$allowDefer = $true
# Get the deferral history from the registry
$deferHistory = Get-DeferHistory
$deferHistoryTimes = $deferHistory | Select-Object -ExpandProperty 'DeferTimesRemaining' -ErrorAction 'SilentlyContinue'
$deferHistoryDeadline = $deferHistory | Select-Object -ExpandProperty 'DeferDeadline' -ErrorAction 'SilentlyContinue'
# Reset Switches
$checkDeferDays = $false
$checkDeferDeadline = $false
If ($DeferDays -ne 0) {
$checkDeferDays = $true
}
If ($DeferDeadline) {
$checkDeferDeadline = $true
}
If ($DeferTimes -ne 0) {
If ($deferHistoryTimes -ge 0) {
Write-Log -Message "Defer history shows [$($deferHistory.DeferTimesRemaining)] deferrals remaining." -Source ${CmdletName}
$DeferTimes = $deferHistory.DeferTimesRemaining - 1
}
Else {
$DeferTimes = $DeferTimes - 1
}
Write-Log -Message "The user has [$deferTimes] deferrals remaining." -Source ${CmdletName}
If ($DeferTimes -lt 0) {
Write-Log -Message 'Deferral has expired.' -Source ${CmdletName}
$AllowDefer = $false
}
}
Else {
If (Test-Path -LiteralPath 'variable:deferTimes') {
Remove-Variable -Name 'deferTimes'
}
$DeferTimes = $null
}
If ($checkDeferDays -and $allowDefer) {
If ($deferHistoryDeadline) {
Write-Log -Message "Defer history shows a deadline date of [$deferHistoryDeadline]." -Source ${CmdletName}
[String]$deferDeadlineUniversal = Get-UniversalDate -DateTime $deferHistoryDeadline
}
Else {
[String]$deferDeadlineUniversal = Get-UniversalDate -DateTime (Get-Date -Date ((Get-Date).AddDays($deferDays)) -Format ($culture).DateTimeFormat.UniversalDateTimePattern).ToString()
}
Write-Log -Message "The user has until [$deferDeadlineUniversal] before deferral expires." -Source ${CmdletName}
If ((Get-UniversalDate) -gt $deferDeadlineUniversal) {
Write-Log -Message 'Deferral has expired.' -Source ${CmdletName}
$AllowDefer = $false
}
}
If ($checkDeferDeadline -and $allowDefer) {
# Validate Date
Try {
[String]$deferDeadlineUniversal = Get-UniversalDate -DateTime $deferDeadline -ErrorAction 'Stop'
}
Catch {
Write-Log -Message "Date is not in the correct format for the current culture. Type the date in the current locale format, such as 20/08/2014 (Europe) or 08/20/2014 (United States). If the script is intended for multiple cultures, specify the date in the universal sortable date/time format, e.g. '2013-08-22 11:51:52Z'. `r`n$(Resolve-Error)" -Severity 3 -Source ${CmdletName}
Throw "Date is not in the correct format for the current culture. Type the date in the current locale format, such as 20/08/2014 (Europe) or 08/20/2014 (United States). If the script is intended for multiple cultures, specify the date in the universal sortable date/time format, e.g. '2013-08-22 11:51:52Z': $($_.Exception.Message)"
}
Write-Log -Message "The user has until [$deferDeadlineUniversal] remaining." -Source ${CmdletName}
If ((Get-UniversalDate) -gt $deferDeadlineUniversal) {
Write-Log -Message 'Deferral has expired.' -Source ${CmdletName}
$AllowDefer = $false
}
}
}
If (($deferTimes -lt 0) -and (-not $deferDeadlineUniversal)) {
$AllowDefer = $false
}
## Prompt the user to close running applications and optionally defer if enabled
If ((-not $deployModeSilent) -and (-not $silent)) {
If ($forceCloseAppsCountdown -gt 0) {
# Keep the same variable for countdown to simplify the code:
$closeAppsCountdown = $forceCloseAppsCountdown
# Change this variable to a boolean now to switch the countdown on even with deferral
[Boolean]$forceCloseAppsCountdown = $true
}
ElseIf ($forceCountdown -gt 0) {
# Keep the same variable for countdown to simplify the code:
$closeAppsCountdown = $forceCountdown
# Change this variable to a boolean now to switch the countdown on
[Boolean]$forceCountdown = $true
}
Set-Variable -Name 'closeAppsCountdownGlobal' -Value $closeAppsCountdown -Scope 'Script'
$promptResult = $null
While ((Get-RunningProcesses -ProcessObjects $processObjects -OutVariable 'runningProcesses') -or (($promptResult -ne 'Defer') -and ($promptResult -ne 'Close'))) {
[String]$runningProcessDescriptions = ($runningProcesses | Select-Object -ExpandProperty 'ProcessDescription' -ErrorAction SilentlyContinue | Sort-Object -Unique) -join ','
# Check if we need to prompt the user to defer, to defer and close apps, or not to prompt them at all
If ($allowDefer) {
# If there is deferral and closing apps is allowed but there are no apps to be closed, break the while loop
If ($AllowDeferCloseApps -and (-not $runningProcessDescriptions)) {
Break
}
# Otherwise, as long as the user has not selected to close the apps or the processes are still running and the user has not selected to continue, prompt user to close running processes with deferral
ElseIf (($promptResult -ne 'Close') -or (($runningProcessDescriptions) -and ($promptResult -ne 'Continue'))) {
[String]$promptResult = Show-WelcomePromptIntune -ProcessDescriptions $runningProcessDescriptions -CloseAppsCountdown $closeAppsCountdownGlobal -ForceCloseAppsCountdown $forceCloseAppsCountdown -ForceCountdown $forceCountdown -PersistPrompt $PersistPrompt -AllowDefer -DeferTimes $deferTimes -DeferDeadline $deferDeadlineUniversal -MinimizeWindows $MinimizeWindows -CustomText:$CustomText -TopMost $TopMost
}
}
# If there is no deferral and processes are running, prompt the user to close running processes with no deferral option
ElseIf (($runningProcessDescriptions) -or ($forceCountdown)) {
[String]$promptResult = Show-WelcomePromptIntune -ProcessDescriptions $runningProcessDescriptions -CloseAppsCountdown $closeAppsCountdownGlobal -ForceCloseAppsCountdown $forceCloseAppsCountdown -ForceCountdown $forceCountdown -PersistPrompt $PersistPrompt -MinimizeWindows $minimizeWindows -CustomText:$CustomText -TopMost $TopMost
}
# If there is no deferral and no processes running, break the while loop
Else {
Break
}
# If the user has clicked OK, wait a few seconds for the process to terminate before evaluating the running processes again
If ($promptResult -eq 'Continue') {
Write-Log -Message 'The user selected to continue...' -Source ${CmdletName}
Start-Sleep -Seconds 2
# Break the while loop if there are no processes to close and the user has clicked OK to continue
If (-not $runningProcesses) {
Break
}
}
# Force the applications to close
ElseIf ($promptResult -eq 'Close') {
Write-Log -Message 'The user selected to force the application(s) to close...' -Source ${CmdletName}
If (($PromptToSave) -and ($SessionZero -and (-not $IsProcessUserInteractive))) {
Write-Log -Message 'Specified [-PromptToSave] option will not be available, because current process is running in session zero and is not interactive.' -Severity 2 -Source ${CmdletName}
}
# Update the process list right before closing, in case it changed
$runningProcesses = Get-RunningProcesses -ProcessObjects $processObjects
# Close running processes
ForEach ($runningProcess in $runningProcesses) {
[PSObject[]]$AllOpenWindowsForRunningProcess = Get-WindowTitle -GetAllWindowTitles -DisableFunctionLogging | Where-Object { $_.ParentProcess -eq $runningProcess.ProcessName }
# If the PromptToSave parameter was specified and the process has a window open, then prompt the user to save work if there is work to be saved when closing window
If (($PromptToSave) -and (-not ($SessionZero -and (-not $IsProcessUserInteractive))) -and ($AllOpenWindowsForRunningProcess) -and ($runningProcess.MainWindowHandle -ne [IntPtr]::Zero)) {
[Timespan]$PromptToSaveTimeout = New-TimeSpan -Seconds $configInstallationPromptToSave
[Diagnostics.StopWatch]$PromptToSaveStopWatch = [Diagnostics.StopWatch]::StartNew()
$PromptToSaveStopWatch.Reset()
ForEach ($OpenWindow in $AllOpenWindowsForRunningProcess) {
Try {
Write-Log -Message "Stopping process [$($runningProcess.ProcessName)] with window title [$($OpenWindow.WindowTitle)] and prompt to save if there is work to be saved (timeout in [$configInstallationPromptToSave] seconds)..." -Source ${CmdletName}
[Boolean]$IsBringWindowToFrontSuccess = [PSADT.UiAutomation]::BringWindowToFront($OpenWindow.WindowHandle)
[Boolean]$IsCloseWindowCallSuccess = $runningProcess.CloseMainWindow()
If (-not $IsCloseWindowCallSuccess) {
Write-Log -Message "Failed to call the CloseMainWindow() method on process [$($runningProcess.ProcessName)] with window title [$($OpenWindow.WindowTitle)] because the main window may be disabled due to a modal dialog being shown." -Severity 3 -Source ${CmdletName}
}
Else {
$PromptToSaveStopWatch.Start()
Do {
[Boolean]$IsWindowOpen = [Boolean](Get-WindowTitle -GetAllWindowTitles -DisableFunctionLogging | Where-Object { $_.WindowHandle -eq $OpenWindow.WindowHandle })
If (-not $IsWindowOpen) {
Break
}
Start-Sleep -Seconds 3
} While (($IsWindowOpen) -and ($PromptToSaveStopWatch.Elapsed -lt $PromptToSaveTimeout))
$PromptToSaveStopWatch.Reset()
If ($IsWindowOpen) {
Write-Log -Message "Exceeded the [$configInstallationPromptToSave] seconds timeout value for the user to save work associated with process [$($runningProcess.ProcessName)] with window title [$($OpenWindow.WindowTitle)]." -Severity 2 -Source ${CmdletName}
}
Else {
Write-Log -Message "Window [$($OpenWindow.WindowTitle)] for process [$($runningProcess.ProcessName)] was successfully closed." -Source ${CmdletName}
}
}
}
Catch {
Write-Log -Message "Failed to close window [$($OpenWindow.WindowTitle)] for process [$($runningProcess.ProcessName)]. `r`n$(Resolve-Error)" -Severity 3 -Source ${CmdletName}
Continue
}
Finally {
$runningProcess.Refresh()
}
}
}
Else {
Write-Log -Message "Stopping process $($runningProcess.ProcessName)..." -Source ${CmdletName}
Stop-Process -Name $runningProcess.ProcessName -Force -ErrorAction 'SilentlyContinue'
}
}
If ($runningProcesses = Get-RunningProcesses -ProcessObjects $processObjects -DisableLogging) {
# Apps are still running, give them 2s to close. If they are still running, the Welcome Window will be displayed again
Write-Log -Message 'Sleeping for 2 seconds because the processes are still not closed...' -Source ${CmdletName}
Start-Sleep -Seconds 2
}
}
# Stop the script (if not actioned before the timeout value)
ElseIf ($promptResult -eq 'Timeout') {
Write-Log -Message 'Installation not actioned before the timeout value.' -Source ${CmdletName}
$BlockExecution = $false
If (($deferTimes -ge 0) -or ($deferDeadlineUniversal)) {
Set-DeferHistory -DeferTimesRemaining $DeferTimes -DeferDeadline $deferDeadlineUniversal
}
## Dispose the welcome prompt timer here because if we dispose it within the Show-WelcomePromptIntune function we risk resetting the timer and missing the specified timeout period
If ($script:welcomeTimer) {
Try {
$script:welcomeTimer.Dispose()
$script:welcomeTimer = $null
}
Catch {
}
}
# Restore minimized windows
$null = $shellApp.UndoMinimizeAll()
Exit-Script -ExitCode $configInstallationUIExitCode
}
# Stop the script (user chose to defer)
ElseIf ($promptResult -eq 'Defer') {
Write-Log -Message 'Installation deferred by the user.' -Source ${CmdletName}
$BlockExecution = $false
Set-DeferHistory -DeferTimesRemaining $DeferTimes -DeferDeadline $deferDeadlineUniversal
# Restore minimized windows
$null = $shellApp.UndoMinimizeAll()
Exit-Script -ExitCode $configInstallationDeferExitCode
}
}
}
## Force the processes to close silently, without prompting the user
If (($Silent -or $deployModeSilent) -and $CloseApps) {
[Array]$runningProcesses = $null
[Array]$runningProcesses = Get-RunningProcesses $processObjects
If ($runningProcesses) {
[String]$runningProcessDescriptions = ($runningProcesses | Where-Object { $_.ProcessDescription } | Select-Object -ExpandProperty 'ProcessDescription' | Sort-Object -Unique) -join ','
Write-Log -Message "Force closing application(s) [$($runningProcessDescriptions)] without prompting user." -Source ${CmdletName}
$runningProcesses.ProcessName | ForEach-Object -Process { Stop-Process -Name $_ -Force -ErrorAction 'SilentlyContinue' }
Start-Sleep -Seconds 2
}
}
## Force nsd.exe to stop if Notes is one of the required applications to close
If (($processObjects | Select-Object -ExpandProperty 'ProcessName') -contains 'notes') {
## Get the path where Notes is installed
[String]$notesPath = Get-Item -LiteralPath $regKeyLotusNotes -ErrorAction 'SilentlyContinue' | Get-ItemProperty | Select-Object -ExpandProperty 'Path'
## Ensure we aren't running as a Local System Account and Notes install directory was found
If ((-not $IsLocalSystemAccount) -and ($notesPath)) {
# Get a list of all the executables in the Notes folder
[string[]]$notesPathExes = Get-ChildItem -LiteralPath $notesPath -Filter '*.exe' -Recurse | Select-Object -ExpandProperty 'BaseName' | Sort-Object
## Check for running Notes executables and run NSD if any are found
$notesPathExes | ForEach-Object {
If ((Get-Process | Select-Object -ExpandProperty 'Name') -contains $_) {
[String]$notesNSDExecutable = Join-Path -Path $notesPath -ChildPath 'NSD.exe'
Try {
If (Test-Path -LiteralPath $notesNSDExecutable -PathType 'Leaf' -ErrorAction 'Stop') {
Write-Log -Message "Executing [$notesNSDExecutable] with the -kill argument..." -Source ${CmdletName}
[Diagnostics.Process]$notesNSDProcess = Start-Process -FilePath $notesNSDExecutable -ArgumentList '-kill' -WindowStyle 'Hidden' -PassThru -ErrorAction 'SilentlyContinue'
If (-not $notesNSDProcess.WaitForExit(10000)) {
Write-Log -Message "[$notesNSDExecutable] did not end in a timely manner. Force terminate process." -Source ${CmdletName}
Stop-Process -Name 'NSD' -Force -ErrorAction 'SilentlyContinue'
}
}
}
Catch {
Write-Log -Message "Failed to launch [$notesNSDExecutable]. `r`n$(Resolve-Error)" -Source ${CmdletName}
}
Write-Log -Message "[$notesNSDExecutable] returned exit code [$($notesNSDProcess.ExitCode)]." -Source ${CmdletName}
# Force NSD process to stop in case the previous command was not successful
Stop-Process -Name 'NSD' -Force -ErrorAction 'SilentlyContinue'
}
}
}
# Strip all Notes processes from the process list except notes.exe, because the other notes processes (e.g. notes2.exe) may be invoked by the Notes installation, so we don't want to block their execution.
If ($notesPathExes) {
[Array]$processesIgnoringNotesExceptions = Compare-Object -ReferenceObject ($processObjects | Select-Object -ExpandProperty 'ProcessName' | Sort-Object) -DifferenceObject $notesPathExes -IncludeEqual | Where-Object { ($_.SideIndicator -eq '<=') -or ($_.InputObject -eq 'notes') } | Select-Object -ExpandProperty 'InputObject'
[Array]$processObjects = $processObjects | Where-Object { $processesIgnoringNotesExceptions -contains $_.ProcessName }
}
}
## If block execution switch is true, call the function to block execution of these processes
If ($BlockExecution) {
# Make this variable globally available so we can check whether we need to call Unblock-AppExecution
Set-Variable -Name 'BlockExecution' -Value $BlockExecution -Scope 'Script'
Write-Log -Message '[-BlockExecution] parameter specified.' -Source ${CmdletName}
Block-AppExecution -ProcessName ($processObjects | Select-Object -ExpandProperty 'ProcessName')
}
}
End {
Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer
}
}
#endregion
#region Function Show-WelcomePromptIntune
Function Show-WelcomePromptIntune {
<#
.SYNOPSIS
Called by Show-InstallationWelcome to prompt the user to optionally do the following:
1) Close the specified running applications.
2) Provide an option to defer the installation.
3) Show a countdown before applications are automatically closed.
.DESCRIPTION
The user is presented with a Windows Forms dialog box to close the applications themselves and continue or to have the script close the applications for them.
If the -AllowDefer option is set to true, an optional "Defer" button will be shown to the user. If they select this option, the script will exit and return a 1618 code (SCCM fast retry code).
The dialog box will timeout after the timeout specified in the XML configuration file (default 1 hour and 55 minutes) to prevent SCCM installations from timing out and returning a failure code to SCCM. When the dialog times out, the script will exit and return a 1618 code (SCCM fast retry code).
.PARAMETER ProcessDescriptions
The descriptive names of the applications that are running and need to be closed.
.PARAMETER CloseAppsCountdown
Specify the countdown time in seconds before running applications are automatically closed when deferral is not allowed or expired.
.PARAMETER ForceCloseAppsCountdown
Specify whether to show the countdown regardless of whether deferral is allowed.
.PARAMETER PersistPrompt
Specify whether to make the prompt persist in the center of the screen every couple of seconds, specified in the AppDeployToolkitConfig.xml.
.PARAMETER AllowDefer !!! DO NOT USE DEFER WITH INTUNE. THAT DOES NOT WORK !!!
Specify whether to provide an option to defer the installation.
.PARAMETER DeferTimes !!! DO NOT USE DEFER WITH INTUNE. THAT DOES NOT WORK !!!
Specify the number of times the user is allowed to defer.
.PARAMETER DeferDeadline !!! DO NOT USE DEFER WITH INTUNE. THAT DOES NOT WORK !!!
Specify the deadline date before the user is allowed to defer.
.PARAMETER MinimizeWindows
Specifies whether to minimize other windows when displaying prompt. Default: $true.
.PARAMETER TopMost
Specifies whether the windows is the topmost window. Default: $true.
.PARAMETER ForceCountdown
Specify a countdown to display before automatically proceeding with the installation when a deferral is enabled.
.PARAMETER CustomText
Specify whether to display a custom message specified in the XML file. Custom message must be populated for each language section in the XML.
.INPUTS
None
You cannot pipe objects to this function.
.OUTPUTS
System.String
Returns the user's selection.
.EXAMPLE
Show-WelcomePromptIntune -ProcessDescriptions 'Lotus Notes, Microsoft Word' -CloseAppsCountdown 600
.NOTES
This is an internal script function and should typically not be called directly. It is used by the Show-InstallationWelcome prompt to display a custom prompt.
.LINK
https://psappdeploytoolkit.com
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory = $false)]
[String]$ProcessDescriptions,
[Parameter(Mandatory = $false)]
[Int32]$CloseAppsCountdown,
[Parameter(Mandatory = $false)]
[ValidateNotNullorEmpty()]
[Boolean]$ForceCloseAppsCountdown,
[Parameter(Mandatory = $false)]
[ValidateNotNullorEmpty()]
[Boolean]$PersistPrompt = $false,
[Parameter(Mandatory = $false)]
[Switch]$AllowDefer = $false,
[Parameter(Mandatory = $false)]
[String]$DeferTimes,
[Parameter(Mandatory = $false)]
[String]$DeferDeadline,
[Parameter(Mandatory = $false)]
[ValidateNotNullorEmpty()]
[Boolean]$MinimizeWindows = $true,
[Parameter(Mandatory = $false)]
[ValidateNotNullorEmpty()]
[Boolean]$TopMost = $true,
[Parameter(Mandatory = $false)]
[ValidateNotNullorEmpty()]
[Int32]$ForceCountdown = 0,
[Parameter(Mandatory = $false)]
[Switch]$CustomText = $false
)
Begin {
## Get the name of this function and write header
[String]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header
$showCountdown = $false
}
Process {
## Reset switches
[Boolean]$showCloseApps = $false
[Boolean]$showDefer = $false
[Boolean]$persistWindow = $false
## Reset times
[DateTime]$startTime = Get-Date
[DateTime]$countdownTime = $startTime
## Check if the countdown was specified
If ($CloseAppsCountdown -and ($CloseAppsCountdown -gt $configInstallationUITimeout)) {
Throw 'The close applications countdown time cannot be longer than the timeout specified in the XML configuration for installation UI dialogs to timeout.'
}
## Initial form layout: Close Applications / Allow Deferral
If ($processDescriptions) {
Write-Log -Message "Prompting the user to close application(s) [$processDescriptions]..." -Source ${CmdletName}
$showCloseApps = $true
}
If (($allowDefer) -and (($deferTimes -ge 0) -or ($deferDeadline))) {
Write-Log -Message 'The user has the option to defer.' -Source ${CmdletName}
$showDefer = $true
If ($deferDeadline) {
# Remove the Z from universal sortable date time format, otherwise it could be converted to a different time zone
$deferDeadline = $deferDeadline -replace 'Z', ''
# Convert the deadline date to a string
[String]$deferDeadline = (Get-Date -Date $deferDeadline).ToString()
}
}
## If deferral is being shown and 'close apps countdown' or 'persist prompt' was specified, enable those features.
If (-not $showDefer) {
If ($closeAppsCountdown -gt 0) {
Write-Log -Message "Close applications countdown has [$closeAppsCountdown] seconds remaining." -Source ${CmdletName}
$showCountdown = $true
}
}
Else {
If ($persistPrompt) {
$persistWindow = $true
}
}
## If 'force close apps countdown' was specified, enable that feature.
If ($forceCloseAppsCountdown -eq $true) {
Write-Log -Message "Close applications countdown has [$closeAppsCountdown] seconds remaining." -Source ${CmdletName}
$showCountdown = $true
}
## If 'force countdown' was specified, enable that feature.
If ($forceCountdown -eq $true) {
Write-Log -Message "Countdown has [$closeAppsCountdown] seconds remaining." -Source ${CmdletName}
$showCountdown = $true
}
[String[]]$processDescriptions = $processDescriptions -split ','
[Windows.Forms.Application]::EnableVisualStyles()
$formWelcome = New-Object -TypeName 'System.Windows.Forms.Form'
$formWelcome.SuspendLayout()
$pictureBanner = New-Object -TypeName 'System.Windows.Forms.PictureBox'
$labelWelcomeMessage = New-Object -TypeName 'System.Windows.Forms.Label'
$labelAppName = New-Object -TypeName 'System.Windows.Forms.Label'
$labelCustomMessage = New-Object -TypeName 'System.Windows.Forms.Label'
$labelCloseAppsMessage = New-Object -TypeName 'System.Windows.Forms.Label'
$labelCountdownMessage = New-Object -TypeName 'System.Windows.Forms.Label'
$labelCountdown = New-Object -TypeName 'System.Windows.Forms.Label'
$labelDeferExpiryMessage = New-Object -TypeName 'System.Windows.Forms.Label'
$labelDeferDeadline = New-Object -TypeName 'System.Windows.Forms.Label'
$labelDeferWarningMessage = New-Object -TypeName 'System.Windows.Forms.Label'
$listBoxCloseApps = New-Object -TypeName 'System.Windows.Forms.ListBox'
$buttonContinue = New-Object -TypeName 'System.Windows.Forms.Button'
$buttonDefer = New-Object -TypeName 'System.Windows.Forms.Button'
$buttonCloseApps = New-Object -TypeName 'System.Windows.Forms.Button'
$buttonAbort = New-Object -TypeName 'System.Windows.Forms.Button'
$buttonRemindMeLater = New-Object -TypeName 'System.Windows.Forms.Button' #RemindMeLater
$flowLayoutPanel = New-Object -TypeName 'System.Windows.Forms.FlowLayoutPanel'
$panelButtons = New-Object -TypeName 'System.Windows.Forms.Panel'
$toolTip = New-Object -TypeName 'System.Windows.Forms.ToolTip'
## Remove all event handlers from the controls
[ScriptBlock]$Welcome_Form_Cleanup_FormClosed = {
Try {
$script:welcomeTimer.remove_Tick($welcomeTimer_Tick)
$welcomeTimerPersist.remove_Tick($welcomeTimerPersist_Tick)
$timerRunningProcesses.remove_Tick($timerRunningProcesses_Tick)
$formWelcome.remove_Load($Welcome_Form_StateCorrection_Load)
$formWelcome.remove_FormClosed($Welcome_Form_Cleanup_FormClosed)
$buttonRemindMeLater.remove_Click($buttonRemindMeLater_OnClick) #RemindMeLater
}
Catch {
}
}
[ScriptBlock]$Welcome_Form_StateCorrection_Load = {
# Disable the X button
Try {
$windowHandle = $formWelcome.Handle
If ($windowHandle -and ($windowHandle -ne [IntPtr]::Zero)) {
$menuHandle = [PSADT.UiAutomation]::GetSystemMenu($windowHandle, $false)
If ($menuHandle -and ($menuHandle -ne [IntPtr]::Zero)) {
[PSADT.UiAutomation]::EnableMenuItem($menuHandle, 0xF060, 0x00000001)
[PSADT.UiAutomation]::DestroyMenu($menuHandle)
}
}
}
Catch {
# Not a terminating error if we can't disable the button. Just disable the Control Box instead
Write-Log 'Failed to disable the Close button. Disabling the Control Box instead.' -Severity 2 -Source ${CmdletName}
$formWelcome.ControlBox = $false
}
# Get the start position of the form so we can return the form to this position if PersistPrompt is enabled
Set-Variable -Name 'formWelcomeStartPosition' -Value $formWelcome.Location -Scope 'Script'
## Initialize the countdown timer
[DateTime]$currentTime = Get-Date
[DateTime]$countdownTime = $startTime.AddSeconds($CloseAppsCountdown)
$script:welcomeTimer.Start()
## Set up the form
[Timespan]$remainingTime = $countdownTime.Subtract($currentTime)
$labelCountdown.Text = [String]::Format('{0}:{1:d2}:{2:d2}', $remainingTime.Days * 24 + $remainingTime.Hours, $remainingTime.Minutes, $remainingTime.Seconds)
}
## Add the timer if it doesn't already exist - this avoids the timer being reset if the continue button is clicked
If (!(Test-Path -LiteralPath 'variable:welcomeTimer')) {
$script:welcomeTimer = New-Object -TypeName 'System.Windows.Forms.Timer'
}
If ($showCountdown) {
[ScriptBlock]$welcomeTimer_Tick = {
## Get the time information
[DateTime]$currentTime = Get-Date
[DateTime]$countdownTime = $startTime.AddSeconds($CloseAppsCountdown)
[Timespan]$remainingTime = $countdownTime.Subtract($currentTime)
Set-Variable -Name 'closeAppsCountdownGlobal' -Value $remainingTime.TotalSeconds -Scope 'Script'
## If the countdown is complete, close the application(s) or continue
If ($countdownTime -le $currentTime) {
If ($forceCountdown -eq $true) {
Write-Log -Message 'Countdown timer has elapsed. Force continue.' -Source ${CmdletName}
$buttonContinue.PerformClick()
}
Else {
Write-Log -Message 'Close application(s) countdown timer has elapsed. Force closing application(s).' -Source ${CmdletName}
If ($buttonCloseApps.CanFocus) {
$buttonCloseApps.PerformClick()
}
Else {
$buttonContinue.PerformClick()
}
}
}
Else {
# Update the form
$labelCountdown.Text = [String]::Format('{0}:{1:d2}:{2:d2}', $remainingTime.Days * 24 + $remainingTime.Hours, $remainingTime.Minutes, $remainingTime.Seconds)
if ($labelCountdown.Text -eq "1:00:00") #RemindMeLater
{
$formWelcome.WindowState = 'Normal'
$formWelcome.BringToFront()
$formWelcome.Refresh()
}
if ($labelCountdown.Text -eq "0:30:00") #RemindMeLater
{
$formWelcome.WindowState = 'Normal'
$formWelcome.BringToFront()
$formWelcome.Refresh()
}
if ($labelCountdown.Text -eq "0:10:00") #RemindMeLater
{
$formWelcome.Location = "$($formWelcomeStartPosition.X),$($formWelcomeStartPosition.Y)"
$labelCountdown.ForeColor = 'Red'
$buttonRemindMeLater.Enabled = $false
$formWelcome.BringToFront()
$formWelcome.WindowState = 'Normal'
$formWelcome.TopMost = $true
$formWelcome.BringToFront()
$formWelcome.Refresh()
}
if (($labelCountdown.Text -gt "0:00:00") -and ($labelCountdown.Text -lt "0:10:00")) #RemindMeLater
{
$labelCountdown.ForeColor = 'Red'
$buttonRemindMeLater.Enabled = $false
If ($labelCountdown.Text[-1] -eq "5") {
$formWelcome.WindowState = 'Normal'
$formWelcome.TopMost = $True
$formWelcome.BringToFront()
$formWelcome.Location = "$($formWelcomeStartPosition.X),$($formWelcomeStartPosition.Y)"
}
$formWelcome.Refresh()
}
}
}
}
Else {
$script:welcomeTimer.Interval = ($configInstallationUITimeout * 1000)
[ScriptBlock]$welcomeTimer_Tick = { $buttonAbort.PerformClick() }
}
$script:welcomeTimer.add_Tick($welcomeTimer_Tick)
## Persistence Timer
If ($persistWindow) {
$welcomeTimerPersist = New-Object -TypeName 'System.Windows.Forms.Timer'
$welcomeTimerPersist.Interval = ($configInstallationPersistInterval * 1000)
[ScriptBlock]$welcomeTimerPersist_Tick = {
$formWelcome.WindowState = 'Normal'
$formWelcome.TopMost = $TopMost
$formWelcome.BringToFront()
$formWelcome.Location = "$($formWelcomeStartPosition.X),$($formWelcomeStartPosition.Y)"
}
$welcomeTimerPersist.add_Tick($welcomeTimerPersist_Tick)
$welcomeTimerPersist.Start()
}
[scriptblock]$buttonRemindMeLater_OnClick = {
## Minimize the form
$shellApp.UndoMinimizeAll() | Out-Null
$formWelcome.WindowState = 'Minimized'
}
## Process Re-Enumeration Timer
If ($configInstallationWelcomePromptDynamicRunningProcessEvaluation) {
$timerRunningProcesses = New-Object -TypeName 'System.Windows.Forms.Timer'
$timerRunningProcesses.Interval = ($configInstallationWelcomePromptDynamicRunningProcessEvaluationInterval * 1000)
[ScriptBlock]$timerRunningProcesses_Tick = {
Try {
$dynamicRunningProcesses = $null
$dynamicRunningProcesses = Get-RunningProcesses -ProcessObjects $processObjects -DisableLogging
[String]$dynamicRunningProcessDescriptions = ($dynamicRunningProcesses | Where-Object { $_.ProcessDescription } | Select-Object -ExpandProperty 'ProcessDescription' | Sort-Object -Unique) -join ','
If ($dynamicRunningProcessDescriptions -ne $script:runningProcessDescriptions) {
# Update the runningProcessDescriptions variable for the next time this function runs
Set-Variable -Name 'runningProcessDescriptions' -Value $dynamicRunningProcessDescriptions -Force -Scope 'Script'
If ($dynamicRunningProcesses) {
Write-Log -Message "The running processes have changed. Updating the apps to close: [$script:runningProcessDescriptions]..." -Source ${CmdletName}
}
# Update the list box with the processes to close
$listboxCloseApps.Items.Clear()
$script:runningProcessDescriptions -split ',' | ForEach-Object { $null = $listboxCloseApps.Items.Add($_) }
}
# If CloseApps processes were running when the prompt was shown, and they are subsequently detected to be closed while the form is showing, then close the form. The deferral and CloseApps conditions will be re-evaluated.
If ($ProcessDescriptions) {
If (-not $dynamicRunningProcesses) {
Write-Log -Message 'Previously detected running processes are no longer running.' -Source ${CmdletName}
$formWelcome.Dispose()
}
}
# If CloseApps processes were not running when the prompt was shown, and they are subsequently detected to be running while the form is showing, then close the form for relaunch. The deferral and CloseApps conditions will be re-evaluated.
Else {
If ($dynamicRunningProcesses) {
Write-Log -Message 'New running processes detected. Updating the form to prompt to close the running applications.' -Source ${CmdletName}
$formWelcome.Dispose()
}
}
}
Catch {
}
}
$timerRunningProcesses.add_Tick($timerRunningProcesses_Tick)
$timerRunningProcesses.Start()
}
## Form
##----------------------------------------------
## Create zero px padding object
$paddingNone = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList (0, 0, 0, 0)
## Create basic control size
$defaultControlSize = New-Object -TypeName 'System.Drawing.Size' -ArgumentList (450, 0)
## Generic Button properties
$buttonSize = New-Object -TypeName 'System.Drawing.Size' -ArgumentList (130, 24)
$buttonSizeRemindMelater = New-Object -TypeName 'System.Drawing.Size' -ArgumentList (130, 24) ##### add
$buttonSizeCloseApps = New-Object -TypeName 'System.Drawing.Size' -ArgumentList (170, 24) ##### add
## Picture Banner
$pictureBanner.DataBindings.DefaultDataSourceUpdateMode = 0
$pictureBanner.ImageLocation = $appDeployLogoBanner
$System_Drawing_Point = New-Object -TypeName 'System.Drawing.Point' -ArgumentList (0, 0)
$pictureBanner.Location = $System_Drawing_Point
$pictureBanner.Name = 'pictureBanner'
$System_Drawing_Size = New-Object -TypeName 'System.Drawing.Size' -ArgumentList (450, $appDeployLogoBannerHeight)
$pictureBanner.ClientSize = $System_Drawing_Size
$pictureBanner.SizeMode = [System.Windows.Forms.PictureBoxSizeMode]::Zoom
$pictureBanner.Margin = $paddingNone
$pictureBanner.TabStop = $false
## Label Welcome Message
$labelWelcomeMessage.DataBindings.DefaultDataSourceUpdateMode = 0
$labelWelcomeMessage.Font = $defaultFont
$labelWelcomeMessage.Name = 'labelWelcomeMessage'
$labelWelcomeMessage.ClientSize = $defaultControlSize
$labelWelcomeMessage.MinimumSize = $defaultControlSize
$labelWelcomeMessage.MaximumSize = $defaultControlSize
$labelWelcomeMessage.Margin = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList (0, 10, 0, 0)
$labelWelcomeMessage.Padding = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList (10, 0, 10, 0)
$labelWelcomeMessage.TabStop = $false
$labelWelcomeMessage.Text = $configDeferPromptWelcomeMessage
$labelWelcomeMessage.TextAlign = 'MiddleCenter'
$labelWelcomeMessage.Anchor = 'Top'
$labelWelcomeMessage.AutoSize = $true
## Label App Name
$labelAppName.DataBindings.DefaultDataSourceUpdateMode = 0
$labelAppName.Font = New-Object -TypeName 'System.Drawing.Font' -ArgumentList ($defaultFont.Name, ($defaultFont.Size + 3), [System.Drawing.FontStyle]::Bold)
$labelAppName.Name = 'labelAppName'
$labelAppName.ClientSize = $defaultControlSize
$labelAppName.MinimumSize = $defaultControlSize
$labelAppName.MaximumSize = $defaultControlSize
$labelAppName.Margin = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList (0, 5, 0, 5)
$labelAppName.Padding = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList (10, 0, 10, 0)
$labelAppName.TabStop = $false
$labelAppName.Text = $installTitle
$labelAppName.TextAlign = 'MiddleCenter'
$labelAppName.Anchor = 'Top'
$labelAppName.AutoSize = $true
## Label CustomMessage
$labelCustomMessage.DataBindings.DefaultDataSourceUpdateMode = 0
$labelCustomMessage.Font = $defaultFont
$labelCustomMessage.Name = 'labelCustomMessage'
$labelCustomMessage.ClientSize = $defaultControlSize
$labelCustomMessage.MinimumSize = $defaultControlSize
$labelCustomMessage.MaximumSize = $defaultControlSize
$labelCustomMessage.Margin = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList (0, 0, 0, 5)
$labelCustomMessage.Padding = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList (10, 0, 10, 0)
$labelCustomMessage.TabStop = $false
$labelCustomMessage.Text = $configClosePromptMessage
$labelCustomMessage.TextAlign = 'MiddleCenter'
$labelCustomMessage.Anchor = 'Top'
$labelCustomMessage.AutoSize = $true
## Label CloseAppsMessage
$labelCloseAppsMessage.DataBindings.DefaultDataSourceUpdateMode = 0
$labelCloseAppsMessage.Font = $defaultFont
$labelCloseAppsMessage.Name = 'labelCloseAppsMessage'
$labelCloseAppsMessage.ClientSize = $defaultControlSize
$labelCloseAppsMessage.MinimumSize = $defaultControlSize
$labelCloseAppsMessage.MaximumSize = $defaultControlSize
$labelCloseAppsMessage.Margin = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList (0, 0, 0, 5)
$labelCloseAppsMessage.Padding = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList (10, 0, 10, 0)
$labelCloseAppsMessage.TabStop = $false
$labelCloseAppsMessage.Text = $configClosePromptMessage
$labelCloseAppsMessage.TextAlign = 'MiddleCenter'
$labelCloseAppsMessage.Anchor = 'Top'
$labelCloseAppsMessage.AutoSize = $true
## Listbox Close Applications
$listBoxCloseApps.DataBindings.DefaultDataSourceUpdateMode = 0
$listboxCloseApps.Font = $defaultFont
$listBoxCloseApps.FormattingEnabled = $true
$listBoxCloseApps.HorizontalScrollbar = $true
$listBoxCloseApps.Name = 'listBoxCloseApps'
$System_Drawing_Size = New-Object -TypeName 'System.Drawing.Size' -ArgumentList (420, 100)
$listBoxCloseApps.ClientSize = $System_Drawing_Size
$listBoxCloseApps.Margin = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList (15, 0, 15, 0)
$listBoxCloseApps.Padding = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList (10, 0, 10, 0)
$listBoxCloseApps.TabIndex = 3
$ProcessDescriptions | ForEach-Object { $null = $listboxCloseApps.Items.Add($_) }
## Label Defer Expiry Message
$labelDeferExpiryMessage.DataBindings.DefaultDataSourceUpdateMode = 0
$labelDeferExpiryMessage.Font = $defaultFont
$labelDeferExpiryMessage.Name = 'labelDeferExpiryMessage'
$labelDeferExpiryMessage.ClientSize = $defaultControlSize
$labelDeferExpiryMessage.MinimumSize = $defaultControlSize
$labelDeferExpiryMessage.MaximumSize = $defaultControlSize
$labelDeferExpiryMessage.Margin = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList (0, 0, 0, 5)
$labelDeferExpiryMessage.Padding = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList (10, 0, 10, 0)
$labelDeferExpiryMessage.TabStop = $false
$labelDeferExpiryMessage.Text = $configDeferPromptExpiryMessage
$labelDeferExpiryMessage.TextAlign = 'MiddleCenter'
$labelDeferExpiryMessage.AutoSize = $true
## Label Defer Deadline
$labelDeferDeadline.DataBindings.DefaultDataSourceUpdateMode = 0
$labelDeferDeadline.Font = New-Object -TypeName 'System.Drawing.Font' -ArgumentList ($defaultFont.Name, $defaultFont.Size, [System.Drawing.FontStyle]::Bold)
$labelDeferDeadline.Name = 'labelDeferDeadline'
$labelDeferDeadline.ClientSize = $defaultControlSize
$labelDeferDeadline.MinimumSize = $defaultControlSize
$labelDeferDeadline.MaximumSize = $defaultControlSize
$labelDeferDeadline.Margin = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList (0, 0, 0, 5)
$labelDeferDeadline.Padding = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList (10, 0, 10, 0)
$labelDeferDeadline.TabStop = $false
If ($deferTimes -ge 0) {
$labelDeferDeadline.Text = "$configDeferPromptRemainingDeferrals $([Int32]$deferTimes + 1)"
}
If ($deferDeadline) {
$labelDeferDeadline.Text = "$configDeferPromptDeadline $deferDeadline"
}
If (($deferTimes -lt 0) -and (-not $DeferDeadline)) {
$labelDeferDeadline.Text = "$configDeferPromptNoDeadline"
}
$labelDeferDeadline.TextAlign = 'MiddleCenter'
$labelDeferDeadline.AutoSize = $true
## Label Defer Expiry Message
$labelDeferWarningMessage.DataBindings.DefaultDataSourceUpdateMode = 0
$labelDeferWarningMessage.Font = $defaultFont
$labelDeferWarningMessage.Name = 'labelDeferWarningMessage'
$labelDeferWarningMessage.ClientSize = $defaultControlSize
$labelDeferWarningMessage.MinimumSize = $defaultControlSize
$labelDeferWarningMessage.MaximumSize = $defaultControlSize
$labelDeferWarningMessage.Margin = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList (0, 0, 0, 5)
$labelDeferWarningMessage.Padding = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList (10, 0, 10, 0)
$labelDeferWarningMessage.TabStop = $false
$labelDeferWarningMessage.Text = $configDeferPromptWarningMessage
$labelDeferWarningMessage.TextAlign = 'MiddleCenter'
$labelDeferWarningMessage.AutoSize = $true
## Label CountdownMessage
$labelCountdownMessage.DataBindings.DefaultDataSourceUpdateMode = 0
$labelCountdownMessage.Name = 'labelCountdownMessage'
$labelCountdownMessage.Font = New-Object -TypeName 'System.Drawing.Font' -ArgumentList ($defaultFont.Name, ($defaultFont.Size + 3), [System.Drawing.FontStyle]::Bold)
$labelCountdownMessage.ClientSize = $defaultControlSize
$labelCountdownMessage.MinimumSize = $defaultControlSize
$labelCountdownMessage.MaximumSize = $defaultControlSize
$labelCountdownMessage.Margin = $paddingNone
$labelCountdownMessage.Padding = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList (10, 0, 10, 0)
$labelCountdownMessage.TabStop = $false
If (($forceCountdown -eq $true) -or (-not $script:runningProcessDescriptions)) {
Switch ($deploymentType) {
'Uninstall' {
$labelCountdownMessage.Text = ($configWelcomePromptCountdownMessage -f $configDeploymentTypeUninstall); Break
}
'Repair' {
$labelCountdownMessage.Text = ($configWelcomePromptCountdownMessage -f $configDeploymentTypeRepair); Break
}
Default {
$labelCountdownMessage.Text = ($configWelcomePromptCountdownMessage -f $configDeploymentTypeInstall); Break
}
}
}
Else {
$labelCountdownMessage.Text = $configClosePromptCountdownMessage
}
$labelCountdownMessage.TextAlign = 'MiddleCenter'
$labelCountdownMessage.Anchor = 'Top'
$labelCountdownMessage.AutoSize = $true
## Label Countdown
$labelCountdown.DataBindings.DefaultDataSourceUpdateMode = 0
$labelCountdown.Name = 'labelCountdown'
$labelCountdown.Font = New-Object -TypeName 'System.Drawing.Font' -ArgumentList ($defaultFont.Name, ($defaultFont.Size + 9), [System.Drawing.FontStyle]::Bold)
$labelCountdown.ClientSize = $defaultControlSize
$labelCountdown.MinimumSize = $defaultControlSize
$labelCountdown.MaximumSize = $defaultControlSize
$labelCountdown.Margin = $paddingNone
$labelCountdown.Padding = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList (10, 0, 10, 0)
$labelCountdown.TabStop = $false
$labelCountdown.Text = '00:00:00'
$labelCountdown.TextAlign = 'MiddleCenter'
$labelCountdown.AutoSize = $true
## Panel Flow Layout
$System_Drawing_Point = New-Object -TypeName 'System.Drawing.Point' -ArgumentList (0, $appDeployLogoBannerHeight)
$flowLayoutPanel.Location = $System_Drawing_Point
$flowLayoutPanel.MinimumSize = $DefaultControlSize
$flowLayoutPanel.MaximumSize = $DefaultControlSize
$flowLayoutPanel.ClientSize = $DefaultControlSize
$flowLayoutPanel.Margin = $paddingNone
$flowLayoutPanel.Padding = $paddingNone
$flowLayoutPanel.AutoSizeMode = 'GrowAndShrink'
$flowLayoutPanel.AutoSize = $true
$flowLayoutPanel.Anchor = 'Top'
$flowLayoutPanel.FlowDirection = 'TopDown'
$flowLayoutPanel.WrapContents = $true
$flowLayoutPanel.Controls.Add($labelWelcomeMessage)
$flowLayoutPanel.Controls.Add($labelAppName)
If ($CustomText -and $configWelcomePromptCustomMessage) {
$labelCustomMessage.Text = $configWelcomePromptCustomMessage
$flowLayoutPanel.Controls.Add($labelCustomMessage)
}
If ($showCloseApps) {
$flowLayoutPanel.Controls.Add($labelCloseAppsMessage)
$flowLayoutPanel.Controls.Add($listBoxCloseApps)
}
If ($showDefer) {
$flowLayoutPanel.Controls.Add($labelDeferExpiryMessage)
$flowLayoutPanel.Controls.Add($labelDeferDeadline)
$flowLayoutPanel.Controls.Add($labelDeferWarningMessage)
}
If ($showCountdown) {
$flowLayoutPanel.Controls.Add($labelCountdownMessage)
$flowLayoutPanel.Controls.Add($labelCountdown)
}
## Button Close For Me
$buttonCloseApps.DataBindings.DefaultDataSourceUpdateMode = 0
$buttonCloseApps.Location = New-Object -TypeName 'System.Drawing.Point' -ArgumentList (14, 4)
$buttonCloseApps.Font = New-Object -TypeName 'System.Drawing.Font' -ArgumentList ($defaultFont.Name, ($defaultFont.Size - 0.5), [System.Drawing.FontStyle]::Regular)
$buttonCloseApps.Name = 'buttonCloseApps'
$buttonCloseApps.ClientSize = $buttonSizeCloseApps
$buttonCloseApps.MinimumSize = $buttonSizeCloseApps
$buttonCloseApps.MaximumSize = $buttonSizeCloseApps
$buttonCloseApps.TabIndex = 1
$buttonCloseApps.Text = $configClosePromptButtonClose
$buttonCloseApps.DialogResult = 'Yes'
$buttonCloseApps.AutoSize = $true
$buttonCloseApps.Margin = $paddingNone
$buttonCloseApps.Padding = $paddingNone
$buttonCloseApps.UseVisualStyleBackColor = $true
## Button Defer
$buttonDefer.DataBindings.DefaultDataSourceUpdateMode = 0
If (-not $showCloseApps) {
$buttonDefer.Location = New-Object -TypeName 'System.Drawing.Point' -ArgumentList (14, 4)
}
Else {
$buttonDefer.Location = New-Object -TypeName 'System.Drawing.Point' -ArgumentList (160, 4)
}
$buttonDefer.Name = 'buttonDefer'
$buttonDefer.Font = New-Object -TypeName 'System.Drawing.Font' -ArgumentList ($defaultFont.Name, ($defaultFont.Size - 0.5), [System.Drawing.FontStyle]::Regular)
$buttonDefer.ClientSize = $buttonSize
$buttonDefer.MinimumSize = $buttonSize
$buttonDefer.MaximumSize = $buttonSize
$buttonDefer.TabIndex = 0
$buttonDefer.Text = $configClosePromptButtonDefer
$buttonDefer.DialogResult = 'No'
$buttonDefer.AutoSize = $true
$buttonDefer.Margin = $paddingNone
$buttonDefer.Padding = $paddingNone
$buttonDefer.UseVisualStyleBackColor = $true
## Button Remind Me Later
[String]$configClosePromptButtonRemindMeLater = [String]::Join("`n", $xmlUIMessages.ClosePrompt_ButtonRemindMeLater.Split("`n").Trim())
$buttonRemindMeLater.DataBindings.DefaultDataSourceUpdateMode = 0
$buttonRemindMeLater.Location = New-Object -TypeName 'System.Drawing.Point' -ArgumentList (306, 4)
$buttonRemindMeLater.Name = 'buttonRemindMeLater'
$buttonRemindMeLater.Font = New-Object -TypeName 'System.Drawing.Font' -ArgumentList ($defaultFont.Name, ($defaultFont.Size - 0.5), [System.Drawing.FontStyle]::Regular)
$buttonRemindMeLater.ClientSize = $buttonSizeRemindMelater
$buttonRemindMeLater.MinimumSize = $buttonSizeRemindMelater
$buttonRemindMeLater.MaximumSize = $buttonSizeRemindMelater
$buttonRemindMeLater.TabIndex = 3
$buttonRemindMeLater.Text = $configClosePromptButtonRemindMeLater
#$buttonRemindMeLater.DialogResult = 'No'
$buttonRemindMeLater.AutoSize = $true
$buttonRemindMeLater.UseVisualStyleBackColor = $true
$buttonRemindMeLater.Margin = $paddingNone
$buttonRemindMeLater.Padding = $paddingNone
$buttonRemindMeLater.add_Click($buttonRemindMeLater_OnClick)
## Button Continue
$buttonContinue.DataBindings.DefaultDataSourceUpdateMode = 0
$buttonContinue.Location = New-Object -TypeName 'System.Drawing.Point' -ArgumentList (306, 4)
$buttonContinue.Name = 'buttonContinue'
$buttonContinue.Font = New-Object -TypeName 'System.Drawing.Font' -ArgumentList ($defaultFont.Name, ($defaultFont.Size - 0.5), [System.Drawing.FontStyle]::Regular)
$buttonContinue.ClientSize = $buttonSize
$buttonContinue.MinimumSize = $buttonSize
$buttonContinue.MaximumSize = $buttonSize
$buttonContinue.TabIndex = 2
$buttonContinue.Text = $configClosePromptButtonContinue
$buttonContinue.DialogResult = 'OK'
$buttonContinue.AutoSize = $true
$buttonContinue.Margin = $paddingNone
$buttonContinue.Padding = $paddingNone
$buttonContinue.UseVisualStyleBackColor = $true
If ($showCloseApps) {
# Add tooltip to Continue button
$toolTip.BackColor = [Drawing.Color]::LightGoldenrodYellow
$toolTip.IsBalloon = $false
$toolTip.InitialDelay = 100
$toolTip.ReshowDelay = 100
$toolTip.SetToolTip($buttonContinue, $configClosePromptButtonContinueTooltip)
}
## Button Abort (Hidden)
$buttonAbort.DataBindings.DefaultDataSourceUpdateMode = 0
$buttonAbort.Name = 'buttonAbort'
$buttonAbort.Font = New-Object -TypeName 'System.Drawing.Font' -ArgumentList ($defaultFont.Name, ($defaultFont.Size - 0.5), [System.Drawing.FontStyle]::Regular)
$buttonAbort.ClientSize = New-Object -TypeName 'System.Drawing.Size' -ArgumentList (0, 0)
$buttonAbort.MinimumSize = New-Object -TypeName 'System.Drawing.Size' -ArgumentList (0, 0)
$buttonAbort.MaximumSize = New-Object -TypeName 'System.Drawing.Size' -ArgumentList (0, 0)
$buttonAbort.BackColor = [System.Drawing.Color]::Transparent
$buttonAbort.ForeColor = [System.Drawing.Color]::Transparent
$buttonAbort.FlatAppearance.BorderSize = 0
$buttonAbort.FlatAppearance.MouseDownBackColor = [System.Drawing.Color]::Transparent
$buttonAbort.FlatAppearance.MouseOverBackColor = [System.Drawing.Color]::Transparent
$buttonAbort.FlatStyle = [System.Windows.Forms.FlatStyle]::System
$buttonAbort.TabStop = $false
$buttonAbort.DialogResult = 'Abort'
$buttonAbort.Visible = $true # Has to be set visible so we can call Click on it
$buttonAbort.Margin = $paddingNone
$buttonAbort.Padding = $paddingNone
$buttonAbort.UseVisualStyleBackColor = $true
## Form Welcome
$formWelcome.ClientSize = $defaultControlSize
$formWelcome.Padding = $paddingNone
$formWelcome.Margin = $paddingNone
$formWelcome.DataBindings.DefaultDataSourceUpdateMode = 0
$formWelcome.Name = 'WelcomeForm'
$formWelcome.Text = $installTitle
$formWelcome.StartPosition = 'CenterScreen'
$formWelcome.FormBorderStyle = 'Fixed3D'
$formWelcome.MaximizeBox = $false
$formWelcome.MinimizeBox = $false
$formWelcome.TopMost = $TopMost
$formWelcome.TopLevel = $true
$formWelcome.Icon = New-Object -TypeName 'System.Drawing.Icon' -ArgumentList ($AppDeployLogoIcon)
$formWelcome.AutoSize = $true
$formWelcome.AutoScaleMode = [System.Windows.Forms.AutoScaleMode]::Dpi
$formWelcome.AutoScaleDimensions = New-Object System.Drawing.SizeF(96,96)
$formWelcome.Controls.Add($pictureBanner)
$formWelcome.Controls.Add($buttonAbort)
## Panel Button
$panelButtons.MinimumSize = New-Object -TypeName 'System.Drawing.Size' -ArgumentList (450, 39)
$panelButtons.ClientSize = New-Object -TypeName 'System.Drawing.Size' -ArgumentList (450, 39)
$panelButtons.MaximumSize = New-Object -TypeName 'System.Drawing.Size' -ArgumentList (450, 39)
$panelButtons.AutoSize = $true
$panelButtons.Padding = $paddingNone
$panelButtons.Margin = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList (0, 10, 0, 0)
If ($showCloseApps) {
$panelButtons.Controls.Add($buttonCloseApps)
}
If ($showDefer) {
$panelButtons.Controls.Add($buttonDefer)
}
#$panelButtons.Controls.Add($buttonContinue) ##### removed
$panelButtons.Controls.Add($buttonRemindMeLater) ##### add
## Add the Buttons Panel to the flowPanel
$flowLayoutPanel.Controls.Add($panelButtons)
## Add FlowPanel to the form
$formWelcome.Controls.Add($flowLayoutPanel)
# Init the OnLoad event to correct the initial state of the form
$formWelcome.add_Load($Welcome_Form_StateCorrection_Load)
# Clean up the control events
$formWelcome.add_FormClosed($Welcome_Form_Cleanup_FormClosed)
## Minimize all other windows
If ($minimizeWindows) {
$null = $shellApp.MinimizeAll()
}
## Show the form
$formWelcome.ResumeLayout()
$result = $formWelcome.ShowDialog()
$formWelcome.Dispose()
Switch ($result) {
OK {
$result = 'Continue'
}
No {
$result = 'Defer'
}
Yes {
$result = 'Close'
}
Abort {
$result = 'Timeout'
}
}
If ($configInstallationWelcomePromptDynamicRunningProcessEvaluation) {
$timerRunningProcesses.Stop()
}
Write-Output -InputObject ($result)
}
End {
Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer
}
}
#endregion
#region Function Configure-ChromeExtension
Function Configure-ChromeExtension {
<#
.SYNOPSIS
Configures an extension for Google Chrome using the ExtensionSettings policy
.DESCRIPTION
This function configures an extension for Microsoft Edge using the ExtensionSettings policy: https://learn.microsoft.com/en-us/deployedge/microsoft-edge-manage-extensions-ref-guide
This enables Chrome Extensions to be installed and managed like applications, enabling extensions to be pushed to specific devices or users alongside existing GPO/Intune extension policies.
.PARAMETER Add
Adds an extension configuration
.PARAMETER Remove
Removes an extension configuration
.PARAMETER ExtensionID
The ID of the extension to install.
.PARAMETER InstallationMode
The installation mode of the extension. Allowed values: blocked, allowed, removed, force_installed, normal_installed
.PARAMETER UpdateUrl
The update URL of the extension. This is the URL where the extension will check for updates.
.PARAMETER MinimumVersionRequired
The minimum version of the extension required for installation.
.EXAMPLE
Configure-ChromeExtension -Add -ExtensionID "extensionID" -InstallationMode "force_installed" -UpdateUrl "https://edge.microsoft.com/extensionwebstorebase/v1/crx"
.EXAMPLE
Configure-ChromeExtension -Remove -ExtensionID "extensionID"
.NOTES
This function is provided as a template to install an extension for Google Chrome.
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true, ParameterSetName = 'Add')]
[Switch]$Add,
[Parameter(Mandatory = $true, ParameterSetName = 'Remove')]
[Switch]$Remove,
[Parameter(Mandatory = $true, ParameterSetName = 'Add')]
[Parameter(Mandatory = $true, ParameterSetName = 'Remove')]
[String]$ExtensionID,
[Parameter(Mandatory = $true, ParameterSetName = 'Add')]
[ValidateSet('blocked', 'allowed', 'removed', 'force_installed', 'normal_installed')]
[String]$InstallationMode,
[Parameter(Mandatory = $true, ParameterSetName = 'Add')]
[String]$UpdateUrl,
[Parameter(Mandatory = $false, ParameterSetName = 'Add')]
[String]$MinimumVersionRequired
)
If ($Add) {
If ($MinimumVersionRequired) {
Write-Log -Message "Configuring extension with ID [$extensionID] with mode [Add] using installation mode [$InstallationMode] and update URL [$UpdateUrl] with minimum version required [$MinimumVersionRequired]." -Severity 1
}
Else {
Write-Log -Message "Configuring extension with ID [$extensionID] with mode [Add] using installation mode [$InstallationMode] and update URL [$UpdateUrl]." -Severity 1
}
}
Else {
Write-Log -Message "Configuring extension with ID [$extensionID] with mode [Add]." -Severity 1
}
$regKeyChromeExtensions = 'HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome'
# Check if the ExtensionSettings registry key exists if not create it
If (!(Test-RegistryValue -Key $regKeyChromeExtensions -Value ExtensionSettings)) {
Set-RegistryKey -Key $regKeyChromeExtensions -Name ExtensionSettings -Value "" | Out-Null
}
Else {
# Get the installed extensions
$installedExtensions = Get-RegistryKey -Key $regKeyChromeExtensions -Value ExtensionSettings | ConvertFrom-Json -ErrorAction SilentlyContinue
Write-Log -Message "Configured extensions: [$($installedExtensions | ConvertTo-Json -Compress -ErrorAction SilentlyContinue)]." -Severity 1
}
Try {
If ($Remove) {
If ($installedExtensions.$($extensionID)) {
# If the deploymentmode is Remove, remove the extension from the list
Write-Log -Message "Removing extension with ID [$extensionID]." -Severity 1
$installedExtensions.PSObject.Properties.Remove($extensionID)
$jsonExtensionSettings = $installedExtensions | ConvertTo-Json -Compress
Set-RegistryKey -Key $regKeyChromeExtensions -Name "ExtensionSettings" -Value $jsonExtensionSettings | Out-Null
}
Else { # If the extension is not configured
Write-Log -Message "Extension with ID [$extensionID] is not configured. Removal not required." -Severity 1
}
}
# Configure the extension
ElseIf ($Add) {
Write-Log -Message "Configuring extension ID [$extensionID]." -Severity 1
If (!$installedExtensions) {
$installedExtensions = @{}
}
If ($MinimumVersionRequired) {
$installedExtensions | Add-Member -Name $($extensionID) -Value $(@{ "installation_mode" = $InstallationMode; "update_url" = $UpdateUrl; "minimum_version_required" = $MinimumVersionRequired }) -MemberType NoteProperty -Force
}
Else {
$installedExtensions | Add-Member -Name $($extensionID) -Value $(@{ "installation_mode" = $InstallationMode; "update_url" = $UpdateUrl }) -MemberType NoteProperty -Force
}
$jsonExtensionSettings = $installedExtensions | ConvertTo-Json -Compress
Set-RegistryKey -Key $regKeyChromeExtensions -Name "ExtensionSettings" -Value $jsonExtensionSettings | Out-Null
}
}
Catch {
Write-Log -Message "Failed to configure extension with ID $extensionID. `r`n$(Resolve-Error)" -Severity 3
Exit-Script -ExitCode 60001
}
} #End Function Configure-ChromeExtension
#endregion
##*===============================================
##* END FUNCTION LISTINGS
##*===============================================
##*===============================================
##* SCRIPT BODY
##*===============================================
If ($scriptParentPath) {
Write-Log -Message "Script [$($MyInvocation.MyCommand.Definition)] dot-source invoked by [$(((Get-Variable -Name MyInvocation).Value).ScriptName)]" -Source $appDeployToolkitExtName
}
Else {
Write-Log -Message "Script [$($MyInvocation.MyCommand.Definition)] invoked directly" -Source $appDeployToolkitExtName
}
##*===============================================
##* END SCRIPT BODY
##*===============================================
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment