Adventures of a Setup Developer

my musings about setups and other things in my life

  Home  |   Contact  |   Syndication    |   Login
  30 Posts | 1 Stories | 11 Comments | 105 Trackbacks

News

Article Categories

Archives

Post Categories

General

Regular Reads

Tools

Tuesday, January 09, 2007 #

I have moved back to blogger. Please update your subscriptions. Inconvenience regretted. :-) Wish you all a very happy and prosperous new year.

Thursday, October 19, 2006 #

This post is for all the indians both in India and abroad who crib about the situations there. These two clips show more than what I would like to say. It is time for all of us to accept this progress and enjoy it. Yes, we do have our problems. We still have incidents of communal riots. We are still bombed by terrorists. Much of Rural India is still to get the taste of this improvement. But we are living in an age with comforts that our previous generation could have only dreamed of. So be happy about it and do your part in making it better.

India before
http://www.youtube.com/watch?v=mKJSDYvxHzw&mode=related&search=

India Now
http://www.youtube.com/watch?v=Y_j-N3QkaVE

Happy Diwali and Id Mubarak. Jai hind.


Sunday, June 11, 2006 #

It is bound to happen. You will have a developer come up to you one day and say, "Remember the DLL file that we shipped in the previous release, we have consolidated its functionality into the core library. We need to remove that library in the latest release." You as a Windows Installer developer would then be frustrated after finding out that you cannot remove components during a minor upgrade and you will have to manage with all the ugliness of a major upgrade.  I will write about the ugliness of a major upgrade in some other blog.

So if you really have to remove the DLL, you need not have to go through the trouble of major upgrade. We can actually uninstall components during a minor upgrade. But the catch is that you should not remove the component from the existing MSI. My colleague Kajal Biswas calls this process as "Puncturing the component". Let me detail the process of puncturing the component.
  1. Consider the component abc.dll which has to be removed. 
  2. Collect the Registry, CLSID and the COM information if you have dynamically acquired them, you will have to fill them in manually later. This is usually the reason why I do not use COM Extract at Build. I usually use 'Extract COM Information' feature of InstallShield DevStudio. This would ensure that I can find out the exact registry entries and COM Settings.  If you had used the WiX Toolset, it is pretty straight forward. You will just have to go to the next step and you can ignore this completely.
  3. Replace abc.dll file with a zero byte file with the same name.
  4. Set the transitive bit of the component. If you are using InstallShield DevStudio, simply set the 'Reevaluate Condition' field in the component property to 'Yes'. 
  5. Replace any existing condition on the component with a condition that evaluates to false. As a habit, I and my colleagues enter the paradoxical '1=0' as the component condition. You can also enter something meaningful like 'GoToHell=666' or something like that.
During a minor upgrade, if the feature associated with the component is reinstalled, the trasitive bit with also force the state of the component to be reevaluated. And since the component is present and the condition is false, the component will be uninstalled during a minor upgrade. It is hack. But it works.

Sunday, April 02, 2006 #

Windows Installer's transitive bit is a really cool thing. It allows you to author your components in such a way that it would install if component's condition is true and uninstall if it is false even during upgrades. Well almost. Refer the documentation for more information. So this can be used to get rid of pesky components that you need to uninstall during a minor upgrade. Now we thought this stuff was really neat and applied it to all the components. We had our justifications that this setup will always be done by admins and will always be per machine. None of the components wrote anything off to HKEY_CURRENT_USER and we were pretty much safe in assuming that it would not be the case in the future either. So we turned on the transitive bit for all our components. The software behaved exceptionally well. The maintenance was driven via a external wrapper which always passed the REINSTALL parameter and hence everything worked very well.

This allowed our MSI to behave as a state machine. You could simply throw in a bunch of properties and set its state and it would just install the ones that are required. This basically solves the problem of setup types and you do not have to write custom actions to set the feature request states.

We had tested most of the scenarios on different operating systems and it seemed to function just fine. We had it going through the last round of validation when we got a call from one of the landscape administrators that the product had uninstalled itself. Windows Installer had triggered a repair after the application of a hotfix by one of the developers and had completely uninstalled the application. The reason being that the Windows Installer repair operation also re-evaluates the component conditions and decides that they should not exist on the machine. The problem rested in the fact that even though we did persist the properties to be retrieved later, but that was entirely upto the bootstrapper to do it as the bootstrapper was the single entity that would do all the MSI related maintenance activities. So when the MSI repair started, which of course is as good as a quick reinstall, it ran through the entire InstallExecuteSequence table and found that most of the components were not supposed to be on the machine and had them removed. The solution was fairly simple. We had to persist all the properties and its values in the registry and we had to author AppSearch entries for all the properties and do a sort of quick check for the settings and swap the values with the actual ones during such a repair operation.

So if you plan to use the MSI transitive bit for your components, be very careful. You have been warned.


Tuesday, January 24, 2006 #

I was recently working with Reflection and Boo and thought this was funny. Hey.. nobody told life is simple right. So, here it goes. :-)

i as int
i=i.GetType().GetMethod("Parse",[typeof(string)].ToArray(typeof(System.Type))).Invoke(null,["42"].ToArray())
print "Answer to the question of life, universe and everything is ", i

Tuesday, January 17, 2006 #

The new GPLv3 draft has been posted for discussion. The Rationale Document explains the changes.

Tuesday, October 18, 2005 #

I found this flash presentation while I was browsing the web today. It was Lawrence Lessig's address at OSCON 2002. An extremely interesting presentation.

I came across an interesting site to create SouthPark style cartoon characters. If I had been a SouthPark character, I would have probably have looked like this.


Friday, October 07, 2005 #

I have lately been pondering over methods to deploy cross platform products. I investigated certain options like InstallShield's Universal Project and ZeroG's InstallAnywhere. But to tell the truth, it goes against the philosophy of installation. They rely on the presence of the right version of JRE. Some installations also package the native versions of JRE along with the installation package. This is quite an overhead. Other products use their custom homegrown installers. As most of the setup developers and administrators know, homegrown installers are a disaster waiting to happen. A good installation technology should do the following.

  • Installation - This is the easy part.
  • Rollback in case of failure
  • Support for dependency and conflict resolution.
  • Support for transactional install/uninstall operations. This is very important for the integrity of the system.
  • Support for upgrades.
  • Heal installed applications.

Creating a custom installation technology with all these features is difficult if not impossible. Also it is an investment that is not required when you can reuse the wheel. Each of the operating systems or the platform targeted have a native installation technology which support all of the above points in some way or the other. Most of these cross platform applications are deployed on Windows, Linux or Solaris. Windows has the Windows Installer a.k.a MSI package format to keep an inventory of software that is installed in an extremely fine granular level. Linux has RPM and DEB formats which deal with packages at a more coarse scale than Windows Installer but have excellent dependency and conflict resolution. Solaris has its native PKG format to install packages.

So instead of a cross-platform installation program, we could have a cross platform tool to create multiple installation packages for multiple platforms. One of the biggest problems working in this direction would be that the setup engineer should have a knowledge of all the platforms that he is packing the application for. This is usually not a easy job. For instance, I could handle Windows MSI and Linux's RPM & DEB files but would be a completely lost on a Solaris platform.

The biggest challenge would also be the fact that the customization scripts and custom actions would need to be coded by the setup engineer for each platform or package format. But this is a small price to pay for the amount of flexibility that can be achieved. The software deployment becomes a breeze and would significantly lower the (holy grail of three letter acronyms) TCO.


Thursday, September 01, 2005 #

I had been experimenting with Ruby/Rake. I took a little while for me to get adjusted to Ruby's syntax. I love closures in Ruby and their effective use in Rake. I was trying if I could do the same in Python. As always, I came up with a very shabby code as the first attempt.

#run target.py
# the global dict to store targets
targets={}
#defining and adding a target
class Target:
    def __init__(self,name='',dependencies=[],fn=None):
        self.name=name
        self.dependencies=dependencies
        self.fn=fn
        self.done=False
        self.success=False

    def run(self):
        if(not self.done):
            self.success=self.fn(self)
            self.done=True
        return self.success

    def describetarget(self):
        print self.fn.__doc__

def addtarget(name,dependencies,callable):
    objtarget=Target(name,dependencies,callable)
    targets[name]=objtarget

# now to add the actual target

def sayHello(target):
    '''Prints hello world with the calling targets' name'''
    print "hello world from " + target.name
    return True
addtarget('sayhello',[],sayHello)

#run the target
targets['sayhello'].run()

As you can clearly see, you would have to first define the function and add it later as the target. For simplicity sake have not written the logic of constructing the dependency tree. You can see that this is nowhere close to writing the same code in Ruby. Consider the following.

Python Code:

def sayHello(target):
    '''Prints hello world with the calling targets' name'''
    print "hello world from " + target.name
    return True
addtarget('sayhello',[],sayHello)

Versus

Rake Code:

task :mytask do |t|
print 'hello world ' + t.name
end

The magic of ruby code is in closures. Python does support closures. Python 2.4 also has a new feature called decorators which can do the same thing. This is what I did in Python using decorators.

#runtargetwithdecorator.py
#global dict to store targets
targets={}

#the target class
class Target:
    def __init__(self,name='',dependencies=[],fn=None):
        self.name=name
        self.dependencies=dependencies
        self.fn=fn
        self.done=False
        self.success=False

    def run(self):
        if(not self.done):
            self.success=self.fn(self)
            self.done=True
        return self.success

#the target decorator function

def target(name,dependencies=[]):
    def decorator(f):
        mytarget=Target(name,dependencies,f)
        targets[name]=mytarget
        return f
    return decorator

#implementing the decorator
@target('mytarget')
def MyFunction(target):
    print "hello world", target.name
    return True


targets['mytarget'].run()

Now consider the following.

Python code:

@target('mytarget')
def MyFunction(target):
   print "hello world", target.name
   return True

versus

Ruby code:

task :mytask do |t|
   print 'hello world ' + t.name
end

Now the python code is a lot more easlier to read than its counterpart in Ruby. Python may not be an effective DSL for build languages but is definitely a lot easier to read and understand than Ruby's code. But you cannot have a empty defaults target defined in python. You should have a valid callable after the @decorator. For example, to my knowledge, you cannot write the following code in python without the following function.

@target('default',['mytarget1','mytarget2'])
def somefn(target):
     pass

But you can write the following code in ruby without any closure method.

task :default => [:mytask1,:mytask2]

In spite of Rake's simplicity, I still would prefer writing Python code. I guess it is just my personal preference. No offence to Rake fans. :-) 


Tuesday, August 30, 2005 #

There exists a lot of literature written on Free and Liberal Open Source Software(GNU/FSF). This is what I feel about it. I had recently been to a school to give an introduction about computers to some kids in a village near Pondicherry. I had access to a laptop and showed them a quick presentation on the basics of computers. I was aware of the fact that the school syllabus has Microsoft Windows as the primary platform for education. Most kids think paint is cool. What would they say if they saw GIMP? But the government has to realize that providing a computer and upgrading the license of the operating system is a costly business. Most of the schools in India run on a shoe string budget. Let us do the math now.

Number of students for a class (at least) 30
Number of client licenses to be purchased if the computer is shared by 3 users 10
Cost of a computer (approx) Rs. 10,000/-
Cost of a Microsoft Windows client license (approx) Rs. 2,500/-
Cost of a Microsoft Windows Server range operating system Rs. 40,000/-
Total cost for client operating system licenses Rs. 25,000/-
Total cost of the workstations (hardware) Rs. 1,00,000/-
Cost of the server hardware (basic p4 based desktop to act as a database server/Web Server) Rs. 30,000/-
Total cost of setting up 10 terminals with 1 server Rs. 1,95,000/-


I have not included the cost of buying software like Microsoft Visual Studio .NET 2003 and Microsoft SQL Server, which would each cost about 35,000 and 45,000 approximately. Please let me know if my figures are wrong. And the SQL Server is for 45,000 is only a standard 5-client edition. Also there exists a problem with upgrades. You would have to pay for them too. You would need to get two licenses which would mean a catastrophic cost for a simple school to setup. The common solution that various schools adopt is to pirate the software. This is much more rampant in the schools in the private sector. A crackdown on such schools would put most of the schools out of business. The moment you open a new school/college you open up a software piracy den and promote criminals. Let us look at the figures for Linux.

Number of students for a class (at least) 30
Number of client licenses to be purchased if the computer is shared by 3 users 10
Cost of a computer (approx) Rs. 10,000/-
Cost of a Microsoft Windows client license (approx) Rs. 0/-
Cost of a Microsoft Windows Server range operating system Rs. 0/-
Total cost for client operating system licenses Rs. 0/-
Total cost of the workstations (hardware) Rs. 1,00,000/-
Cost of the server hardware (basic p4 based desktop to act as a database server/Web Server) Rs. 30,000/-
Total cost of setting up 10 terminals with 1 server Rs. 1,30,000/-


As it is clearly seen, it is important for all the schools in India to adopt Free and Liberal Open Source Software. This step would mean the following advantages for the Indian society.

  1. The cost of setting up new computer labs is significantly lowered.
  2. Students will gain the ability to produce software on open source platforms.
  3. Projects targetted at open source platform can serve the Indian Society.
  4. This will free the small businesses from the requirement of purchasing pricey software. They could use open source software built by our future software professionals.
  5. Open Source Software engineers in my belief have a better understanding of the underlying technology and can really benefit from the community.
Necessity is the mother of invention. As more and more people adopt open source software, I am sure that they in turn will create a need for better software. I have been using Linux on and off for the past several years and have seen it progress in leaps and bounds. I would strongly suggest open source operating systems like FreeBSD or Linux as the operating systems. They have a host of open source products to suffice every need in the closed source world. I do agree that there are open source products available in the closed source platform that are on par with their commercial counterparts. For example, SharpDevelop would suffice the need of any normal C# programmer who has not been pampered with Visual Studio .NET 2003 features. But on the other hand you have Eclipse which would seem VS.NET 2003 seem like a baby. MySQL competes with Microsoft SQL Server in the DB Server arena. PHP can serve as a commendable alternative to ASP and ASP.NET. Apache Web Server gives IIS a run for their money in the Web Server segment. Java and Mono provide solid cross platform application frameworks. Even though Java is not strictly open source, its free availability on open source platform makes it a lucrative option. Apart for these compilable languages, Python and Ruby offer a quick application development cycle which is ideal for developing small business tools.

It is now time for the educational institutions in India to decide if they still want to pay a huge sum or indulge in criminal practices to learn/teach software or adopt the clean and relatively hassle free path of Open Source software to produce better software engineers.

Friday, July 01, 2005 #

After a hectic schedule, I decided to give myself a break. So I decided that I will install Linux just for fun to refresh my memory on the linux front. It is also a small bet with my colleague that I could be productive and have fun with Linux just the way I work with Windows. I was arguing on the fact that there is no better learning platform than Linux. I've worked only on RedHat 7,8,9 distros that are distributed with magazines. There is this old joke about Open Source communities vs. Closed source vendors . It goes something like this. "Commercial vendors sell software but give away free T-Shirts. Open source communities give free software but sell T-Shirts."

So, here I am blogging from my linux (yeah!!!) machine. It has been a long time since I had worked on any of the linux distros. I tried downloading Fedora, but it was just too big. So is Novell Desktop Linux. I've got the bandwidth and everything but shelling so much space for it from my hard disk did not seem worth it. Who needs so much stuff anyway. So I settled for Ubuntu. Ubuntu still retains the open source passion that makes it attractive. The installation was lightweight and text based which suits me. I do not want any fancy graphics for the installation. Ubuntu installed GNOME by default and to my surprise had disabled the root account. I first felt a little uncomfortable running as a non-privileged user. We've all got used to running as administrator on our windows machines. People would laugh if you said you did not have administrative privileges on a Windows Machine. But on a linux machine it is normal that you are a restricted user. Everytime I used Nautilus to copy files I was reminded that I was running under a non-privileged user login. Although I could launch Nautilus itself with sudo, I used the command line. I still remembered basic linux commands. That was quite a relief.

Unfortunately, I was not able to listen to LaunchCast and I miss my MSN Radio already. :-( Apart from that I am pretty happy with the distribution. It seems to be pretty stable. I have installed (rather extracted) Eclipse, the latest Java Runtime and the pydev Eclipse plugin. It is a pretty neat. If time permits, I will try to write a small sudoku puzzle solver in Python on my Linux machine. And finally, just for the pure visual treat and to prove that everything is working, here is the screenshot of my desktop.


Wednesday, April 27, 2005 #

 While I was browsing the MSDN, I came across this very interesting video which features Anders Helsberg and his skills with the whiteboard. I really wish that he teach me programming. A must see for every C# programmer.

Tuesday, April 19, 2005 #

This morning I was working on something and my mind kept wandering. So I just went blog browsing and I saw the link to a nerd quiz. Not finding anything better to do, I took it. Here is the score.


 I am nerdier than 88% of all people. Are you nerdier? Click here to find out!


Guess, I am not that nerdy after all. Okay, time to get back to work. <SMILE />


Monday, March 28, 2005 #

With the new .NET framework API providing such wonderful functionality, everybody is programming in C# or VB.NET except setup developers. There was quite some commotion in the WIX users list on writing managed custom actions. It was finally decided that it is generally harmful to write managed code custom actions as they would depend on .NET Framework and having dependencies for setup is plain bad design. But IMHO, we can use managed code custom actions if we are 100% sure that it would be present on the target platform or if it is a prerequisite and a part of your LaunchConditions.
 
Writing managed code custom actions can be tricky. The easiest way is to implement the System.Configuration.Install.Installer class. This Installer class has four methods which can be executed during the MSI thread. The Installer class was only meant for developers to do some dirty stuff. But it is far from perfect to be used as a part of the MSI thread. Some of my strong reasons for not using the Installer class are as below.
  • InstallUtilLib.DLL is not completely silent. It still pops up an ugly message box with it fails.
  • The methods do not have access to the MSI thread. So we would not be able to use properties or write to the MSI log file.
We are then left with only one choice. Writing DLLs with C++ which export functions which use managed code. One of the several places that this could be used is handling XML configuration data. Windows Installer supports writing properties to both INI and Registry files but we do not have such facility for XML files. I guess this should be on the TODO list for the next version of Windows Installer <smile/>. But until then we would have to do it ourselves. There is a neat task in NAnt named <xmlpoke>. This custom action is programmed in pretty much the same way without namespace support. Given the path to the XML file and the XPath expression, we would be able to replace the value with our own using the installer. And it should also support properties.
 
I start off by creating a class library project in VS.NET 2003 and start editing the ProjectName.CPP file. Note that you would have to create a Module Definition file to support exporting the DLL functions that you write as symbols. Now for the fun stuff.
 
Lets first write functions that can write to the log file.
 

UINT WriteToLog(MSIHANDLE hMSI, CHAR *strMessage)
{

MSIHANDLE hrec=MsiCreateRecord(1);
MsiRecordSetString(hrec,0, strMessage);
MsiProcessMessage(hMSI,INSTALLMESSAGE_INFO,hrec);
MsiCloseHandle(hrec);
return ERROR_SUCCESS;

}

The above function is a pretty simple function that just writes any string of characters to the log file. While this function is good enough, we will write another function that accepts the managed String type.

void WriteToLog1(MSIHANDLE hMSI, String* strMessage)
{

CHAR* strMessageArr={0};
strMessageArr=(char*)(void
*)Marshal::StringToHGlobalAnsi(strMessage);
WriteToLog(hMSI,strMessageArr);

}

This functions accepts the managed String* and Marshals it to a character array and then writes it to the log file. Now that we have the logging functions ready, lets concentrate on the actual juice. The idea here is that we use a deferred custom action to poke values into the XML file. The custom action expects data in a '|' delimited list in the following format.

[XMLFILEPATH]|[XPATHEXPRESSION]|[NEWVALUE]

A immediate type 51 custom action sets the CustomActionData property in the above format.

extern "C" __declspec(dllexport) UINT XmlPoke(MSIHANDLE hMSI)
{

TCHAR propval[261]={0};
DWORD len=261;
MsiGetProperty(hMSI,TEXT("CustomActionData"),propval,&len);
WriteToLog(hMSI,propval);
String *str=new
String(propval);
String *delims="|";
Char delim[]=delims->ToCharArray();
String* split[]=str->Split(delim);
FileInfo* fi=new
FileInfo(split[0]);
if(fi->Exists){

try{

XmlDocument* xdoc=new XmlDocument();
xdoc->Load(split[0]);
XmlNodeList * nl=xdoc->SelectNodes(split[1]);
IEnumerator * nodesenum=nl->GetEnumerator();
WriteToLog1(hMSI,split[1]);
while
(nodesenum->MoveNext())
{

XmlNode* xNode=__try_cast<XmlNode*>(nodesenum->Current);
String* oldValue1=xNode->InnerXml;
WriteToLog1(hMSI,String::Concat(oldValue1, new
String(" was the old value")));
xNode->InnerXml=split[2];
String* NewValue="New Value is ";
WriteToLog1(hMSI,String::Concat(NewValue,split[2]));

}

xdoc->Save(split[0]);

}

catch(Exception* exc){

WriteToLog1(hMSI,exc->Message);

}

}

else

{

WriteToLog(hMSI,"Error: The file does not exist");

}

return ERROR_SUCCESS;

}

The above function reads the CustomActionData property and splits it into an array. I assume that setup developers are good and they always give good data. So I have only gone for rudimentary error handling. The custom action never fails unless the error is catastrophic and is not handled by the try-catch block. It tries find if the XML file exists and if it does, it loads the XML file. I then use the SelectNodes() function to select the list of nodes as per the XPath expression. And finally I set the InnerXml value of the function to the new value. I also log these values as and when required. Thats it... as simple as it seems. 

Dont forget to have the following header files and namespaces on top of the file. You would also have to include reference to System.Xml.Dll.

#include "stdafx.h"
#include "windows.h" //Required by MSI.h
#include "XmlTasksManaged.h"
//The MSI Stuff
#include "Msi.h"

#include "MsiQuery.h"

//.NET Stuff goes here
using namespace System;
using namespace System::IO;
//For the FileInfo Object
using namespace System::Collections; //For the IEnumerator
using namespace System::Xml; //The XML Stuff
using namespace System::Runtime::InteropServices; //For the Marshal object

As mentioned earlier the Namespace support is still not included. It is not a very difficult functionality to add. We can pass it to the CustomActionData property. We can then cut and slice the CustomActionData property as we please. <smile/>