Aaron Stebner from Microsoft comes through with some source code (in C++) to determine the installed version of the .NET framework, including service packs. Great job, Aaron!
Sunday, September 19, 2004
Too good to pass up
While I'd usually like to abstain from sharing personal or political viewpoints on a technical blog, this one was too funny. I take for granted everyone on the blogsphere knows about the Dan Rather / 60 Minutes II documents regarding Bush that appear highly likely to be forged. Well, there is a new product that can be used for document forgery. I can see Clippy saying, "So... It appears you are trying to forge a document..."
Tuesday, September 14, 2004
Detecting .NET Framework 1.1 SP1
Aaron Stebner from Microsoft blogged about how to determine if the 1.1 version of the .NET framework is installed. Unfortunately, that is just to detect if 1.1 is installed.
One method of checking that Aaron quoted is described by a Microsoft Knowledge Base article. This approach is impossible to perform programmaticly. He also gives a registry key to check for the 1.1 .NET framework, but is a bit unclear if this key is for 1.1 or 1.1 with the service pack.
Other methods of checking I uncovered are difficult to do in code in a manner that makes sense to work in the generic case. The confusion that led to Aaron's post in the first place are questions (and the answers/lack of answers) posed in newsgroups, like this one.
The .NET framework version checking problem reminds me of old InstallShield code. I can't recall if this was built into InstallShield or if it was a bad code sample, but lots of installs failed on Windows 2000 simply because the install code checked to see if you were NT 4.0 SP3 or higher. The detection code was not based on proper logic, but rather if OS version >= 4 and SP version < 3, do not install. What a headache (at least until Windows 2000 SP3 came out)!
Fortunately, Aaron is going to research getting a code sample to detect the old versions and hopefully Microsoft can provide a future-proof method to determine this in upcoming revisions.
Saturday, September 11, 2004
Wrestling with the MSI setup editor in Visual Studio
Tim Anderson blogs about editing an MSI after it is built by Visual Studio .NET. There are various tips hidden in here about how to cope with duplicate assemblies being added to the MSI, marking a file so upgrades don't overwrite it, invoking the Custom Action editor, and MSI SQL.
For more detailed information related to editing an MSI after it is built, check my previous blog entry "MSI - Modifying the Installer Database at Runtime" and scroll down to Solution 1.
For more information as to how to launch a program after installation, see How do I use a custom action to launch an installed file at the end of the installation? from MSDN.
Tim also mentions as to why he stuck with using the Visual Studio MSI packager instead of a commercial editor. He gives 4 reasons, and I would add purity as a fifth. Regardless of the authoring tool he uses, his code will always work. Granted, pretty much any tool he uses will eliminate the need for his particular edits to the MSI, but the concept is still valid.
Monday, September 06, 2004
MSI - Modifying the Installer Database at Runtime
A recent question posted to the Microsoft.public.platformsdk.msi newsgroup:
I'm trying to prevent some NT services installation with a VBScript custom action (type 38) that removes the corresponding rows from the ServiceInstall table.The poster shows some example code that does not seem to work. Let's look at some approaches to try to solve this problem.
Solution 1
Here's some theoretical JScript to run in a Custom Action that modifies the installer Database:
oDatabase = Session.Database;
oView = oDatabase.OpenView("select * from ServiceInstall");
oView.Execute();
//now our view object has records we can "fetch"
oRecord = oView.Fetch(); //get the first record from the query
while (oRecord != null) //make sure we are not at the end.
{
//TODO: Either handle the logic on what to remove based
// on the oRecord here or in the query. Assumes all
// records matching the query are removed.
oView.Modify(6,oRecord); //6 = msiViewModifyDelete
//get the next record from the query
oRecord = oView.Fetch();
}
oView.Close(); //close the view
oDatabase.Commit(); //commit our changes
Looks pretty good, right? We are opening the database, fetching records, modifying the records, and committing the changes. Sad to say even though the API logic is correct, this won't solve the problem at hand! Why?
Custom actions can only add, modify, or remove temporary rows, columns, or tables from a database. Custom actions cannot modify persistent data in a database, such as data that is a part of the database stored on disk. (View.Modify documentation excerpt)As an aside, you could substitute the following 2 lines for the first one and modify the msi (permanently) outside a CA:
oInstaller = new ActiveXObject("WindowsInstaller.Installer");
oDatabase = oInstaller.OpenDatabase("myInstall,msi",2);
Solution 2
So, with this restriction, how do we solve your problem? The answer lies (buried) in the View.Modify documentation. The action type msiViewModifyInsertTemporary is our friend. Instead of solving the problem by deleting the services you don't want to install, we can add the services you DO want to install at runtime! Remember to delete the lines from the ServiceInstall table if you use this method
Lets look at an example of this:
oDatabase = Session.Database;
oView = oDatabase.OpenView("select * from ServiceInstall");
oView.Execute();
oRecord = Installer.CreateRecord(13);
oRecord.StringData(1)="MyService";
oRecord.StringData(2)="MyServiceName";
oRecord.StringData(3)="MyServiceDisplayName";
//SERVICE_WIN32_OWN_PROCESS
oRecord.IntegerData(4)=16;
//TODO: continue for the rest of the columns in this table. See
// the ServiceInstall table docs for more information
//msiViewModifyInsertTemporary the record.
oView.Modify(7,oRecord);
oView.Close(); //NOTE: no need to call commit..
You do need to deal with the uninstall issue using another CA. That is not covered here, but the concept is similar.
As an aside, I use the above technique to adjust the ReserveCost table when I need to create a large database. If the DB exists, I do nothing, otherwise, I add the size of the database to the table for proper costing.
Solution 3
Although Solution 2 will solve this problem, why would you want to go through that complexity when there is a much better (and simpler) method built into the MSI engine? In the ServiceInstall table, there is a Foreign Key into the Component table. If the component is to be installed, so is the service.
Simply create a hidden feature, assign the components necessary for the service to that feature, and conditionally install (or not install) that feature.
