top of page
Search

(196) Microsoft Intune - Uninstall/Reinstall App with removing Root CA certs

  • Writer: Mr B SOE way
    Mr B SOE way
  • 7 days ago
  • 5 min read

Applications like GlobalProtect when installed will deploy Root CA certificates (when setup properly), what if a customer wants to remove the Root CA certificates along with an uninstall of GlobalProtect then re-install with another version of GlobalProtect, this is what can be done to achieve this.

What the application will do overall:

  • Kills GlobalProtect process

  • Run fast reference package to uninstall

  • Remove certs based on thumbprints

  • Sleeps for 30 seconds

  • Then installs GlobalProtect 6.3.3

What will we need to do first is to get the thumbnail print of those certificates, running in PowerShell ISE (Without administrator rights): Get-ChildItem -Path Cert:LocalMachine\Root, this will generate:


Make a copy of those thumbprint IDs as we will need to place it in the Install.ps1 script.

$pname = "GlobalProtect"
    $Pversion = "6.3.3"
    $fastPackageReference = (Get-Package $pname).FastPackageReference
    $msiExecCommand = "MsiExec.exe /X `"$fastPackageReference`" /qn"
    #region Config
    $AppName = $PName + "_" + $Pversion + "_UninstallPackage"
    $client = "MrBSOEWay"
    $logPath = "$env:ProgramData\$client\logs"
    $logFile = "$logPath\$appName.log"
    $installer = $PName + "_" + $Pversion + "_Uninstaller.log"
    
    #endregion
    #region Logging
    if (!(Test-Path -Path $logPath)) {
        New-Item -Path $logPath -ItemType Directory -Force | Out-Null
    }


    Start-Transcript -Path $logFile -Force
    Write-Host "Uninstalling $PName $Pversion"

    #Kill processes
    Stop-Process -Name "PanGPA" -Force

    $process = (Start-Process -FilePath "MsiExec.exe" -ArgumentList "/X `"{9B5DB5BD-96A1-4830-8C73-E53204374E17}`" /qn" -Wait -PassThru)

    #Sleep for 3 seconds
    Start-Sleep 3
   
    #Remove certs
Remove-Item -Path "Cert:\LocalMachine\Root\DE0A181E42E6FFCBF4DEDFCCE9BF1ECE06437551" -Force
Remove-Item -Path "Cert:\LocalMachine\Root\7CC55538F5ADB0BE0487E893D2CA37ACF871AFCB" -Force

    #Sleep for 30 seconds
    Start-Sleep 30
    
    #Installing app
    Copy-Item ".\GlobalInstaller" "C:\" -Recurse -Force
    #This is kicking the install fromm this location because of Airlock whitelisting limitations in client's environment.
    #Region Functions
function Get-MsiInformation {
    [CmdletBinding(SupportsShouldProcess = $true, 
        PositionalBinding = $false,
        ConfirmImpact = 'Medium')]
    [Alias("gmsi")]
    Param(
        [parameter(Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = "Provide the path to an MSI")]
        [ValidateNotNullOrEmpty()]
        [System.IO.FileInfo[]]$Path,
 
        [parameter(Mandatory = $false)]
        [ValidateSet( "ProductCode", "Manufacturer", "ProductName", "ProductVersion", "ProductLanguage" )]
        [string[]]$Property = ( "ProductCode", "Manufacturer", "ProductName", "ProductVersion", "ProductLanguage" )
    )

    Begin {
        # Do nothing for prep
    }
    Process {
        
        ForEach ( $P in $Path ) {
            if ($pscmdlet.ShouldProcess($P, "Get MSI Properties")) {            
                try {
                    Write-Verbose -Message "Resolving file information for $P"
                    $MsiFile = Get-Item -Path $P
                    Write-Verbose -Message "Executing on $P"
                    
                    # Read property from MSI database
                    $WindowsInstaller = New-Object -ComObject WindowsInstaller.Installer
                    $MSIDatabase = $WindowsInstaller.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $null, $WindowsInstaller, @($MsiFile.FullName, 0))
                    
                    # Build hashtable for retruned objects properties
                    $PSObjectPropHash = [ordered]@{File = $MsiFile.FullName }
                    ForEach ( $Prop in $Property ) {
                        Write-Verbose -Message "Enumerating Property: $Prop"
                        $Query = "SELECT Value FROM Property WHERE Property = '$( $Prop )'"
                        $View = $MSIDatabase.GetType().InvokeMember("OpenView", "InvokeMethod", $null, $MSIDatabase, ($Query))
                        $View.GetType().InvokeMember("Execute", "InvokeMethod", $null, $View, $null)
                        $Record = $View.GetType().InvokeMember("Fetch", "InvokeMethod", $null, $View, $null)
                        $Value = $Record.GetType().InvokeMember("StringData", "GetProperty", $null, $Record, 1)
 
                        # Return the value to the Property Hash
                        $PSObjectPropHash.Add($Prop, $Value)

                    }
                    
                    # Build the Object to Return
                    $Object = @( New-Object -TypeName PSObject -Property $PSObjectPropHash )
                    
                    # Commit database and close view
                    $MSIDatabase.GetType().InvokeMember("Commit", "InvokeMethod", $null, $MSIDatabase, $null)
                    $View.GetType().InvokeMember("Close", "InvokeMethod", $null, $View, $null)           
                    $MSIDatabase = $null
                    $View = $null
                }
                catch {
                    Write-Error -Message $_.Exception.Message
                }
                finally {
                    Write-Output -InputObject @( $Object )
                }
            } # End of ShouldProcess If
        } # End For $P in $Path Loop

    }
    End {
        # Run garbage collection and release ComObject
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($WindowsInstaller) | Out-Null
        [System.GC]::Collect()
    }
}
<#
End of Function
#>

Try {
    #Read file info
    #$exe = (Get-ChildItem -Recurse -Path "C:\GlobalInstaller\.exe").FullName
    #Add support for multiple exes. In that case the one containing setup.exe will be used.
    if ($exe.count -gt 1) { $exe = $exe | Where-Object { $_ -like "*Setup.exe" } }

    if ($exe) {
        $file = $exe
        $info = (Get-ItemProperty $file).VersionInfo
        $PName = ($info.ProductName -replace '\s+', '')
        $Pversion = ($info.ProductVersion -replace '\s+', '')
    }

    if (!($exe)) {
        $file = (Get-ChildItem -Path "C:\GlobalInstaller\GlobalProtect64.msi").FullName
        $msi = Get-MsiInformation -Path $file
        $PName = ($msi.ProductName -replace '\s+', '')
        $Pversion = ($msi.ProductVersion -replace '\s+', '')
    }


    #region Config
    $AppName = $PName + "_" + $Pversion + "_Package"
    $client = "MrBSOEWay"
    $logPath = "$env:ProgramData\$client\logs"
    $logFile = "$logPath\$appName.log"
    $installer = $PName + "_" + $Pversion + "_Installer.log"
    #endregion
    #region Logging
    if (!(Test-Path -Path $logPath)) {
        New-Item -Path $logPath -ItemType Directory -Force | Out-Null
    }


    Start-Transcript -Path $logFile -Force
    Write-Host "Installing $PName $Pversion"
    #$mst = (Get-ChildItem -Recurse -Path ".\*.mst").Name
    if ($mst) {
        $process = (Start-Process -FilePath $file -ArgumentList TRANSFORMS="$mst", /qn, /norestart, "/l*v $logPath\$installer" -Wait -PassThru).ExitCode

    }
    Else {
        $process = (Start-Process -FilePath $file -ArgumentList /qn, /norestart, "/l*v $logPath\$installer" -Wait -PassThru).ExitCode
        Start-Sleep -seconds 02
        reg import "C:\GlobalInstaller\GlobalProtectPortal.reg" 
        }
        Start-Sleep -Second 03
        Remove-item "C:\GlobalInstaller" -Recurse -Force

}
catch {
    $errorMsg = $_.Exception.Message
}
finally {
    if ($errorMsg) {
        Write-Warning $errorMsg
        Stop-Transcript
        throw $errorMsg
        Exit $process
    }
    else {
        Write-Host "Script completed successfully.."
        Stop-Transcript
        Exit $process
    }
}
    
    Write-Host "Script completed successfully.."
    Stop-Transcript

To break it down from lines 1 to 35, we use Fast Reference to remove GlobalProtect Version 6.3.3, but before we do that we have to stop the process from running which is PanGPA. We don’t want to stop the service. Once the service has stopped running, we execute the command to uninstall it - we allow up to 3 seconds for it to do whatever it needs to do before.


With removing the thumbnails for those Root certs, we use the “ReplaceWithThumbNailID1” and “ReplaceWithThumbNailID2” with whatever was gathered when running Get-ChildItem -Path Cert:LocalMachine\Root


Remove-Item -Path "Cert:\LocalMachine\Root\ReplaceWithThumbNailID1" -Force

Remove-Item -Path "Cert:\LocalMachine\Root\ReplaceWithThumbNailID2" -Force

$pname = "GlobalProtect"
    $Pversion = "6.3.3"
    $fastPackageReference = (Get-Package $pname).FastPackageReference
    $msiExecCommand = "MsiExec.exe /X `"$fastPackageReference`" /qn"
    #region Config
    $AppName = $PName + "_" + $Pversion + "_UninstallPackage"
    $client = "MrBSOEWay"
    $logPath = "$env:ProgramData\$client\logs"
    $logFile = "$logPath\$appName.log"
    $installer = $PName + "_" + $Pversion + "_Uninstaller.log"
    
    #endregion
    #region Logging
    if (!(Test-Path -Path $logPath)) {
        New-Item -Path $logPath -ItemType Directory -Force | Out-Null
    }


    Start-Transcript -Path $logFile -Force
    Write-Host "Uninstalling $PName $Pversion"

    #Kill processes from running
    Stop-Process -Name "PanGPA" -Force

    $process = (Start-Process -FilePath "MsiExec.exe" -ArgumentList "/X `"{9B5DB5BD-96A1-4830-8C73-E53204374E17}`" /qn" -Wait -PassThru)

    #Sleep for 3 seconds
    Start-Sleep 3
   
    #Remove certs
Remove-Item -Path "Cert:\LocalMachine\Root\ReplaceWithThumbNailID1" -Force
Remove-Item -Path "Cert:\LocalMachine\Root\ReplaceWithThumbNailID2" -Force

    #Sleep for 30 seconds
    Start-Sleep 30

For detect.ps1 - as we are re-installing GlobalProtect 6.3.3 again, this would not work with as the detect script will kick in and “Go hey this installed”

try {
    $appname = 'GlobalProtect';
    $output = 'Detected';
    $Newversion = [System.Version]'6.3.3';
    $Currentversion = ((Get-Package -Name $appname -ErrorAction SilentlyContinue).version)

    if ($Currentversion.count -gt 1 ) {
        if ([System.Version]$Currentversion[0] -ge $Newversion) {
            return $output
        }
    }
    else {
        if ([System.Version]$Currentversion -ge $Newversion) {
            return $output
        }
    }
}

catch { exit }

I added another detection script to check for the log on line 4 where $LogPath

try {
    $appname = 'GlobalProtect'
    $output = 'Detected'
    $Newversion = [System.Version]'6.3.3'
    $LogPath = "C:\ProgramData\MrBSOEWay\logs\GlobalProtect_6.3.3_Installer.log"

    # 1. Check if the specific Installer Log exists
    $LogExists = Test-Path -Path $LogPath

    # 2. Get the current installed version
    $Package = Get-Package -Name $appname -ErrorAction SilentlyContinue
    $Currentversion = $Package.Version

    if ($Currentversion) {
        # Handle cases where multiple versions might be returned (array)
        $LatestInstalled = ($Currentversion | ForEach-Object { [System.Version]$_ } | Sort-Object -Descending)[0]

        # 3. Success Criteria: Version is >= 6.3.3 AND the log file exists
        if ($LatestInstalled -ge $Newversion -and $LogExists) {
            return $output
        }
    }
}
catch {
    # Exit with non-zero if you want the detection to fail on error
    exit 1
}

The overall package should look like this:

Where GlobalInstaller folder shows:


 
 
 

Comments


bottom of page