Managing software installations across a large organization can be a challenging task. Problems can arise when system-wide installations conflict with per-user installations, especially if end-users don’t have administrative privileges. In this blog post, we’ll cover a PowerShell script that can help you uninstall any software application from both the system level and the user level. We’ll also discuss how to deploy this script via InTune for automated execution across multiple devices.

The Problem

When software is installed system-wide, users without administrative rights find it difficult to update or uninstall it. Even if you direct them to install a per-user version of the same software, shortcuts and application references might still point to the system-wide version, creating conflicts.

The Solution

The script performs several key actions:

  1. It searches for the target software at both system-wide and user-specific levels.
  2. Uninstalls all found versions of the software.
  3. Logs all actions to a text file for auditing.

Here is a generic version of the script, which you can adapt to fit your specific needs.

# Ensure the target logging directory exists
$directory = "C:\tmp"
if (-not (Test-Path $directory)) {
    New-Item -ItemType Directory -Path $directory -Force
}

$outputFile = "C:\tmp\Uninstall-Software.txt"

# Target software name
$softwareName = "TargetSoftware" # Replace this with the name of the software you want to uninstall

$uninstallPaths = @{
    'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall' = 'System-wide'
    'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall' = 'System-wide'
    'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall' = 'User-specific'
}

$softwareKeys = @()

foreach ($path in $uninstallPaths.Keys) {
    $softwareKeys += Get-ChildItem -Path $path | Where-Object { (Get-ItemProperty -Path $_.PsPath).DisplayName -like "*$softwareName*" } | ForEach-Object {
        [PSCustomObject]@{
            Key          = $_.PsPath
            DisplayName  = (Get-ItemProperty -Path $_.PsPath).DisplayName
            Version      = (Get-ItemProperty -Path $_.PsPath).DisplayVersion
            InstallScope = $uninstallPaths[$path]
            UninstallString = (Get-ItemProperty -Path $_.PsPath).UninstallString
        }
    }
}

function WriteLog {
    param (
        [Parameter(Mandatory=$true)]
        [string]$message
    )

    # Format current date and time
    $currentDateTime = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")

    # Prefix message with current date and time
    $logMessage = "[$currentDateTime] $message"

    Write-Output $logMessage
    Add-Content -Path $outputFile -Value $logMessage
}

foreach ($software in $softwareKeys) {
    $message = "Uninstalling $($software.DisplayName) (Version: $($software.Version)) [Install Scope: $($software.InstallScope)]..."
    
    WriteLog $message
    
    if ($software.UninstallString) {
        # Execute the uninstall command
        Start-Process -Wait -FilePath "cmd.exe" -ArgumentList "/c $($software.UninstallString) /quiet /norestart"
        
        $completedMessage = "$($software.DisplayName) (Version: $($software.Version)) has been uninstalled."
        
        WriteLog $completedMessage
    }
}

if (-not $softwareKeys) {
    $noSoftwareMessage = "No $softwareName versions found."
    WriteLog $noSoftwareMessage
}

How the Script Works

  1. Log Directory: The script first ensures that a directory for logs exists, creating one if it doesn’t.

  2. Registry Scanning: The script then looks into specific registry paths where installed software details are usually kept, both for 32-bit and 64-bit installations.

  3. Software Search: It identifies any software with a specific keyword in its display name from the registry entries.

  4. Uninstallation: For each version found, it uses the UninstallString from the registry to execute a silent uninstallation.

  5. Logging: Finally, it logs all actions taken during the process to a text file, which can be audited later if needed.

Deploying Through InTune

  1. Package the Script: Use the IntuneWinAppUtil.exe utility to package your PowerShell script into an .intunewin file.

  2. InTune Upload: Go to the Microsoft Endpoint Manager admin center, navigate to Apps > All apps > Add, and upload your .intunewin package.

  3. Configuration: During the upload, set the install and uninstall commands. Use something like powershell -executionpolicy bypass -file YourScriptName.ps1 as the install command.

  4. User Assignment: Finally, assign the ‘app’ to the relevant user groups in your organization.

Managing software installations in a large organization need not be a daunting task. With the right PowerShell script and InTune, you can efficiently handle software conflicts and ensure that users are running the most up-to-date versions of all necessary applications. This approach reduces the need for administrative intervention and ensures a smoother user experience across your organization.