September 2008 - Posts

CVS and CruiseControl.NET and password exposure

I'm setting up a new build server using these applications:

  • CVSNT 2.5.03.2382 (open source source control, command line based)
  • TortoiseCVS 1.10.9 (GUI for interacting with CVS)
  • CruiseControl.NET 1.4 (service for automating builds)

When using CruiseControl to automate the checkout from CVS of the most recent source code, you have to setup a CVS section in your CruiseControl project.

According to the CruiseControl documentation, you are required to provide the cvsroot, which includes among other things the name of the CVS server, the user name with which to interact with CVS and that user's password. (Caveat: I don't know much about security and connection methods, but I do know that CVS offers several connection methods and I believe part of the reason that a password is required is because I'm using the pserver access method.)

As you can imagine, I wasn't excited about having to have my build user password in plain text in the cruise control project files.

I discovered a way to avoid this requirement.

  1. Go to the build machine on which the cruise control project will run.
  2. At a command prompt run this CVS command: cvs -d :pserver:cvsuser@cvsserver:/cvsrepository login
  3. After hitting Enter, supply the password

Now you've setup a remembered recognition of the cvsuser on that cvs repository and it won't require the password every time you try to run a CVS command.

If you move the CruiseControl project to a different machine, I believe you need to repeat the process of logging in from the new machine.

 

Posted by SusanGorman with 1 comment(s)
Filed under: ,

Common Task: Optionally Cleanup User Data on Uninstall

This is a common scenario... Your application, when it runs, creates log files, registry entries, and customized data files on the machine. These are files that were not installed by the installer. Therefore when you go to uninstall, these files would not normally be cleaned up.

It ought to be simple to add code to the installer to tackle removing these files. But... how do you handle it if an uninstall is happening because the user is upgrading? In that case, the user probably wants to KEEP those 'hanging chads'.

So you are left with a situation where you want the removal of items to be conditional - under some conditions remove them, under other conditions don't.

This gets tricky. I've worked up a solution to it. I'm not claiming it's ideal or anything.

Deleting Files and Folders

Before I begin, let me say that as I understand it the best practice is for an application NOT to create data files in its INSTALLDIR. Instead, files like this should go under a Documents and Settings folder either for the current user or under All Users. Further, my understanding is that you don't technically have to remove files under Doc n Settings. However, the requirements I'm usually given say "must remove all traces of product on uninstall". So having said all that...

For this example let's say that the product creates a folder called Logs, under INSTALLDIR. You'll need a Directory table entry for this path, such as INSTALLDIR_LOGS. To cause this folder to get removed on uninstall you add 2 entries to the RemoveFile table. One entry with FileName of *.* and DirProperty of INSTALLDIR_LOGS. This entry causes all files under Logs to get deleted. A second entry with FileName blank and DirProperty of INSTALLDIR_LOGS. This entry causes the (now empty) folder to get deleted. Both of these table entries should be associated with a component that will be uninstalled at uninstall time.

Now let's get tricky and make this conditional. Let's assume that by default you want to KEEP this folder. The reason to keep it by default is so that a silent uninstall (such as when upgrading) will keep the folder.

In the Directory table, create an entry for UNINSTALL_INSTALLDIR_LOGS. Directory_Parent is TempFolder. DefaultDir is ThisFolderShouldNotExist. Yeah, that's a weird name, but it is self-explanatory. We are setting the path to a path which we do NOT expect to actually exist on the target machine - a subdirectory of the Temp folder. The point being, we don't intend for any files to actually get removed at this path.

Go back to RemoveFile table and change the DirProperty for both entries so that they point to UNINSTALL_INSTALLDIR_LOGS.

Now.. when uninstall occurs, it looks in RemoveFile table and tries to delete TempFolder\ThisFolderShouldNotExist\*.*. This folder won't exist and there will not be any errors as a result of that. All is well.

Now we go to Custom Actions. Create a Set Directory custom action that will, based on a flag, set UNINSTALL_INSTALLDIR_LOGS to the REAL path where the log files exist. For example, my cusotm action is called Uninstall_SetLogsDir. The Directory value is [INSTALLDIR_LOGS]. This goes in the Install Exec sequence with a condition of FULLCLEANUP="yes".

So if the FULLCLEANUP property is set to "yes", then our fake path will get reset to the real path, and as a result the log files will get deleted.

Now all you need is a method of setting FULLCLEANUP to yes. In the Property table, create FULLCLEANUP and set it to no, by default. Using whatever method you want, cause a dialog to pop up during uninstall that asks the user if they want to do a full cleanup and if they say yes, then set FULLCLEANUP to yes.

NOTE: When the user uninstalls from Add/Remove Programs using the Remove button, it launches the uninstall with reduced UI, so any custom dialog you write will NOT appear. Therefore, you have to either A) disable the Remove button and force them to use Change button instead or B) display your full cleanup question dialog using some other method via a custom action (InstallScript?).

 Deleting Registry Keys

You can apply the same basic methods to conditionally removing a registry key using the Property table (instead of the Directory table) and the Registry table (instead of the RemoveFile table).

Create a Property that holds the path to the registry key. By default set the Property to something that will not exist, for example, a Property called MyRegKey which is set to "Software\ThisKeyWillNotExist".

In the Registry table, create an entry for the key you want to conditionally remove. The fields for this entry are:

Key = "[MyRegKey]"
Name="-"
Value=""

And set the Component to whatever component makes sense. It is the minus sign in the Name column that tells MSI to remove the reg key on uninstall.

In the Custom Actions, create an action that will set the MyRegKey property to the *real* path when FULLCLEANUP="yes". The real path might be something like: "Software\[CompanyName]\[ProductName]\MyCustomRegKey".

NOTE: This only works with whole reg keys, you cannot remove a registry VALUE using this method. In fact, there is not a clean way to remove only a registry value, as far as I know. I find this odd, but true.

 

There ya go. It appears to work, and it isn't too clunky. Have fun!

I'm a slacker!

I was reminded that I haven't posted in a while and that I've been a slacker. I profusely apologize and hereby seek to remedy that situation. Smile

So here's what I've been up to...

 Why should you avoid using the TypeLib table ?

I have a customer trying to consume a merge module that we produce. The customer is creating his installer using Wise.

I created the merge module using InstallShield 12. The merge module includes a COM dll that is self-registered. In my merge module I set the DLL to extract COM data at build time. This is so that the registration data stays up to date in the installer. Unfortunately, the way that InstallShield works, this causes it to create the registry entries in the TypeLib, AppId, Class and ProgID tables instead of lumping it all in the Registry table. 

The Wise system refuses to consume a merge module with entries in the TypeLib table and errors out. Why?

See the Remarks in this MSDN article about the TypeLib table http://msdn.microsoft.com/en-us/library/aa372092(VS.85).aspx.

So... I'm left wondering at this point, how am I supposed to use InstallShield to dynamically update the COM registration information at build time and yet still produce an 'acceptable' MSI/MSM? I haven't resolved this issue yet. So far I've had to manually modify the MSM for that one customer who's using Wise. Customers using InstallShield don't have a problem.

 CVS is problematic and my builds aren't reliable

I've been having problems with builds failing on an irregular, but annoyingly often basis. I use CruiseControl.NET and CVS at the moment, because these are open source (free!) tools. I'm NOT on the most up to date version of these tools due to the fact that I need to test out these tools in a non-production environment before I can implement the newer versions and I don't have access to a non-production environment yet. It's in process though! The problems I'm having with the builds are these main issues:

  • CVS "cannot change permissions on temporary directory" errors. This error appears multiple times a week. I haven't been able to find any solution or explanation for this error in all my googling. All I can find is "yeah, this happens, we don't know why, just try again and it will work". This is true, in that if you immediately do another build it does succeed. But that doesn't make it acceptable to have builds failing. This is an unresolved problem for me at this time.
  • CVS times out when trying to apply a label/tag. CruiseControl is using its internal APIs (?) to effect the CVS tagging so I can't get a good detailed log of what's going on during this process of why it's freezing up. Since tagging doesn't happen until after the build is finished and successful, this is really annoying to have it fail at the very end. One potential handling is to stop using CruiseControl to do the tagging, but I hate to so obviously bypass the built-in functionality.I'll probably try this technique soon. Unresolved at this time though I do have this technique to try out.
  • Network briefly blips and loses access to the network device on which files are being copied. Using MSBuild to do a copy task which copies dozens to hundeds of files. If there's a momentary blip and it can't copy a single file -- this causes the entire build to fail. The network shouldn't be blipping in the first place, but.. until that is solved, it would be ideal to have a copy function that is more robust which could try copying - if it fails, wait a sec and try again, and don't fail out unless more than one copy attempt fails. I could do this pretty easily in nAnt, but this particular build is in MSBuild and I'm not very familiar with MSBuild yet. The suggestion that was given me was that I need a custom MSBuild task written in a DLL. (sigh! More DLL writing) Haven't tackled this one as yet because we'd prefer to fix the network instead.

InstallScript custom actions fail due to ISBE.dll not registering on Windows Server 2008

Lastly, in a big installer I started working on converting all the VBScripts to InstallScript because management prefers InstallScript to writing new DLLs for custom actions. Unfortunately, The test machine for this installer is a Windows Server 2008 OS and we immediately started gettting install failures due to the InstallScript CA engine (ISBE.dll) failing to register on the machine. I made a test installer and tried it out on other 64-bit machines (XP and Vista) and it works fine, but it fails on this one Windows Server 2008 machine. Currently working with InstallShield to see if they can help get rid of this error. Until this is solved, I can't work any further on converting scripts to InstallScript because it creates a perception in management that InstallScript is unreliable.