A lot is side in the MSI SDK about servicing strategies. Much of it is confusing and has to do with the intricate details of Major vs Minor vs Small Upgrades and Upgrades shipped as MSI's or as Patches. The UpgradeCode property itself is defined as a "GUID representing a related set of products".
If you do this all correctly you get a nice user experience. For example I might install one version of a text editor and then later upgrade to the latest version of the text editor.
But what really isn't mentioned in the SDK is what to do when you have a related product but you don't want to do an upgrade. Perhaps what you really want is a side by side installation of a new version of a product.
For example, how you feel if you installed Visual Studio 2008 and then when you installed Visual Studio 2010 it remove Visual Studio 2008? What if you insalled InstallShield 12 and when you installed InstallShield 2011 it removed InstallShield 12?
Would you really be ok with that? I wouldn't. I couldn't possibly move all of my software baselines to a newer tool or have 2 machines on my desktop for the two different sets of tools.
These are times when you must design both your application and your installer to be able to support side by side installations of different versions of the software.
Sadly, the WiX team has made this choice for me. Rather then support Votive 3.0 and Votive 3.5 side by side installations with Visual Studio 2008 and Votive 3.5 with Visual Studio 2010 they have chosen to do a Major Upgrade and support only Votive 3.5 on Visual Studio 2008 and Visual Studio 2010.
To make it even more interesting they have broken backwards compatibly by changing the name of the MSBuild Targets file that the .wixproj points to.
Basically the software was designed that if you want to use Votive 3.5, you gotta be all in.
Well, perhaps some people will be ok with that. If you are new to WiX without a huge previous investment or you don't use Votive perhaps you will be ok also. However, for the massive environment that keeps me busy at my day job I say:
Pass
I make this call because WiX 3.5 only brings marginal benefit to my situation while requiring me to make a substantial commitment with a lot of associated risk.
It's obviously too late to fix this for 3.5 as this would be the sort of high risk last minute activity that any setup developer hates. However I hope to see a different story for 3.6. I'd like to be able to have the latest 3.5 installed on my personal machine at home while also installing the latest 3.6 weekly release. When 3.6 goes RTM one day I hope to have 3.5 and 3.6 installed side by side.
As an aside, Industrial Strength Windows Installer XML will work with any combination of Visual Studio 2008, 2010 or None and Windows Installer XML 3.0, 3.5, 3.6 or none. IsWiX technically just authors XML files so it has no physical dependencies on either VS or WiX other then a shared understanding of the wix.xsd file.
Saturday, January 22, 2011
Wednesday, January 19, 2011
Augmenting InstallShield using Windows Installer XML - Windows Services
In my last blog I explained how you could use a WiX module to extend InstallShield to accomplish installing certificates. I also mentioned that you could use WiX to inject Windows Services into InstallShield 2010 Limited Edition even though the tool wasn't designed to support this.
Below is an example of what said WiX source code would look like. I post this not as some attempt to 'hack' InstallShield LE but more a spirit of I really don't want to see former VDPROJ developers continuing the horrible practice of writing InstallUtil custom actions to install services.
As an aside, 14 of the 16 lines of XML were authored by IsWiX. I only had to add the ServiceInstall and ServiceControl elements. One of these days I'll get around to finishing the Services Designer so IsWiX can do all the authoring.
Below is an example of what said WiX source code would look like. I post this not as some attempt to 'hack' InstallShield LE but more a spirit of I really don't want to see former VDPROJ developers continuing the horrible practice of writing InstallUtil custom actions to install services.
As an aside, 14 of the 16 lines of XML were authored by IsWiX. I only had to add the ServiceInstall and ServiceControl elements. One of these days I'll get around to finishing the Services Designer so IsWiX can do all the authoring.
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<?define SourceDir="."?>
<Module Id="WindowsService" Language="1033" Version="1.0.0.0">
<Package Id="f4e004d1-4fc1-4dab-855d-c46fc2ec3702" Manufacturer="WindowsService" InstallerVersion="200" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="MergeRedirectFolder">
<Component Id="owcB7461B87B05DCA45F57E1CB0917F32A7" Guid="380bbddd-daa7-0744-517b-37da768f5570">
<File Id="owfB7461B87B05DCA45F57E1CB0917F32A7" Source="$(var.SourceDir)\WindowsService.exe" KeyPath="yes" />
<ServiceInstall Id="installWS" Name="WindowsService" DisplayName="Windows Service" Description="Windows Service" Start="auto" Type ="ownProcess" ErrorControl ="normal"/>
<ServiceControl Id="controlWS" Name="WindowsService" Remove="both" Stop ="both" Start="install" Wait ="no"/>
</Component>
</Directory>
</Directory>
</Module>
</Wix>
Augmenting InstallShield using Windows Installer XML - Certificates
I've blogged in the past how I like to blend WiX with InstallShield and today I'd like to post another example of the usefulness of these two tools together.
Recently I was creating an installer that need to install a trusted root certificate. I looked at InstallShield 2010 and the only certificate support I could find was for creating IIS websites and assigning server certificates. ( As an aside, I never quite understood the usefulness of that outside of a controlled corporate environment where you are setting up many servers to belong to the same farm or to an application where you will register a hostname in DNS and all customers know the server by the same name. Also let's not mention distributing private keys in MSI's along with the passphrases needed to deploy them. Really?? )
So I looked at WiX's documentation and found the Certificate Element in the IIS Extension. Sure enough it has much broader support and can easily get the job. So I decide to create a WiX Merge Module with a snippet of code similar to:
I now end up with a nice encapsulation ( InstallCertificate.msm ) that I can add to my InstallShield project via the Redistributables tab. Build the MSI and test on a VM proves that it works.
This pattern can also be used with InstallShield 2010 Limited Edition and with a little bit of creative thinking you can quickly get IS2010LE to do all kinds of things that it was crippled to not be able to do. The best part is you are following very good practices while doing it and not trying to hack a solution together like you would be with Visual Studio Deployment Projects and InstallUtil Custom Actions.
Recently I was creating an installer that need to install a trusted root certificate. I looked at InstallShield 2010 and the only certificate support I could find was for creating IIS websites and assigning server certificates. ( As an aside, I never quite understood the usefulness of that outside of a controlled corporate environment where you are setting up many servers to belong to the same farm or to an application where you will register a hostname in DNS and all customers know the server by the same name. Also let's not mention distributing private keys in MSI's along with the passphrases needed to deploy them. Really?? )
So I looked at WiX's documentation and found the Certificate Element in the IIS Extension. Sure enough it has much broader support and can easily get the job. So I decide to create a WiX Merge Module with a snippet of code similar to:
<?xml version="1.0" encoding="UTF-8"?>
<?define SourceDir="."?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:iis="http://schemas.microsoft.com/wix/IIsExtension">
<Module Id="InstallCertificate" Language="1033" Version="1.0.0.0">
<Package Id="00000000-0000-0000-0000-000000000000" Manufacturer="YourCompanyHere" InstallerVersion="200" />
<Binary Id="cert" SourceFile="$(var.SourceDir)\mycert.pfx"/>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="MergeRedirectFolder">
<Component Id="certs" Guid="00000000-0000-0000-0000-000000000000">
<CreateFolder/>
<iis:Certificate Id="cert" BinaryKey="cert" Name="cert" Overwrite="yes" SoureLocation="localMachine" StoreName="root"/>
</Component>
</Directory>
</Directory>
</Module>
</Wix>
I now end up with a nice encapsulation ( InstallCertificate.msm ) that I can add to my InstallShield project via the Redistributables tab. Build the MSI and test on a VM proves that it works.
This pattern can also be used with InstallShield 2010 Limited Edition and with a little bit of creative thinking you can quickly get IS2010LE to do all kinds of things that it was crippled to not be able to do. The best part is you are following very good practices while doing it and not trying to hack a solution together like you would be with Visual Studio Deployment Projects and InstallUtil Custom Actions.
Monday, January 10, 2011
DC Universe Online Uninstaller Issues
I was searching twitter for InstallShield when a couple of links led me to this great thread:
DC Universe Online Uninstaller Issues ( Google Cache of Sony Online Entertainment )
It seems customer service was announcing some bugs in their uninstall. Now I'm not sure what will actually happen if the user clicks "Yes" but I do know I don't ever, ever want to see this dialog in my uninstall:
But the real kicker came when Customer Service "TSR-AndyD" posted:
Whiskey - Tango - Foxtrot????
It's now 2010 and with all this talk of Agile and CI, developers still don't have a clue why it's so important to make a proper install ( and uninstall ).
Course this is Sony we are talking about. Less we forget about their rootkit incident.
The bright side is some bright folks discussing the issue over this thread actually seem to get it.
DC Universe Online Uninstaller Issues ( Google Cache of Sony Online Entertainment )
It seems customer service was announcing some bugs in their uninstall. Now I'm not sure what will actually happen if the user clicks "Yes" but I do know I don't ever, ever want to see this dialog in my uninstall:
But the real kicker came when Customer Service "TSR-AndyD" posted:
I'm sure you all realize that the focus of the development staff is currently going to be bmaking [sic] the game as awesome as possible for the retail release. The broken uninstaller is a forgivable and understandable over-sight.
Whiskey - Tango - Foxtrot????
It's now 2010 and with all this talk of Agile and CI, developers still don't have a clue why it's so important to make a proper install ( and uninstall ).
Course this is Sony we are talking about. Less we forget about their rootkit incident.
The bright side is some bright folks discussing the issue over this thread actually seem to get it.
Wednesday, January 5, 2011
Resuming Course
A few months ago I mentioned that I was going to take a break from installers and do .NET development. It was a very enlightening experience as I was able to gain a new perspective while improving my skills and helping a project that was in crunch mode. Alas things have slowed down a bit and I'm resuming my duties on the Install Services team.
One thing I did realize about myself is that I'm a High Functioning Windows Installer Addict. It's just so in my blood that it's sometimes hard to think about any thing else. Sorry, I'm just a freak of nature or something. So I'm back... ready to get my fix.
One thing I did realize about myself is that I'm a High Functioning Windows Installer Addict. It's just so in my blood that it's sometimes hard to think about any thing else. Sorry, I'm just a freak of nature or something. So I'm back... ready to get my fix.
Thursday, December 16, 2010
Thoughts on using C# in Build Automation
As I mentioned in my last blog post, Cary Roys has posted a blog article titled Getting Started with InstallShield Automation and C# over at InstallShield. It's a good read that raises a couple thoughts for me. I've already covered my first thought so this blog post will address my second thought.
Cary's sample starts with ( abbreviated ):
Now I'm sure that Cary wrote this to keep things quick and simple but I wanted to use it as an opportunity to share some thoughts on build automation using C#.
In case you've never used NAnt or MSBuild ( they are very similar so everything I write about MSBuild will mostly apply to NAnt ) I'll start by saying that just as MSI shuns imperative programming in favor of declarative programming for installs, NAnt and MSBuild do the same for build automation. And just as MSI provides a mechanism for calling custom actions, so does MSBuild.
So, IMO, it's kind of hard to talk about using C# to write build automation without also talking about either MSBuild or NAnt. That said, you typically won't call custom EXE's using the Exec Task for many of the same reasons why you wouldn't call an EXE from an installer except for a low risk or last resort situation. Instead you'll write a custom MSBuild Task.
Let's look at a simple example:
This will create a DLL that exports a task called ExampleMSBuildTask. Now let's see how we actually wire it into our MSBuild targets file. ( Think Binary, CustomAction and Sequence tables in MSI )
In this example we have an MSBuild file that has a default target of Build which in turn calls our task. However, I've found that debugging the task isn't really straight forward. What I like to do is create a "test harness". This is just a fancy way of saying move my custom code into it's own class and consume it from both a Windows Application and an MSBuild task.
First I create a simple base class to inherit from:
Now let's create a class that inherits from this base class:
The Build method can now easily call the logging message which will in turn call it's delegate it it exists. This basically allows the consuming class to be able to subscribe to logging messages and display it to the user in a way that's appropriate. Let's look at an example:
The task now constructs the engine, assigns it's delegate and calls the Build member. Any messages get routed to the Console as StdOut.
Now let's look at another example:
Basically we have the same code only now it's adding it to a RichTextBox on a Windows Forms. Now we have a program that we can easily run, observe and step into with a debugger without jumping through a lot of hoops. This allows you to establish your contract ( inputs and outputs ) up front, get that wired into the build automation and then do most of your development on your own box then check it all in when you are done. It also allows you to convert your logic to an EXE or NAnt task if you need to.
All in all, it's a good way to roll IMO.
Cary's sample starts with ( abbreviated ):
static void Main(string[] args)
{
ISWiAuto17.ISWiProject m_ISWiProj = new ISWiAuto17.ISWiProject();
}
Now I'm sure that Cary wrote this to keep things quick and simple but I wanted to use it as an opportunity to share some thoughts on build automation using C#.
In case you've never used NAnt or MSBuild ( they are very similar so everything I write about MSBuild will mostly apply to NAnt ) I'll start by saying that just as MSI shuns imperative programming in favor of declarative programming for installs, NAnt and MSBuild do the same for build automation. And just as MSI provides a mechanism for calling custom actions, so does MSBuild.
So, IMO, it's kind of hard to talk about using C# to write build automation without also talking about either MSBuild or NAnt. That said, you typically won't call custom EXE's using the Exec Task for many of the same reasons why you wouldn't call an EXE from an installer except for a low risk or last resort situation. Instead you'll write a custom MSBuild Task.
Let's look at a simple example:
using System;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Example
{
public class ExampleMSBuildTask : Task
{
public string Configuration { protected get; set; }
public override bool Execute()
{
}
}
}
This will create a DLL that exports a task called ExampleMSBuildTask. Now let's see how we actually wire it into our MSBuild targets file. ( Think Binary, CustomAction and Sequence tables in MSI )
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="3.5" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask AssemblyFile="Example.exe" TaskName="ExampleMSBuildTask"/>
<Target Name="Build">
<ExampleMSBuildTask Configuration="Debug|Release"/>
</Target>
</Project>
In this example we have an MSBuild file that has a default target of Build which in turn calls our task. However, I've found that debugging the task isn't really straight forward. What I like to do is create a "test harness". This is just a fancy way of saying move my custom code into it's own class and consume it from both a Windows Application and an MSBuild task.
First I create a simple base class to inherit from:
using System;
namespace Example
{
class EngineBase
{
public delegate void LogHandler(string message);
public event LogHandler Logger;
virtual protected void Log(string message)
{
if (Logger != null)
Logger(message);
}
}
}
Now let's create a class that inherits from this base class:
using System;
namespace Example
{
class SampleEngine : EngineBase
{
public void Build(string Configuration)
{
Log(string.Format("Building {0}", Configuration));
}
}
}
The Build method can now easily call the logging message which will in turn call it's delegate it it exists. This basically allows the consuming class to be able to subscribe to logging messages and display it to the user in a way that's appropriate. Let's look at an example:
using System;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Example
{
public class ExampleMSBuildTask : Task
{
public string Configuration { protected get; set; }
public override bool Execute()
{
SampleEngine engine = new SampleEngine();
engine.Logger += Logger;
engine.Build( Configuration );
return true;
}
void Logger(string Message)
{
Console.WriteLine(Message);
}
}
}
The task now constructs the engine, assigns it's delegate and calls the Build member. Any messages get routed to the Console as StdOut.
Now let's look at another example:
using System;
using System.Windows.Forms;
namespace Example
{
public partial class FormTestHarness : Form
{
public FormTestHarness()
{
InitializeComponent();
}
private void buttonExecute_Click(object sender, EventArgs e)
{
var engine = new SampleEngine();
engine.Logger += Logger;
engine.Build( textBoxInput1.Text );
}
void Logger(string Message)
{
richTextBox1.Text = richTextBox1.Text + Message + "\r\n";
}
}
}
Basically we have the same code only now it's adding it to a RichTextBox on a Windows Forms. Now we have a program that we can easily run, observe and step into with a debugger without jumping through a lot of hoops. This allows you to establish your contract ( inputs and outputs ) up front, get that wired into the build automation and then do most of your development on your own box then check it all in when you are done. It also allows you to convert your logic to an EXE or NAnt task if you need to.
All in all, it's a good way to roll IMO.
Tuesday, December 14, 2010
InstallShield Automation Interop with .NET / C#
Cary Roys has posted a blog article titled Getting Started with InstallShield Automation and C# over at InstallShield. It's a good read that raises a couple thoughts for me. This blog post will address the first issue.
When you add a COM reference to InstallShield, Visual Studio will generate interop libraries specific to the version that you added. This allows you to call into the automation interface with code like:
ISWiAuto17.ISWiProject project = new ISWiAuto17.ISWiProject();
The problem with that though is the ISWiProject class is now coupled (strongly typed) to InstallShield 2011. If you need to reuse this code with other versions of InstallShield it simply isn't going to work.
There are several ways around this problem but none of them are pretty:
1) Use reflection for COM late binding.
//Untested Example
object projectType = Type.GetTypeFromProgID("ISWiAuto17.ISWiProject");
project = Activator.CreateInstance(projectType );
This gets ugly real fast with no type safety and a ton of line noise to invoke the members. If a version of InstallShield doesn't support a member, you'll get a run time error.
2) Switch to C# 4.0 and use the dynamic type. With this approach you create some initialization code that selects the correct version of InstallShield and assigns the project class to a dynamic class. This cuts down greatly on the line noise needed to write the code but it's still not type safe and what could be a build time error will be a run time error. Also you might be working in a build automation environment that hasn't yet migrated over to .NET 4.0.
3) Write your own interfaces and then implement provider pass through wrappers for all of the automation calls you'll need to support. This allows you to create a factory or use dependency injection. This is a very clean approach that will be type safe but takes a lot of time to write all the tedious plumbing.
4) It's been suggested to me that the the generated runtime callable wrapper could be modified so that the type names are constant. ( Version Neutral / Neutered interop ) but it's above my skill to know how to do this.
5) Hack it! Write your logic once, clone it to multiple class files and do a search and replace to update all the references. Then write a factory around it to let you control which one gets used. Any time you update your code you have to copy it over and search and replace again. This is a horrible design but it takes a lot less time to implement then option 3 but could possibly take more time to maintain over the long run. I've done this in the past and got lucky since the code base was pretty mature.
I'd love to see a better solution ( believe me, I've asked ), especially if it came built into InstallShield out of the box. Anyone have any suggestions?
When you add a COM reference to InstallShield, Visual Studio will generate interop libraries specific to the version that you added. This allows you to call into the automation interface with code like:
ISWiAuto17.ISWiProject project = new ISWiAuto17.ISWiProject();
The problem with that though is the ISWiProject class is now coupled (strongly typed) to InstallShield 2011. If you need to reuse this code with other versions of InstallShield it simply isn't going to work.
There are several ways around this problem but none of them are pretty:
1) Use reflection for COM late binding.
//Untested Example
object projectType = Type.GetTypeFromProgID("ISWiAuto17.ISWiProject");
project = Activator.CreateInstance(projectType );
This gets ugly real fast with no type safety and a ton of line noise to invoke the members. If a version of InstallShield doesn't support a member, you'll get a run time error.
2) Switch to C# 4.0 and use the dynamic type. With this approach you create some initialization code that selects the correct version of InstallShield and assigns the project class to a dynamic class. This cuts down greatly on the line noise needed to write the code but it's still not type safe and what could be a build time error will be a run time error. Also you might be working in a build automation environment that hasn't yet migrated over to .NET 4.0.
3) Write your own interfaces and then implement provider pass through wrappers for all of the automation calls you'll need to support. This allows you to create a factory or use dependency injection. This is a very clean approach that will be type safe but takes a lot of time to write all the tedious plumbing.
4) It's been suggested to me that the the generated runtime callable wrapper could be modified so that the type names are constant. ( Version Neutral / Neutered interop ) but it's above my skill to know how to do this.
5) Hack it! Write your logic once, clone it to multiple class files and do a search and replace to update all the references. Then write a factory around it to let you control which one gets used. Any time you update your code you have to copy it over and search and replace again. This is a horrible design but it takes a lot less time to implement then option 3 but could possibly take more time to maintain over the long run. I've done this in the past and got lucky since the code base was pretty mature.
I'd love to see a better solution ( believe me, I've asked ), especially if it came built into InstallShield out of the box. Anyone have any suggestions?
Subscribe to:
Posts (Atom)
