Showing posts with label msiexec. Show all posts
Showing posts with label msiexec. Show all posts

Monday, December 10, 2012

"Clever" uninstall of msi packages/applications using PowerShell

When I created my own PowerShell script library for BizTalk deployment automation I ran across the need to uninstall applications, both BizTalk applications and non-BizTalk ones, by only knowing their name.

At first, I solved it using a WMI query like so:
$name = "application name"
$product = Get-WmiObject -Class Win32_Product -Filter "name='$name'" -ComputerName "localhost"
[void]$product.Uninstall()

This is not a recommended way of doing it though. It is both very slow and also causes a bit of spamming in the Windows Eventlog since the query in fact does a reconfigure of ALL applications installed. This reconfiguration can also cause a bit of other issues in some cases.

I then created another way of trying to uninstall applications in a more failsafe and secure way by using msiexec with the uninstall flag. The tricky part was to find a way to get the product key in order to be able to use msiexec since it requires this for uninstalling an application. The result can be found below.

The script function will take the application name as argument. It will then via the registry (note that this is configured for x64, so change the path if you are running x86) look up the application settings. If the application can be found via name (it should), we extract the product key. Then this is used as a parameter to msiexec.

If the product key cannot be found (it happens), we will instead try to read the uninstall string that is set when installing the application. Windows will run this string when you choose to uninstall an application, so why do not we use it? If found, we extract the product key and do our msiexec call.

If all fail, we throw an exception to be caught in the real part of the script.

This is the full script function:

Function Uninstall-Program([string]$name)
{
    $success = $false

    # Read installation information from the registry
    $registryLocation = Get-ChildItem "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\"
    foreach ($registryItem in $registryLocation)
    {
        # If we get a match on the application name
        if ((Get-itemproperty $registryItem.PSPath).DisplayName -eq $name)
        {
            # Get the product code if possible
            $productCode = (Get-itemproperty $registryItem.PSPath).ProductCode
           
            # If a product code is available, uninstall using it
            if ([string]::IsNullOrEmpty($productCode) -eq $false)
            {
                Write-Host "Uninstalling $name, ProductCode:$code"
           
                $args="/uninstall $code"

                [diagnostics.process]::start("msiexec", $args).WaitForExit()
               
                $success = $true
            }
            # If there is no product code, try to read the uninstall string
            else
            {
                $uninstallString = (Get-itemproperty $registryItem.PSPath).UninstallString
               
                if ([string]::IsNullOrEmpty($uninstallString) -eq $false)
                {
                    # Grab the product key and create an argument string
                    $match = [RegEx]::Match($uninstallString, "{.*?}")
                    $args = "/x $($match.Value) /qb"

                    [diagnostics.process]::start("msiexec", $args).WaitForExit()
                   
                    $success = $true
                }
                else { throw "Unable to uninstall $name" }
            }
        }
    }
   
    if ($success -eq $false)
    { throw "Unable to find application $name" }
}

Friday, October 29, 2010

Find all possible parameters for an MSI package installation using msiexec

While working on a library of powershell scripts to do unattended installations of BizTalk applications (and all adjacent files and packages) I needed to find out how to specify the settings for an MSI package in order to do a complete unattended install of it using msiexec.exe.

The MSI I was working with was a setup package for a WCF service. Since this installs to the IIS, both website, virtual directory as well as application pool is needed to be specified during the installation. The question is, what are the correct parameter switches for setting these?

Simple enough, these can be found by doing an install of the MSI and logging a verbose output to file. First, run msiexec with logging enabled:

msiexec /I package.msi /L*V installationlog.txt

Then look in the logfile for the text PROPERTY CHANGE. In the following example, the virtual directory is set using the property TARGETVDIR which then also can be used as a parameter to the msiexec command to set the property from outside the GUI:

Action start 15:41:42: WEBCA_TARGETVDIR.
MSI (c) (F4:8C) [15:41:42:943]: Note: 1: 2235 2: 3: ExtendedType 4: SELECT `Action`,`Type`,`Source`,`Target`, NULL, `ExtendedType` FROM `CustomAction` WHERE `Action` = 'WEBCA_TARGETVDIR'
MSI (c) (F4:8C) [15:41:42:943]: PROPERTY CHANGE: Adding TARGETVDIR property. Its value is 'MyWcfServiceLibrary'.
Action ended 15:41:42: WEBCA_TARGETVDIR. Return value 1.
MSI (c) (F4:8C) [15:41:42:943]: Doing action: WEBCA_SetTARGETSITE
Action 15:41:42: WEBCA_SetTARGETSITE.
Action start 15:41:42: WEBCA_SetTARGETSITE.

Note that the custom parameters are not to be set as normal switches with a leading slash /. In my case, the command will look like this:

msiexec /I package.msi /qb TARGETSITE="/LM/W3SVC/1" TARGET VDIR="MyWCFLibrary" TARGETAPPPOOL="BtsAppPoolC"

This will do a complete unattended install of the WCF service to IIS with basic UI and set the needed properties to my preferred values instead of the defaults.