Public/Invoke-MsiExec.ps1

function Invoke-MsiExec
{
    <#
        .DESCRIPTION
            Install or uninstall software using msiexec
        
        .PARAMETER FilePath
            Specify the path to the MSI or MSP file
    
        .PARAMETER Guid
            Specify the GUID of a software to uninstall
    
        .PARAMETER Install
            Instruct msiexec to operate in install mode
    
        .PARAMETER Uninstall
            Instruct msiexec to operate in uninstall mode
    
        .PARAMETER Patch
            Instruct msiexec to operate in patch mode
    
        .PARAMETER Arguments
            Specify additional arguments to pass to msiexec
        
        .PARAMETER ExitCodes
            Specify non-standard exit codes (Comma seperated list)
    
        .EXAMPLE
            Install an MSI
                Invoke-MsiExec -Install -FilePath "$PSScriptRoot\Setup.msi" -Arguments '/qn /norestart TRANSFORMS="$PSScriptRoot\Settings.mst"'
    
            Install an MSI file with non-standard exit codes 2 and 8
                Invoke-MsiExec -Install -FilePath "$PSScriptRoot\Setup.msi" -Arguments '/qn /norestart' -ExitCodes '2,8'
            
            Install an MSP file
                Invoke-Msiexec -Patch -FilePath "$PSScriptRoot\Patch.msp" -Arguments '/qn /norestart'
    
            Uninstall an MSI
                Invoke-MsiExec -Uninstall -Guid "{00000000-0000-0000-0000-000000000000}" -Arguments '/qn /norestart'
    
        .NOTES
            Created by: Jon Anderson (@ConfigJon)
            Modified: 2023-07-03
    #>

    [CmdletBinding()]
    param(
        [parameter(Mandatory = $false)][ValidateScript({
            if(!($_ | Test-Path))
            {
                throw "$_ was not found"
            }
            if(!($_ | Test-Path -PathType Leaf))
            {
                throw "$_ is not a file path"
            }
            if(($_ -notmatch "(\.msi)") -and ($_ -notmatch "(\.msp)"))
            {
                throw "$_ is not an MSI or MSP file"
            }
            return $true
        })]
        [System.IO.FileInfo]$FilePath,
        [parameter(Mandatory = $false)][ValidateScript({
            if($_ -notmatch ("^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$"))
            {
                throw "$_ is not a properly formatted GUID"
            }
        })]
        [String]$Guid,
        [parameter(Mandatory = $false)][ValidateNotNullOrEmpty()]
        [String]$Arguments,
        [parameter(Mandatory = $false)][ValidateNotNullOrEmpty()]
        [String]$ExitCodes,
        [parameter(Mandatory = $false)][ValidateNotNullOrEmpty()]
        [Switch]$Install,
        [parameter(Mandatory = $false)][ValidateNotNullOrEmpty()]
        [Switch]$Uninstall,
        [parameter(Mandatory = $false)][ValidateNotNullOrEmpty()]
        [Switch]$Patch
    )

    if(!($Install) -and !($Uninstall) -and !($Patch))
    {
        Stop-Script -ErrorMessage "One of the Install or Uninstall or Patch parameters must be specified"
    }
    if($Install -and ($Uninstall -or $Patch))
    {
        Stop-Script -ErrorMessage "Only one of the Install or Uninstall or Patch parameters may be specified"
    }
    if($Uninstall -and ($Install -or $Patch))
    {
        Stop-Script -ErrorMessage "Only one of the Install or Uninstall or Patch parameters may be specified"
    }
    if($Patch -and ($Install -or $Uninstall))
    {
        Stop-Script -ErrorMessage "Only one of the Install or Uninstall or Patch parameters may be specified"
    }
    if($Install -and !($FilePath))
    {
        Stop-Script -ErrorMessage "The FilePath parameter must be specified when using the Install parameter"
    }
    if($Patch -and !($FilePath))
    {
        Stop-Script -ErrorMessage "The FilePath parameter must be specified when using the Patch parameter"
    }
    if($Install -and $Guid)
    {
        Stop-Script -ErrorMessage "The Guid parameter should not be specified when using the Install parameter"
    }
    if($Patch -and $Guid)
    {
        Stop-Script -ErrorMessage "The Guid parameter should not be specified when using the Patch parameter"
    }

    $ArgumentList = New-Object 'System.Collections.Generic.List[string]'
    if($ExitCodes)
    {
        $ExitSplit = $ExitCodes.Split(',')
    }
    if($Install)
    {
        $ArgumentList.Add('/i')
    }
    if($Uninstall)
    {
        $ArgumentList.Add('/x')
    }
    if($Patch)
    {
        $ArgumentList.Add('/p')
    }
    if($FilePath)
    {
        $StringFilePath = $FilePath.ToString()
        $StringFilePath = $StringFilePath.insert(0,'"')
        $StringFilePath+='"'
        $ArgumentList.Add($StringFilePath)
    }
    if($Guid)
    {
        $ArgumentList.Add($Guid)
    }
    if($Arguments)
    {
        $Arguments = $ExecutionContext.InvokeCommand.ExpandString($Arguments)
        $ArgumentList.Add($Arguments)
    }
    Write-LogEntry -Value "Running Command: MsiExec.exe $ArgumentList" -Severity 1
    $ExitCode = (Start-Process -FilePath "MsiExec.exe" -ArgumentList $ArgumentList -Wait -PassThru).ExitCode
    if(($ExitCode -ne 0) -and ($ExitCode -ne 3010) -and !($ExitSplit -contains $ExitCode))
    {
        Stop-Script -ErrorMessage "Msiexec.exe terminated with an error: $ExitCode"
    }
    Write-LogEntry -Value "The exit code is $ExitCode" -Severity 1
}