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:
- Use InstallShield Basic MSI as the installer.
- Any InstallShield Custom Action must be written in C#.
- The XAP file must be modified after the file has been copied to the server by the installer.
- 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