Tuesday, April 6, 2010

XML text replacement

A new product I was working on required a UI prompt then replace in a config file, this case using xml. In the past I have tried using the XML File Changes view in InstallShield 2009 (and before) and had limited success. The task of supplying a property instead of a static value was simple enough, but the issue I kept encountering was additional decoration that was being placed on the xml file, which in turn caused the problems when trying to load the xml file at a later time, usually by the intended application.

In the past I had found some batch util that did text replace, but I found I was not comfortable putting this in my installer because batch files are like a black hole that get no reporting back to the log file. After the success with my previous post I felt confident that I could find something in c# that would allow this and reporting back to the log, plus it allowed me to learn a little more c#.

After searching I found a suitable sample. What this offered was to treat the xml file like a flat file because all I wanted was to replace a matched phrase with the value supplied by the user during install. This used the System.Text.RegularExpressions namespace to do the matching. What this sample does is load the file at once and do a match across the entire file. Because of this, it is only recommended for small files. Larger files will cause this to fail. With that in mind, here is my code used:

using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Deployment.WindowsInstaller;

namespace TextReplaceUtil
{
public class CustomActions
{
[CustomAction]
public static ActionResult TextReplaceAll(Session session)
{
string AutoLogPrefix = "### COMPANY MESSAGE ### ";

// setup variables using properties from MSI database
// this will come from a deferred action, which will be comma delimited
// format of "What to find", "What to replace with", "absolute path + name of file"
string CustomActionData = session["CustomActionData"];
string[] MSIProperties = CustomActionData.Split(',');
string TextToReplace = MSIProperties[0];
string ReplaceWith = MSIProperties[1];
string FilePath = MSIProperties[2];

session.Log(AutoLogPrefix + "Begin TextReplaceAll action, values passed are: " + CustomActionData);

try
{
StreamReader reader = new StreamReader(FilePath);
string content = reader.ReadToEnd();
reader.Close();

content = Regex.Replace(content, TextToReplace, ReplaceWith);
StreamWriter writer = new StreamWriter(FilePath);
writer.Write(content);
writer.Close();
session.Log(AutoLogPrefix + "Successfully wrote to file " + FilePath);
}

catch(Exception e)
{
session.Log(AutoLogPrefix + "exception thrown was " + e.Message);
return ActionResult.Failure;
}
return ActionResult.Success;
}
}
}



Just like before, you use the .ca.dll after it is built. In this case you also need to create a set property action in addition to the MSI DLL action to build up the CustomActionData value that we will be using here. The comment in the code indicates the format of what is expected and the order it should arrive in. With a utility like this, you can use this for any need for text replacement in your installers.

IIS 7 with InstallShield 2009

One of the recent situations we are addressing is not having the latest InstallShield software. Doing this and relying on their functionality can create complications. One we recently encountered was a defect that occurs with IIS 7 when virtual directories are created.

If you happen to have an installer that creates a virtual directory which also contains a subfolder by the same name for the virtual directories path, it will create an additional virtual directory at the subfolder's location. This was a very odd bug which was raised by only one of our products, but it of course causes it to fail because the path is now invalid.

In our efforts to use more built in capabilities, usually for MSI tables, but on some occasions InstallShield as well, we started using the IIS view in InstallShield. Up until 2009 I used to write custom actions to handle IIS tasks. Now I am no programmer, I guess I could be called a scripter, so my custom actions were not as robust as they could be, but they were functional. The driving force to using custom actions was that InstallShield was not properly handling a dynamic site id in previous versions.

To address the defect of the second virtual directory, I decided to start using c# custom actions after years of VBScript(I know, I'm awful) use. The necessary namespace I was using was based on sample code I found that referenced System.DirectoryServices. Intially I used InstallShield's built in managed code custom actions, but I converted to using the Custom Action project type you get with WiX and then adding MSI DLL custom actions. This worked far easier as this will demonstrate because you are using the session object. My main issue was I wanted to be writing to the Windows Installer log file at run time. Here is my simple code to address the defect:

using System;
using System.Collections.Generic;
using System.Text;
using System.DirectoryServices;
using Microsoft.Deployment.WindowsInstaller;

namespace AutoIISCAUtils
{
public class CustomActions
{
[CustomAction]

public static ActionResult DeleteVDir(Session session)
{
string AutoLogPrefix = "### COMPANY MESSAGE ### ";

// setup variables using properties from MSI database
string IISSiteID = session["IISSITEID"];
string AppName = session["APPNAME"];
string VDirName = session["VIRTUALDIR"];
session.Log(AutoLogPrefix + "Properties are " + IISSiteID + "|" + AppName + "|" + VDirName);

string VDirPath = @"IIS://localhost/W3SVC/" + IISSiteID + "/Root/" + AppName + "/" + VDirName;
session.Log(AutoLogPrefix + "deletion will occur on: " + VDirPath);

try
{
DirectoryEntry VDir = new DirectoryEntry(VDirPath);
if (VDir != null)
{
DirectoryEntry parent = VDir.Parent;
if (parent != null)
{
parent.Children.Remove(VDir);
parent.CommitChanges();
session.Log(AutoLogPrefix + "changes were successfully committed to the Virtual Directory " + VDir.Name + " which has now been removed.");
}
}
}
catch (Exception e)
{
session.Log(AutoLogPrefix + "exception thrown was " + e.Message);
return ActionResult.Failure;
}
return ActionResult.Success;
}
After the project is built it will generate two binaries that are dlls. The file to reference when you create the MSI DLL custom action is the .ca.dll version. Add the entry for the function name to call, change the execution and insert in the sequence. Make sure to add .Net as a prerequisite as well.

My reference for this task can be found here:

Thursday, February 25, 2010

Purpose and Background

I am creating this blog as a means for getting my installation successes and struggles out to others so that it may in turn help them. Hopefully someone saves some effort by what I will supply here, or you get a laugh out of my errors. Either way, it was useful. I will be posting code snippets as well as parts of log files and InstallShield screen shots to show what I am doing to accomplish tasks in our installers.

My background was that after school I started work at InstallShield as a support person on their MSI product which was InstallShield for Windows Installer 2 (2.03 to be precise). I started my career off right by hanging up on my first customer (kind of like what you have to do your first day in prison, or so the movies tell us). I would challenge myself by giving support to Express customers without using the IDE, just going off memory. Anyone who has worked support knows that it can wear on you after time, though after the fact I am thankful that we had shifts and were not locked down on the phone the entire day. Because of the grind I looked for a new challenge and it ended up being as a Technical Trainer for InstallShield.

As a trainer I was fortunate to work with a few intelligent people who helped bring my knowledge of MSI to another level and that was E. S. and R. D., who both still work for now Flexera in different roles. I enjoyed educating others on MSI (as well as the other products I taught) . My main angle in the classroom was to teach them not to memorize, but how to learn MSI so that after they left they knew where to go for assistance. For example, when an entry is made in InstallShield's System Search for a registry key search, what really happens in the background of the ISM (what will become the MSI) tables. After teaching for a bit it was nice to get a bit of a break from the travel and I was able to work on writing with our training manuals and for a short period of time they offered training videos which, along with R.D., I was a part of recording (or as they refer to it in their business, the "talent"). After the video recording I will never understand how people can talk into a camera as a job. Eventually traveling took its toll on me and I moved to a new role, one that became not as technical as I thought it would be.

A few years later I ended up at a company in Chicago as an Install Engineer. I got a lead on the position from a former InstallShield person who wanted more of a developer role. The funniest part of the process was that during my interviews I kept getting pounded for being a trainer and they wanted me (the recruiter and even the managers who were hiring me) to focus on my support experience. Evidently most trainers know little of the product, just the manual they teach from. It worked out well because during my interview I actually solved a problem the lead was having at the time.

My role here consists of creating automated build cycles, setting up and managing our build environments (both virtual and physical), and creating and troubleshooting installers. Our installers span a range from one where we simply call a batch file (not my favorite) to one where we have many custom actions that interact with IIS to accomplish tasks such as (the Iraq, Maps) auto-populate a combo box based on those retrieved values and setting properties on application pools. As for tools, we use InstallShield 2009, Install Anywhere, Visual Build Professional, MS Platform SDK, VMWare Workstation and VMWare ESXi server, and our source control system is Team Foundation. I have dabbled a little in Wix and maybe one day will convert to that. Most of our custom actions are done in VBScript with some InstallScript, but I am trying to force myself to go to managed code for all my future actions. If that is a success then one day to go back and rewrite all others as well.

Hope you enjoy,
WW