Major Upgrade using Product Code

July 27, 2011 Leave a comment

Caution: This is NOT best practice!  But when your back is against a wall, you need a way out.  This was my way out/solution.

We recently got ourselves stuck up a creek without a paddle, so I had to fabricate a paddle and here’s how I did it. This code will comb the registry for the existence of a list of supplied product codes and attempt to uninstall the first one found in the UI Sequence using msiexec /x.

The back story of why we needed this isn’t really important, but I’ll share the synopsis.  We have 2 types of installers and the second type is more or less a trial that has ~25 flavors.  The two types of installers can co-exists on the same machine (unique upgrade codes).   Well for one reason or another it was decided that this should no longer be the case and for manageability we would combine the two sets of flavors into one upgrade code so only one product can be installed on a machine at a time.  This was done and we performed a release.  Months later the Marketing dept caught wind of this and wanted the old behavior back to allow for both the flavors to co-exist on the machine at a time. (2 different Upgrade Codes)  THIS WAS A PROBLEM.  Now that the 2 flavors of our software share the same upgrade code, they are identical as far as MSI is concerned and there is no way to delineate between the two in order to perform major upgrades.  — So this is when I came up with the following solution we’ve had in place for awhile. (Sorry, .. long ‘synopsis’)

MSI can only perform major upgrades using Upgrade Codes.  There’s no built in mechanism for using a Product Code to uninstall a product.  Here’s how I’m targeting specific Product Codes of our past release (25 individual products) and manually uninstalling the first one found using msiexec.exe /x during the UI Sequence.  (Can’t be done during the Execute sequence because msi-chaining is not permitted).  The following is Installscript.


//===========================================================================
//
//  Function:    UninstallIfProductCode()
//
//    Description: Removes specific product codes.  Removes one and then returns.
//    Note:      (There would only ever be one on the system.)
//===========================================================================

export prototype VOID UninstallIfProductCode(HWND);
function VOID UninstallIfProductCode(hMSI)

STRING  szProductNumber,szCompressedGuid,szKey;
LIST productCodes;
NUMBER nvItem,nResult;

begin
    productCodes = ListCreate(STRINGLIST);
    ListAddString(productCodes, "{EAFD321F-7441-49F7-845F-162AFE9A9E89}", AFTER);
    ListAddString(productCodes, "{15219BB7-218A-4CA9-B904-E9730A5AE831}", AFTER);
    ListAddString(productCodes, "{831E34B1-844F-485D-95F6-0B4324D30F3F}", AFTER);
    ListAddString(productCodes, "{2ECC9820-1A29-4860-8A0B-BD1CE320B4B2}", AFTER);
    ListAddString(productCodes, "{A8CA60A2-5C80-4862-B539-72A0FE164631}", AFTER);
    ListAddString(productCodes, "{BF0ADDB3-FBF4-45F3-8862-739B43D10F09}", AFTER);
    ListAddString(productCodes, "{240047B1-1ED9-42DB-AC97-547D9C7B639C}", AFTER);
    ListAddString(productCodes, "{02161687-5FB6-4123-9786-C6D41DA34B8E}", AFTER);
    ListAddString(productCodes, "{33CAB057-95D1-40EC-A0C3-C116E4E3A58D}", AFTER);
    ListAddString(productCodes, "{E3028A13-E396-47AA-ACB6-DE4F51954F0C}", AFTER);
    ListAddString(productCodes, "{DCD725C7-BB93-49FF-AFA3-BD2FA96EC048}", AFTER);
    ListAddString(productCodes, "{57CB7704-9BAA-407C-B3A8-EA8184CABF66}", AFTER);
    ListAddString(productCodes, "{F67AA111-D082-4187-932B-9186DA4E2F6B}", AFTER);
    ListAddString(productCodes, "{AEC516A4-6929-43B0-A4FD-3E7F9BFBBCBA}", AFTER);
    ListAddString(productCodes, "{BB9B1A98-7A23-400D-B8BD-CBD7C3D2B839}", AFTER);
    ListAddString(productCodes, "{E1BB77D9-C487-4AB1-BA9C-7CD752A63E5E}", AFTER);
    ListAddString(productCodes, "{08C40B1B-B1D2-4DD2-87E1-677AAE040CBF}", AFTER);
    ListAddString(productCodes, "{FB485301-53C7-4011-9256-3E7F9C92AC5F}", AFTER);
    ListAddString(productCodes, "{9CAAE3D3-17D8-44CD-8659-62E29E64C64B}", AFTER);
    ListAddString(productCodes, "{9CE9C04A-ADD6-45C6-90E3-4E56EDB2CB3C}", AFTER);
    ListAddString(productCodes, "{724FCA6E-07C0-4D18-95A2-04CE0450A912}", AFTER);
    ListAddString(productCodes, "{35DC018B-4DCF-42B9-B898-718EDEACAFD8}", AFTER);
    ListAddString(productCodes, "{AA8B768D-5483-4D6C-8E73-12983D727C59}", AFTER);
    ListAddString(productCodes, "{59C22288-4019-4179-B303-D7274F23457A}", AFTER);

    nResult = ListGetFirstString(productCodes, szProductNumber);

    while (nResult != END_OF_LIST)

        szCompressedGuid = CompressedGUID(szProductNumber);

        RegDBSetDefaultRoot(HKEY_CLASSES_ROOT);
        REGDB_OPTIONS = REGDB_OPTIONS | REGDB_OPTION_WOW64_64KEY;  //let's be 64bit aware

        if(RegDBKeyExist(szKey) > 0) then
            LaunchAppAndWait("msiexec","/x " + szProductNumber + " /qb /passive", WAIT);
            goto productFound:
        endif;

        nResult = ListGetNextString (productCodes, szProductNumber);
     endwhile;

    productFound:
    ListDestroy(productCodes);
end;

Note: The code for the CompressedGuid() function can be found in one of my earlier blog posts: Installscript to Transform GUID into ‘Compressed GUID’.

Like I mentioned, we’ve had this in place for a couple releases without any reported problems.

Advertisements

TweetDeck and Adobe AIR constant annoying updates.

July 19, 2011 3 comments

This isn’t really related to software deployment.  But, because twitter is a tool of my BuildMaestro online persona, this is where I’m gonna rant!   It seems like every other time I start TweetDeck I get a VERY annoying popup notifying me there’s an update for Adobe AIR (what Tweet Deck, a twitter client, runs on).  I usually hit the ‘update later’ button, but this just kicks the can down the road (sort of like QE and the impending sovereign debt crisis) and the nag screen just waits for the next time I use the app.

I’m but a mere user who has no idea what your update does, and I’m certanly the last person to know if this particular update is critical or just an agile development 2 week release cycle.  I say, if you need to update, at least give me the option to have it silently done in the background. But Alas, digging around through the settings and Googling provided me with no hints.

Then I stumbled on this:

Q: Is it possible to disable Adobe AIR auto-updates?

A: Yes. You can turn the AIR update feature on or off via the AIR SettingsManager application. The AIR SettingsManager application is available for download athttp://airdownload.adobe.com/air/applications/SettingsManager/SettingsManager.air.

Adobe highly recommends that, if you disable automatic updates, you manually apply all runtime updates as soon as possible.

Yes!  No more nagging.  I’ll update when there’s a critical exploit in the news or my twitter client falls to it’s knees with version obsolescence.    I think it’s kind of funny that you have to download a separate Adobe AIR application just to change a setting!

Another very annoying update notifier is the Java VM client.  Same applies here and I was able to also turn this off via the Control Panel java applet settings.

.NET Version Detector 2007

July 13, 2011 Leave a comment

I just wanted to share a recent find of mine (where have a I been?) that’s a pretty handy tool to show which versions of the .NET Framework are installed on a machine.   Sure, there are plenty of ways to find out which version is on the machine:  Add/Remove programs and updates, Server Manager, Registry keys, the version of fusion.dll.. etc etc..  But I found a tiny tool that simplifies it.  I put this on a network drive and I can run it remotely from any machine.

Link to download: .NET Version Detector 2007

Validating Installshield Prerequisites Actually Installed with Install Conditions (LaunchConditions)

July 5, 2011 Leave a comment

EDIT: This post if flawed.  There actually is a built in method Installshield’s boot-strapper uses to validate the prerequisite install was successful.  In a Basic MSI project: Right click on the prerequisite > edit > Behavior tab. Here there’s a drop down: “If, after installing the prerequisite, the conditions still indicate it is required” >  Abort the setup.

However, this setting wouldn’t work if for instance you were installing .NET Framework 3.5 SP1 which could fail to install it’s bundled copy of .NET Framework 2.0 SP2.  (Of which I’ve had reported).  In this case, using the suggestions below to use Launch Conditions that check for 2.0 SP2 would be prudent.

__________________________________________________________________________

We’ve all come to love the ease of adding redistributables to our deployment package through Installshield’s Prerequisite editor.  One thing that’s not so great about it though is that there’s no validation performed to ensure the prerequisite was actually installed.  Our QA department discovered and edge case relating to a failed prerequisite download and I’m going to share the fix I employed to validate that the prerequisite was really installed.

The summary of this post is to point out that prerequisite downloads can fail, but you can validate they were actually installed using MSI Install Conditions.  Additionally, silent installs using the msiexec rather than the Installshield’s setup.exe bootstrapper will also not install prerequisites.  This is another reason to use MSI Install Conditions.

We’re redistributing the .NET Framework 3.5 sp1, but due to media size constraints we can only bundle the web download boot-strapper for the framework.  I talk about this scenario in a previous blog post.  The problem with web downloads is that you’re relying on a stateless Internet connection that could lose network connectivity at anytime causing your download to fail.  (You can replicate this by disconnecting the internet while the download is being performed).  Upon download failure, Installshield’s boot-strapper presents you with the following screen with an option:

At this point the user can choose to cancel or proceed.  In our case, if they chose to proceed our installer would error when trying to register some objects that are dependent on methods only present in the Framework 2.0 sp1 (which would have been updated after the Framework 3.5 sp1 installation on certain OS’s– ya, it’s confusing!)

To catch this ‘edge case’, it’s a good idea to also add an install condition (LaunchCondition table) to your msi. In a Basic MSI project you can find these settings under General Information > Install Condition.

There’s a few different ways to detect the .NET Framework version, but  I’m using the MsiNetAssemblySupport property.

To check for 2.0 sp1, my condition looks like the following:

Condition: MsiNetAssemblySupport >= “2.0.50727.1434”

Message: You must install .NET Framework 2.0 Service Pack 1 or higher before installing this product.

Additionally Wikipedia has a great table showing the Frameworks along with their associated version information.  (3.5 SP1 RTM would be version “3.5.30729.01”)

It’s also worth noting that there are two methods of silent installation for Installshield based deployment packages:

  1. The msi method: msiexec /i yourMSI.msi /qn
  2. The Installshield boot-strapper method: setup.exe /s /v”/qn”

Only the later will install prerequisites, so it’s up to you to author install conditions to ensure the system does indeed have the perquisite present on the machine.

Concurrent builds w/ Installshield Stand-Alone Release Builder (IsCmdBld.exe)

June 18, 2011 1 comment

My team has recently undergone a project to simplify and optimize our build scripts.  My team member worked on transitioning from Perl to Ms-Build to simplify the script logic,.. and it’s a welcomed change.  To optimize the build, we’ve been experimenting with concurrent builds with the IsCmdBld.exe stand-alone builder and while we’ve realized incredible speed increases, there are some errors I’m going to share as well as how we’re getting around them.

This post is mainly about working around the ISDEV : fatal error -5092: Internal build error

1. Re-Author components to all use the same base path variable.

We have around 35 installers with varying sets of files that were being copied to a sub directory of ISProject in the old script.  To reduce the disk churning, I went through every file component and changed the source path to use a path variable we named SRCROOT (which evaluates to something like c:\project\release-win32\).  SRCROOT is where-ever our ms-build script outputs our files for each build.  When our script calls IsCmdBld.exe, we use the -l argument to override the path variable ‘SRCROOT’ to point to where-ever this particular installers assets can be found.

2. There were various hacks needed to overcome the -5092 Internal build error that other users are also recieving:

User slomicka writes:

I too saw problems. All of the builds run without error stand alone. When I run concurrent builds the IS portion of the builds crashes with “ISDEV : fatal error -5092: Internal build error” – regularly.

To overcome this error we had to do a few hacks:

a. Make a seperate copy of the Prerequisite and Merge module folders that each instance of IsCmdBld.exe and use the arguments -prqpath and -o, respectively. (I’d link to a KB from Acresso, but a Google search only returns a link to the outdated IS12 documentation that doesn’t contain these arguments)  Do a IsCmdBld.exe /? to return all available arguments.  (on my Win7 x64 box running IS 2011, IsCmdBld.exe is found in “C:\Program Files (x86)\InstallShield\2011 SAB\System\IsCmdBld.exe”)

b. Stagger the builds… and this is where it gets ugly. 🙂 I assume this is what Chris Painter was referring to when I (prematurely) said that we got concurrent builds working :

Concurrent builds only recently started working. They used to throw errors as the procs stepped on each others temp files.

It appears this is still a problem and we had to implement a good old fashioned .sleep hack.  yuck!   We had to stagger the build starts and the magic number we came up with was 3 minutes.  Your millage may vary.  We’re running AMD Hexacores (x6 1090 T @3.20GHz).  The only reason I’m mentioning this is our 3 minutes worked on this select hardware and environment,.. so if you have a faster or slower machine that time would vary.

Sleep statements are very unreliable. It may be slightly better to use a command line wrapper that parses each line of standard out text and when the  -5092 error is found, it should restart the process.  This can be done in any language such as C# or even vbscript’s .exec method found in another of my blog posts.

Until Installshield fixes these ‘internal error’ bugs and fully supports concurrent builds to take advantage of modern day processors, we’ll have to resort to hacks like these to get concurrent builds working.

The combination of optimizing the scripts (Ms-Build) as well as implementing concurrent builds has yield a 4.5x speed increase from our old system, so the development and time spent figuring out the hacks was well worth it.

PRODUCTLANGUAGE is still ProductLanguage

June 16, 2011 Leave a comment

Lesson learned: PRODUCTLANGUAGE is still ProductLanguage, so don’t use it as a custom property because it’s a reserved keyword even though Installshield allows you to add it.

I had a frustrating day working on a mysterious Jira and I’m going to discuss it for the benefit of those who helped me on twitter. Thanks @robertdickau @chrpai @installsite

User reported an application would crash on startup on silent install, but worked fine for attended installs. eventvwr only mentioned a .net 2.0 error, but after installing on my development machine I was able to obtain a stack trace.

Retrieved the code from our depot and found the offending GetLanguage() function on the top of the stack trace. It was looking for a registry key that should be set by the installer but was missing. (I’ll write up a change request for the group that works on this app because I don’t think an app should crash because of missing reg key 🙂 )

msi install log showed the component was selected to install (Request: Local;), but the Action was Null (Action: Null;), which tells me it’s failing to install because of the component condition:

MSI (s) (54:0C) [14:24:46:591]: Component: L_ENU_BT_REG_HKLM; Installed: Absent; Request: Local; Action: Null; Client State: Unknown

The component condition we had in the installer was:
(UI_INSTALL=”YES” AND ProductLanguage=1033) or (UI_INSTALL=”NO” AND PRODUCTLANGUAGE=1033)

The msi log said it was deleting this property basically at the start of the log so this explained why the condition was never evaluating as true on /qn installs:
PROPERTY CHANGE: Deleting PRODUCTLANGUAGE property. Its current value is ‘BLANK’

But why was it deleting my property?! After spending some time poking around in the Installscript, C++ dll and C# dll I couldnt’ find a reference to it being deleted I gave up and asked for help on twitter.

@robertdickau was on the right path:
Does anything change if you use a different name (say, PL)? There’s already a ProductLanguage property built in…

Sure enough, if I add a custom property to a new project named PRODUCTLANGUAGE I see it also gets deleted. Discovering this leaves me to conclude PRODUCTLANGAUGE, even though it’s caps and IS let’s you insert it into the property table, is indeed a reserved property name. I would recommend Installshield dis-allow users adding reserved keywords such as this to the property table.

Chris Painter suggests:
@buildmaestro also test against old msi runtimes to see if the behavior changed.

Of course I did this and I see this was a problem all the way back to 2007.

For the heck of it, I wanted to see if maybe this did work at one time with a different environment. I’m testing against Windows Installer 4.5, .. maybe an older version of Windows Installer handled this? So I fired up an ancient image of Windows 2000 that used Windows Installer 2.0 and while I didn’t see the msi log explicity say it was deleting the property, it also wasn’t listed in the “Property(s)” dump at the end of the log. I assume it also deleted the property.

I have to wonder, did anyone ever test this functionality!?! ha. Why did they create a copy of the ProductLanguage variable when ProductLanguage is also accessible on silent installs…? All I can do at this point is fix the problem and be happy nobody has reported this failure.

I said cancel! – UI Sequence tip

June 3, 2011 3 comments

The default UI sequence that you’re given when creating a new project with Installshield has a few unessasary and redundant screens, in my opinion.

Take the CancelSetup dialog for instance.  If at any time in the UI Sequence I hit the cancel button the following dialog is displayed:

I said I wanted to cancel, and then answered a confirmation prompt – at that point, the installer should just exit.  It doesn’t and displays the SetupInterupted dialog instead:

Now, I understand why it’s displaying this dialog.  When you click yes on the cancel dialog it fires and EndDialog Exit event and the return code is 2 if the user cancels.  The SetupInterupted dialog’s sequence is -2 and so this dialog is displayed. From a user’s experience it’s needless.  If I cancel, I don’t need the option to see an msi log either.

Here’s a simple change I made to not show the SetupInterupted dialog if the user clicks cancel:

  1. Create a ‘Set Property’ custom action: CancelButtonPressed. (doesn’t need to be public).  Property Name: USER_CANCELLED – Property Value: 1
  2. On the behavior view for the CancelSetup dialog’s YES button I added a DoAction event that calls the custom action: CancelButtonPressed with a condition of 1 (always run).
  3. On the Custom Action view, find the SetupInterrupted dialog in the UI Sequence.  Add the following condition: (USER_CANCELLED <> “1”)

UPDATE: Stefan Krueger @installsite was kind enough to point out that you can set properties from the UI dialog without the step of creating a custom action.  Funny, because I realized this later that night and didn’t have the energy to update the post at the time.

you don’t need a custom action to set a property from a dialog button. Use [PROPERTYNAME] in the first field & value in second

And he’s right,.. take a look at the ReadyToInstall dialog’s InstallNow button to see that [ProgressType#] properties are set in this manner. Thanks Stefan!

Here are those steps updated:

  1. On the behavior view for the CancelSetup dialog’s YES button I added an event to set a property as follows: [USER_CANCELLED], 1, 1
  2. On the Custom Action view, find the SetupInterrupted dialog in the UI Sequence.  Add the following condition: (USER_CANCELLED <> “1”)

Now when I click cancel and confirm with the YES button, the installer actually exits.. imagine that!