Public/Templates/Application/Intune-I-MainInstaller.ps1

param(
    [switch]$Uninstall
)
# Install application defined in the Intune-I-MainInstaller.json file and run and pre and post installation scripts

# Importing the install configuration json file
$InstallConfig = Get-Content -Path "$PSScriptRoot\config.installer.json" | ConvertFrom-Json

#region Global Variables
$ConfigBase = if ($InstallConfig.target -eq "user") { $env:APPDATA } else { ${env:ProgramFiles(x86)} }
$APFBase = "APF"
$InvalidChars = [System.IO.Path]::GetInvalidFileNameChars()
$Extention = $InstallConfig.filename -split "\." | Select-Object -Last 1
$LogName = $InstallConfig.filename.Replace($Extention, "log")
$InvalidChars | % { $LogName = $LogName -replace [regex]::Escape($_), "" }
$LoggingPath = Join-Path -Path (Join-Path -Path $ConfigBase -ChildPath "\$APFBase\UserLogs\") -ChildPath $LogName
$ShortcutBasePath = if ($InstallConfig.target -eq "user") { "$((New-Object -ComObject Shell.Application).Namespace(0x10).Self.Path)" } else { "C:\Users\Public\Desktop" }
$ExistingConfig = $false
$StartTime = Get-Date
#endregion Global Variables

#region Logging function
try {
    Import-Module -Name "$PSScriptRoot\Write-DeploymentLog.ps1" -Force -ErrorAction Stop
}
Catch {
    Write-Host "Failed to import the logging function with error: $_"
    exit 1
}
#endregion Logging Function
Write-DeploymentLog -Message "APF Started." -MessageType "Info" -LogPath $LoggingPath
# Setup Working Directory

# Create config directory if it dosent exist

if (-not (Test-Path -Path "$ConfigBase\$APFBase\AppConfigs")) {
    Write-DeploymentLog -Message "Creating the Rothesay App Configs folder" -MessageType "Info" -LogPath $LoggingPath
    New-Item -Path "$ConfigBase\$APFBase\AppConfigs" -ItemType Directory -Force
}

if (-not (Test-Path -Path "$ConfigBase\$APFBase\AppConfigs\$($InstallConfig.name)_config.installer.json")) {
    Write-DeploymentLog -Message "Installer config dosen't exist in the Rothesay App Configs folder" -MessageType "Info" -LogPath $LoggingPath
}
else {
    $ExistingConfig = $true
    $LocalConfig = Get-Content -Path "$ConfigBase\$APFBase\AppConfigs\$($InstallConfig.name)_config.installer.json" | ConvertFrom-Json
    if ($LocalConfig -ne $InstallConfig) {
        if (([version]$InstallConfig.version -gt [version]$LocalConfig.version) -and (-not $Uninstall)) {
            Write-DeploymentLog -Message "The installer config is newer than the one in the Rothesay App Configs folder, this installation will upgrade the existing installation" -MessageType "Info" -LogPath $LoggingPath
        }
        elseif (([version]$InstallConfig.version -lt [version]$LocalConfig.version) -and (-not $Uninstall)) {
            Write-DeploymentLog -Message "The installer config is older than the one in the Rothesay App Configs folder, this installation will not be performed" -MessageType "Info" -LogPath $LoggingPath
            exit 0
        }
        elseif (([version]$InstallConfig.version -eq [version]$LocalConfig.version) -and (-not $Uninstall)) {
            Write-DeploymentLog -Message "The version number in the installer config is the same as the one in the Rothesay App Configs folder, check if another value has changed, this installation will not be performed" -MessageType "Info" -LogPath $LoggingPath
            exit 0
        }
    }
    else {
        Write-DeploymentLog -Message "The installer config is the same as the one in the Rothesay Scripts folder, Detection might have failed. Aborting" -MessageType "Info" -LogPath $LoggingPath
        exit 1
    }
}

# Check if the pre-install script exists

if (-not [string]::IsNullOrEmpty($InstallConfig.precommandfile)) {
    Write-DeploymentLog -Message "Pre-install script found, running $($InstallConfig.precommandfile)" -MessageType "Info" -LogPath $LoggingPath
    try {
        Start-Process powershell.exe -ArgumentList "-ExecutionPolicy Bypass -File `"$PSScriptRoot\$($InstallConfig.precommandfile)`"" -Wait -NoNewWindow -ErrorAction Stop
    }
    Catch {
        Write-DeploymentLog -Message "Failed to run pre-install script with error: $_" -MessageType "Error" -LogPath $LoggingPath
        exit 1
    }
}

if ($Uninstall) {
    if ($InstallConfig.uninstallpath -eq "") {
        Write-DeploymentLog -Message "Uninstall path not found in the config.installer.json file, trying Uninstall Switches on setup file" -MessageType "Warning" -LogPath $LoggingPath
        $Script:UninstallPath = Join-Path -Path $PSScriptRoot -ChildPath $InstallConfig.filename
        Write-DeploymentLog -Message "Uninstall Path: $UninstallPath" -MessageType "Info" -LogPath $LoggingPath
    }
    else {
        if ($InstallConfig.target -eq "user") {
            $Script:UninstallPath = $InstallConfig.uninstallpath -replace "c:\\Users\\[^\\]*\\", "C:\Users\$($Env:USERNAME)\"
            Write-DeploymentLog -Message "Uninstall Path: $UninstallPath" -MessageType "Info" -LogPath $LoggingPath
        }
        else {
            Write-DeploymentLog -Message "Uninstall Path: $UninstallPath" -MessageType "Info" -LogPath $LoggingPath
            $Script:UninstallPath = $InstallConfig.uninstallpath
        }
    }

    Write-DeploymentLog -Message "Uninstalling $($InstallConfig.name), Version $($InstallConfig.version) with Uninstall Path $($UninstallPath)" -MessageType "Info" -LogPath $LoggingPath
    Write-DeploymentLog -Message "Imported the following configuration: `n$($InstallConfig | ConvertTo-Json -Depth 5)" -MessageType "Info" -LogPath $LoggingPath
    if ($UninstallPath -match ".msi") {
        try {
            $UninstallSplat = @{
                FilePath         = "msiexec.exe"
                WorkingDirectory = $(if ($UninstallPath -contains " ") { Split-Path -Path $UninstallPath -Parent } else { $UninstallPath })
                ArgumentList     = "/x `"$(Split-Path -Path $UninstallPath -Leaf)`" $($InstallConfig.uninstallswitches)"
                NoNewWindow      = $true
                Wait             = $true
                ErrorAction      = "Stop"
                PassThru         = $true
            }
            Write-DeploymentLog -Message "Splat contains: `n$($UninstallSplat | ConvertTo-Json -Depth 5)" -MessageType "Info" -LogPath $LoggingPath
            Write-DeploymentLog -Message "MSI Found. Running msiexec.exe /x `"$UninstallPath`" $($InstallConfig.uninstallswitches)" -MessageType "Info" -LogPath $LoggingPath
            Start-Process @UninstallSplat
            Write-DeploymentLog -Message "Process ID: $($Process.Id)" -MessageType "Info" -LogPath $LoggingPath
            Write-DeploymentLog -Message "Process Exit Code: $($Process.ExitCode)" -MessageType "Info" -LogPath $LoggingPath
            if ($Process.ExitCode -ne 0) {
                Write-Error -Message "ExitCode: $($Process.ExitCode)"
                throw "ExitCode: $($Process.ExitCode)"
            }
            else {
                Write-DeploymentLog -Message "Uninstall completed successfully" -MessageType "Info" -LogPath $LoggingPath
                if ($InstallConfig.shortcut) {
                    Write-DeploymentLog -Message "Removing shortcuts" -MessageType "Info" -LogPath $LoggingPath
                    foreach ($Shortcut in $InstallConfig.CreatedShortcuts) {
                        if (Test-Path -Path $Shortcut) {
                            Remove-Item -Path $Shortcut -Force -Confirm:$false
                        }
                    }
                }
                if ($ExistingConfig -eq $true) {
                    Remove-Item -Path "$ConfigBase\$APFBase\AppConfigs\$($InstallConfig.name)_config.installer.json" -Force -Confirm:$false
                }
            }
        }
        Catch {
            Write-DeploymentLog -Message "Failed to uninstall $($InstallConfig.name), Version $($InstallConfig.version) with error: $($_.Exception.Message)" -MessageType "Error" -LogPath $LoggingPath
            exit 1
        }
    }
    else {
        try {
            $UninstallSplat = @{
                FilePath         = "cmd.exe"
                WorkingDirectory = $(if ($UninstallPath -match " ") { Split-Path -Path $UninstallPath -Parent } else { $UninstallPath })
                ArgumentList     = "/c $(Split-Path -Path $UninstallPath -Leaf) $($InstallConfig.uninstallswitches)"
                NoNewWindow      = $true
                Wait             = $true
                ErrorAction      = "Stop"
                PassThru         = $true
            }
            Write-DeploymentLog -Message "Splat contains: `n$($UninstallSplat | ConvertTo-Json -Depth 5)" -MessageType "Info" -LogPath $LoggingPath
            Write-DeploymentLog -Message "EXE Found. Running cmd.exe /c `"$UninstallPath`" $($InstallConfig.uninstallswitches)" -MessageType "Info" -LogPath $LoggingPath
            $Process = Start-Process @UninstallSplat
            Write-DeploymentLog -Message "Process ID: $($Process.Id)" -MessageType "Info" -LogPath $LoggingPath
            Write-DeploymentLog -Message "Process Exit Code: $($Process.ExitCode)" -MessageType "Info" -LogPath $LoggingPath
            if ($Process.ExitCode -ne 0) {
                Write-Error -Message "ExitCode: $($Process.ExitCode)"
                throw "ExitCode: $($Process.ExitCode)"
            }
            else {
                Write-DeploymentLog -Message "Uninstall completed successfully" -MessageType "Info" -LogPath $LoggingPath
                if ($InstallConfig.shortcut) {
                    Write-DeploymentLog -Message "Removing shortcuts" -MessageType "Info" -LogPath $LoggingPath
                    foreach ($Shortcut in $InstallConfig.CreatedShortcuts) {
                        if (Test-Path -Path $Shortcut) {
                            Remove-Item -Path $Shortcut -Force -Confirm:$false
                        }
                    }
                }
                if ($ExistingConfig -eq $true) {
                    Remove-Item -Path "$ConfigBase\$APFBase\AppConfigs\$($InstallConfig.name)_config.installer.json" -Force -Confirm:$false
                }
            }
        }
        Catch {
            Write-DeploymentLog -Message "Failed to uninstall $($InstallConfig.name), Version $($InstallConfig.version) with error: $($_.Exception.Message)" -MessageType "Error" -LogPath $LoggingPath
            exit 1
        }
    }
    $Script:TimeTaken = $(Get-Date) - $StartTime
    Write-DeploymentLog -Message "Uninstallation of $($InstallConfig.name), Version $($InstallConfig.version) took $($TimeTaken.ToString('hh\:mm\:ss\.fff'))" -MessageType "Info" -LogPath $LoggingPath
    exit 0
}
else {
    Write-DeploymentLog -Message "Starting installation of $($InstallConfig.name), Version $($InstallConfig.version)" -MessageType "Info" -LogPath $LoggingPath
    # Log the config import
    Write-DeploymentLog -Message "Imported the following configuration: `n$($InstallConfig | ConvertTo-Json -Depth 5)" -MessageType "Info" -LogPath $LoggingPath
    # Set the execution policy to bypass
    Write-DeploymentLog -Message "Setting the execution policy to bypass" -MessageType "Info" -LogPath $LoggingPath
    Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force
    # Install the application
    Write-DeploymentLog -Message "Installing $($InstallConfig.name), Version $($InstallConfig.version) with Installation Switches $($InstallConfig.installswitches)" -MessageType "Info" -LogPath $LoggingPath
    # Check if the application is an MSI or an executable
    if ($InstallConfig.filename -match ".msi") {
        # The application is an MSI, install it using msiexec
        try {
            $InstallSplat = @{
                FilePath         = "msiexec.exe"
                WorkingDirectory = $PSScriptRoot
                ArgumentList     = "/i `"$($InstallConfig.filename)`" $($InstallConfig.installswitches)"
                NoNewWindow      = $true
                Wait             = $true
                ErrorAction      = "Stop"
                PassThru         = $true
            }
            Write-DeploymentLog -Message "Splat contains: `n$($InstallSplat | ConvertTo-Json -Depth 5)" -MessageType "Info" -LogPath $LoggingPath
            Write-DeploymentLog -Message "MSI Found. Running Command: $($InstallSplat.FilePath) $($InstallSplat.ArgumentList)" -MessageType "Info" -LogPath $LoggingPath
            $Process = Start-Process @InstallSplat
            Write-DeploymentLog -Message "Process ID: $($Process.Id)" -MessageType "Info" -LogPath $LoggingPath
            Write-DeploymentLog -Message "Process Exit Code: $($Process.ExitCode)" -MessageType "Info" -LogPath $LoggingPath
            if ($Process.ExitCode -ne 0) {
                Write-Error -Message "ExitCode: $($Process.ExitCode)"
                throw "ExitCode: $($Process.ExitCode)"
            }
            else {
                Write-DeploymentLog -Message "Installation completed successfully" -MessageType "Info" -LogPath $LoggingPath
                # Create a config file for the installed application
                if ($ExistingConfig -eq $false) {
                    Write-DeploymentLog -Message "Creating the config file for the installed application" -MessageType "Info" -LogPath $LoggingPath
                    $InstallConfig | ConvertTo-Json | Set-Content -Path "$ConfigBase\$APFBase\AppConfigs\$($InstallConfig.name)_config.installer.json"
                }
                else {
                    # Backup previous config file
                    Write-DeploymentLog -Message "Backing up the previous config file for the installed application" -MessageType "Info" -LogPath $LoggingPath
                    Copy-Item -Path "$ConfigBase\$APFBase\AppConfigs\$($InstallConfig.name)_config.installer.json" -Destination "$ConfigBase\$APFBase\AppConfigs\$($InstallConfig.name)_config.installer.json.bak" -Force
                    Write-DeploymentLog -Message "Updating the config file for the installed application" -MessageType "Info" -LogPath $LoggingPath
                    $InstallConfig | ConvertTo-Json | Set-Content -Path "$ConfigBase\$APFBase\AppConfigs\$($InstallConfig.name)_config.installer.json"
                }
            }
        }
        Catch {
            $Script:TimeTaken = $(Get-Date) - $StartTime
            Write-DeploymentLog -Message "Failed to install $($InstallConfig.name), Version $($InstallConfig.version) with error: $_" -MessageType "Error" -LogPath $LoggingPath
            exit 1
        }
    }
    else {
        # The application is not an MSI, install it using the executable
        try {
            $InstallSplat = @{
                FilePath         = "cmd.exe"
                WorkingDirectory = $PSScriptRoot
                ArgumentList     = "/c $($InstallConfig.filename) $($InstallConfig.installswitches)"
                NoNewWindow      = $true
                Wait             = $true
                ErrorAction      = "Stop"
                PassThru         = $true
            }
            Write-DeploymentLog -Message "Splat contains: `n$($InstallSplat | ConvertTo-Json -Depth 5)" -MessageType "Info" -LogPath $LoggingPath
            Write-DeploymentLog -Message "EXE Found. Running Command: $($InstallSplat.FilePath) $($InstallSplat.ArgumentList)" -MessageType "Info" -LogPath $LoggingPath
            $Process = Start-Process @InstallSplat
            Write-DeploymentLog -Message "Process ID: $($Process.Id)" -MessageType "Info" -LogPath $LoggingPath
            Write-DeploymentLog -Message "Process Exit Code: $($Process.ExitCode)" -MessageType "Info" -LogPath $LoggingPath
            if ($Process.ExitCode -ne 0) {
                Write-Error -Message "ExitCode: $($Process.ExitCode)"
                throw "ExitCode: $($Process.ExitCode)"
            }
            else {
                Write-DeploymentLog -Message "Installation completed successfully" -MessageType "Info" -LogPath $LoggingPath
                if ( -not [string]::IsNullOrEmpty($InstallConfig.shortcut)) {
                    # Get all the Start Menu Shotcuts just created so we can create the defined shortcut in the config file
                    $TimeLimit = (Get-Date).AddSeconds(-5)
                    $StartMenuShortcuts = Get-ChildItem -Path "$env:APPDATA\Microsoft\Windows\Start Menu\Programs" -Recurse -Include "*.lnk" | Where-Object { $_.CreationTime -gt $TimeLimit } | Where-Object { $_.Name -notmatch "Uninstall|remove|readme|guide" }
                    if ($StartMenuShortcuts -eq $null) {
                        Write-DeploymentLog -Message "No Start Menu Shortcuts found" -MessageType "Info" -LogPath $LoggingPath
                    }
                    else {
                        foreach ($Shortcut in $StartMenuShortcuts) {
                            $WshShell = New-Object -ComObject WScript.Shell
                            $ShortcutObject = $WshShell.CreateShortcut($Shortcut)
                            try {                            
                                Write-DeploymentLog -Message "Creating shortcut $($Shortcut.BaseName)" -MessageType "Info" -LogPath $LoggingPath
                                $ShortcutFullPath = Join-Path -Path $ShortcutBasePath -ChildPath "$($Shortcut.BaseName).lnk"
                                $WshShell = New-Object -ComObject WScript.Shell
                                $Shortcut = $WshShell.CreateShortcut($ShortcutFullPath)
                                $Shortcut.TargetPath = $ShortcutObject.TargetPath
                                $Shortcut.Save()
                                $InstallConfig.CreatedShortcuts += $ShortcutFullPath
                            }
                            Catch {
                                Write-DeploymentLog -Message "Failed to create shortcut with error: $_" -MessageType "Error" -LogPath $LoggingPath
                            }
                        }
                    }
                }
                # Create a config file for the installed application
                if ($ExistingConfig -eq $false) {
                    Write-DeploymentLog -Message "Creating the config file for the installed application" -MessageType "Info" -LogPath $LoggingPath
                    $InstallConfig | ConvertTo-Json | Set-Content -Path "$ConfigBase\$APFBase\AppConfigs\$($InstallConfig.name)_config.installer.json"
                }
                else {
                    # Backup previous config file
                    Write-DeploymentLog -Message "Backing up the previous config file for the installed application" -MessageType "Info" -LogPath $LoggingPath
                    Copy-Item -Path "$ConfigBase\$APFBase\AppConfigs\$($InstallConfig.name)_config.installer.json" -Destination "$ConfigBase\$APFBase\AppConfigs\$($InstallConfig.name)_config.installer.json.bak" -Force
                    Write-DeploymentLog -Message "Updating the config file for the installed application" -MessageType "Info" -LogPath $LoggingPath
                    $InstallConfig | ConvertTo-Json | Set-Content -Path "$ConfigBase\$APFBase\AppConfigs\$($InstallConfig.name)_config.installer.json"
                }
            }          
        }
        Catch {
            Write-DeploymentLog -Message "Failed to install $($InstallConfig.name), Version $($InstallConfig.version) with error: $_" -MessageType "Error" -LogPath $LoggingPath
            exit 1
        }
    }
    Write-DeploymentLog -Message "Installation of $($InstallConfig.name), Version $($InstallConfig.version) completed successfully" -MessageType "Info" -LogPath $LoggingPath
    $Script:TimeTaken = $(Get-Date) - $StartTime
    Write-DeploymentLog -Message "Installation of $($InstallConfig.name), Version $($InstallConfig.version) took $($TimeTaken.ToString('hh\:mm\:ss\.fff'))" -MessageType "Info" -LogPath $LoggingPath
}
# Check if the post-install script exists

if (-not [string]::IsNullOrEmpty($InstallConfig.postcommandfile)) {
    Write-DeploymentLog -Message "Post-install script found, running $($InstallConfig.postcommandfile)" -MessageType "Info" -LogPath $LoggingPath
    # Run the post-install script
    try {
        Start-Process powershell.exe -ArgumentList "-ExecutionPolicy Bypass -File `"$PSScriptRoot\$($InstallConfig.postcommandfile)`"" -Wait -NoNewWindow -ErrorAction Stop
    }
    Catch {
        Write-DeploymentLog -Message "Failed to run post-install script with error: $_" -MessageType "Error" -LogPath $LoggingPath
        exit 1
    }
}
Write-DeploymentLog -Message "APF Completed." -MessageType "Info" -LogPath $LoggingPath
exit 0