I've had the need to export BizTalk Applications using scripts, but with the requirement to not export web directories or global parties to the .msi file.
This can be done using the GUI:
However, it cannot be done (that I know of) using the btstask command with the exportapp parameter.
I opted for creating a PowerShell script that more or less extends the btstask exportapp command. The script takes a few parameters making it a bit more versatile:
$appName is the name of the BizTalk application to export
$exportPath is the path to write the .msi file to
$exportWebDir is a boolean indicating whether to also export web directories or not
$removeDefaultBinding is a boolean controlling whether the default bindings will be written to the .msi file or not. This is handy to remove in those cases you want to just keep bindings for dev/test/production environments.
$exportGlobalParties is a boolean controlling whether to include the Global Parties in the binding file or not. These will of course not be written if for instance you only have the default bindings and set the parameter $removeDefaultBinding to false and $exportGlobalParties to true, since the binding file never will be written at all.
The script basically dumps a ResourceSpecification file which then gets modified depending on the parameters entered. This ResourceSpecification is then used as input to the btstask exportapp command creating an .msi file according to our needs.
The full script looks like this:
param($appName, $exportPath, $exportWebDir, $removeDefaultBinding, $exportGlobalParties)
Write-Output "Exporting ResourceSpec..."
BTSTask ListApp /ApplicationName:$appName /ResourceSpec:$pwd\ResourceSpecTemp.xml
If (!($?))
{
throw "Could not export resource specification. Verify application name."
}
Write-Output "Reading ResourceSpec..."
$xmlResource = [xml] (Get-Content $pwd\ResourceSpecTemp.xml)
If ($exportWebDir -eq $false)
{
Write-Output "Removing web directories..."
$delnodes = $xmlResource.SelectNodes("/*[local-name()='ResourceSpec' and namespace-uri()='http://schemas.microsoft.com/BizTalk/ApplicationDeployment/ResourceSpec/2004/12']/*[local-name()='Resources' and namespace-uri()='http://schemas.microsoft.com/BizTalk/ApplicationDeployment/ResourceSpec/2004/12']/*[local-name()='Resource' and namespace-uri()='http://schemas.microsoft.com/BizTalk/ApplicationDeployment/ResourceSpec/2004/12'][@Type='System.BizTalk:WebDirectory']")
ForEach($delnode in $delnodes)
{
[void]$xmlResource.ResourceSpec.Resources.RemoveChild($delnode)
}
}
If ($removeDefaultBinding -eq $true)
{
Write-Output "Removing Default binding info..."
$delnodes = $xmlResource.SelectNodes("/*[local-name()='ResourceSpec' and namespace-uri()='http://schemas.microsoft.com/BizTalk/ApplicationDeployment/ResourceSpec/2004/12']/*[local-name()='Resources' and namespace-uri()='http://schemas.microsoft.com/BizTalk/ApplicationDeployment/ResourceSpec/2004/12']/*[local-name()='Resource' and namespace-uri()='http://schemas.microsoft.com/BizTalk/ApplicationDeployment/ResourceSpec/2004/12'][@Type='System.BizTalk:BizTalkBinding'][@Luid='Application/$appName']")
ForEach($delnode in $delnodes)
{
[void]$xmlResource.ResourceSpec.Resources.RemoveChild($delnode)
}
}
Write-Output "Saving modified ResourceSpec..."
$xmlResource.Save("$pwd\ResourceSpecTemp.xml")
Write-Output "Exporting application $appName..."
If ($exportGlobalParties -eq $true)
{
$globalPartiesParam = "/G"
}
else
{
$globalPartiesParam = ""
}
BTSTask ExportApp /ApplicationName:$appName /Package:$exportPath\$appName.msi /ResourceSpec:$pwd\ResourceSpecTemp.xml $globalPartiesParam
If (!($?))
{
throw "Could not export application. Verify application name and parameters."
}
Write-Output "Cleaning up..."
Remove-Item $pwd\ResourceSpecTemp.xml
Write-Output "DONE!"
Exit 0
And will be called like so:
ExportBtsMsi.ps1 BtsDemo1 "C:\" false false false
Which will result in output like this:
Ramblings, thoughts and experiences from the life as a BizTalk architect (as well as everything else I catch sight of).
Showing posts with label MSI package. Show all posts
Showing posts with label MSI package. Show all posts
Tuesday, February 26, 2013
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:
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" }
}
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:
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:
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:
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.
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.
Subscribe to:
Posts (Atom)


