Invoke-InstalledApplicationManagement.ps1


<#PSScriptInfo
 
.VERSION 1.1.0
 
.GUID b7544f87-8485-445a-9d05-d50cb36c3e67
 
.AUTHOR Francisco Nabas
 
.COMPANYNAME
 
.COPYRIGHT (c) 2021. All rights reserved.
 
.TAGS
 
.LICENSEURI https://github.com/FranciscoNabas/PowerShell/blob/main/LICENSE
 
.PROJECTURI https://github.com/FranciscoNabas/PowerShell/blob/main/Invoke-InstalledApplicationManagement.ps1
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
 
 
#>
 



<#
 
.DESCRIPTION
- Check for apllications installed on the machine with the input Name.
- If a match is found on the registry, creates an object with its Name, Version and UninstallString.
- If no match is found on the registry, the Win32_Product CIM/WMI class is queried and an object is created with the app Name, Version and CimInstance.
- If the Uninstall switch is called, check if the installed version is less than input version and uninstall the application(s).
- If the ForceUninstall switch is called, uninstall the application(s) without checking the version.
 
#>
 

Param()
function Invoke-InstalledApplicationManagement {

    <#
 
    .SYNOPSIS
 
        This solution was designed to identify applications installed on the machine and remove it if required.
 
    .PARAMETER Name
 
        Name of the application to manage.
        The input will be set between wildcards.
 
    .PARAMETER Version
 
        Application current version. Versions older than this will be consider superseded.
 
    .PARAMETER Uninstall
 
        If called will uninstall superseded versions found.
 
    .PARAMETER ForceUninstall
 
        Caled with the Uninstall switch. Skips the version check and uninstall the application(s) regardless of the version.
 
    .PARAMETER MsiParameters
 
        MSI parameters and switches to be used on the uninstallation.
        Used ONLY when the object is found on the registry AND the UninstallString uses MsiExec.exe. In any other case it's ignored.
 
    .EXAMPLE
 
        Invoke-InstalledApplicationManagement -Name 'ApplicationName' -Version '1.1.0'
        Invoke-InstalledApplicationManagement 'ApplicationName' '1.1.0'
        Invoke-InstalledApplicationManagement 'ApplicationName' '1.1.0' -Uninstall -MsiParameters '/qn /norestart'
        Invoke-InstalledApplicationManagement 'ApplicationName' -Uninstall -MsiParameters '/qn /norestart' -ForceInstall
 
    #>


    #Requires -RunAsAdministrator

    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'UseVersion')]
    param (

        [Parameter  (Mandatory, Position = 0, ParameterSetName = 'UseVersion')]
        [Parameter  (Mandatory, Position = 0, ParameterSetName = 'ForceUninstall')]
        [Parameter  (Mandatory, Position = 0, ParameterSetName = 'UninstallWVersion')]
        [ValidateNotNullOrEmpty()]
        [string]    $Name,

        [Parameter  (Mandatory, Position = 1, ParameterSetName = 'UseVersion')]
        [Parameter  (Mandatory, Position = 1, ParameterSetName = 'UninstallWVersion')]
        [ValidateNotNullOrEmpty()]
        [string]    $Version,

        [Parameter  (Mandatory, ParameterSetName = 'UninstallWVersion')]
        [Parameter  (Mandatory, ParameterSetName = 'ForceUninstall')]
        [switch]    $Uninstall,

        [Parameter  (Mandatory, ParameterSetName = 'ForceUninstall')]
        [switch]    $ForceUninstall,

        [Parameter  (ParameterSetName = 'ForceUninstall')]
        [Parameter  (ParameterSetName = 'UninstallWVersion')]
        [ValidateNotNullOrEmpty()]
        [string]    $MsiParameters

    )
    
    begin {

        #region Functions
        function Add-Log {
        
            param (

                [Parameter  (Mandatory = $true)]
                [string]    $LogValue,

                [Parameter  (Mandatory = $true)]
                [ValidateSet("Info", "Warning", "Error")]
                [string]    $Type,

                [Parameter  (Mandatory = $true)]
                [ValidateNotNullOrEmpty()]
                [string]    $Component
            )
            switch ($Type) {
                "Info" {
                    [int]$Type = 1
                }
                "Warning" {
                    [int]$Type = 2
                }
                "Error" {
                    [int]$Type = 3
                }
            }    
            $Source = $MyInvocation.MyCommand.Name
            $Content =  "<![LOG[$LogValue]LOG]!>" +`
                        "<time=`"$(Get-Date -Format "HH:mm:ss.ffffff")`" " +`
                        "date=`"$(Get-Date -Format "M-d-yyyy")`" " +`
                        "component=`"$Component`" " +`
                        "context=`"$([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)`" " +`
                        "type=`"$Type`" " +`
                        "thread=`"$([Threading.Thread]::CurrentThread.ManagedThreadId)`" " +`
                        "file=`"$Source`">"

            if (!(Test-Path "$Env:windir\Logs\InstalledApplicationManagement" -PathType Container)) {
                $null = mkdir "$Env:windir\Logs\InstalledApplicationManagement"
            }
            try {
                Add-Content -Path "$Env:windir\Logs\InstalledApplicationManagement\InstalledApplicationManagement-$Name-$Env:COMPUTERNAME.log" -Value $Content -Force -ErrorAction Stop
            }
            catch {
                Start-Sleep -Milliseconds 700
                Add-Content -Path "$Env:windir\Logs\InstalledApplicationManagement\InstalledApplicationManagement-$Name-$Env:COMPUTERNAME.log" -Value $Content -Force
            }
        }

        function Get-InstalledApplicationManagement {
            
            [CmdletBinding()]
            param (

                [Parameter (Mandatory)]
                [string]   $AppName,

                [Parameter  ()]
                [string]    $uninstallParameters

            )

            #region InitialVariablePayload
            Write-Verbose "Searching for installed applications on the machine with name: '$AppName'. $(Get-Date)."
            Add-Log -Type "Info" -Component "ApplicationSearch" -LogValue "Searching for installed applications on the machine with name: $AppName."
            $RegObjects = $null
            $AppProperties = @()
            $RegPath = @(
                'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\'
                'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\'
            )
            $RegObjects = $RegPath | Get-ChildItem | Get-ItemProperty | Where-Object {$_.DisplayName -like "*$AppName*"} ## Looking for the application on the registry.
            #endregion

            #region UsingRegistry
            if ($RegObjects) {
                foreach ($Object in $RegObjects) {
                    if ($Object.UninstallString -like 'MsiExec*') { ## If UninstallString uses 'MsiExec.exe', we parse it and add the MsiParameters
                        $parameters = "$uninstallParameters /l*vx+! ""%windir%\Logs\InstalledApplicationManagement\[MSI]$($Object.DisplayName)-$($Object.DisplayVersion)-Uninstall.log"""
                        $UninstallString = ($Object.UninstallString).Replace('/I', '/X')
                        $UninstallString = $UninstallString -replace '$', " $parameters"
                        Write-Verbose "Adding application '$($Object.DisplayName)' version '$($Object.DisplayVersion)' to the control. $(Get-Date)"
                        Add-Log -Type "Info" -Component "ApplicationSearch" -LogValue "Adding application '$($Object.DisplayName)' version '$($Object.DisplayVersion)' to the control."
                        $AppProperties += [PSCustomObject]@{
                            Name = $Object.DisplayName
                            Version = $Object.DisplayVersion
                            UninstallString = $UninstallString
                            UninstallMethod = 'Registry'
                        }
                    }
                    else { ## If UninstallString don't use MsiExec.exe (probably uses the app .exe), we just Invoke it,
                        Write-Verbose "Adding application '$($Object.DisplayName)' version '$($Object.DisplayVersion)' to the control. $(Get-Date)"
                        Add-Log -Type "Info" -Component "ApplicationSearch" -LogValue "Adding application '$($Object.DisplayName)' version '$($Object.DisplayVersion)' to the control."
                        $AppProperties += [PSCustomObject]@{
                            Name = $Object.DisplayName
                            Version = $Object.DisplayVersion
                            UninstallString = $Object.UninstallString
                            UninstallMethod = 'Registry'
                        }
                    }
                }
            }
            #endregion
    
            #region UsginCimInstance
            else { ## If no registry keys were found with the provided Name we query Win32_Product CIM\WMI class.
                if ($Verbose) { $params = @{ Verbose = $true; Query = "Select * From Win32_Product Where Name Like '%$AppName%'" } } ## Preparing the parameters for splatting on Invoke-CimInstance.
                else { $params = @{ Query = "Select * From Win32_Product Where Name Like '%$AppName%'" } }
                $cimInstance = Get-CimInstance @params
                if ($cimInstance) {
                    foreach ($product in $cimInstance) {
                        Write-Verbose "Adding application '$($Object.DisplayName)' version '$($Object.DisplayVersion)' to the control. $(Get-Date)"
                        Add-Log -Type "Info" -Component "ApplicationSearch" -LogValue "Adding application '$($Object.DisplayName)' version '$($Object.DisplayVersion)' to the control."
                        $AppProperties += [PSCustomObject]@{
                            Name = $product.Name
                            Version = $product.Version
                            UninstallMethod = 'CimMethod'
                            CimInstance = $product
                        }
                    }
                }
                else {
                    Write-Verbose "Not found applications with name: $Name on the machine. $(Get-Date)."
                    Add-Log -Type "Warning" -Component "ApplicationSearch" -LogValue "Not found applications with name: $Name on the machine."
                    continue
                }
            }
            #endregion

            return $AppProperties
            
        }

        function Remove-InstalledApplicationManagement {
            
            [CmdletBinding(SupportsShouldProcess)]
            param (
                
                [Parameter  (Mandatory = $true)]
                [PSCustomObject]    $appObject,

                [Parameter  (Mandatory = $false)]
                [System.Version]    $appVersion
            )

            if ($appObject) {
                foreach ($Application in $appObject) {
                    $uninstallSwitch = $false
                    if ($appVersion -and ([System.Version]::Parse($Application.version) -lt $appVersion)) {
                        $uninstallSwitch = $true
                    }
                    elseif ($appVersion -and ([System.Version]::Parse($Application.version) -ge $appVersion)) {
                        if ($PSCmdlet.ShouldProcess(("Application {0} with version equal or greater than current {1}. Skipping uninstallation." -f $Application.Name, $Application.Version), $null, $null)) {
                            Write-Verbose "Application $($Application.Name) with version equal or greater than current $($Application.Version). Skipping uninstallation. $(Get-Date)."
                            Add-Log -Type "Info" -Component "UninstallApplications" -LogValue "Application $($Application.Name) with version equal or greater than current $($Application.Version). Skipping uninstallation."    
                        }
                    }
                    elseif (!$appVersion) {
                        $uninstallSwitch = $true
                    }
                    if ($uninstallSwitch) {
                        switch ($Application.UninstallMethod) {
                            ## Uninstalling using the UninstallString found on HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall (x64 AND x86) ##
                            'Registry' {
                                if ($PSCmdlet.ShouldProcess(("[Registry] - Uninstalling application '{0}'." -f $Application.Name), ("Are you sure you want to uninstall the application '{0}'?" -f $Application.Name), $null)) {
                                    Write-Verbose "Uninstalling application '$($Application.Name)' version '$($Application.Version)'. $(Get-Date)."
                                    Add-Log -Type "Info" -Component "UninstallApplications" -LogValue "Uninstalling application '$($Application.Name)' version '$($Application.Version)'."
                                    Write-Verbose "Uninstall string: $($Application.UninstallString). $(Get-Date)."
                                    Add-Log -Type "Info" -Component "UninstallApplications" -LogValue "Uninstall string: $($Application.UninstallString)."
                                    try {
                                        Start-Process -FilePath cmd -ArgumentList '/c', $Application.UninstallString -NoNewWindow -Wait
                                        Write-Verbose "Uninstallation completed for application $($Application.Name). $(Get-Date)."
                                        Add-Log -Type "Info" -Component "UninstallApplications" -LogValue "Uninstallation completed for application $($Application.Name)."
                                    }
                                    catch {
                                        Write-Verbose "Unable to uninstall application $($Application.Name). $($_.Exception.Message) $(Get-Date)."
                                        Add-Log -Type "Error" -Component "UninstallApplications" -LogValue "Unable to uninstall application $($Application.Name). $($_.Exception.Message)"
                                    }
                                }
                            }
                            ## Application not found on registry. Uninstalling Calling the CIM Method 'Uninstall' ##
                            'CimMethod' {
                                if ($PSCmdlet.ShouldProcess(("[cimInstance] - Uninstalling application '{0}'." -f $Application.Name), ("Are you sure you want to uninstall the application '{0}'?" -f $Application.Name), $null)) {
                                    Write-Verbose "Uninstalling application '$($Application.Name)' version '$($Application.Version)'. $(Get-Date)."
                                    Add-Log -Type "Info" -Component "UninstallApplications" -LogValue "Uninstalling application '$($Application.Name)' version '$($Application.Version)'."
                                    try {
                                        $process = $Application.CimInstance | Invoke-CimMethod -MethodName 'Uninstall'
                                        if ($process.ReturnValue -eq 0) {
                                            Write-Verbose "Uninstallation completed for application $($Application.Name). $(Get-Date)."
                                            Add-Log -Type "Info" -Component "UninstallApplications" -LogValue "Uninstallation completed for application $($Application.Name)."
                                        }
                                        else {
                                            Write-Verbose "Unable to uninstall application $($Application.Name). $($_.Exception.Message) $(Get-Date)."
                                            Add-Log -Type "Error" -Component "UninstallApplications" -LogValue "Unable to uninstall application $($Application.Name). $($_.Exception.Message)"
                                        }
                                    }
                                    catch {
                                        Write-Verbose "Unable to uninstall application $($Application.Name). $($_.Exception.Message) $(Get-Date)."
                                        Add-Log -Type "Error" -Component "UninstallApplications" -LogValue "Unable to uninstall application $($Application.Name). $($_.Exception.Message)"
                                    }    
                                }
                            }
                        }
                    }
                }
            }
            else {
                if ($PSCmdlet.ShouldProcess("No applications with name $Name found to uninstall. Finishing execution.", $null, $null)) {
                    Write-Verbose "No applications with name $Name found to uninstall. Finishing execution. $(Get-Date)."
                    Add-Log -Type "Info" -Component "UninstallApplications" -LogValue "No applications with name $Name found to uninstall. Finishing execution."
                }
            }
            
        }

        #endregion

        Write-Verbose "Parameter set name: '$($PSCmdlet.ParameterSetName)'"
        #region StartingNewExecution
        Add-Log -Type 'Info' -Component 'StartingNewExecution' -LogValue "################################"
        Add-Log -Type 'Info' -Component 'StartingNewExecution' -LogValue "###### Starting New Execution. #######"
        Add-Log -Type 'Info' -Component 'StartingNewExecution' -LogValue "################################"
        #endregion

    }
    
    process {

        switch ($PSCmdlet.ParameterSetName) {

            'UseVersion' {
                if ($PSCmdlet.ShouldProcess("Searching for applications with name '$Name' on the machine.", $null, $null)) {
                    if ($MsiParameters) { $queryParams = @{ AppName = $Name; uninstallParameters = $MsiParameters } }
                    else { $queryParams = @{ AppName = $Name } }
                    $appProperties = Get-InstalledApplicationManagement @queryParams
                }
            }

            'UninstallWVersion' {
                if ($MsiParameters) { $queryParams = @{ AppName = $Name; uninstallParameters = $MsiParameters } }
                else { $queryParams = @{ AppName = $Name } }
                $appProperties = Get-InstalledApplicationManagement @queryParams
                
                if ($WhatIf) { $removeParams = @{ appObject = $appProperties; appVersion = $Version; WhatIf = $true } }
                elseif ($Verbose) { $removeParams = @{ appObject = $appProperties; appVersion = $Version; Verbose = $true } }
                elseif ($WhatIf -and $Verbose) { $removeParams = @{ appObject = $appProperties; appVersion = $Version; WhatIf = $true; Verbose = $true } }
                else { $removeParams = @{ appObject = $appProperties; appVersion = $Version } }
                Remove-InstalledApplicationManagement @removeParams
            }

            'ForceUninstall' {
                if ($MsiParameters) { $queryParams = @{ AppName = $Name; uninstallParameters = $MsiParameters } }
                else { $queryParams = @{ AppName = $Name } }
                $appProperties = Get-InstalledApplicationManagement @queryParams
                
                if ($WhatIf) { $removeParams = @{ appObject = $appProperties; WhatIf = $true } }
                elseif ($Verbose) { $removeParams = @{ appObject = $appProperties; Verbose = $true } }
                elseif ($WhatIf -and $Verbose) { $removeParams = @{ appObject = $appProperties; WhatIf = $true; Verbose = $true } }
                else { $removeParams = @{ appObject = $appProperties } }
                Remove-InstalledApplicationManagement @removeParams
            }

        }

    }
           
    end {
        
    }

}