Building an MSI in Visual Studio 2005/2008/2010

Here’s my rig:

I am running a Windows 2008 Enterprise Server with SharePoint 2007 64 bit and Visual Studio 2008.

I have been trying to create an MSI (using VS), that installs Action Server on a SharePoint 64 bit platform. My main problem arrives when the installer attempts to run my Custom Action. The Custom Action is responsbile for installing evaluation licenses into the SharePoint Config databases. Every time, my Custom Action hits a line containing, “SPFarm.Local”, the installer would throw an error like this :

“Attempted to Load a 64 bit assembly on a 32 bit platform”

After a bit of research, the main crux of the problem is that (by default) the Custom Action runs as a 32bit process and attempts to load the Microsoft.SharePoint.dll (which is 64 bit). Hence, the error. So, how do you get around this ?

Well in short, we offer big respect to Heath Stewart! The solution isn’t a coded solution, or some property you can flick inside of the Visual Studio Project. You need to build your MSI as normal, with a TargetPlatform = x64 set and then download “Orca” and edit the entry for ‘InstallUtilLib’ so that it points to the 64 bit version.

Heaths original post (and instructions) are here :

http://blogs.msdn.com/heaths/archive/2006/02/01/64-bit-managed-custom-actions-with-visual-studio.aspx

Update!!!

Ok, I got bored of the Orca Solution so did some googling – check out Nikhil’s blog entry on this. He shows how to do the above in code, so well worth a look.

http://blogs.msdn.com/nikhiln/archive/2007/04/25/post-build-script-to-fix-msi-issues-in-vista-for-vs-2005-setup-and-deployment-project.aspx

However, he didnt solve the “InstallUtilLib.dll” piece, but did highlight 2 other potential gotchas (Errors and Impersonation). I added some more script to ensure that the 64bit InstallUtilLib gets used (instead of the 32bit one).

Instructions…

  1. Copy and paste the file (at the end of this post) into a “.js” file into your VS Solution.
  2. In the PostBuild of your MSI call it like this (I Called my file “FixMSI.Js” and saved it into a “SolutionsItems” folder in the root of my sln). IMPORTANT: I also placed the 64 bit version of the “InstallUtillib.dll” in SolutionItems.

cd $(ProjectDir)..\SolutionItems
WScript FixMSI.js $(BuiltOuputPath)

Contents of “FixMSI.js”….

var msiOpenDatabaseModeTransact = 1;
var msiViewModifyInsert = 1
var msiViewModifyUpdate = 2
var msiViewModifyAssign = 3
var msiViewModifyReplace = 4
var msiViewModifyDelete = 6

var msidbCustomActionTypeInScript = 0x00000400;
var msidbCustomActionTypeNoImpersonate = 0x00000800;

var filespec = WScript.Arguments(0);
var installer = WScript.CreateObject(“WindowsInstaller.Installer”);
var database = installer.OpenDatabase(filespec, msiOpenDatabaseModeTransact);

CheckImpersonation();

CheckErrors();

AmendFor64Bit();

database.Commit();
database = null;
installer = null;
function AmendFor64Bit()
{
var sql;
var view;
var record;
sql = “SELECT * FROM Binary WHERE `Name`=’InstallUtil'”
view = database.OpenView(sql);
view.Execute();
record = view.Fetch();
if (record != null)
{
var dataCol = 2;
record.SetStream (dataCol, “c:\InstallUtilLib.dll”);
view.Modify (msiViewModifyUpdate, record);
}
record = null;
view.close();
view = null;
}

// Check the Errors Row
function CheckErrors()
{
var sql;
var view;
var record;
var errorRecordFound = false;
// Sort the Errors problem
// check whether a row exists in the error table, if it doesn`t create it
sql = “SELECT `Error`, `Message` from `Error` where `Error`=1001” ;
view = database.OpenView(sql);
view.Execute();
record = view.Fetch();

if(record)
{
errorRecordFound = true;
}
view.Close();

if( !errorRecordFound )
{
// create a new view to insert an error row in the error table so
// that an error message is displayed in vista, instead of package error!
sql = “INSERT INTO `Error` (`Error`, `Message`) VALUES (1001, ‘[2]’)”;
view = database.OpenView(sql);
view.Execute();
}
record = null;
view.Close();
view = null;

}

function CheckImpersonation()
{
var sql;
var view;
var record;
// first set the NO_IMPERSONATE bit for all the custom actions
sql = “SELECT `Action`, `Type`, `Source`, `Target` FROM `CustomAction`”;
view = database.OpenView(sql);
view.Execute();
record = view.Fetch();

while (record)
{
if (record.IntegerData(2) & msidbCustomActionTypeInScript)
{
record.IntegerData(2) = record.IntegerData(2) | msidbCustomActionTypeNoImpersonate;
view.Modify(msiViewModifyReplace, record);
}
record = view.Fetch();
}
record = null;
// close the view.
view.Close();
view = null;
}