Microsoft Visual Studio Code 1.70.2

InnoSetup

About

Visual Studio Code, also commonly referred to as VS Code, is a source-code editor made by Microsoft for Windows, Linux and macOS. Features include support for debugging, syntax highlighting, intelligent code completion, snippets, code refactoring, and embedded Git.

Main executable

C:\Program Files\Microsoft VS Code\Code.exe

Vendor configuration for silent install.

The vendor installer is based on the Inno Setup installer. Configuration options for installation including silent install can be found at jrsoftware.org.

deployInfo.xml

This file is referred to by the script. The variables in the script are called from this file. For each updated version, simply change the version number in the appropriate field, and the script will find the installer based on that. This file is stored in the SupportFiles directory.

<Metadata>
	<Publisher>Microsoft</Publisher>
	<DisplayName>Visual Studio Code</DisplayName>
	<DisplayVersion>1.70.2</DisplayVersion>
	<Architecture>x64</Architecture>
	<Revision>R1</Revision>
	<Date>18/08/2022</Date>
	<Author>njholmes.com</Author>
</Metadata>

Install

Generate a config file for preferred settings for silent install as follows:

VSCodeSetup-x64-1.70.2.exe /SAVEINF=<path>\config.inf

The installer is run with the following parameters:

VSCodeSetup-x64-1.70.2.exe /LOADINF=<path>\config.inf /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /LOG=<Path>\<logname>.log

Suppress Updates

This settings file must be copied to the user profile to suppress updates. The location to copy to is %AppData\Code\User\.

This can be done a number of ways. I don’t use the Set-ActiveSetup function as it has some known issues. Additionally, in some environments Active Setups are not preferred. Further options are below if you prefer not to use Active Setup.

settings.json

{
    "update.enableWindowsBackgroundUpdates": false,
    "update.mode": "none"
}

Active Setup

The Active setup copies a settings file to the user profile at login. In the PSADT script, I also run it as the logged-on user using Execute-ProcessAsUser.

# This script copies configuration files to the logged-on user's profile as part of an Active Setup.
# Created 18/08/2022 | njholmes.com


# Variables
[string]$currentPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
[string]$appConfig = "$env:AppData\Code\User"

If (!(Test-Path $appConfig))
    {New-Item -ItemType Directory $appConfig -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null}
Copy-Item "$currentPath\settings.json" $appConfig\ -Recurse -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null

Alternative to Active Setup

If Active Setups are not preferred, the settings file can be copied to all the existing local user profiles on the machine using the following code in the main script:

# Copy config to all user profiles
$dirAllUsers = Get-ChildItem "C:\Users"
ForEach ($userName in $dirAllUsers)
  {
  Write-Log -Message "Copying settings file to user $userName to suppress updates" -LogType 'CMTrace'
  Copy-File -Path "$dirSupportFiles\settings.json" -Destination "C:\Users\$userName\AppData\Roaming\Code\User\" -Recurse
  }

Uninstall

The uninstaller is usually located in <InstallDir>\unins000.exe. However, with InnoSetup installers, occasionally the uninstaller may be named unins001.exe, if the package has been installed or uninstalled multiple times. The Uninstall script will extract the uninstaller from the registry to ensure the correct filename is called.

The uninstaller is run with the following parameters:

<InstallDir>\unins000.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /LOG=<Path>\<logname>.log

Logging

Can be found at C:\Windows\Logs\Software. This location is readable by all but not writeable for non-admin users.
Additionally, every time the package is run, start and finish times are written to a custom registry key.
Since this key is overwritten each time the package is run, it is also written to a CSV file in the logs location, on a new line each time.

Uninstall registry key

Useful for Endpoint Manager detection.

Inno Setup: Setup Version    : 6.0.5 (u)
Inno Setup: App Path         : C:\Program Files\Microsoft VS Code
InstallLocation              : C:\Program Files\Microsoft VS Code\
Inno Setup: Icon Group       : Visual Studio Code
Inno Setup: User             : User
Inno Setup: Selected Tasks   : addcontextmenufiles,addcontextmenufolders,associatewithfiles,addtopath
Inno Setup: Deselected Tasks : desktopicon,runcode
Inno Setup: Language         : english
DisplayName                  : Microsoft Visual Studio Code
DisplayIcon                  : C:\Program Files\Microsoft VS Code\Code.exe
UninstallString              : "C:\Program Files\Microsoft VS Code\unins000.exe"
QuietUninstallString         : "C:\Program Files\Microsoft VS Code\unins000.exe" /SILENT
DisplayVersion               : 1.70.2
Publisher                    : Microsoft Corporation
URLInfoAbout                 : https://code.visualstudio.com/
HelpLink                     : https://code.visualstudio.com/
URLUpdateInfo                : https://code.visualstudio.com/
NoModify                     : 1
NoRepair                     : 1
InstallDate                  : 20220818
MajorVersion                 : 1
MinorVersion                 : 70
VersionMajor                 : 1
VersionMinor                 : 70
EstimatedSize                : 324077
PSPath                       : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{EA457B21-F73E-494C-ACAB-524FDE069978}_is1
PSParentPath                 : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
PSChildName                  : {EA457B21-F73E-494C-ACAB-524FDE069978}_is1
PSProvider                   : Microsoft.PowerShell.Core\Registry

PSADT script

This is the full PSADT script.

<#
.SYNOPSIS
    This script performs the installation or uninstallation of an application(s).
    # LICENSE #
    PowerShell App Deployment Toolkit - Provides a set of functions to perform common application deployment tasks on Windows.
    Copyright (C) 2017 - Sean Lillis, Dan Cunningham, Muhammad Mashwani, Aman Motazedian.
    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/>.
.DESCRIPTION
    The script is provided as a template to perform an install or uninstall of an application(s).
    The script either performs an "Install" deployment type or an "Uninstall" deployment type.
    The install deployment type is broken down into 3 main sections/phases: Pre-Install, Install, and Post-Install.
    The script dot-sources the AppDeployToolkitMain.ps1 script which contains the logic and functions required to install or uninstall an application.
.PARAMETER DeploymentType
    The type of deployment to perform. Default is: Install.
.PARAMETER DeployMode
    Specifies whether the installation should be run in Interactive, Silent, or NonInteractive mode. Default is: Interactive. Options: Interactive = Shows dialogs, Silent = No dialogs, NonInteractive = Very silent, i.e. no blocking apps. NonInteractive mode is automatically set if it is detected that the process is not user interactive.
.PARAMETER AllowRebootPassThru
    Allows the 3010 return code (requires restart) to be passed back to the parent process (e.g. SCCM) if detected from an installation. If 3010 is passed back to SCCM, a reboot prompt will be triggered.
.PARAMETER TerminalServerMode
    Changes to "user install mode" and back to "user execute mode" for installing/uninstalling applications for Remote Destkop Session Hosts/Citrix servers.
.PARAMETER DisableLogging
    Disables logging to file for the script. Default is: $false.
.EXAMPLE
    powershell.exe -Command "& { & '.\Deploy-Application.ps1' -DeployMode 'Silent'; Exit $LastExitCode }"
.EXAMPLE
    powershell.exe -Command "& { & '.\Deploy-Application.ps1' -AllowRebootPassThru; Exit $LastExitCode }"
.EXAMPLE
    powershell.exe -Command "& { & '.\Deploy-Application.ps1' -DeploymentType 'Uninstall'; Exit $LastExitCode }"
.EXAMPLE
    Deploy-Application.exe -DeploymentType "Install" -DeployMode "Silent"
.NOTES
    Toolkit Exit Code Ranges:
    60000 - 68999: Reserved for built-in exit codes in Deploy-Application.ps1, Deploy-Application.exe, and AppDeployToolkitMain.ps1
    69000 - 69999: Recommended for user customized exit codes in Deploy-Application.ps1
    70000 - 79999: Recommended for user customized exit codes in AppDeployToolkitExtensions.ps1
.LINK
    http://psappdeploytoolkit.com
#>
[CmdletBinding()]
Param (
    [Parameter(Mandatory=$false)]
    [ValidateSet('Install','Uninstall','Repair')]
    [string]$DeploymentType = 'Install',
    [Parameter(Mandatory=$false)]
    [ValidateSet('Interactive','Silent','NonInteractive')]
    [string]$DeployMode = 'Interactive',
    [Parameter(Mandatory=$false)]
    [switch]$AllowRebootPassThru = $false,
    [Parameter(Mandatory=$false)]
    [switch]$TerminalServerMode = $false,
    [Parameter(Mandatory=$false)]
    [switch]$DisableLogging = $false
)

Try {
    ## Set the script execution policy for this process
    Try { Set-ExecutionPolicy -ExecutionPolicy 'ByPass' -Scope 'Process' -Force -ErrorAction 'Stop' } Catch {}

    ##*===============================================
    ##* VARIABLE DECLARATION
    ##*===============================================
    ## Variables: Application
    [string]$currentPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
    [XML]$infoXML = (Get-Content "$currentPath\SupportFiles\deployInfo.xml")
    [string]$appVendor = $infoXML.Metadata.Publisher
    [string]$appName = $infoXML.Metadata.DisplayName
    [string]$appVersion = $infoXML.Metadata.DisplayVersion
    [string]$appArch = $infoXML.Metadata.Architecture
    [string]$appLang = 'EN'
    [string]$appRevision = $infoXML.Metadata.Revision
    [string]$appScriptVersion = '1.0.0'
    [string]$appScriptDate = $infoXML.Metadata.Date
    [string]$appScriptAuthor = $infoXML.Metadata.Author
    [string]$appFullName = "$appVendor $appName $appVersion"
    [string]$installDate = (Get-Date -f "yyyyMMdd")
    [string]$logPath = "$env:WinDir\Logs\Software"
    [string]$histFile = $env:ComputerName + '_history.csv'
    [string]$envStartMenu = "$env:ProgramData\Microsoft\Windows\Start Menu\Programs"
    [string]$logRegKey = "HKLM:\SOFTWARE\Applications\$appFullName"
    [string]$activeSetupPath = "$env:ProgramData\ActiveSetup\$appName"
    [string]$activeSetupConfig = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\$appName"
    [string]$nameSearch = "*Visual Studio Code*"
    [string]$pkgInstaller = 'VSCodeSetup-x64-' + $appVersion + '.exe'
    ##*===============================================
    ## Variables: Install Titles (Only set here to override defaults set by the toolkit)
    [string]$installName = ''
    [string]$installTitle = ''

    ##* Do not modify section below
    #region DoNotModify

    ## Variables: Exit Code
    [int32]$mainExitCode = 0

    ## Variables: Script
    [string]$deployAppScriptFriendlyName = 'Deploy Application'
    [version]$deployAppScriptVersion = [version]'3.8.4'
    [string]$deployAppScriptDate = '26/01/2021'
    [hashtable]$deployAppScriptParameters = $psBoundParameters

    ## Variables: Environment
    If (Test-Path -LiteralPath 'variable:HostInvocation') { $InvocationInfo = $HostInvocation } Else { $InvocationInfo = $MyInvocation }
    [string]$scriptDirectory = Split-Path -Path $InvocationInfo.MyCommand.Definition -Parent

    ## Dot source the required App Deploy Toolkit Functions
    Try {
        [string]$moduleAppDeployToolkitMain = "$scriptDirectory\AppDeployToolkit\AppDeployToolkitMain.ps1"
        If (-not (Test-Path -LiteralPath $moduleAppDeployToolkitMain -PathType 'Leaf')) { Throw "Module does not exist at the specified location [$moduleAppDeployToolkitMain]." }
        If ($DisableLogging) { . $moduleAppDeployToolkitMain -DisableLogging } Else { . $moduleAppDeployToolkitMain }
    }
    Catch {
        If ($mainExitCode -eq 0){ [int32]$mainExitCode = 60008 }
        Write-Error -Message "Module [$moduleAppDeployToolkitMain] failed to load: `n$($_.Exception.Message)`n `n$($_.InvocationInfo.PositionMessage)" -ErrorAction 'Continue'
        ## Exit the script, returning the exit code to SCCM
        If (Test-Path -LiteralPath 'variable:HostInvocation') { $script:ExitCode = $mainExitCode; Exit } Else { Exit $mainExitCode }
    }

    #endregion
    ##* Do not modify section above
    ##*===============================================
    ##* END VARIABLE DECLARATION
    ##*===============================================

    If ($deploymentType -ine 'Uninstall' -and $deploymentType -ine 'Repair') {
        ##*===============================================
        ##* PRE-INSTALLATION
        ##*===============================================
        [string]$installPhase = 'Pre-Installation'

        ## Show Welcome Message, close Internet Explorer if required, allow up to 3 deferrals, verify there is enough disk space to complete the install, and persist the prompt
        # Show-InstallationWelcome -CloseApps 'iexplore' -AllowDefer -DeferTimes 3 -CheckDiskSpace -PersistPrompt

        ## Show Progress Message (with the default message)
        # Show-InstallationProgress

        ## <Perform Pre-Installation tasks here>

        # Start Support Logs
        $StartTime = (Get-Date -f "dd/MM/yyyy HH:mm:ss")
        Set-RegistryKey -Key $logRegKey -Name 'StartTime' -Value $StartTime
        Write-Log -Message "Start Time: $StartTime" -LogType 'CMTrace'
        Set-RegistryKey -Key $logRegKey -Name 'AppName' -Value $appFullName
        Write-Log -Message "Name: $appFullName" -LogType 'CMTrace'
        Set-RegistryKey -Key $logRegKey -Name 'MachineName' -Value $env:ComputerName
        Write-Log -Message "Machine Name: $env:ComputerName" -LogType 'CMTrace'
        Set-RegistryKey -Key $logRegKey -Name 'InstalledBy' -Value $ProcessNTAccount
        Write-Log -Message "Installed by: $ProcessNTAccount" -LogType 'CMTrace'
        Set-RegistryKey -Key $logRegKey -Name 'InstalledFrom' -Value $scriptParentPath
        Write-Log -Message "Installed from: $scriptParentPath" -LogType 'CMTrace'
        Set-RegistryKey -Key $logRegKey -Name 'Revision' -Value $appRevision

        ##*===============================================
        ##* INSTALLATION
        ##*===============================================
        [string]$installPhase = 'Installation'

        ## Handle Zero-Config MSI Installations
        If ($useDefaultMsi) {
            [hashtable]$ExecuteDefaultMSISplat =  @{ Action = 'Install'; Path = $defaultMsiFile }; If ($defaultMstFile) { $ExecuteDefaultMSISplat.Add('Transform', $defaultMstFile) }
            Execute-MSI @ExecuteDefaultMSISplat; If ($defaultMspFiles) { $defaultMspFiles | ForEach-Object { Execute-MSI -Action 'Patch' -Path $_ } }
        }

        ## <Perform Installation tasks here>

        # Remove legacy versions if exist
        ForEach ($regKey in $regKeyApplications)
            {
            $appVer = Get-ChildItem -Path $regKey |
                Get-ItemProperty |
                    Where-Object {$_.DisplayName -like $nameSearch } |
                        Select-Object -Property DisplayName, DisplayVersion, UninstallString
            ForEach ($ver in $appVer)
                {
                If ($ver.UninstallString)
                    {
                    $appRemove = $ver.DisplayName
                    $oldVersion = $ver.DisplayVersion
                    $uninstPath = $ver.UninstallString
                    $Parms = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /LOG=`"$logPath\$appRemove $oldVersion Uninstall.log`""
                    Write-Log -Message "Uninstalling $appRemove $oldVersion" -LogType 'CMTrace'
                    Write-Log -Message "Executing $uninstPath $Parms" -LogType 'CMTrace'
                    Start-Process $uninstPath -ArgumentList $Parms -Wait -PassThru -NoNewWindow -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null
                    }
                }
            }
            $dirAllUsers = Get-ChildItem "C:\Users"
            ForEach ($userName in $dirAllUsers)
                {
                $userProfile = "C:\Users\$userName"
                $userStartMenu = "$userProfile\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Visual Studio Code"
                $appPath = "$userProfile\AppData\Local\Programs\Microsoft VS Code"
                $UninstEXE = dir -Path $appPath -Filter 'unins*.exe' -Force -ErrorAction SilentlyContinue | %{$_.FullName}
                ForEach ($Uninst in $UninstEXE)
                    {
                    If ($Uninst -ne $null)
                        {
                        $oldVersion = (Get-Item $Uninst).VersionInfo.ProductVersion
                        $Parms = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /LOG=`"$logPath\$appName $oldVersion Uninstall.log`""
                        Write-Log -Message "Uninstalling $appName $oldVersion" -LogType 'CMTrace'
                        Write-Log -Message "Executing `"$Uninst`" $Parms" -LogType 'CMTrace'
                        Start-Process $Uninst -ArgumentList $Parms -Wait -PassThru -NoNewWindow -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null
                        }
                    }
                If (Test-Path $appPath)
                    {Remove-Item $appPath -Recurse -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue}
                If (Test-Path $userStartMenu)
                    {Remove-Item $userStartMenu -Recurse -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue}
                }
        [scriptblock]$HKCURegistrySettings = {
        Remove-RegistryKey -Key "HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\{771FD6B0-FA20-440A-A002-3B3BAC16DC50}_is1" -Recurse -SID $UserProfile.SID
            }
        Invoke-HKCURegistrySettingsForAllUsers -RegistrySettings $HKCURegistrySettings

        # Cleanup Active Setups
        Remove-Folder -Path $activeSetupPath
        Remove-RegistryKey -Key $activeSetupConfig -Recurse

        # Install
        Execute-Process -Path $pkgInstaller -Parameters "/LOADINF=`"$dirFiles\config.inf`" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /LOG=`"$logPath\$appVendor $appName $appVersion Install.log`"" -WindowStyle Hidden

        # Copy config to all user profiles
        $dirAllUsers = Get-ChildItem "C:\Users"
        ForEach ($userName in $dirAllUsers)
            {
            Write-Log -Message "Copying settings file to user $userName to suppress updates" -LogType 'CMTrace'
            Copy-File -Path "$dirSupportFiles\settings.json" -Destination "C:\Users\$userName\AppData\Roaming\Code\User\" -Recurse
            }
        
        # Create Active Setup
        $activeSetupDate = (Get-Date -UFormat %Y`,`%m`,`%d)
        $stubPath = "`"$PSHOME\powershell.exe`" -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$activeSetupPath\activeSetup.ps1`""
        New-Folder -Path $activeSetupPath
        Set-RegistryKey -Key $activeSetupConfig -Name "(Default)" -Value "$appName $appVersion"
        Set-RegistryKey -Key $activeSetupConfig -Name "StubPath" -Value $stubPath
        Set-RegistryKey -Key $activeSetupConfig -Name "Version" -Value $activeSetupDate
        Copy-File -Path "$dirSupportFiles\*" -Destination "$activeSetupPath\" -Recurse

        ##*===============================================
        ##* POST-INSTALLATION
        ##*===============================================
        [string]$installPhase = 'Post-Installation'

        ## <Perform Post-Installation tasks here>

        # End Support Logs
        If ($LastExitCode -eq $null)
            {
            Set-RegistryKey -Key $logRegKey -Name 'ErrorLevel' -Value '0'
            Write-Log -Message "ErrorLevel: 0" -LogType 'CMTrace'
            }
        Else
            {
            Set-RegistryKey -Key $logRegKey -Name 'ErrorLevel' -Value $LastExitCode
            Write-Log -Message "ErrorLevel: $LastExitCode" -LogType 'CMTrace'
            }
        $finishTime = (Get-Date -f "dd/MM/yyyy HH:mm:ss")
        Set-RegistryKey -Key $logRegKey -Name 'FinishTime' -Value $finishTime
        Write-Log -Message "Finish Time: $finishTime" -LogType 'CMTrace'

        $results = @()
        $keys = Get-Item $logRegKey |
        ForEach {
            $obj = New-Object psobject
            Add-Member -InputObject $obj -MemberType NoteProperty -Name 'AppName' -Value $_.GetValue("AppName")
            Add-Member -InputObject $obj -MemberType NoteProperty -Name 'MachineName' -Value $_.GetValue("MachineName")
            Add-Member -InputObject $obj -MemberType NoteProperty -Name 'StartTime' -Value $_.GetValue("StartTime")
            Add-Member -InputObject $obj -MemberType NoteProperty -Name 'InstalledBy' -Value $_.GetValue("InstalledBy")
            Add-Member -InputObject $obj -MemberType NoteProperty -Name 'InstalledFrom' -Value $_.GetValue("InstalledFrom")
            Add-Member -InputObject $obj -MemberType NoteProperty -Name 'ErrorLevel' -Value $_.GetValue("ErrorLevel")
            Add-Member -InputObject $obj -MemberType NoteProperty -Name 'FinishTime' -Value $_.GetValue("finishTime")
            $results += $obj
            }
        $results | Export-Csv -Path $logPath\$histFile -Append -Force -NoTypeInformation

        ## Display a message at the end of the install
        # If (-not $useDefaultMsi) { Show-InstallationPrompt -Message 'You can customize text to appear at the end of an install or remove it completely for unattended installations.' -ButtonRightText 'OK' -Icon Information -NoWait }
    }
    ElseIf ($deploymentType -ieq 'Uninstall')
    {
        ##*===============================================
        ##* PRE-UNINSTALLATION
        ##*===============================================
        [string]$installPhase = 'Pre-Uninstallation'

        ## Show Welcome Message, close Internet Explorer with a 60 second countdown before automatically closing
        # Show-InstallationWelcome -CloseApps 'iexplore' -CloseAppsCountdown 60

        ## Show Progress Message (with the default message)
        # Show-InstallationProgress

        ## <Perform Pre-Uninstallation tasks here>

        ##*===============================================
        ##* UNINSTALLATION
        ##*===============================================
        [string]$installPhase = 'Uninstallation'

        ## Handle Zero-Config MSI Uninstallations
        If ($useDefaultMsi) {
            [hashtable]$ExecuteDefaultMSISplat =  @{ Action = 'Uninstall'; Path = $defaultMsiFile }; If ($defaultMstFile) { $ExecuteDefaultMSISplat.Add('Transform', $defaultMstFile) }
            Execute-MSI @ExecuteDefaultMSISplat
        }

        # <Perform Uninstallation tasks here>

        # Remove any version if exist
        ForEach ($regKey in $regKeyApplications)
            {
            $appVer = Get-ChildItem -Path $regKey |
                Get-ItemProperty |
                    Where-Object {$_.DisplayName -like $nameSearch } |
                        Select-Object -Property DisplayName, DisplayVersion, UninstallString
            ForEach ($ver in $appVer)
                {
                If ($ver.UninstallString)
                    {
                    $appRemove = $ver.DisplayName
                    $oldVersion = $ver.DisplayVersion
                    $uninstPath = $ver.UninstallString
                    $Parms = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /LOG=`"$logPath\$appRemove $oldVersion Uninstall.log`""
                    Write-Log -Message "Uninstalling $appRemove $oldVersion" -LogType 'CMTrace'
                    Write-Log -Message "Executing $uninstPath $Parms" -LogType 'CMTrace'
                    Start-Process $uninstPath -ArgumentList $Parms -Wait -PassThru -NoNewWindow -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null
                    }
                }
            }
            $dirAllUsers = Get-ChildItem "C:\Users"
            ForEach ($userName in $dirAllUsers)
                {
                $userProfile = "C:\Users\$userName"
                $userStartMenu = "$userProfile\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Visual Studio Code"
                $appPath = "$userProfile\AppData\Local\Programs\Microsoft VS Code"
                $UninstEXE = dir -Path $appPath -Filter 'unins*.exe' -Force -ErrorAction SilentlyContinue | %{$_.FullName}
                ForEach ($Uninst in $UninstEXE)
                    {
                    If ($Uninst -ne $null)
                        {
                        $oldVersion = (Get-Item $Uninst).VersionInfo.ProductVersion
                        $Parms = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /LOG=`"$logPath\$appName $oldVersion Uninstall.log`""
                        Write-Log -Message "Uninstalling $appName $oldVersion" -LogType 'CMTrace'
                        Write-Log -Message "Executing `"$Uninst`" $Parms" -LogType 'CMTrace'
                        Start-Process $Uninst -ArgumentList $Parms -Wait -PassThru -NoNewWindow -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null
                        }
                    }
                If (Test-Path $appPath)
                    {Remove-Item $appPath -Recurse -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue}
                If (Test-Path $userStartMenu)
                    {Remove-Item $userStartMenu -Recurse -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue}
                }
        [scriptblock]$HKCURegistrySettings = {
        Remove-RegistryKey -Key "HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\{771FD6B0-FA20-440A-A002-3B3BAC16DC50}_is1" -Recurse -SID $UserProfile.SID
            }
        Invoke-HKCURegistrySettingsForAllUsers -RegistrySettings $HKCURegistrySettings

        # Cleanup Active Setups
        Remove-Folder -Path $activeSetupPath
        Remove-RegistryKey -Key $activeSetupConfig -Recurse

        ##*===============================================
        ##* POST-UNINSTALLATION
        ##*===============================================
        [string]$installPhase = 'Post-Uninstallation'

        ## <Perform Post-Uninstallation tasks here>

    }
    ElseIf ($deploymentType -ieq 'Repair')
    {
        ##*===============================================
        ##* PRE-REPAIR
        ##*===============================================
        [string]$installPhase = 'Pre-Repair'

        ## Show Progress Message (with the default message)
        # Show-InstallationProgress

        ## <Perform Pre-Repair tasks here>

        $StartTime = (Get-Date -f "dd/MM/yyyy HH:mm:ss")
        Set-RegistryKey -Key $logRegKey -Name 'StartTime' -Value $StartTime
        Write-Log -Message "Start Time: $StartTime" -LogType 'CMTrace'
        Set-RegistryKey -Key $logRegKey -Name 'AppName' -Value $appFullName
        Write-Log -Message "Name: $appFullName" -LogType 'CMTrace'
        Set-RegistryKey -Key $logRegKey -Name 'MachineName' -Value $env:ComputerName
        Write-Log -Message "Machine Name: $env:ComputerName" -LogType 'CMTrace'
        Set-RegistryKey -Key $logRegKey -Name 'InstalledBy' -Value $ProcessNTAccount
        Write-Log -Message "Installed by: $ProcessNTAccount" -LogType 'CMTrace'
        Set-RegistryKey -Key $logRegKey -Name 'InstalledFrom' -Value $scriptParentPath
        Write-Log -Message "Installed from: $scriptParentPath" -LogType 'CMTrace'
        Set-RegistryKey -Key $logRegKey -Name 'Revision' -Value $appRevision

        ##*===============================================
        ##* REPAIR
        ##*===============================================
        [string]$installPhase = 'Repair'

        ## Handle Zero-Config MSI Repairs
        If ($useDefaultMsi) {
            [hashtable]$ExecuteDefaultMSISplat =  @{ Action = 'Repair'; Path = $defaultMsiFile; }; If ($defaultMstFile) { $ExecuteDefaultMSISplat.Add('Transform', $defaultMstFile) }
            Execute-MSI @ExecuteDefaultMSISplat
        }
        # <Perform Repair tasks here>

        ForEach ($regKey in $regKeyApplications)
            {
            $appVer = Get-ChildItem -Path $regKey |
                Get-ItemProperty |
                    Where-Object {$_.DisplayName -like $nameSearch } |
                        Select-Object -Property DisplayName, DisplayVersion, UninstallString
            ForEach ($ver in $appVer)
                {
                If ($ver.UninstallString)
                    {
                    $appRemove = $ver.DisplayName
                    $oldVersion = $ver.DisplayVersion
                    $uninstPath = $ver.UninstallString
                    $Parms = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /LOG=`"$logPath\$appRemove $oldVersion Uninstall.log`""
                    Write-Log -Message "Uninstalling $appRemove $oldVersion" -LogType 'CMTrace'
                    Write-Log -Message "Executing $uninstPath $Parms" -LogType 'CMTrace'
                    Start-Process $uninstPath -ArgumentList $Parms -Wait -PassThru -NoNewWindow -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null
                    }
                }
            }
            $dirAllUsers = Get-ChildItem "C:\Users"
            ForEach ($userName in $dirAllUsers)
                {
                $userProfile = "C:\Users\$userName"
                $userStartMenu = "$userProfile\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Visual Studio Code"
                $appPath = "$userProfile\AppData\Local\Programs\Microsoft VS Code"
                $UninstEXE = dir -Path $appPath -Filter 'unins*.exe' -Force -ErrorAction SilentlyContinue | %{$_.FullName}
                ForEach ($Uninst in $UninstEXE)
                    {
                    If ($Uninst -ne $null)
                        {
                        $oldVersion = (Get-Item $Uninst).VersionInfo.ProductVersion
                        $Parms = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /LOG=`"$logPath\$appName $oldVersion Uninstall.log`""
                        Write-Log -Message "Uninstalling $appName $oldVersion" -LogType 'CMTrace'
                        Write-Log -Message "Executing `"$Uninst`" $Parms" -LogType 'CMTrace'
                        Start-Process $Uninst -ArgumentList $Parms -Wait -PassThru -NoNewWindow -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null
                        }
                    }
                If (Test-Path $appPath)
                    {Remove-Item $appPath -Recurse -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue}
                If (Test-Path $userStartMenu)
                    {Remove-Item $userStartMenu -Recurse -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue}
                }
        [scriptblock]$HKCURegistrySettings = {
        Remove-RegistryKey -Key "HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\{771FD6B0-FA20-440A-A002-3B3BAC16DC50}_is1" -Recurse -SID $UserProfile.SID
            }
        Invoke-HKCURegistrySettingsForAllUsers -RegistrySettings $HKCURegistrySettings
        Remove-Folder -Path $activeSetupPath
        Remove-RegistryKey -Key $activeSetupConfig -Recurse
        Execute-Process -Path $pkgInstaller -Parameters "/LOADINF=`"$dirFiles\config.inf`" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /LOG=`"$logPath\$appVendor $appName $appVersion Install.log`"" -WindowStyle Hidden
        $dirAllUsers = Get-ChildItem "C:\Users"
        ForEach ($userName in $dirAllUsers)
            {
            Write-Log -Message "Copying settings file to user $userName to suppress updates" -LogType 'CMTrace'
            Copy-File -Path "$dirSupportFiles\settings.json" -Destination "C:\Users\$userName\AppData\Roaming\Code\User\" -Recurse
            }
        $activeSetupDate = (Get-Date -UFormat %Y`,`%m`,`%d)
        $stubPath = "`"$PSHOME\powershell.exe`" -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$activeSetupPath\activeSetup.ps1`""
        New-Folder -Path $activeSetupPath
        Set-RegistryKey -Key $activeSetupConfig -Name "(Default)" -Value "$appName $appVersion"
        Set-RegistryKey -Key $activeSetupConfig -Name "StubPath" -Value $stubPath
        Set-RegistryKey -Key $activeSetupConfig -Name "Version" -Value $activeSetupDate
        Copy-File -Path "$dirSupportFiles\*" -Destination "$activeSetupPath\" -Recurse

        ##*===============================================
        ##* POST-REPAIR
        ##*===============================================
        [string]$installPhase = 'Post-Repair'

        ## <Perform Post-Repair tasks here>

        If ($LastExitCode -eq $null)
            {
            Set-RegistryKey -Key $logRegKey -Name 'ErrorLevel' -Value '0'
            Write-Log -Message "ErrorLevel: 0" -LogType 'CMTrace'
            }
        Else
            {
            Set-RegistryKey -Key $logRegKey -Name 'ErrorLevel' -Value $LastExitCode
            Write-Log -Message "ErrorLevel: $LastExitCode" -LogType 'CMTrace'
            }
        $finishTime = (Get-Date -f "dd/MM/yyyy HH:mm:ss")
        Set-RegistryKey -Key $logRegKey -Name 'FinishTime' -Value $finishTime
        Write-Log -Message "Finish Time: $finishTime" -LogType 'CMTrace'

        $results = @()
        $keys = Get-Item $logRegKey |
        ForEach {
            $obj = New-Object psobject
            Add-Member -InputObject $obj -MemberType NoteProperty -Name 'AppName' -Value $_.GetValue("AppName")
            Add-Member -InputObject $obj -MemberType NoteProperty -Name 'MachineName' -Value $_.GetValue("MachineName")
            Add-Member -InputObject $obj -MemberType NoteProperty -Name 'StartTime' -Value $_.GetValue("StartTime")
            Add-Member -InputObject $obj -MemberType NoteProperty -Name 'InstalledBy' -Value $_.GetValue("InstalledBy")
            Add-Member -InputObject $obj -MemberType NoteProperty -Name 'InstalledFrom' -Value $_.GetValue("InstalledFrom")
            Add-Member -InputObject $obj -MemberType NoteProperty -Name 'ErrorLevel' -Value $_.GetValue("ErrorLevel")
            Add-Member -InputObject $obj -MemberType NoteProperty -Name 'FinishTime' -Value $_.GetValue("finishTime")
            $results += $obj
            }
        $results | Export-Csv -Path $logPath\$histFile -Append -Force -NoTypeInformation

    }
    ##*===============================================
    ##* END SCRIPT BODY
    ##*===============================================

    ## Call the Exit-Script function to perform final cleanup operations
    Exit-Script -ExitCode $mainExitCode
}
Catch {
    [int32]$mainExitCode = 60001
    [string]$mainErrorMessage = "$(Resolve-Error)"
    Write-Log -Message $mainErrorMessage -Severity 3 -Source $deployAppScriptFriendlyName
    Show-DialogBox -Text $mainErrorMessage -Icon 'Stop'
    Exit-Script -ExitCode $mainExitCode
}