Click here to Skip to main content
15,860,943 members
Articles / Operating Systems / Windows

Uninstall a Previously Installed Application when Upgrading an Application with Setups created in VS.NET- Part 1

Rate me:
Please Sign up or sign in to vote.
4.77/5 (61 votes)
22 Oct 2005CPOL25 min read 288.9K   143   53
Configure your upgrade setups to uninstall a previously installed application using VS.NET and Windows installer technologies.

Background

First of all, thanks for the interest and valuable comments on my initial submission. This article has been revised, twice now, and hopefully for the last time. I have corrected some errors and omissions and added a few additional comments. The original version was much "simpler", but rarely are things simple when you are dealing with installations. I have corrected some errors pointed out by others and those found by me. The article has now become more complex. This is also part 1 of the overall methodology I want to present. There is now a companion Part 2 article. The techniques shown here do not go into the gritty details of the rules applied to Windows Installer setups known as "Component Rules". Fortunately, VS.NET enforces Component Rules for you behind the scenes - as long as it can find either your previous setup, or the previous version of the application itself on the machine you are building your setup on. We will discuss them in Part 2.

Introduction

How to uninstall a previous application install during an upgrade with VS.NET created setups is a question that frequently appears in the forums. No matter what options you select in the VS IDE when you create a new setup for an upgraded application, it never uninstalls the existing application!

One thing that most developers are not aware of is how to properly use the update code, which is what triggers uninstall of a previous version. Windows Installer includes the capability to find and uninstall previous versions of an application. But there are some steps that are required to make this happen which are not well documented in VS.NET.

It starts with configuring the upgrade codes - GUIDs that the installer engine uses to "identify" multiple MSI packages related to each other. However, even when you properly configure the update code, the uninstall works - only for a per-user install. It always fails for a per-machine install.

This article describes how to properly author upgrade setups so that they can uninstall under commonly encountered "real world" conditions...

What you'll need

You need a utility called Orca that allows you to manually edit the MSI package. There was a time when you could download a Windows Installer SDK from Microsoft. However, the brain tank at Redmond, in their mysterious "wisdom", decided to make this unavailable. Now you have to download the 250 MB Platform SDK just to get Orca??? But there's a good news brothers and sisters! You can still get the ancient version 1 MSI tools, and luckily, the original version of Orca will allow you to open version 2.0 MSI created by VS.NET. (I don't know about 2005, but VS 2002 and VS 2003 create version 2.0 installers). Get it here, while you still can! (As soon as someone from the powers sees that I've posted this link, no doubt it will be gone :-) Version 1 Windows Installer SDK. Then find and run the Orca install - that is all you need for this process!

What the zip contains

The zip file contains two simple Hello World setups. Version 1 setup has been built using VS.NET with no modifications whatsoever to the MSI after the VS build - much like one of your applications already out in the field. Version 2 has been built using VS.NET and then modified during/post build to cleanly upgrade Version 1.

First run Version 1 and you will see that it installs a "Help" file and a shortcut to that file for demonstration purposes. Version 2 does not contain a "Help" file. Next run Version 2 you will see that it not only upgrades the application version, but also uninstalls the old help file and the shortcut since they are no longer part of the "new and improved" version 2. (I realize that new versions usually contain more features, but the setup adds new features on top of the existing ones, even if it is not properly authored to uninstall the old application. So the demo is intended to demonstrate that the previous application uninstall works cleanly.)

The main purpose of the MSI files is, you can look at them in ORCA and see the changes that I have made.

I have not included any projects - you will not get much out of this article unless you work through it with your own setups. Feel free to use the two Hello World executables as sources to create your own setups, or use your own applications. Either way, you need to go through the entire process starting with VS.NET to build your own setups and then modify them as described.

Configuring an uninstall during an upgrade

OK, suppose you have released version 1 of your application. Now you are ready to release version 2, but you want to uninstall version 1 in the process. Here's how you can do that!

1. Find the original setup upgrade code

First you need to correctly configure the upgrade codes. Luckily, even though you probably did not know how to use it, VS.NET created an upgrade code for you automatically when you built the first version setup of your application. First what you need to do is find it. Reopen the VS.NET setup project you had created for version 1, look in the Properties Window and you will see a GUID named UpdateCode. Copy this - you will need it when you create your new setup for version 2.

Here is how it looked like:

2. Create your version 2 setup

Create your version 2 setup as you would normally do. (We'll discuss some version issues later, but for the time being, just create your new VS.NET setup, but do not build it.)

3. Configure your version 2 setup to uninstall previous versions

You will need to change two properties in your version 2 setup to trigger an uninstall of the existing version. Set the RemovePreviousVersions property to True. Set the UpgradeCode property to the GUID you retrieved from your first version setup. This is how the new installation finds the previous version for reinstall. Save and build the new setup. VS.NET will apply some complex component rules, that we will not discuss for now, to your new setup after it recognizes that the UpgradeCode already exists. (Again, to ensure this happens without having to worry about the details, simply build your new setup on a machine that has the previous version installed!)

Here is how it looked like:

4. Test the old and new setups!

That is all you need to do to make it work! Well sort of. That is all you need to do ... if your application was installed per-user, and if you are not worried about uninstalling and reinstalling certain files - like an Access database for example. We'll get to that ...

I suggest you demonstrate this for yourself. First backup if you need to, and then uninstall any existing application install manually. Then run your original "Version 1" install - by selecting the "Just Me" option. (For simplicity, we'll call them "Version 1" and "Version 2" although your actual version numbers maybe higher.) Now run your newly created "Version 2" install - it does not matter whether you select "Just Me" or "All". Notice that your version 1 application has been uninstalled, and there is only one entry in the Add/Remove programs utility. This is exactly what we wanted to happen! Now clean everything up again. This time run your version 1 install but select "Everyone". Now run your version 2 install. You will notice that all the new files actually get upgraded. But any old version 1 files that are no longer needed are also on the system. And you now have entries for both setups in the Add/Remove programs window. This is not exactly what we wanted to happen!

5. To try to fix or not to fix the per-machine uninstall failure issue

Speaking in simple terms, a major upgrade in Window Installer works only if the version 2 per-user or per-machine status is the same as how version 1 was installed - either per-user or per-machine. You will see this if you have tested the setups as suggested above. To try to address this issue, we should first define some terms:

  • "Per-user" is the equivalent of installing with the "Just Me" option.
  • "Per-Machine" is the equivalent of installing with the "Everyone" option.

Because of the way VS.NET creates installer packages, it always defaults to a per-user install. This fouls up your uninstall if version 1 was installed for "Everyone" - i.e. a per-machine install. Even if the user selects "Everyone" in the version 2 install, the package skips the uninstall for version 1 because the initial default per-user does not match the previous per-machine level, and by the time the user changes this, the installer is already determined that it cannot uninstall version 1 because the levels are not the same.

Keep in mind that when the uninstall does not "work" the behavior reverts back to the same as it would have if you created a version 2 setup in VS.NET in the "conventional" fashion - you end up with an install on top of an install - two entries in the Add/Remove program etc.

Unfortunately, there is no way to guarantee you that your new version 2 setup will also always uninstall the existing version 1 install in all possible deployment scenarios using the modifications described below. See Part 2 for a custom action that solves this problem.

Now you have to make decisions - you can "hedge" your bet in some cases if you know how your application is typically installed.

Option 1: May be best option if your deployment scenario is mostly applicable to business environments on NT systems that have an administrator.

In many business settings, you have an administrated system where most users are not administrators, and an administrator usually installs the software for everyone. If this is your deployment scenario, you may probably want to modify the setup behavior of version 2. You can set the property - ALLUSERS - to a value that allows version 2 to make some decisions about how it is being installed. This will work in most cases - assuming that the administrator typically installs the software for his user group as per-machine. I think this is a reasonable assumption basically because it is the simplest way for an administrator to manage his user group, but again, nothing is guaranteed. The "typical" administrator is as imaginary as the "typical" user, but I cannot cover every real possibility under the sun. :-)

Here is how ALLUSERS = 2 will cause version 2 to behave for the basic NT user levels:

  1. Restricted user: Cannot install programs. Since an admin must install the program for them, the most likely case is the admin logs into their machine, and selects "per-machine" because if the admin selects "per-user" then the app is installed only for the admin, not for the user. The upgrade works correctly in this scenario, because the original install is per-machine, and version 2 setup will first try to do a per-machine install if ALLUSERS = 2.
  2. Standard user: Can install programs themselves, but only as a per-user. No option is displayed to install for "Everyone". The upgrade still works because setting ALLUSERS = 2 causes the setup to default to the highest allowed level. In this case, that is per-user. The upgrade works correctly in this scenario because it is forced to default to the per-user level.
  3. Administrator installing the software for all other users of the machine: Since the admin is intentionally installing the software for "all users", presumably they choose per-machine for version 1, and will again choose a per-machine, for version 2 install because after all, they are installing it for all the users that might have access to the machine. The upgrade works correctly in this scenario because as in case 1, the original install is per-machine, and the version 2 setup will first try to do a per-machine install if ALLUSERS = 2.
  4. Administrators installing software for themselves: This is the case where the upgrade may or may not run as expected - if they choose the per-machine "Everyone" option for the original install, and you set ALLUSERS = 2, it works correctly.

If, however, they choose to install the original only for themselves, i.e. per-user, the two setups won't match, and this is the only case where it will not work correctly.

Again what happens in this case is basically you revert back to the behavior that you would have if version 2 did not attempt to uninstall version 1. So it does not fail catastrophically. It works like an unmodified VS.NET version 2 install. The various files are upgraded, version 2 should install correctly in most cases, but you now have two entries in Add/Remove programs, etc.

Option 2: Maybe the best option if your deployment scenario is mostly applicable to individuals who install your application on their own computer for personal use.

Leave the default behavior as per-user. Your version 1 setup was distributed as a "standard" VS.NET MSI. That means it defaults to per user, and since there is no way to really know what the user's preference was, probably your best assumption would be that they accepted the defaults.

Which option to choose? I cannot answer that question for you. I can only show you how to change the MSI to use Option 1 if you feel that is your best choice. If you choose Option 2, no modifications are required.

To change an install to default to the behavior described in Option 1 we need to modify the MSI with ORCA. Right click on the version 2 MSI you had created and select the option to edit with Orca. This will open the MSI database tables. Find the Property table and click on it in the left view window of Orca.. Select Tables, Add Row, and enter the following name and value: Name: ALLUSERS Value: 2.

You should see something like this:

This changes the default startup level of the setup to the per-machine - "Everyone" - option.

But if you include the dialog where this option is displayed, the default radio button will not reflect this change. So we need to fix that too. In the same table, you will see a property named "FolderForm_AllUsers". This will have a value of "ME". Double click on the Value row and you can edit the value. Change the value to ALL.

From the Orca menu select File, Save and save the MSI. You also need to close it otherwise it will not run. Now, your version 2 setup defaults to a per-machine and will behave as described in Option 1 above.

Now the burning question is: "Is there a way to ensure that the uninstall always works?" The answer is Yes, but it requires some advanced techniques. You have to create a custom action to find the existing install of version 1, see how it was installed, and set your version 2 install to match that behavior. See Part 2 to learn how to do this.

In the meantime, even though I have not given you a "perfect" solution, your worst outcome is no different than if you used a standard VS.NET Version 2 that does not even attempt to uninstall Version 1.

6. "OMG, Where's my existing data???" or How to prevent unwanted file replacements

The above technique works fine - as long as you don't mind everything from version 1 being uninstalled. Suppose you need some of the old files? Just put them in the version 2 install - you need them either way, because a person might be doing a new install of version 2, right?

Well. maybe. But suppose your application uses an Access database. And the person has been using version 1. That means they have the data. And although you have put your Access database in version 2 as well, suddenly your phone starts ringing off the hook with angry customers asking "I upgraded to version 2. Where's my existing data?"

The problem is version 2 MSI completely uninstalled version 1 first, and then completely installed all the files in version 2. So the existing database file - with your customer's data! - was first removed, and then replaced with the "as delivered from the factory with no user data" copy in the version 2 setup. Can you prevent this? Yes!

The first way is to mark your Access database as Permanent. This is something you should have done when you created your original install. You should not be uninstalling something like a database that contains user's data. If they want to delete the database when they uninstall your application, they can choose to do so.

If you have marked your database as Permanent, you don't have to worry about the install order or overwriting the existing database. If you have accepted the VS.NET IDE defaults in your version 1 setup, the database will not be marked Permanent by default. You should mark your database as Permanent in the second version. You can recover from this error and still upgrade without losing the old data. But again, like with most things in an MSI modification, there are some potential gotchas. I would like to acknowledge Philip for correcting some inaccuracies in my original version on this subject. See the threads below for more information.

I will present two options on how to control how your uninstall/upgrade/reinstall behaves, and these are determined by the Windows Installer sequence of execution. (Actually you have more than two options, but they require modifications that are beyond the scope of this article.)

Option 1: The first option you can view as "Uninstall original version first, install new version last".

If you choose to "uninstall old first, install new version last", the behavior of the MSI is simple and predictable. This behaves as if you had uninstalled the original application manually, then installed the new version. Any files that were installed in version 1 and that were not marked permanent will be removed. All the files installed by version 2 are the files actually included in the version 2 setup build.

Note: If you do not want any existing files preserved, you should use this option. It is simple and ensures that all the files that ultimately exist on the target machine are the ones you have included in the version 2 setup.

This option completely wipes out the original install, and completely installs the version 2 setup as if it were a "fresh, virgin" install. This approach guarantees all the files that exist on the target machine are the newest ones included in version 2 setup.

If you are absolutely sure there is nothing that needs to be preserved from the use of version 1 during the upgrade, you should choose this option because it guarantees both a clean uninstall of existing files installed by version 1, and a clean fresh install of all the files in version 2. If you want to perform the upgrade in this manner, you are done at this point - no more modifications of the version 2 MSI are required.

Option 2: The second option you can view as "Install new version first, uninstall old version last".

Suppose your application installs an Access database? If you choose the "uninstall old first, install new version last" option, the existing Access database will be removed, and then the new "fresh from the factory version" will be installed. This of course means that your user looses all the historical data they may have added to the Access database while using version 1. However you can prevent this if you modify the MSI so that it uses the "Install new version first, uninstall old version last" option.

This approach can be used to preserve existing files, like an Access database that has already been in use. But it also presents more complex possible behaviors, and if you use this approach, you must be familiar with how MSI replaces, or does not replace, existing files that are included in your version 2 setup. Again thanks to Philip for correcting some mistakes in my first submit.

I will try to present some basic rules in the simplest way I can. So here is a summary of the most important possibilities, from simplest to most complex:

  1. Any files that were installed on the target machine during the version 1 setup, but are no longer part of version 2 will always be removed - assuming they were not marked permanent.
  2. If a file has a version, like an application or a DLL, the highest version always wins. This is always what you want unless you are a psycho developer.
  3. Things get tricky when un-versioned files with the same name are part of both versions.

If a file, say MyFile1.ext is already installed on the target machine and has not been modified after being installed by version 1, and the exact same version of the file is in the version 2 setup, then the files are perfectly identical, and there are no "un-versioned" issues. MyFile1.ext will be on the target machine after the version 2 setup completes.

If a file, say MyFile2.ext is already installed on the target machine and has not been modified after being installed by version 1, and there is a newer copy of the file in version 2 setup, i.e. MyFile2.ext in version 2 setup has a "Last Modified Date" later than the MyFile2.ext that already exists on the target machine, then the highest "Last Modified Date" wins - the copy of the file MyFile2.ext that was included in the version 2 setup will be on the target machine after the version 2 setup completes.

But if a file, say MyFile3.ext is already installed on the target machine and it has also been modified after being installed by version 1 setup, then the existing file always wins and the copy of the file MyFile3.ext that was included in the version 2 setup will be not be the one installed on the target machine after the version 2 setup completes.

This usually enforces the behavior you want. For example, if you have an existing Access database, then the "Last Modified Date" of the database already on the target machine will be different from the date it was first installed by version 1 - because the application was using it at some point after the database was first installed - and it will not be replaced by the Access database included in the version 2 setup.

However, it also means that if you have, for example, some .xml CONFIG files that were installed by version 1, and were then modified after being installed, they will not be replaced by the files of the same name in version 2. This may be a potential problem.

Which option to choose?

Again, I cannot answer that question for you. I can only show you how to change the MSI to use Option 2 if you feel that is your best choice. If you know that you are facing a "mixed scenario" - you have files that must be preserved, but you also have files that must be replaced and you have good reason to believe that Option 2 will not work, you really need some better tools to work with. Assuming you don't have them, you will need to resort to custom actions.

Again, you have two basic options. The first is to run a custom action that backs up existing files to a temporary folder, use an "Option 1" wipe install, then run a final custom action to move the old files back from the temp folder. The second is to run an "Option 2" install to preserve existing files. This setup also needs to install new copies of the un-versioned files that must be updated to a secondary folder. Then you need to run a final custom action that overwrites these files to update them in the folder where they are required.

Each approach has pros and cons. As a general rule, I prefer the uninstall last approach because I think it works in many common scenarios. If it works in all my tests but occasionally fails to install an updated file in the field, I would rather deal with that problem than delete my customer's data.

But if you know with reasonable certainty that the uninstall last is not going to work then saving the existing files is "cleaner" and the better approach, but be aware of the fact that you cannot use a VS Installer class custom action to do this because this type of custom action does not run until the end of the install - too late to save your existing files.

If you choose the second option, you can use an Installer class. But there are a lot of negative consequences. First, you must leave the copies of the files in the secondary folder, or else the MSI will detect that they are "missing" and will again run to "repair" the "problem". Also, the version 2 MSI does not know how to uninstall the final copies of the files - they are not where it had put them. So they will be left behind if the user completely uninstalls the application.

Complications, complications, complications!

But again, I cannot tell you how to solve the problems of the world in a single article, so ... :-) Back to the task at hand now: If you choose Option 1, again no further modifications are required. So what if we want to use Option 2 - install the new application first, then remove the existing application? We can change the execution sequence to enforce this behavior by installing version 2 first, and removing version 1 last.

Back to ORCA. Reopen version 2 MSI, and find the table named "InstallExecuteSequence". First click on the sequence header in the right hand view to find the highest sequence number. Make a mental note of this - in my install the number is "6600". Now click on the action header to sort in alphabetic order. Find the action named: RemoveExistingProducts. Change the value to a value that is higher than any existing ones - I used 6700.

Save and you are done with Orca.

With the install first, uninstall last design shown here, the MSI actions preclude the need for complex conditional install actions because the execution sequence will enforce the final application image over the lifetime of multiple version upgrades - as long as you are careful with your file organization, versioning, and date control! The drawback is that there may be cases where newer version files won't get installed, as described in the rules above.

7. TEST, TEST, TEST

Obviously setups are very complex. There is absolutely no perfect solution to the complexity of upgrading an existing application. I cannot give you an answer to exactly how to proceed in your specific case. The only thing I can tell you is TEST, TEST, TEST until you are confident you understand how the install configuration you've chosen behaves.

We're done!

Other issues

Suppose, you say, "But my version 2 uses a new design of my Access database!" Sorry! This is not a Windows Installer issue. Upgrading an existing database architecture to a new one while preserving the existing data is a complex issue that depends on many factors. That is not what this article is about. :-)

Another issue is custom actions: An MSI does not log anything that happens in a custom action, so these are not uninstalled by a later version MSI, unless you've specifically written a custom action that runs on uninstall in the original setup. Often this enforces the behavior you want - for example, if your version 1 had a custom action that built a SQL Server database from script, and you probably did not write an uninstall action. Your version 2 install will not affect your original database build in any way.

As with upgrading an existing database, more complex problems with custom actions between version upgrades are beyond the scope of this article.

MSDE and merge modules

One final area I wished to at least comment on. I have not considered some issues, like, does your setup also install MSDE, or use other merge modules. Authoring complex setups using Windows Installer without the aid of a professional tool like InstallShield is really pretty difficult.

And with MSDE, as you may be aware of, there are a number of details that make this kind of install especially tricky. Plus Microsoft has changed their approach multiple times over the history of the MSDE. First it was like SQL Server - you had to install it separately from any application install. Then there were various incarnations of merge modules that were released for MSDE, but most if not all had a variety of bugs and other problems. Last time when I checked, Microsoft recommended not to use merge modules for MSDE so you are presumably back to a separate install.

This has always been a troublesome issue, and as much as I feel it needs addressing. I felt that this was not an article of that scope - maybe in future I'll try to tackle that problem.

Conclusion

This article has been substantially modified based on some inaccuracies pointed out by Philip, as well as some errors I found. I suggest you to read the technical comments given below. Working with setups is very complex and you can never have too much exposure or perspective. Recently, I submitted a companion article Part 2 that resolves the per-user/per-machine issue. Always, carefully test any setup under as many conditions as possible before you release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
I've worked in the chemical industry, nuclear power industry, and now healthcare (in that order).

My specialized area of IT interest is installer technologies.

Comments and Discussions

 
GeneralRe: Bigger Issues If User Data is Removed on Uninstall Pin
rwestgraham18-Oct-05 9:20
rwestgraham18-Oct-05 9:20 
GeneralRe: Bigger Issues If User Data is Removed on Uninstall Pin
Shane Jimmerson18-Oct-05 11:55
Shane Jimmerson18-Oct-05 11:55 
GeneralSearching for the previous package to uninstall Pin
Eleonore H.7-Oct-05 10:24
sussEleonore H.7-Oct-05 10:24 
GeneralRe: Searching for the previous package to uninstall Pin
rwestgraham7-Oct-05 19:13
rwestgraham7-Oct-05 19:13 
GeneralRe: Searching for the previous package to uninstall Pin
Eleonore H.11-Oct-05 4:42
sussEleonore H.11-Oct-05 4:42 
GeneralRe: Searching for the previous package to uninstall Pin
rwestgraham11-Oct-05 11:37
rwestgraham11-Oct-05 11:37 
GeneralRe: Searching for the previous package to uninstall Pin
Shane Jimmerson17-Oct-05 14:07
Shane Jimmerson17-Oct-05 14:07 
QuestionWho da man...? Pin
Willem Le Roux5-Oct-05 21:45
Willem Le Roux5-Oct-05 21:45 
Rwestgraham

You are the man! This little article is going to make my life a LOT easier! Thanks a bunch! No more running around, making backups of client DBs, uninstalling, and installing the update version any more!

This was exatly what I was looking for! If you do any more articles like this one, please let me know! You got my 5!

Smile | :)

Willem Le Roux
GeneralCommand Line to modify installer Pin
Nigel Aston5-Oct-05 19:30
sussNigel Aston5-Oct-05 19:30 
GeneralRe: Command Line to modify installer Pin
Martin Giebat26-Oct-05 22:57
Martin Giebat26-Oct-05 22:57 
GeneralNot everything accurate... Pin
erdoes5-Oct-05 3:11
erdoes5-Oct-05 3:11 
GeneralRe: Not everything accurate... Pin
rwestgraham5-Oct-05 11:26
rwestgraham5-Oct-05 11:26 
GeneralOptimal design (Replace all files always) and philosophy Pin
erdoes6-Oct-05 5:14
erdoes6-Oct-05 5:14 
GeneralRe: Optimal design (Replace all files always) and philosophy Pin
rwestgraham6-Oct-05 12:40
rwestgraham6-Oct-05 12:40 
GeneralRe: Optimal design (Replace all files always) and philosophy Pin
erdoes7-Oct-05 4:52
erdoes7-Oct-05 4:52 
GeneralRe: Optimal design (Replace all files always) and philosophy Pin
rwestgraham9-Oct-05 9:39
rwestgraham9-Oct-05 9:39 
GeneralRe: Not everything accurate... Pin
PhilWilson2-Nov-05 13:37
PhilWilson2-Nov-05 13:37 
GeneralPackageCodes and ProductCode Information Pin
rwestgraham4-Oct-05 11:22
rwestgraham4-Oct-05 11:22 
GeneralRe: PackageCodes and ProductCode Information Pin
Shane Jimmerson19-Oct-05 4:38
Shane Jimmerson19-Oct-05 4:38 
GeneralNice!!! ...just 2 questions Pin
FMasi4-Oct-05 7:16
FMasi4-Oct-05 7:16 
GeneralRe: Nice!!! ...just 2 questions Pin
rwestgraham4-Oct-05 9:16
rwestgraham4-Oct-05 9:16 
GeneralRe: Nice!!! ...just 2 questions Pin
PhilWilson2-Nov-05 13:44
PhilWilson2-Nov-05 13:44 
GeneralVery Nice Pin
David Roh4-Oct-05 3:06
David Roh4-Oct-05 3:06 
GeneralRe: Very Nice Pin
rwestgraham4-Oct-05 6:37
rwestgraham4-Oct-05 6:37 
GeneralGreat work! Pin
Super Lloyd3-Oct-05 1:12
Super Lloyd3-Oct-05 1:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.