1

I have an interesting dilemma to which, I hope, someone may have an answer. My company has a very complex web application which we deliver to our clients via an InstallShield multi-instance installer package. The application is written in ASP.NET MVC3 (using the Web Forms View Engine), C# 4.0, and Silverlight. It is the Silverlight component of the app with which we are having problems during installation.

Many of our clients wish to install our web app in what we refer to as “mixed-binding mode”. This may not be the correct terminology. What I mean to say is that the client will install the web app on a web server that is internal to the client’s firewalls and expose it to the web via a proxy server in the DMZ. In this way, everything internal to the firewall will resolve as HTTP, while every external request will resolve as HTTPS.

This does not pose a problem to most of the web application during installation because it will be running inside the firewall and will ALWAYS be HTTP when the app is installed in “mixed-binding mode”. This is not the case with the Silverlight component. Because it runs in the end-user’s browser process space, it is external to the proxy and firewall and must resolve via HTTPS.

The Silverlight files are in the XAP file. Within the XAP file, we have a configuration file (in XML format) that must be modified based on the binding mode of the web application (HTTP, HTTPS, or MIXED). As we all know, XAP files are simply Zip files, so theoretically all that is needed to edit a file contained in the XAP is to rename it from “.xap” to “.zip” and use any zip compatible utility or library component to extract the configuration file, edit it by some manual or automatic means, and then use the same zip component to re-archive the modified file back into the XAP file.

“Therein lies the rub!” This must take place automatically within the InstallShield Basic MSI process. At first we tried using an InstallShield Managed Code Custom Action using the DotNetZip library; however, DotNetZip seems to have an incompatibility with InstallShield. Every time InstallShield launches the Custom action, the installer would throw a InstallShield 1603 error the moment the custom action tried to execute the first DotNetZip command. (Yes we did rename the XAP file from “.xap” to “.zip” prior to trying to unzip the file.) We encountered the same problem with SharpZipLib.

Next, we dropped down to a lower level algorithm using the Shell32 library. We knew there would be timing considerations to take into account using this method since Shell32 processes run in a separate thread so we built wait states into the process, as shown below:

//Extract the config file from the .zip archive

Shell32.Shell shell = new Shell32.Shell();

string extractedFolderPath = tempZipFileName.Substring(0, tempZipFileName.Length - 4) + "\";

if (Directory.Exists(extractedFolderPath))

Directory.Delete(extractedFolderPath, true);

Directory.CreateDirectory(extractedFolderPath);

//Folder name will have the file extension removed.

Shell32.Folder output = shell.NameSpace(extractedFolderPath);

Shell32.Folder input = shell.NameSpace(tempZipFileName);

foreach (FolderItem F in input.Items())

{

string name = F.Name;

//We only extract the config file

if (name.Equals(CFG_FILE))

{

output.MoveHere(F, 4); System.Threading.Thread.Sleep(LONG_TIMEOUT);

}

}

//You have to sleep some time here,

// because the shell32 runs in a separate thread.

System.Threading.Thread.Sleep(MED_TIMEOUT);

This seemed to work the “majority” of the time.

NOTE: the use of the word “majority” above. The process works inconsistently on a fast server, whereas it works consistently on a slower machine. Don’t ask me why, my math professors always taught me that a second is a second (in this Universe at least).

We have even tried this with a PowerShell script. In this case, we are able to extract the configuration file and rename it in the target folder to which it was extracted; however, everything we have tried to do copy the renamed file back to the ZIP archive has failed. This is very unusual since we added a second call to push the target folder and all its children into the Zip archive and that works just fine (See the code reference below)!

#Extract_XAP.ps1

#===============

$shell=new-object -com shell.application

#Establish Current Location as Current Location

$CurrentLocation=get-location

#Determine the path of the $CurrentLocation

$CurrentPath=$CurrentLocation.path

#Create a namespace using the $CurrentPath

$Location=$shell.namespace($CurrentPath)

#Rename the XAP File to a ZIP file for extraction will work.

Get-ChildItem *.xap|Rename-Item -NewName {

$_.Name -replace "xap","zip"

}

#Create a ChildItem object using the ZIP file.

$ZipFile = get-childitem SunGard.OmniWA.WebAdmin.zip

#Insure the ZIP File is not readonly

(dir $ZipFile).IsReadOnly = $false

#Create a shell namespace for the ZIP Folder within the ZIP File.

$ZipFolder = $shell.namespace($ZipFile.fullname)

\Iterate through the ZIP Items in the ZIP Folder

foreach ($CurFile in $ZipFolder.items())

{

if ($CurFile.name -eq "ServiceReferences.ClientConfig")

{

#Current item's name matches the file we want to extract,

# so copy it from the ZIP Folder to the Current $Location.

$Location.Copyhere($CurFile)

#Wait briefly to insure process stays in sync

Start-sleep -milliseconds 1000

#Change the extention to the extracted file, so we can tell it from the original when we

# insert it back to the ZIP Folder.

Get-ChildItem *.ClientConfig|Rename-Item -NewName {

$_.Name -replace "ClientConfig","ClientConfig_Test"

}

#Create an object containing the extracted file.

$ClientConfigFile = get-childitem ServiceReferences.ClientConfig_Test

#Wait briefly to insure process stays in sync

Start-sleep -milliseconds 2000

#No need to search further

break

}

}

#For some reason this WILL not copy the renamed file back

# into the ZipFolder, even though it should (see below)!

$ZipFolder.CopyHere($ClientConfigFile)

#For some reason copying the entire current directory

# works just fine! Go figure!

$ZipFolder.CopyHere($Location)

#I hoped this would work, but it doesn't????

$ZipFolder.Close

write-output "renaming to the original extension"

#Rename ZIP file back to a XAP file, so Silverlight can use it.

Get-ChildItem *.zip|Rename-Item -NewName {

$_.Name -replace "zip","xap"

}

This shouldn’t be this hard to do. Somebody has to have done something like this before! I mean any complex web installation is going to need an automated install process and is going to have to be eventually installed using proxies and DMZ’s.

Any assistance would be appreciated. Here are the specifications we must follow:

  1. Use InstallShield Basic MSI as the installer.
  2. Any InstallShield Custom Action must be written in C#.
  3. The XAP file must be modified after the file has been copied to the server by the installer.
  4. Management wants me to stay away from BSD and GNU "free-ware" like Info-Zip, 7Zip, et.al.

Ken Jinks

Senior Web Developer

Sungard Omni Wealth Services

Birmingham AL

  • You mention a 1603 error with your first two approaches. Did the verbose log have any indication why there was an error? It should help you identify what's going wrong in most or all cases that result in a 1603. – Michael Urman Jun 11 '12 at 11:31
  • Ken, FYI, for your pointy-heasded boss: DotNetZip is "free ware". It's not licensed with a BSD license, or a GNU license, but it's still free and open-sourced. – Cheeso Jun 14 '12 at 20:31
  • Also - I suspect the reason you cannot copy-over the modified ZIP is there is some file-locking being done. Suggestion: extract the .ZIP-to-be-modified into a temp directory, then modify it and copy it into the final directory. Careful! You will need to unwind this in the case of cancellation or install failure. – Cheeso Jun 14 '12 at 20:34
  • Chesso; I've tried both of those. #1 Corporate Lawyers work at their own pace and look down upon us peons. #2. Already tried that it made no difference; however, I have gotten the process to work. – Kenneth Jinks Jun 15 '12 at 20:49

2 Answers2

0

Option 2

Address the 1603 error.

A note I found on this page says:

Verify the correct prototype syntax for entry-point function declarations of InstallScript custom actions, if any have been included in the setup.

An example of a correct prototype is: export prototype MyFunction(HWND);

Note: HWND must be specifically referenced in the prototype. All InstallScript functions called via custom actions must accept a HWND parameter. This parameter is a handle to the Microsoft Windows Installer (MSI) database.

Option 1 (not an option)

Putting on my pragmatic hat here:

If you only have the two scenarios, e.g. Mixed-binding and "normal", I would suggest simply generating two ZAP files in your project and installing only one, or (my preference) install both and dynamically deciding which ZAP filename to inject into the hosting page at runtime (if you can identify the running mode in your MVC web app).

This keeps the installer simple and does not add much work to have a few shared main files in each of two Silverlight projects connected to the same web project.

You can use a shared TT include file to generate any configs using a mostly shared config template.

Community
  • 1
  • 1
iCollect.it Ltd
  • 92,391
  • 25
  • 181
  • 202
  • Sorry, I left out a third scenario: straight HTTPS protocol. Also, I've checked with our silverlight team before and generating 3 different XAPs is unacceptable because not only can the binding be modified during installation, but other URL itself can be modified to allow the user to install the site into any physical/virtual path they wish. – Kenneth Jinks Jun 11 '12 at 14:03
  • Ouch... allowing that much install flexibility make everything so much harder. Updated the answer with some more clues I found about the 1603. – iCollect.it Ltd Jun 11 '12 at 15:41
0

O.K. I have finally figured it out using PowerShell. As it turns out, I could not get any of the CopyHere flags parameter settings to work and CopyHere will not overwrite a file in a zip folder object unless it has been told to do so by the Flags Parameter.

The simplest solution was to change all of the CopyHere methods to MoveHere methods. In that way I was never having to try to overwrite a file and I also never had to delete my working file because it got moved back into the Zip Folder.

Thanks for all of your help.

Ken Jinks