Monday, April 25, 2005

Installing an Internet Explorer ActiveX Control Part II

In part I of this article, I expressed concern over the various methods of installing an ActiveX control normally distributed and/or updated in a CAB file via Internet Explorer. This follow-up post addresses some concerns of a commenter to that last post, and describes what my solution was.

The comment made by Troy was from the perspective of a repackaging solution. Troy is quite correct - using pre and post installation snapshots will grab the changes to the ModuleUsage subkey. However, this solution fails to achieve the other goals for this particular installation. We still have the issue of a later update coupled with the MSI repair (or self-repair) operation. A point I failed to mention but Troy unintentionally reminded me of is compatibility across versions of IE. With the type of software this is, it may have a shelf life that would include IE7. Are the changes made to the system under IE6 applicable to all versions of IE under all OS's? I don't know. Using the API to install the control alleviates most (but not all) of the potential future incompatibility concerns.

I especially like how he disliked option #3 - changing the IE security settings to assure a successful installation, calling the API, and resetting them. I laughed as I wrote this option, as I could not imagine anyone doing this. In the middle of last week, I came across the "Get In the Zone" section of an issue of "Web Team Talking." They actually suggest and provide code for doing just that - although in a different context for a slightly different reason. I happen to agree with the KISS directive - "Keep it Simple, Stupid" - but sometimes to assure a successful operation you need to handle the corner cases.

The last part of his comment - "[this is] why many enterprise customers choose to repackage installations from ISVs" is a completely different blog post. I cannot tell you how true this statement is - and it is usually caused by setup developers, either intentionally or unintentionally. I'll add this one to the list of future topics for sure.

For the sake of discussion we were using the Crystal Reports Web Viewer which can generically be replaced with any properly signed cab file that works in the normal deployment mechanism of IE using the OBJECT tag. I have not tried this with unsigned controls, but I tested the approach using Macromedia's Flash player. You can play along by visiting the Macromedia's Flash download page and getting the CLSID from the OBJECT tag in the page's source. I then downloaded the file from the codebase attribute of the object tag and stored it on my local disk. The example below is going to use this flash cab file and the flash Class ID. Additionally, I am going to assume the reader is familiar with COM, C++, and MFC. The code is a bit lengthy to post here, but using the steps below a reasonable Windows developer should be able to complete this exercise in an hour or two.

I chose MFC since we need to implement IBindStatusCallback. This is easily accomplished by deriving a class from CCmdTarget. Make sure the implementations of QueryInterface(), AddRef(), and Release() call the External...() base class implementations. GetPriority() and OnLowResource() can simply return E_NOTIMPL. All the other methods can return S_OK. Since this is an async callback we need to have an event handle member variable, initialized in the Constructor, and have the event set in the OnObjectAvailable() method. That's the essential plumbing for the callback class. The callbacks for download progress can be implemented for the progress bars in the MSI.

Before we continue, let's make sure we have all the data we need to install the control.

  • The class id of the control is {D27CDB6E-AE6D-11cf-96B8-444553540000}. It needs to be in this format as a string (with the braces) so we can call CLSIDFromString to convert it to a CLSID type.
  • We are going to use major and minor versions of 0xFFFFFFFF to assure it is always installed.
  • Finally, we need a codebase - or place to "download" the cab file from. Since we are installing the cab file locally, the CA needs to be appropriately place in the sequence and we need to get the local path to the file. The path needs to be in appropriate format - for a local disk where the file is at C:\, we need to make it file://c:\\

The entry point code needs to hook up the plumbing - instantiate the IBindStatusCallback implementation class, call ExternalQueryInterface() on it to get the interface pointer. Then we need to create and register the binding context by calling CreateBindCtx() and RegisterBindStatusCallback().

The meat of what we are doing is a call to CoGetClassObjectFromURL() with the parameters described above. The two common returns from this call will be
MK_S_ASYNCHRONOUS, S_OK, or an error. If S_OK is returned, the object is already installed. If the async value is returned, we need to loop until the event in the callback class is set or some predefined timeout elapses. After the event is set, check to see that the object was created based on what was set in the OnObjectAvailable() method.

Based on testing, this method appears to work all of the time, regardless of the various IE security zone settings. This is likely due to using the file:// protocol. Remember that we are dealing with a signed control - with unsigned controls, your mileage may vary. This method was not tested in IE with Server 2003 in Enhanced Security Configuration, so all bets are off there as well.

If you wish to have the installation always grab the "latest" version of the code from a URL, you will need to implement the code to change and restore the IE security settings from the article linked to above and also implement the download callbacks to move the MSI progress bar. Be sure to alert the user that you will be temporarily altering the IE security settings, and make sure when handling a cancel button press from the MSI that you restore the settings. This particular corollary to the problem can be useful if you want to refresh the clients using a simple repair/maintainence/reinstall option after the server-side control was updated without having Bob the network admin do much extra work (or having to release client side patches).

Sunday, April 17, 2005

Installing an Internet Explorer ActiveX Control

This post is different from my usual posts, mainly because I have no idea what I am talking about! Well, at least I am admitting it this time!

I am tasked with creating an installation for an intranet type application. I need to create a shortcut to a URL and install a client side ActiveX control. For the sake of argument lets make it a fairly common one, such as the Crystal ActiveX viewer control. Sounds pretty simple, eh?

Let's go over a typical usage scenario. Hopefully, when designing applications and their deployment packaging you have some made-up profiles of typical users. Lets make up a few for our purposes. "Bob the system admin," "Mary the power user," and "Joe the restricted user" should be good enough. We can be a bit more specific and cover XP SP2 users, users with severely restricted ActiveX Browser settings, and non-IE browser users as well, but lets ignore these for now.

Essentially, Joe has a problem. We can give him the URL to the web application or even create a shortcut and/or favorite for him. The problem is, the first time he tries to run a Crystal Report, he gets a security warning, and the Crystal ActiveX control does not install. Of course, it is because he has no permissions to install software, even if everything else we ignored above is in good shape. Incidentally, if we can't figure out that Joe's problem is that he is a limited user, you can try the CDLLogViewer application to find out why.

Bob and Mary never have issues with the system. In fact, if Bob and/or Mary perform the same steps as Joe everything works, and Joe logs in later on, even Joe can view and print reports. The problem is that Bob and Mary would never run reports on the system. Heck, Bob doesn't even launch the application. Bob may even deploy the install package using SMS or Group Policy.

In the corporate network world, pretty much everyone is a Joe. There are a few Privileged Mary's, and even less Bob's. To make all this work, Bob needs to install applications for the likes of Joe. This behavior is not only acceptable, but practically expected.

So how would Steve, our installation guru, make all this work? He would not only need it to work, but be practically bulletproof.

Option 1: The easiest approach. 99% of the time, this is synonymous with the wrong approach. We can extract the Crystal Reports ActiveX viewer cab file, read the .inf file to figure out what it is doing, add the files to a MSI project selecting the proper registration options, create the shortcut file, and be done with the project in under 20 minutes.

Why it doesn't work: Since Joe is not the only user of the machine (remember Mary and Bob?), any updates to the control will cause the MSI-based installation to screw up at some point. If the control is updated by Mary, and Bob uninstalls the application, the component/reference count then causes havoc with Mary by possibly removing the file on uninstall. Effectively, we have precluded the normal installation/updating/removal of the ActiveX Viewer by repackaging it in an MSI. More information on the hazards of this approach can be found on MSDN in the "Registry Details" page. Bad stuff.

Option 2: Install the shortcuts as in Option 1. Call the appropriate API's to install the CAB file from the web server. In this case, (after a bit of digging through MSDN I see this process is called "Internet Component Download" or ICD for short) we need to call CoGetClassObjectFromURL.

Why it doesn't work: Referring to the ICD link above, we assume the web server is up and running at installation time - #1 in the "Download and install process". We also assume that WinVerifyTrust returns a value that is compatible with the setting for ActiveX controls for the Zone in which the server we are pulling the cab file from resides - #2 in the "Download and install process". Last, but not least, we are assuming that good old Bob didn't muck with the CODEBASE setting of the Internet Search Path.

Option 3: Completely decimate the user's machine by querying the IE Security Settings, saving them, adding the server to a trusted zone, modifying the zone parameters to set the ActiveX settings appropriately, call CoGetClassObjectFromURL, after it succeeds, set everything back to the way it was.

This seems like a heck of a lot of work, but it appears to be the only way to bulletproof an installation of an ActiveX component. Plus, we ignored several issues. If the default browser was (or will be) Firefox or even Lynx, a shortcut to this URL will always fail to display the reports. To solve this problem, an EXE wrapper would need to be written to host the IE control to assure that IE is actually the browser engine used to access the page. Joel, the API war is far from over - it just made things more complicated!

Of course, I'll get to play around with these API's and see if installing the cab file from a file:// UNC path changes anything. I'll report back what I find. If anyone else has already "solved" this problem using an approach I haven't considered, please let me know. In all likelihood, I'll end up calling CoGetClassObjectFromURL, and if it fails, try to provide some meaningful feedback to the user as to why the installation can not succeed and let Bob worry about the corner cases. After all, he ultimately created them.

UPDATE: Please see Part II of this post!

Tuesday, April 05, 2005

A look in the keyhole

Google just added satellite imagery to their maps using their Keyhole technology. I'm wondering how long it will be before someone sells ads mowed into their front lawns on ebay. Perhaps I should patent the idea...

Monday, April 04, 2005

New version of XML-RPC Library Released!

XML-RPC is a spec that allows software running on different operating systems to make remote procedure calls over the internet using XML over HTTP. It was inspired by a more simplistic RPC protocol and an early draft of the SOAP protocol. xml-rpc-c is one such implementation of XML-RPC.

I used this library in the past for multiple reasons. First, I needed to (essentially) perform a remote procedure call. Secondly, I needed it to be cross platform (*nix and Windows). Third was the library must not run under some compatibility layer for Windows such as MinGW or CygWin (too many headaches in the past for my taste). On the wishlist was a single library that can be used on both platforms so I could concentrate on core application functionality and not worry about writing interface classes to several different implementations of a protocol. At the time, SOAP was out, and this library was in.

Granted, there was a bit of work needed on the Windows side. I wanted to use the WinInet API's so I wouldn't have to deal with configuring proxy settings in my application - if IE worked, I wanted my app to work, too. WinInet support wasn't working out of the box - in fact the original maintainer sort of dropped out of sight. Thanks to the community at the time (especially Alex), I was able to share a few patches and get it working.

When I saw that Bryan Henderson had taken over the library as the official admin and began active development, I saw it as an opportunity to contribute something back to the community. I suppose my role is maintainer of all things Windows-related.

The latest release features a new possibility for Windows users. I created an XML-RPC server using the http.sys dll shipped with Windows Server 2003 and Windows XP SP2. Applications using this dll can share the same port as other applications using this dll. Windows Server 2003's IIS 6 happens to use it, so you can even share port 80 and/or 443 with it seamlessly - no more ISAPI dll's to accomplish that feat!

For developers looking to use http.sys in your own code, as well as using it with SSL, this code may serve as an example for your own purposes - such as dealing with basic authentication or SSL connections which is not provided for in the http.sys API's or documentation. There is a dearth of example code out there for this particular API for some reason.

For making client-to-server calls on Windows, the only out-of-the-box solution was the WinInet transport. The problem with this API is it can't be used in a service. Although the xml-rpc-c library always supported using libcurl out of the box, the compilation and linking of this was difficult to figure out and required a few creative changes. With this release, I hope to have made it easier.

Finally, the last major change was tightening up the WinInet transport a bit. Previously, communication over SSL would succeed even if the server certificate was invalid. That functionality was great when developing using a test certificate, but probably not what you want to use in production. By default, the old behavior is now only active if set by a specific transport option at runtime.

If anyone is using this library, on Windows or otherwise, please feel free to give me a shout. If you have any feature requests, let me know. One note to downloaders - for some reason the tarball does not extract properly using WinRar. If you rename the download to have a .tgz extension and use WinZip to decompress it, you will be able to open the project files in Visual C++ 6 without problems. Visual Studio 2003 will also open, convert, and compile the project without problems.

Sunday, April 03, 2005

Integrate the latest MSDN Library Help with VC++ 6

*See update at bottom of this entry*

There is an awesome article just published on Code Project that shows how to Integrate the latest MSDN Library Help with VC++ 6. Although you can simply download the demo project and follow the instructions after "How to set the default help collection?" at the end of the article, the article itself is a great primer on reverse engineering.

Of course, the newer-than-October-2001 MSDN libraries do not have the help files for the actual Visual C++ 6 development environment and/or compiler, if you are still using VC6, I would hope you don't need this documentation anymore.

I am curious why Microsoft did not include this integration ability (with the normal caveats) with the newer MSDN libraries, but I guess they sold a couple of extra copies of Studio .NET this way...

UPDATE: With this plugin enabled, I get a sharing violation on the .opt file if opening the project from explorer - a double-click or "Open With". Opening Visual Studio, then opening the workspace seems to not have this problem. Please let me know if you have similar problems.

Saturday, April 02, 2005

More Setup Pet Peeves

Everywhere you look there are more setup pet peeves that keep popping up. Many of these new ones are in my top 20 list - some of them aren't.

Microsoft's JeffDav writes one about two installation issues - a 3rd party application and a Microsoft application. The second one was probably a MSI based installation, and I blame the MSI engine's design on this one - what ever happened to "Couldn't copy file from source media, retry" dialogs?

Raymond Chen chimed in with his own story, and the usual 2 billion responses to it - some of the catchy ones captured below.

Janus points out that several Java based apps have issues - write once, run anywhere (as long as there are no spaces in the path). Other commenters chimed in issues with *nix application ports having similar path issues. (My peeve #19).

Foxyshadis points out a few more: "are you sure you want to create the folder" AND "are you sure you want to install in an existing folder" - I don't get this one either (My peeve #10). "Once you install with custom options and later upgrade, and try to use typical, not only will it always forget which options you chose, it'll install into a completely new default folder." Why is that? This is a nice addition to my peeve #11.

Gryphonvere points out some applications "ask you rather forcefully to install some older version of DirectX" - some of this is the fault of the dependent application not providing a mechanism to determine the current version of itself, and some on the setup developer for not doing the smart thing and saying "This application was not tested with Acrobat Reader 12, if you have issues, try using Acrobat Reader 4.0." This is a corollary to my peeve's #17 and #13.

David Walker complains about the "Company Name\Product Name" default directory convention (My peeve #14). He also hits the "Common Files" directory issue. This latter point is somewhat valid, but for keeping the redistribution, duplicate files, and simplified patching for a company's product line shared components down to a minimum, I can live with that. We all have to understand that x-copy deployment of applications or product lines is not always possible. While on the subject (and expanding on my earlier peeve #20), my belief is that "Application Data" folders are underused. If I want to back up my system or use OS features such as roaming profiles, I want to have a one-stop method of doing so. Backing up the per-user application data folder, the per-system application data folder, and the "My documents" folders should be the only backup philosophy I ever have to use.

Doesn't anyone test the key features of an installation for both correctness and usability anymore?