Showing posts with label deployment. Show all posts
Showing posts with label deployment. Show all posts

Tuesday, February 26, 2013

Script BizTalk Application export to .msi with options using PowerShell

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:



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" }
}

Monday, August 27, 2012

Why I created my own PowerShell scripts for BizTalk deployment

Most of us working with BizTalk has at one point or another started scripting our deployments instead of doing them manually. I have during the years seen many variants of automating the deployment process. At one place the "old" way of using bat-files calling BTSTASK where used extensively. At another, a custom .Net program with a GUI was developed that would call the different API:s available. I myself resorted to using PowerShell to create a fully automatic deployment routine.

My scripts has two parts. One with different functions that I wrote and one part that encapsulates the functions and executes them in the correct order. As input I create an XML file that specifies which .msi packages that are to be deployed, which applications to remove/update/add, settings for the IIS virtual directories and so on. Since it is quite common during complicated deployments that something will err, I added functions to test run the scripts in advance. The test run simply checks for dependencies in the BizTalk configuration, making sure that applications can be modified as configured. It also checks that all files needed are provided in the setup package.

When creating the scripts, I briefly looked at using the BizTalk Deployment Framework and other extensions to PowerShell, but opted on developing it all in my own scripts. This due to several reasons, each quite strong in my case:
  • I would learn more about PowerShell as well as the API:s and tools found in BizTalk
  • I would know exactly where things could go wrong and what to do about it.
  • It allowed me a greater flexibility since nothing was hidden in dll files or needed installation. As long as PowerShell was installed on the servers, the scripts would work. Nothing else is needed. This is also something that administrators liked. Some of them are a bit weary about installing "some stuff I found on the net". 
This post is the theoretical background to posts that are to follow that will showcase and explain my scripts in individual blocks. I have already started in advance with the post Recycle IIS application pools using PowerShell, and more snippets are to come.

Wednesday, January 19, 2011

Recycle IIS application pools using PowerShell

I have been working on a collection of deployment scripts for BizTalk solutions using PowerShell. One functionality is installation of services hosted in the IIS. After installation, the application pool need a restart in order to properly pick up the new/updated services.

At first, I just recycled all available application pools by the following command line

& $env:windir\system32\inetsrv\appcmd list apppools /xml | & $env:windir\system32\inetsrv\appcmd recycle apppools /in

First we list all available application pools and then we pipe this list as input into the recycle command.

While nice, it is unnecessary to recycle those application pools that are unaffected by our installation.

The main finesse in my scripts are that they are picking up all information on what to do from a configuration file. This XML file includes among other things the application pool for each service to install in IIS. Based on this I updated my recycle command to recycle each and every application pool that were specified in the configuration file. In order to not restart an application pool more than once, I pipe the foreach with sort-object and get-unique.

# List to hold the apppools
$appPools = New-Object System.Collections.ArrayList

# Loop through the objects in the xml file
# and extract the apppool name and add to the list
ForEach($wcfSetup in $xmlFile.DeployConfiguration.WcfSetups.WcfSetup)
{
    [void]$appPools.Add($wcfSetup.ApplicationPool.Trim())
}

# Recycle each unique apppool in the list
foreach($appPool in $appPools | sort-object | get-unique)
{
    & $env:windir\system32\inetsrv\appcmd recycle apppool /apppool.name:"$appPool"
}