Installshield – great for developers, sucks ass for victims (aka everyone else)

Holy crap, what a safari this is turning into.  I’m trying to uninstall a piece of software (the Intel Debugger v9.1.x) that was apparently packaged with Installshield.  However, every time I tell Windows to uninstall it, it returns to me the following error:

image

Idiot move #1: look for help from InstallShield Corp

So I Google for this error and come up with a half-dozen links to various “support” articles from InstallShield and related on how to resolve this error.  It tells me to download various versions of their runtimes and engine installers, none of which make any improvement in the situation.

I went away for a while, came back again today and tried a whole different attack:

Forget the vendor, just debug it yourself (using Process Monitor)

  • Launch Process Monitor, filtering out all running processes except msiexec.exe
  • Near the end we finally see some activity that’s related to the problem:

MsiExec.exe    RegQueryKey    HKCR\CLSID\{8B1670C8-DC4A-4ED4-974B-81737A23826B}\LocalServer32

MsiExec.exe    RegQueryValue    HKCR\CLSID\{8B1670C8-DC4A-4ED4-974B-81737A23826B}\LocalServer32\(Default)    Data: C:\PROGRA~1\COMMON~1\INSTAL~1\Driver\8\INTEL3~1\IDriver.exe

  • Then there are four attempts to launch the IDriver.exe, all of which immediately halt
  • Lastly, there’s an update to the MSI log file which says this:
1: The InstallScript engine on this machine is older than the version required to run this setup.  If available, please install the latest version of ISScript.msi, or contact your support personnel for further assistance.
=== Logging stopped: 09/06/2008  14:02:26 ===

At least I know which file is “older than the version required”.

However, the next problem is figuring out how to get the ‘right one’ executed in its place:

Where Your Hero* Learns Just How Screwed Up InstallShield’s Model Really Is

* aka “just some dick on the Internet”

From what I can tell, Installshield only cares about one person: the dork who blindly builds the Installer package for their one little application.  Apparently, if you need to call on the Installshield components, don’t ever even try to discover whether they’re already installed on the target.  Instead, assume that they must *not* be installed (presumably because every developer on the planet has the privilege of being the first to get software installed on each PC where it’s being used), and always install a copy of some Installshield dependency on the end-user’s PC.  And then for good measure, make sure that there’s a hard-coded dependency on the version of the InstallShield bits that went with the installer.

They sure as hell don’t seem to care about the lowly end-user or IT administrator, who might have to actually *deal* with the nightmare of conflicting/overwriting/installed-to-every-conceivable-corner-of-the-filesystem versions of these hard-coded InstallShield dependencies.

Just for s**ts and giggles, try this at home:

  • fire up REGEDIT.EXE
  • Press [Ctrl]-F to bring up the Search dialog
  • Type Installshield and [Enter]
  • Click the [F3] button a few dozen (or hundred) times

A bit more digging on my own system:

Current version of IDriver.exe in the logged directory = 8.0.0.123

One article in the InstallShield/Macrovision/Acresso library confirms the noted location is where the IDriver.exe version 7 or 8 should be found.

Once more, I downloaded and installed the latest InstallScript 8 package (which turns out to be the 8.0.0.123 I already had installed), so I then decided to try downloading all the later versions and install them one by one as well.  I was hoping that the Registry setting that resolves to this particular IDriver.exe would be overridden (at least in the “Version Independent ProgID” or something similar) by a later install.  Here’s one set of settings that I figured were related:

  • CLSID: {8B1670C8-DC4A-4ED4-974B-81737A23826B}
  • (Default) value: InstallShield InstallDriver
  • AppID: {1BB3D82F-9803-4d29-B232-1F2F14E52A2E}
  • LocalServer32: C:\PROGRA~1\COMMON~1\INSTAL~1\Driver\8\INTEL3~1\IDriver.exe
  • ProgID: ISInstallDriver.InstallDriver.1
  • VersionIndependentProgID: ISInstallDriver.InstallDriver

Yep, after installing IScript9.msi, the CLSID under the entry HKCR\ISInstallDriver.InstallDriver changed to {B3EDE298-AE75-4A1C-AB7E-1B9229B77BBE}.  However, the uninstall “Fatal error” continued to crop up.  Apparently the fatal application’s uninstaller doesn’t chase the ProgIDs but some other reference instead.

Then by some wild fortune, I happened to stumble on a very obscure directory in which the “later version” of the InstallScript MSI installer (isn’t there some irony embedded in that?) was actually still cached.  WHY this wasn’t available from the vendor’s own web site, I’ll never know.  However, installing this version of IScript.msi did overwrite the ProgID once again, and the version of IDriver.exe installed in the target location was 8.1.0.293.

Somehow, finally, that did the trick.  Finally got that Installshield-driven crap off my system.  Trying to resist the impulse to wipe all traces of Installshield product off my system as well, and reminding myself that I could create this same hell for myself ten times over in so doing.

So, apparently all it takes is for some ancient application to overwrite the better version of an “Installation Engine”, and all hell breaks loose.  I’m beginning to see why the Installshield product line has been bought and sold more times than… well, I’m drawing a blank on a family-friendly comparison, so let’s just say this was not one of the more profitable software businesses out there.  And no freakin’ wonder.

Advertisements

ComponentChk.cpp – I resolved my first C++ compiler "fatal error"!

Yes, for those of you with even more grey in your beard/hair than I, this is probably a “meh” event.  However, for me, who’s always looked at C++ code and wondered how the H*** anyone could ever make sense of it, this is a big event.  [Patting myself on the back.]

Situation

I’m working on finishing up the Setup bootstrapper for my VSTO add-in Word2MediaWiki.NET.  I’m targeting Word 2003 and Word 2007 (despite Microsoft’s best efforts/worst neglect to the contrary), and I’m trying to achieve what many have done before, but which seemed impossible for me: allow the Setup.exe to detect which version of Word is installed, and then conditionally install the prerequisite software (i.e. the so-called Office PIAs) for whichever version of Word is discovered.

Problem 1: most folks in the VSTO space seem to think that, despite the fact that a version of the .NET framework is required to support VSTO, the Setup.exe that goes with your VSTO add-in install package should use UNmanaged code to detect whether the pre-requisites are installed.

Problem 2: I know squat about writing unmanaged code.

Problem 3: Microsoft’s VSTO & Office teams left it up as an exercise to the VSTO app developer to figure out how to assemble the amazing number of parts necessary to make a VSTO install work transparently to the end user.

Problem 4: There’ve been many articles written posthumously (I mean, long after VSTO v2 was released) on various aspects of automating VSTO-add-in installation, but none of them addressed the very inevitable scenario where the app developer needs to support both Word 2003 and Word 2007.  [Don’t even *think* about pre-2003 versions of Office — you’d have to be clinically insane.]

Manna from heaven

One ridiculously brave soul, Mark Hogan, actually took the time to not only figure out how to build such a conditional pre-requisite detection app in C++, but he was so overcome with glee that he beat Microsoft at something even they were too scared to do, that he published the full set of source code and XML configuration files for the entire world to use.

Now, masochist that I am, I took it upon myself to try to integrate the code that Mark Hogan published into the existing code that I’d already slotted into my Office PIA bootstrapper files.  However, I didn’t anticipate the foreseeable result that, because I’m coding this stuff in my spare time, and usually late at night, I would (a) not finish the integration work in one sitting, (b) forget what I’d originally set out to do and (c) forget to integrate any of the critical code that actually performed the conditional logic.

Mike chases his tail

Miraculously, I was left with a .CPP file that would compile and that appeared to be significantly different from the original file I started from, and that threw off an error code that created a spectacular wild goose-chase:

“Unable to satisfy all prerequisites for Word2MediaWikiDotNET.  Setup cannot continue until all system components have been successfully installed.”

Details:

“Prerequisite check for system component Microsoft Office 2003/2007 Primary Interop Assemblies (Word Only) failed.

See the setup log file located at ‘C:\DOCUME~1\msmithlo\LOCALS~1\Temp\VSD33.tmp\install.log’ for more information.”

And in the INSTALL.LOG file was the illusory answer:

Running external check with command ‘C:\DOCUME~1\msmithlo\LOCALS~1\Temp\VSD33.tmp\Office2003PIAor2007_WordOnly\ComponentChk.exe’ and parameters ‘/delete /save /word {1EBDE4BC-9A51-4630-B541-2561FA45CCC5}’

“Process exited with code 1607”

Setting value ‘1607 {int}’ for property ‘SupportedWordVersionInstalled’

Setting value ‘1607 {int}’ for property ‘PIAsRegistered’

This led me down the path of chasing InstallShield errors, since the only Google search results that looked at all related were things like these.  After a few days of trying to figure out how the InstallShield scripting engine could be related to a Visual Studio SETUP.EXE or custom C++ application, I finally got my brain back and tried to isolate where in the code or configuration files the problem existed.  That led me to a line of C++ code that threw ERROR_UNKNOWN_COMPONENT, which as it turns out also shares the 1607 error/exit value.

[The whole gruesome story is illustrated in the Bug I filed to myself here.]

Back to the original goal, with a new mission

Once I realized what I’d left out of the C++ code, I quickly got it to the point where I could try compiling it.  Now a new mission emerged – how to debug a C++ compiler error?

C:\>cl.exe /Oxs /MT /GS ComponentChk.cpp advapi32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 15.00.21022.08 for 80×86
Copyright (C) Microsoft Corporation.  All rights reserved.

ComponentChk.cpp
Microsoft (R) Incremental Linker Version 9.00.21022.08
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:ComponentChk.exe
ComponentChk.obj
advapi32.lib
ComponentChk.obj : error LNK2019: unresolved external symbol __imp__WaitForInputIdle@8 referenced in function “int __cdecl MyStart(char const *,char const *)” (?MyStart@@YAHPBD0@Z)
ComponentChk.exe : fatal error LNK1120: 1 unresolved externals

That command line (cl.exe /Oxs /MT /GS ComponentChk.cpp advapi32.lib) was derived from the original article from which we were all working (or stumbling around half-blindly, it felt to me).  I just ran this without a second thought, assuming that since Mark Hogan didn’t seem to mention any modifications to it, any errors it showed must’ve been my fault.

Where is “__imp__WaitForInputIdle@8” I wondered?  It didn’t show up in the source code when I searched for it, and nor did “int __cdecl MyStart“.

After staring at this for a while longer, I figured some substring searches would work, which after a few attempts finally showed me that I was actually looking for the code

WaitForInputIdle(pi.hProcess, INFINITE);

which is called in the function MyStart().  I tried some silly things first, of course, but I eventually realized that if WaitForInputIdle() didn’t exist as a named function in the source code, perhaps it existed in another library that wasn’t yet connected to the code?  A quick MSDN Library search told me this function was actually part of USER32.DLL, and it wasn’t too painful a leap of logic to try adding USER32.LIB to the compilation parameters, like so:

cl.exe /Oxs /MT /GS ComponentChk.cpp advapi32.lib user32.lib

And when I loaded up the now-successfully-compiled COMPONENTCHK.EXE into DEPENDS.EXE, it confirmed exactly what I expected to see:

image

Mark, many thanks to you, and my apologies for calling into question your mad C++ skillz.