Showing posts with label InstallAware. Show all posts
Showing posts with label InstallAware. Show all posts

Tuesday, May 27, 2008

Ack! The Windows Installer does not permit installation from a Remote Desktop Connection

I'm testing a installer that I had migrated from Wise For Windows to InstallAware. I testing the installer from inside a virtual machine. I used do it with VMware Workstation, but now I'm using virtual machines hosted on our ESX server. That's usually much faster and doesn't my bog down my development machine.

I connected to a virtual Server 2003 session using Terminals. If you spend anymore of time using Remote Desktop, then you'll want Terminals. It lets you have multiple sessions open in a single instance of the client by displaying each session in a tab. In addition to RDP, it supports VNC, Telnet/SSH, and a few other protocols. And it's an open source project, you can't beat the price tag.

With a RDP connection, you have the ability to automagically have the remote connection access your local resources (drives, printers, serial ports, etc). I was using the local access to disks to allow the remote connection to access my hard drive. I figured that would be a quick way to compile the installer on my local machine and run it remotely through the RDP connection.

Or so I thought. As a prerequisite for this app, I need to install the ASP.NET 2.0 AJAX Extensions. That comes as a Windows Installer .msi files. When I ran that .msi, I got the following error message:


In other words: FAIL. To add insult to injury, that's not even a standard Windows error dialog. With most Windows error dialog boxes, you can press Ctrl-C and the contents of the dialog box will be copied to the clipboard. You'll get all of the information from the dialog, something like this (from a different installer):

---------------------------
Microsoft .NET Framework 2.0 SP1 Setup
---------------------------
The Windows Installer package:


c:\c6b28b8b6e56383fbc455e9977dd00\vs_setup.msi


could not be opened.



Choose Retry to try again. Choose Cancel for exit setup.
---------------------------
Retry Cancel
---------------------------



That's very handy for reporting errors or for researching with the text from the dialog. I hate it when I see a bug logged in our internal bug tracking system that has embedded a screen shot of an error message. They screen shot the whole page (hello? Alt-Prtscn anyone), save the image, then attach it as a file attachment. Instead, they have press CTRL-C and then CTRL-V and be done with it. Rant over, back to original rant.


For some reason, the AJAX installer team chose not to follow that convention. So I went to Google and manually typed in the text of the error message (Oh, the huge manatee!) and start viewing the results. Sure enough, it's in the MS Knowledge base as 927063. Windows Installer does not allow installs in Server 2003 from a RDP connection shared drive. When you allow access to your local C: drive to the remote session, it sees it as \\TSClient\c and flat out disallows installs from anything in the \\TSClient namespace. This was determined to be a security risk and the tssclient blocking was added Windows Installer 2.0 at the time of the Windows Server 2003 release.


This KB article lists two resolutions, use the standard UNC notation to reference that drive or map a drive letter to that location. To that list, I'll a third option. Copy the file from the \\tsclient share to the remote desktop and run it from there. For one off tasks, that's usually faster than accessing a UNC named object.


Side note: While writing this post, I read on the Terminals home page on CodePlex that Microsoft will be removing the ability to connect to the console session through the RDP protocol in version 6.1. Being able to connect to the console is a little known feature of the RDP client, mstsc.exe. I use it when people leave their RDP sessions hanging on a 2003 Server box that only has the admin remote connections enabled. I can connect over the console session and blow away the left over sessions. I can query and kill the remote sessions via the command line, but sometimes you just need one session open. If you want to see this feature left in the RDP stack, place your vote here.

Thursday, May 22, 2008

Checking for the presence of .Net Framework 2.0 SP1

Installware has a lot of built in support for checking for the presence of the various flavors of the .NET Framework.  In it's current incarnation, 7.5, it doesn't have anything for checking for the presence of Service Pack 1 of the .NET Framework 1.0.  It turned out to be pretty easy to add that check to my installer script.

A couple of years back Heath Stewart did a blog post about how to check for the presence of a .NET Service Pack.  You just look for the the value of "SP" in the registry key for the version of the .NET runtime that you want to check.  For .NET 2.0, that key would be labeled:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727

If you want to check against a language, you just check for the language id.  For example, English is ID 1033.  The key to check for the English version of the .NET 2.0 runtime would be.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727\1033

The SP is DWORD, so you will get back an integer result.  If the .NET runtime has been installed, it will have the SP value.   For the RTM release, it will be 0.

With InstallAware, I use the following syntax to check for .NET 2.0 SP1 and terminate the install if SP1 (or later) has not been installed.

Read Registry Key HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727\SP into CHECKSYSTEM
if Variable CHECKSYSTEM not Greater Than 0
MessageBox: $TITLE$ Setup Error, This product requires that the Microsoft .Net Framework 2.0 Service Pack 1 has been installed.$NEWLINE$$NEWLINE$Setup cannot continue.
Terminate Installation
end




For some reason, InstallAware does not have an "Less Than" operator, you have to do "not Greater Than".  InstallAware is a leaky abstraction of the Windows Installer experience, I just shrug at those little oddities.


[Updated on 5/27/08]
I had a typo in the line that starts with "if Variable CHECKSYSTEM" where it should have been comparing against the value of 0, not 1.  My bad.

InstallAware doesn't automatically remove assemblies from the GAC at uninstall time

I am updating the installer for one of our products to check for the presence of the SP1 version of the .NET 2.0 Framework, when I noticed something odd.  The assemblies that I was putting into the GAC were still there after an uninstall.  The guys at InstallAware seem to think that this is not a problem.  They refer to a topic in the MSDN, Removal of Assemblies from the Global Assembly Cache, which describes how the Windows Installer is not responsible for the removal of entries in the GAC:

The Windows Installer determines whether to allow the removal of a common language runtime assembly based upon a client list it keeps independently of the assembly. The Windows Installer keeps one pin bit to represent Windows Installer clients of the assembly. The installer pins the assembly for the first Windows Installer client and unpins it when the last Windows Installer client is removed. The assembly maintains a pin bit for every client of an assembly.

The Windows Installer is not directly responsible for the physical removal of common language runtime assemblies from the computer. The installer unpins the assembly when the last Windows Installer client is removed. If the Windows Installer is the last client of the assembly, the common language runtime provides the option to force a synchronous cleanup of the assembly. The cleanup process is atomic and there is no provision for a "rollback" at this point. All unpinning of common language runtime assemblies must occur after the user has had a chance to cancel the installation or removal.

That being said, the users want to uninstall everything.  Wise and InstallShield apparently do this.  With InstallShield, there is a property for the installed file called "Permanent".  If it's not set, it will be removed from the GAC if there are no other references to the assembly.

What InstallAware wants you to do is to explicitly remove the assembly during the uninstall portion of the install script.  They provide a command named "Remove Unpinned Assemblies" (it's listed as "Remove Assemblies").  This command will remove all unpinned assemblies that your installer had places into the GAC.  The code should look like this: 

  Apply Uninstall (get result into variable SUCCESS)
Remove Unpinned Assemblies


I'm still not sure if that will always work.  According to InstallAware, there is a bug in Windows Installer where sometimes the installer corrupts the Global Registry cache on .NET 2.0 assembles and there will always be a reference to unreferenced assembly.  The only work around is to delete the (Default) registry key for the assembly in the user and local machine hives.  This mess is documented here in the InstallAware support forum.


The work around sounds worse than the problem.  I'm not going to do a strafing run on the registry to deal with a Windows Installer bug.  Not when the only consequence is that a few assemblies (in my case, it's only a few) get left in the GAC.  There are other ways of removing assemblies from the GAC.  The gacutil.exe utility can be used to check the reference count and to remove the assembly,  The "/lr" command line parameter will list the reference counts for each assembly.  The "/u" parameter will remove an assenbly from the GAC.  For the command-line shy, the free tool GacView provides an Explorer like view of the GAC and it's easy to use to remove assemblies from the GAC.

Tuesday, April 01, 2008

Saving application settings over installs

We have been using Windows Installer (WI) based setups for all of our newer applications.  I used to use Wise, but I have migrated our installers to InstallAware.  Why I made that change will be another blog post, but needless to say I had very good reasons to make the switch.  When we release a new version or upgrade for one of our applications, we do full installs.  We don't do patches.

WI technology supports the distribution of patch files.  If you are only updating one or two files, the patch file will only contain what has changed, greatly reducing the size of the installable bits needed to be distributed.  I did use patch files when I first started doing WI based setups, but I abandoned the practice early on.  Patches were hard to automate as part of the build process and they had to account for cumulative changes.  The other drawback was that they could add files, but not remove them.  Another drawback was that it yet another file extension, .msp instead of .msi, to explain to the users.

I decide to have the installers upgrade in place. When a new version was installed over a previous version, the previous version would be silently uninstalled, then the new version would be installed.  This basically gives you a clean slate per install, which is great because you only install the files that are needed and the dependant objects will be up to date.  The bad part is that the user settings will be removed as part of the previous versions uninstall.

What I needed to do was to persist the user defined settings.  Consider a web application, if all of the settings are in the web.config file, then you just need to keep that web.config file around.  The simple solution is to tell the installer not to delete that file on the uninstall.  There's a couple of problems with that approach.  The first one is that the new install is a full install and the user gets to pick the destination.  They may want to put the web application in a different folder or drive and I didn't want to take that option away just to make my job easier.

The other drawback was far more serious.  I could keep the web.config around between installs, but what if the new version added content to the web.config file?  You don't want to save the old settings and lose the new settings.  That could cause hard to diagnose bad things to happen.   What was needed was the ability to merge selected content from the old web.confile and into the new web.config.  You can do many things with WI technology and InstallAware provides powerful functions, but this is a task best left outside the installer code.

I decided to write a Win32 command line app that would be able to save the old web.config and merge selected values from that file into the new one.  The installer would be include that file and be able to run it without permanently installing it.  I could have used C# with .NET (and I even had most of the code already written), but I didn't want to deal with .NET security issues or make the .NET Framework a prerequisite for non .NET based installs.  Delphi 2007 provided everything that I needed.

From the installer, I call this app just before the previous version is uninstalled with a command line parameter to designate which app is being installed.  It will locate the current web.config file for that application and copy it to a folder in the user's temp directory.  I always have our installers write the location of the install folder to the registry, it makes tasks like this much simpler.  After the new version has been installed, I run the app one more time, with a "-restore" command line parameter.  It locates the cached copy of the old web.config and the new web.config and updates the new web.config file with the settings that need to be persisted across installs.  Once that task is complete, it removes that temporary folder.

The app knows what settings to use from rules encoded into the source code.  I could have used a file to store a list of settings to persist, but that would have made the installer a bit more complicated.  It's fairly easy to run an app from the installer without actually installing it, but if you want it to have it read an external file, then you need to make sure that file is in a place where it can be read from.  That comes one more part of the installer to be tested and the mechanism for handling that would be specific to installer tool used.  By making the app contain the rules it needed to follow, running it from the installer becomes a trivial task.

The other advantage of having a separate app to handle to the saving of the user settings is that the code is much easier to test and debug.   It becomes a modular piece of the install and can even be unit tested in a scriptable manner.  This type of code can be easily adapted to the types of application.  I use a similar type of program to persist settings and current run start for some of our Win32 service applications.