If all you need to do is install your web application into Default Web Site, life is easy. Especially since Windows Installer XML (WiX) has all that support right in the box. Where things get nasty is if you need an installer that lets the user choose the web site, set the web application name, and the application pool. If you’re newish to WiX, that can be a daunting task. You might be tempted to skip WiX and use a Web Setup Project built into Visual Studio because those do offer a solution to the requirements. Sadly, Web Setup Projects are a giant world of hurt because of their other limitations. Add in the fun fact that Microsoft is dropping support for them in future releases of Visual Studio and it’s pretty obvious you need to avoid them.
Since this seems like a very common request, I thought I’d create an example WiX project that does what the now dying Web Setup Projects so there’s a path to a supported installation technology. While pieces of this type of installer have been covered before, especially the excellent article by Jon Torresdal, but there wasn’t a complete example. As always, if you’ve got any questions, feel free to email me or ask them in the comments. Grab the code for this installer here.
To mimic the Web Setup Project, I needed to do the following tasks:
- Enumerate the web sites on the server and put them into a combo box.
- Have an edit control that lets the user set the name of the application and does not allow a blank entry. This name is also used for the virtual directory name as well.
- Installs the web application under the default directory for the web site.
- Optionally let the user decide if they want to choose a different application pool for the web application. If they chose the default, the application pool for the web application will be set to the one in use by the web site.
- Enumerate the application pools on the server and put them into a combo box.
- Full support for IIS7 and higher. Note that the example does not work with IIS6, but it wouldn’t be hard to add that support.
- Properly uninstalls the web application, virtual directory, and all files. As anyone who’s used the Web Setup Project knows, it does not correctly uninstall the web application from IIS (at least on IIS7).
As they say, a picture is worth a thousand words, so here’s a dialog that meets the requirements.
As the user interface in any MSI-based installer is the hard part, I wanted to get the UI out of the way first. Since my installer is very much like a standard WiXUI_InstallDir, which allows the user to choose the installation path, I downloaded the WiX source code and pulled that file as the basis of my UI. As the file for my UI is a decent size XML file, I didn’t want to just drop it into this article, so you should download the sample project and open.InstallerHelloWorldInstaller.SLN and look at WixUI_SimpleWebAppInstall.wxs. I’ll discus the highlights here.
If you’ve never done a custom WiX UI project before it can appear pretty daunting. The basic idea is that you reference existing dialogs, DialogRef elements, and add your own custom ones with the Dialog elements. The key to creating dialogs is to use the dialog editor in the great WiXEdit project. Being able to add controls and see their layouts visually drastically speeds up your development. Towards the bottom of WixUI_SimpleWebAppInstall.wxs, you’ll see the XML for two dialogs, the WebAppInstallDlg, which is the dialog shown above, and an error dialog, InvalidWebAliasAliasDlg, that is shown at the bottom of the file.
Defining dialogs isn’t too hard, but the fun work is getting them properly hooked into your installers UI flow. That’s done with the Publish elements in the middle of my file. Once I realized that Publish elements are essentially like your event handlers in normal .NET development, the Windows Installer approach started making a lot more sense to me. Let’s take a look at the button processing for the WebAppInstallDlg.
<!– Handle the back button–>
<Publish Dialog=”WebAppInstallDlg”
Control=”Back”
Event=”NewDialog”
Value=”LicenseAgreementDlg”>1</Publish>
<!– Check to see the web app name has something in it. –>
<Publish Dialog=”WebAppInstallDlg”
Control=”Next”
Event=”SpawnDialog”
Value=”InvalidWebAliasAliasDlg”
Order=”1″>WEB_APP_NAME=””</Publish>
<!– Set the INSTALLDIR property based on the selected web site’s physical path using
my custom action. –>
<Publish Dialog=”WebAppInstallDlg”
Control=”Next”
Event=”DoAction”
Value=”SetInstallDirBasedOnSelectedWebSite”
Order=”2″>1</Publish>
<!– Set the APP_POOL_NAME to the web site’s default if that’s what the
user wants.–>
<Publish Dialog=”WebAppInstallDlg”
Control=”Next”
Event=”DoAction”
Value=”SetAppPoolNameToWebSiteDefault”
Order=”3″><![CDATA[USE_CUSTOM_APP_POOL <> 1]]></Publish>
<!– Finally move to the VerifyReadyDlg if all values are looking good.–>
<Publish Dialog=”WebAppInstallDlg”
Control=”Next”
Event=”NewDialog”
Value=”VerifyReadyDlg”
Order=”4″><![CDATA[(WEB_APP_NAME<>””)]]></Publish>
At a glance you figured out the Back button processing but it’s the Next button that’s a little more interesting. The first act is to check that the WEB_APP_NAME property is empty. If it’s a null value, I want the InvalidWebAliasDlg to be shown. The second action is to call a custom action I wrote to set the INSTALLDIR property based on the chosen web site. As that element value is 1, that action is always executed. The third action is if the user did not check the use custom app pool checkbox, I want to run another custom action to set the app pool name for the web application to the same that the web site is using. Lastly, if we get to the last Publish element, I’ll move to the VerifyReadyDlg if the WEB_APP_NAME is not null.
One trick I found helpful when developing the UI was to initially insert my dialog and have the next button just go to the VerifyReadDlg. That way I could ensure the dialog was properly being shown and could build up the custom actions and get them hooked up to the real UI.
The final part of WixUI_SimpleWebAppInstall.wxs I want to mention is near the top of the XML and shown below.
<!– This is very important. As I am filling in the web site and app
pool combo boxes dynamically, I need to force create the ComboBox
table.
This nice little fellow gets it into the output .MSI. However,
as there’s nothing in the table, you’re going to get an ICE17
warning that the combo box associated with WEBSITE_NAME does not
exist. It’s safe to turn off ICE17 as the enumerate custom action
will take care of doing the filling so it exists before being
needed. –>
<EnsureTable Id=‘ComboBox‘/>
<!– The custom action DLL itself.–>
<Binary Id=“WebAppCA“
SourceFile=“$(var.CAFileLocation)“ />
<!– The custom action to enumerate the web sites and app pools into
the appropriate combo boxes.–>
<CustomAction Id=“EnumerateIISWebSitesAndAppPools“
BinaryKey=“WebAppCA“
DllEntry=“EnumerateIISWebSitesAndAppPools“
Execute=“immediate“
Return=“check“ />
<!– Make sure the enumerate web sites and app pools custom action
gets called, but only called if we are doing and install. –>
<InstallUISequence>
<Custom Action=“EnumerateIISWebSitesAndAppPools“
After=“CostFinalize“
Overridable=“yes“>NOT Installed</Custom>
</InstallUISequence>
As I have the requirement to let the user chose the web site and optionally the application pool, I need to get those items filled in. As there’s no built in support for enumerating those items in WiX or Windows Installer, I needed to write a custom action, EnumerateIISWebSitesAndAppPools, to do the work for me. The above section of code shows incorporating my custom action and getting it scheduled. What EnumerateIISWebSitesAndAppPools does is add the items to the ComboBox table in the in-memory MSI file that is where all combo boxes go for their data. Once gotcha I ran into is that just declaring a ComboBox in your UI does not create the table so your custom action will fail. The WiX trick to work around that is to use the EnsureTable element to have WiX put the table in the output MSI.
While I had bumped into discussions of custom actions through my reading on WiX and Windows Installer, I’d never had to write one before. Conventional wisdom dictates that you should use native C++ to write your custom actions. As I’m not afraid of C++, I took a look at the IIS 7 documentation for their C++ interfaces for administration. Just as I was afraid of, it’s all PCOM. You know, Painful COM, whose motto is “all the pain of COM and none of the benefits.” Because I didn’t want to spend two weeks in COM hell, I thought it time to take a good look at the Deployment Tools Framework (DTF) which we can use to write out custom actions in any managed language you so desire.
In all, using DTF made writing my custom actions nearly painless especially since the IIS7 managed administration API is very clean. As an example, here’s the code to my custom action that sets the installation directory based on the selected web site.
[CustomAction]
public static ActionResult SetInstallDirBasedOnSelectedWebSite(
Session session)
{
if (null == session)
{
throw new ArgumentNullException(“session”);
}
try
{
// Debugger.Break();
session.Log(“SetInstallDir: Begin”);
// Let’s get the selected website.
String webSite = session[“WEBSITE_NAME”];
session.Log(“SetInstallDir: Working with the web site: {0}”,
webSite);
// Grab that web sites based physical directory and get it’s
// “/”
// (base path physical directory).
String basePath;
using (ServerManager iisManager = new ServerManager())
{
Site site = iisManager.Sites[webSite];
basePath = site.Applications[“/”].
VirtualDirectories[“/”].PhysicalPath;
}
session.Log(“SetInstallDir: Physical path : {0}”, basePath);
// Environment variables are used in IIS7 so expand them.
basePath = Environment.ExpandEnvironmentVariables(basePath);
// Get the web application name and poke that onto the end.
String webAppName = session[“WEB_APP_NAME”];
String finalPath = Path.Combine(basePath, webAppName);
// Set INSTALLDIR to the calculate path.
session.Log(“SetInstallDir: Setting INSTALLDIR to {0}”,
finalPath);
session[“INSTALLDIR”] = finalPath;
}
catch (Exception ex)
{
session.Log(“SetInstallDir: exception: {0}”, ex.Message);
throw;
}
return ActionResult.Success;
}
Over half of the code there is logging but you can get the idea pretty quickly. There were a few things that I stumbled with developing my custom actions. The first was since I was installing on Server 2008 R2, which is a 64-bit operating system, I was concerned that my custom actions had to be 64-bit as well. It made sense to me, but after experimenting, that’s not the case at all. When the 64-bit MSIEXEC.EXE is executing your custom actions, it’s doing so out of process and takes into account the “bitness” of the DLL you’re executing. That was a pleasant surprise so if you are looking to use custom actions written by others, you don’t have to worry if they are 32-bit only. Obviously, the Windows Installer team hasn’t figured out how to run 64-bit custom actions on 32-bit windows.
Debugging managed custom actions is a bit interesting. There’s the MMSIBREAK environment variable, which breaks into the managed debugger when your custom action is called. What I found even easier was to just put in a call to Debugger.Break at the top of my method. Since your custom actions can be run in various processes, getting the environment variable set system wide can be a pain. You can find a nice overview of debugging managed custom actions at Jon Torresdal’s web site.
The last thing that threw me for a loop debugging custom actions was that I couldn’t seem to get the diagnostic logging for those custom actions I invoked as part of a button click DoAction control event. It was so weird that the custom action worked, but I never saw the logging. After much hair pulling, I realized that it’s a limitation of Windows Installer. It’s sad that Windows Installer is nasty enough without surprises like this floating around. What I ended up doing was adding a bunch of property sets, which do get logged instead of calling Session.Log.
As prompting the user for web sites, app pools, and virtual directories seems to be a very common question on the WiX-Users mailing list, I hope others can use my code to get past that hurdle. Once last thing I need to mention is that accessing the IIS 7 APIs requires full administrator rights. As my code needs to enumerate the web sites and web pool as soon as the MSI loads, if you double click on it from Explorer and are not running elevated, the install will immediately fail. To be a full installer you need to wrap the MSI in a boot strapper EXE that requests elevation before executing the MSI. As WiX 3.6 is all about the Burn, you may want to start there, but it’s still early in the development cycle.
2075
The Right Solution for Your Needs.
We deliver a hybrid spectrum of Microsoft Cloud Platform and Azure solutions to government agencies and application developers who demand a modern, open and flexible cloud service platform. We offer trusted, transparent, and secure Infrastructure as a Service (IaaS) and Platform as a Service (PaaS) solutions for production business applications, Business Intelligence (BI), continuous data protection, application availability, test/development, and Software as a Service (SaaS)
Architected to meet your needs.
Deployed flawlessly.
Operated reliably 24x7x365.
Assess:
Rely on our team to map your existing environment to a corresponding Azure cloud.
Migrate:
Easily move from your existing environment to a public or private Microsoft cloud.
Re-platform:
Understand how to transform your applications to better take advantage of cloud capabilities.
Operate:
Our team actively manages all maintenance & optimization to keep your environment running at its best.
Why Microsoft Cloud Platform and Azure?
Microsoft has made major strides in the public cloud space over the past few years. They are gaining market momentum with companies and also the analysts community who recognize the more than USD15B investment made. At this point Azure has more data centers than AWS and Google Cloud combined. It spans an unparalleled global reach with 36 regions available and more being added. Azure offers companies wanting to put workload in the a public cloud with a secure, scalable and competitive option.
2206
Azure Certified for Hybrid Cloud.
Atmosera has developed core competencies and intellectual property to take full advantage of all Azure has to offer. All Atmosera Azure services receive the “Azure Certified for Hybrid Cloud” designation which confirms it passed Microsoft’s rigorous validation process under the Cloud OS Network (COSN) program. Customers benefit from knowing that their solution was carefully planned and executed using best practices and proven methods.