The Windows Server Solutions SDK gives us some great new functionality for deploying Add-In packages:

  • A proper upgrade process
  • Signing
  • Client installers that can be automatically applied to any joined PCs
  • EULA support
  • Localization support

But, because there’s no such thing as a free lunch, the new functions come with added complexity. As I’ve discussed previously, in order for your Add-In to support in-place upgrades there are a bunch of properties that need to be changed whenever you build a new version of your Add-In:

  1. Package Version in AddIn.xml
  2. ServerBinary File Version in AddIn.xml
  3. Product Version in your installer project (<product>.wxs if you’re using WiX)
  4. Assembly File Version and Assembly Version in AssemblyInfo.cs for any changed assemblies

It’s really easy to forget one of those and break the upgrade process. So, how do we take the pain away?

What follows is my solution for build automation with WSSX files. This is only what works for me, and there are certainly other ways to accomplish the same thing, so feel free to offer your methods in the comments below.

This is going to be a long one so go grab a coffee!

Note: As always, don’t use any GUIDs from my sample code in your projects. Create your own using Tools –> Create GUID in Visual Studio.

Overview

Goals

I have two major goals for this automation process:

  1. Automatically build a valid WSSX package whenever I build my Add-In solution in Visual Studio
  2. Automatically support in-place upgrades for my Add-In

The first one is easy; run makecab.exe with the appropriate metadata files in a post-build target and we’re set. If we want to continue to use the old Windows Home Server v1 “upgrade” model, which means forcing our users to manually uninstall previous versions of our Add-In before installing the latest, then we don’t need to go any further. Can you tell I think that’s a bad idea?

A much better plan is to allow our users to install the new version of our Add-In without worrying about prior installs; the install process is a key first impression we make with our users, and they’ll judge our product harshly if the install experience is crap.

So, our second goal is where the magic happens, and what this post is all about; allowing hassle-free (for the user and for us) in-place upgrades.

Dependencies

I rely heavily on MSBuild, WiX v3.5, Build Version Increment Add-In for Visual Studio, and our WSSX project for Visual Studio 2010. I’m going to assume you’ve had some experience with MSBuild and editing project files.

To follow my process you’ll need to install these Visual Studio extensions:

Unfortunately, the WSSX project doesn’t work with Visual Studio Express. You’ll need Visual Studio 2010 Professional or higher.

If you’re not using WiX to build your installers you’ll need to customize the MSBuild targets I’ll show you below. I don’t have any experience with other Windows Installer builders, so you’re on your own there!

Sample Solution

I’ve put together a sample Visual Studio 2010 solution that demonstrates everything we’re going to walk through below. You’ll need to:

To give you a navigation reference, this is what Solution Explorer looks like in the sample solution:

  • WssxPackageExample is our Dashboard Add-In assembly
  • WssxPackageExample.AddIn is our WSSX project that will build the final *.wssx file, and contains our AddIn.xml metadata file
  • WssxPackageExample.Client32 is the installer for our (fake) client component
  • WssxPackageExample.Client64 is the installer for our (fake) 64-bit client component
  • WssxPackageExample.Server is the installer for our (real) Dashboard Add-in

image

The sample solution actually builds and can be deployed to your test server and the Dashboard Add-In will load. The “fake” client installers will install on any joined PCs, but they don’t actually create any files (we just create a directory tree).

High-level Process

The automation process is driven from the bottom up. The idea is that any change to the project causes a chain of version updates that will ensure that the final *.wssx output will work as an in-place upgrade for existing installations of our Add-In.

There are three distinct stages to our automation, each happening in different projects:

  1. Add-In assembly project: Build Version Incremement Add-in updates the Assembly and File versions of the changed assemblies
  2. Add-In installer project: A pre-build target in the WiX project that builds the installer for the changed assemblies which:
    • Updates the installer’s product version and output file name
    • Updates its file name and file version entry in AddIn.xml
  3. WSSX Package project: A pre-build target in the WSSX project checks all file versions in AddIn.xml and compares them to the current package version, then: 
    • Replaces the package version with the highest file version, if the highest file version is higher than the current package version

    OR

    • Increments the package version by one, if none of the file versions are higher than the current package version

The end result is that:

  • Any changed files get a new version number
  • Their parent Windows Installer packages get a new version number (both in the Windows Installer package itself and the WSSX package’s metadata)
  • The main WSSX package gets a new version number

And, just like that, we hit all our requirements for building a WSSX package that supports upgrades.

1. Add-In Assembly Project

In the sample project, we’re deploying a Dashboard Add-In called “WssxPackageExample”. This project is a C# Class Library project that contains our Dashboard tab. It actually works, so you can build the sample project and deploy it to your test server.

We’re going to use the output of this project to determine the version number of the WiX installer; it’s the core file we’ll be delivering with this WiX installer.

Build Version Increment Settings

We don’t need to do much here. Essentially, we just want to set up Build Version Increment for this product to increase the version number of the output assembly each time we build the project.

  1. Open the Tools drop-down menu inside Visual Studio
  2. Choose Build Version Incremement –> Settings
  3. Select the appropriate project from the list

image

Now we change the versioning style so that our Build and Revision fields of the version number are incremented for each build, and make sure that both Update AssemblyFileVersion and Update AssemblyVersion are set to ‘true’.

image

Now, before every build of this project, the Build and Revision fields of the assembly’s version will be incremented to indicate that this is a new version of the assembly.

Note: Windows Installer only uses the first three fields of a product’s version to determine if an upgrade needs to occur, so we must make sure we’re incrementing at least the Build field each time.

When you make a major change to the product (a large new feature, for example), it’s a good idea to manually change the Major or Minor field as well.

For example, if the current version for the assembly is 1.2.23.1006, and you add a brand new shiny feature, you should edit AssemblyInfo.cs for the project and change the version to 1.3.0.1006. Build Version Increment will change the version to 1.3.1.1007 on the next build.

I like to increment the Revision field to keep track of how many builds the project has had. I don’t reset this field when manually updating the version for a product.

Note: If your Add-In also has client components, you’d apply these changes to the core project reference for each client installer as well.

2. Add-In Installer Project

Our Add-In installer project (WiX, in our case) wraps up our Add-In’s files into a Windows Installer (*.msi) package.

For each WiX project, I designate a target file that serves as the source for version numbers. In the case of Dashboard Add-Ins, this is usually the assembly that contains the main Dashboard tab and associated code.

The target file is used to set the Product Version property and output file name for the Windows Installer package. The Product Version, Product Code, and output file name from the Windows Installer package are then pushed to the AddIn.xml file in the WSSX project.

I’ve talked about automatic version numbers for WiX projects before. This method is a modified version of what we use for WHS v1 Add-Ins.

Note: I’m using a static Product Id in my WiX definitions.

There’s some debate as to whether it’s better to use Major upgrades for every build (by setting Product Id to “*” and allowing WiX to auto-generate a new GUID every build) or to use Minor upgrades unless the there has been a comprehensive change to the product.

The MSDN documentation seems to encourage the use of Minor upgrades, so that’s what I’m doing below. Also, AddIn.xml requires that you use the exact Product Id/Code for the Windows Installer packages you’re including, so I’ve found it much easier to use a static Id value.

If you use “*” for Product ID, then you’ll need to figure out another way of getting the Product Code for AddIn.xml.

Product.wxs

We need to set the Product Version for the *.wxs file, so that the Windows Installer package is built with the correct version metadata.

The magic here is in Project References. When you add a Project Reference to a WiX project, you can use some awesome shortcuts when referencing project outputs in your *.wxs file.

  1. Add a new reference to the WiX project
  2. Change to the Projects tab and select the core assembly for your Add-In
  3. Click Add, and OK

image

Now we can use the TargetFileName property of our Project Reference to bind its output FileVersion as our Product Version.

Edit the *.wxs file and change the Product Version:

  <Product
    Name='WssxPackageExample'
    Id='{EDBAEE8D-BDD1-48AA-8F35-2B2ABCA43E8A}'
    UpgradeCode='{45C6030C-766A-46A5-A635-6FA167018CE3}'
    Language='1033'
    Codepage='1252'
    Version='!(bind.FileVersion.$(var.WssxPackageExample.TargetFileName))'
    Manufacturer='Tentacle Software Ltd.'>

As you can see, we can use our Project Reference with $(var.<ProjectName>.<Property>). We’re using more WiX-magic with bind.FileVersion that extracts the version from our target assembly and returns it as a text string.

Each time we build this *.wxs file WiX will extract the version of our target assembly automatically and assign it to the Version of our Windows Installer package

*.wixproj

Now we need to continue our adventure with MSBuild, by adding a “BeforeBuild” target to our WiX project file that sets the correct output file name and updates AddIn.xml with correct metadata.

  1. Right-click the WiX project node in Solution Explorer and choose “Unload Project”
  2. Right-click the project node again and choose “Edit <projectname>.wixproj”
  3. You’ll be presented with the *.wixproj file in an XML editor

Note: In the sample project, we’re editing WssxPackageExample.Server.wixproj.

At the bottom of the project file, before the closing </Project> node, you’ll see the following comments:

  <!--
	To modify your build process, add your task inside one of the targets below and uncomment it.
	Other similar extension points exist, see Wix.targets.
	<Target Name="BeforeBuild">
	</Target>
	<Target Name="AfterBuild">
	</Target>
	-->

Sounds like us, right? We’re going to paste a big chunk of XML after that comment, and modify the crap out of your build process.

Note: If you find the following hard to read or copy, download the sample project; you’ll find this XML at the bottom of the WssxPackageExample.Server.wixproj file.

  <UsingTask 
	TaskName="Microsoft.Build.Tasks.XmlPoke" 
	AssemblyName="Microsoft.Build.Tasks.v4.0, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
  <UsingTask 
	TaskName="Microsoft.Build.Tasks.XmlPeek" 
	AssemblyName="Microsoft.Build.Tasks.v4.0, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
  <PropertyGroup>
    <TargetProjectName>WssxPackageExample</TargetProjectName>
    <WsxFileName>Product.wxs</WsxFileName>
    <AddInXmlProjectName>WssxPackageExample.AddIn</AddInXmlProjectName>
    <WxsPath>$(ProjectDir)$(WsxFileName)</WxsPath>
    <AddInXmlPath>$(SolutionDir)$(AddInXmlProjectName)\AddIn.xml</AddInXmlPath>
    <WxsNamespace>&lt;Namespace Prefix='wxs' Uri='http://schemas.microsoft.com/wix/2006/wi' /&gt;</WxsNamespace>
    <AddInXmlNamespace>&lt;Namespace Prefix='wss' Uri='http://schemas.microsoft.com/WindowsServerSolutions/2010/03/Addins' /&gt;</AddInXmlNamespace>
  </PropertyGroup>
  <Target Name="BeforeBuild" DependsOnTargets="SetWixOutputFilename;UpdateAddInXmlFileNode" />
  <Target Name="SetWixOutputFilename">
    <!-- Get ProjectReference output assembly versions for project 'WssDeploymentTest' -->
    <AssignProjectConfiguration ProjectReferences="@(ProjectReference)" Condition="'%(Name)' == '$(TargetProjectName)'" SolutionConfigurationContents="$(CurrentSolutionConfigurationContents)">
      <Output TaskParameter="AssignedProjects" ItemName="ProjectReferenceWithBuildConfig" />
    </AssignProjectConfiguration>
    <Message Text="Found project reference %(ProjectReferenceWithBuildConfig.Name) with config %(ProjectReferenceWithBuildConfig.FullConfiguration)" Condition="'@(ProjectReferenceWithBuildConfig)' != ''" />
    <MSBuild Projects="@(ProjectReferenceWithBuildConfig)" UseResultsCache="true" Targets="Build" Properties="%(ProjectReferenceWithBuildConfig.SetConfiguration);%(ProjectReferenceWithBuildConfig.SetPlatform)" ContinueOnError="false">
      <Output TaskParameter="TargetOutputs" ItemName="ProjectReferenceOutputs" />
    </MSBuild>
    <Message Text="Project Outputs: @(ProjectReferenceOutputs)" />
    <!-- Get AssemblyVersions for ProjectReference output assembly -->
    <GetAssemblyIdentity AssemblyFiles="@(ProjectReferenceOutputs)">
      <Output TaskParameter="Assemblies" ItemName="AssemblyVersions" />
    </GetAssemblyIdentity>
    <CreateProperty Value="%(AssemblyVersions.Version)">
      <Output TaskParameter="Value" PropertyName="TargetAssemblyVersion" />
    </CreateProperty>
    <Message Text="Target Assembly Version: $(TargetAssemblyVersion)" />
    <!-- Set WiX TargetName based on OutputName and AssemblyVersions -->
    <CreateProperty Value="$(OutputName).$(TargetAssemblyVersion)">
      <Output TaskParameter="Value" PropertyName="TargetName" />
    </CreateProperty>
    <CreateProperty Value="$(TargetName)$(TargetExt)">
      <Output TaskParameter="Value" PropertyName="TargetFileName" />
    </CreateProperty>
    <CreateProperty Value="$(TargetDir)$(TargetFileName)">
      <Output TaskParameter="Value" PropertyName="TargetPath" />
    </CreateProperty>
    <Message Text="Target File Name: $(TargetFileName)" />
  </Target>
  <Target Name="UpdateAddInXmlFileNode">
    <!-- Get ProductCode from .wxs file -->
    <XmlPeek XmlInputPath="$(WxsPath)" Query="//wxs:Wix/wxs:Product/@Id" Namespaces="$(WxsNamespace)">
      <Output TaskParameter="Result" ItemName="ProductCode" />
    </XmlPeek>
    <Message Text="Product Code: @(ProductCode)"/>
    <!-- Update AddIn.xml file to use correct Name, Version and ProductCode -->
    <XmlPoke XmlInputPath="$(AddInXmlPath)" Query="//wss:Package/wss:ServerBinary/wss:File/wss:Name" Namespaces="$(AddInXmlNamespace)" Value="$(TargetFileName)" />
    <XmlPoke XmlInputPath="$(AddInXmlPath)" Query="//wss:Package/wss:ServerBinary/wss:File/wss:Version" Namespaces="$(AddInXmlNamespace)" Value="$(TargetAssemblyVersion)" />
    <XmlPoke XmlInputPath="$(AddInXmlPath)" Query="//wss:Package/wss:ServerBinary/wss:File/wss:ProductCode" Namespaces="$(AddInXmlNamespace)" Value="@(ProductCode)" />
  </Target>

The comments in the XML above should give you a hint about what’s going on here:

  1. Find our target project’s output
  2. Grab its AssemblyVersion
  3. Set our output file name to <Output>.<Version>.msi
  4. Get the Product ID value from our *.wxs file
  5. Update AddIn.xml with the Product ID, version, and output file name

The key bits you’ll need to customize to get this to work in your projects are in the PropertyGroup at the top of our XML block:

  <PropertyGroup>
    <TargetProjectName>WssxPackageExample</TargetProjectName>
    <WsxFileName>Product.wxs</WsxFileName>
    <AddInXmlProjectName>WssxPackageExample.AddIn</AddInXmlProjectName>

Because you’ll probably have multiple Project References when you build a real Add-In installer, we need to know which project is the target that we’ll use for version numbering. We also need to know the name of the *.wxs file we’re compiling and the name of the WSSX project that contains our AddIn.xml file.

The other bit we need to look at is the File node we’re editing in AddIn.xml. In this case, we’re adding this BeforeBuild target to the *.wixproj that builds the ServerBinary for our Add-In, so we’re setting metadata in AddIn.xml for the ServerBinary node.

Your ClientBinary32 and ClientBinary64 WiX projects will need to edit the following tasks to use the correct xpath query:

    <XmlPoke XmlInputPath="$(AddInXmlPath)" Query="//wss:Package/wss:ServerBinary/wss:File/wss:Name" Namespaces="$(AddInXmlNamespace)" Value="$(TargetFileName)" />
    <XmlPoke XmlInputPath="$(AddInXmlPath)" Query="//wss:Package/wss:ServerBinary/wss:File/wss:Version" Namespaces="$(AddInXmlNamespace)" Value="$(TargetAssemblyVersion)" />
    <XmlPoke XmlInputPath="$(AddInXmlPath)" Query="//wss:Package/wss:ServerBinary/wss:File/wss:ProductCode" Namespaces="$(AddInXmlNamespace)" Value="@(ProductCode)" />

Change ServerBinary to ClientBinary32 or ClientBinary64 in the Query string above. Check out the BeforeBuild targets in WssxPackageExample.Client32 and WssxPackageExample.Client64 in the sample project for concrete examples.

Now every time we build this WiX project we’ll set the output Windows Installer package filename to include the version number of the package (which is also the version number of the target assembly we’re delivering). Then we’ll update the appropriate File node in AddIn.xml with the correct ProductCode, Version, and file Name.

3. WSSX Package Project

Now that we’ve set and updated version numbers for our assemblies and Windows Installer packages, and set correct metadata in AddIn.xml, we need to make sure that we’re also updating the version for the WSSX package itself.

*.wssxproj

Edit your *.wssxproj file, and you’ll find a similar “To modify your build process…” comment as you did in the WiX project file.

We’re going to paste another big chunk of XML right after that comment.

Note: If you find the following hard to read or copy, download the sample project; you’ll find this XML at the bottom of the WssxPackageExample.AddIn.wssxproj file.

  <UsingTask 
TaskName="Microsoft.Build.Tasks.XmlPoke" 
AssemblyName="Microsoft.Build.Tasks.v4.0, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
  <UsingTask 
TaskName="Microsoft.Build.Tasks.XmlPeek" 
AssemblyName="Microsoft.Build.Tasks.v4.0, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
  <UsingTask 
TaskName="GetHighestVersionNumber" 
TaskFactory="CodeTaskFactory" 
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
    <ParameterGroup>
      <VersionList Required="True" ParameterType="System.String[]"/>
      <Result ParameterType="System.String" Output="True"/>
    </ParameterGroup>
    <Task>
      <Reference Include="mscorlib" />
      <Using Namespace="System" />
      <Using Namespace="System.Linq" />
      <Code Type="Fragment" Language="cs">
        <![CDATA[
          Result = VersionList.Select(v => Version.Parse(v)).OrderByDescending(v => v).FirstOrDefault().ToString();
        ]]>
      </Code>
    </Task>
  </UsingTask>
  <UsingTask 
TaskName="IncrementVersionNumber" 
TaskFactory="CodeTaskFactory" 
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
    <ParameterGroup>
      <VersionString Required="True" ParameterType="System.String"/>
      <Result ParameterType="System.String" Output="True"/>
    </ParameterGroup>
    <Task>
      <Reference Include="mscorlib" />
      <Using Namespace="System" />
      <Using Namespace="System.Linq" />
      <Code Type="Fragment" Language="cs">
        <![CDATA[
            Version current = Version.Parse(VersionString);
            Result = new Version(current.Major, current.Minor, current.Build + 1, current.Revision).ToString();
        ]]>
      </Code>
    </Task>
  </UsingTask>
  <PropertyGroup>
    <AddInXmlPath>$(ProjectDir)AddIn.xml</AddInXmlPath>
    <WxsNamespace>&lt;Namespace Prefix='wxs' Uri='http://schemas.microsoft.com/wix/2006/wi' /&gt;</WxsNamespace>
    <AddInXmlNamespace>&lt;Namespace Prefix='wss' Uri='http://schemas.microsoft.com/WindowsServerSolutions/2010/03/Addins' /&gt;</AddInXmlNamespace>
  </PropertyGroup>
  <Target Name="BeforePack" DependsOnTargets="GetPackageVersions;ReplaceAddInXmlPackageVersion;IncrementAddInXmlPackageVersion" />
  <Target Name="GetPackageVersions">
    <!-- Get current Package Version from AddIn.Xml -->
    <XmlPeek XmlInputPath="$(AddInXmlPath)" Query="//wss:Package/wss:Version/text()" Namespaces="$(AddInXmlNamespace)">
      <Output TaskParameter="Result" PropertyName="PackageVersion" />
    </XmlPeek>
    <Message Text="Package Version: $(PackageVersion)"/>
    <!-- Get current ServerBinary version from AddIn.xml -->
    <XmlPeek XmlInputPath="$(AddInXmlPath)" Query="//wss:Package/wss:ServerBinary/wss:File/wss:Version/text()" Namespaces="$(AddInXmlNamespace)">
      <Output TaskParameter="Result" ItemName="FileVersion" />
    </XmlPeek>
    <!-- Get current ClientBinary32 version from AddIn.xml -->
    <XmlPeek XmlInputPath="$(AddInXmlPath)" Query="//wss:Package/wss:ClientBinary32/wss:File/wss:Version/text()" Namespaces="$(AddInXmlNamespace)">
      <Output TaskParameter="Result" ItemName="FileVersion" />
    </XmlPeek>
    <!-- Get current ClientBinary64 version from AddIn.xml -->
    <XmlPeek XmlInputPath="$(AddInXmlPath)" Query="//wss:Package/wss:ClientBinary64/wss:File/wss:Version/text()" Namespaces="$(AddInXmlNamespace)">
      <Output TaskParameter="Result" ItemName="FileVersion" />
    </XmlPeek>
    <!-- Get the current highest file version -->
    <GetHighestVersionNumber VersionList="@(FileVersion)">
      <Output PropertyName="HighestFileVersion" TaskParameter="Result" />
    </GetHighestVersionNumber>
    <Message Text="Highest File Version: $(HighestFileVersion)" />
    <!-- Compare Package version with our version (we Version.Parse() to perform the correct comparison) -->
    <CreateProperty Value="$([System.Version]::Parse($(HighestFileVersion)).CompareTo($([System.Version]::Parse($(PackageVersion)))))">
      <Output TaskParameter="Value" PropertyName="VersionCompareResult" />
    </CreateProperty>
  </Target>
  <Target Name="ReplaceAddInXmlPackageVersion" Condition="$(VersionCompareResult) == 1">
    <!-- Replace Package Version if highest file version is greater than -->
    <XmlPoke XmlInputPath="$(AddInXmlPath)" Query="//wss:Package/wss:Version" Namespaces="$(AddInXmlNamespace)" Value="$(HighestFileVersion)" />
    <Message Text="Package Version Updated: $(HighestFileVersion)" />
  </Target>
  <Target Name="IncrementAddInXmlPackageVersion" Condition="$(VersionCompareResult) &lt;= 0">
    <!-- Increment current Package Version if highest file version is less than or equal -->
    <IncrementVersionNumber VersionString="$(PackageVersion)">
      <Output PropertyName="IncrementedVersion" TaskParameter="Result" />
    </IncrementVersionNumber>
    <XmlPoke XmlInputPath="$(AddInXmlPath)" Query="//wss:Package/wss:Version" Namespaces="$(AddInXmlNamespace)" Value="$(IncrementedVersion)" />
    <Message Text="Package Version Updated: $(IncrementedVersion)" />
  </Target>

The comments should let you know what we’re doing:

  1. Find our current Package Version
  2. Get the versions of all our Binaries (the Windows Installer packages we’re including in the final *.wssx file)
  3. Compare the Package Version with the highest Version from our Binaries
  4. If our highest Binary Version is higher than the current Package Version, we replace the Package Version
  5. If our Package Version is the same as, or higher than, the highest Binary Version, we increment the Package Version by one

No need to do any customization here, it’s all based on the previous steps that have injected the right values into our AddIn.xml file.

The whole point of this chunk of MSBuild voodoo is to make sure our *.wssx package always has a higher Package Version for each build than when it started.

Look Ma, No Hands!

And that’s it, for now.

When you make a change to one of the assemblies that makes up your Add-In, those changes will ensure that your final *.wssx file is built with a new version number, and as a bonus your Add-In will support in-place upgrades. All without manual edits to any files in the process.

Removing manual steps from any build process is a win for me; any time I can reduce cognitive load, and therefore reduce the chance for screw-ups, I’m a happy camper.

If you have any questions (or if you’ve actually read to the end of this post!) please drop something into the comments below, or fire us an email using the Contact page.

So what’s next? Well, we still need to talk about signing our *.wssx files, because the signature is actually checked during install now! And we want to avoid that nasty “publisher could not be verified” message, right?

posted on Friday, July 01, 2011 4:26 PM | Filed Under [ Windows Home Server Development WSSX ]

Comments

Gravatar
# re: Automating Windows Server Solutions Add-In Package (*.wssx) Builds to Support In-Place Upgrades (Jonas @ 8/15/2011 9:08 PM)

Thanks mate! I was out do the same but this saved me plenty of time and headaces :)
 
Gravatar
# re: Automating Windows Server Solutions Add-In Package (*.wssx) Builds to Support In-Place Upgrades (Ross Dargan @ 10/26/2011 11:44 AM)

Wow, this has just saved me a load of time! Thanks for writing this great article!

Post Comment

Title *
Name *
Email
Url
Comment *  
Remember me
Please add 3 and 6 and type the answer here:

Search

Site Sections

Recent Posts

Archives

Post Categories

WHS Add-In Tutorial

WHS Blogs

WHS Development