ClickOnce and Expiring Code Signing Certificates

There’s a really nasty bug in .NET 2.0’s ClickOnce deployment technology. If you deploy a smart client as availaible “off line” (launchable from the start menu) and you sign your manifest with a certificate from an official certificate authority (such as Thawte or VeriSign), you cannot renew your certificate and deploy to the same location! If you do, the next time someone starts your client it will barf up an error and won’t start. Examining the log will reveal the problem in the somewhat cryptic message “The deployment identity does not match the subscription”.

The problem is that the certificate authorities issue “renewed” certificates with a different private key. This makes up part of the ClickOnce app’s “identity” and ClickOnce validates this when starting an app to prevent tampering.

The only workaround is to uninstall and reinstall the app via the add/remove programs control panel applet. This really puts a kink in the whole “click once” thing, doesn’t it? I ran into this issue recently at work, as our code signing certificate was set to expire. Since I’m experienced enough to never trust Microsoft to do the right thing, I tested the “renewed” certificate in a test environment. Several Google searches later, I see that I’m not alone in discovering this problem.

To avoid having over 200 users fart around in the “add/remove programs” control panel applet, I came up with a kludge to have the client application uninstall itself and launch the ClickOnce installer, signed with the new certificate, from a new location. So after deploying the client signed with the new certificate, I deploy an update with the old certificate that contains the “reinstall” logic. Part of this trickery involves obtaining the “Public Key Token” for your app (I believe this comes from the certificate used to sign the ClickOnce manifest). The following snippet illustrates how to determine the public key token programmatically:

/// <summary>
/// Gets the public key token for the current ClickOnce app.
/// </summary>
private static string GetPublicKeyToken()
{
    ApplicationSecurityInfo asi =
        new ApplicationSecurityInfo(
                    AppDomain.CurrentDomain.ActivationContext);
    byte[] pk = asi.ApplicationId.PublicKeyToken;
    StringBuilder pkt = new StringBuilder();
    for (int i = 0; i < pk.GetLength(0); i++)
        pkt.Append(String.Format("{0:x}", pk[i]));
    return pkt.ToString();
}

Next, we need to find the uninstall string in the registry, based on the PublicKeyToken:

/// <summary>
/// Gets the uninstall string for the current ClickOnce app from the
/// Windows Registry.
/// </summary>
/// <param name="PublicKeyToken">The public key token of the app.
/// </param>
/// <returns>The command line to execute that will uninstall the app.
/// </returns>
private static string GetUninstallString(string PublicKeyToken, 
  out string DisplayName)
{
    string uninstallString = null;
    string searchString = "PublicKeyToken=" + PublicKeyToken;
    RegistryKey uninstallKey = Registry.CurrentUser.OpenSubKey(
        "SoftwareMicrosoftWindowsCurrentVersionUninstall");
    string[] appKeyNames = uninstallKey.GetSubKeyNames();
    DisplayName = null;
    foreach(string appKeyName in appKeyNames)
    {
        RegistryKey appKey = uninstallKey.OpenSubKey(appKeyName);
        uninstallString = (string)appKey.GetValue("UninstallString");
        DisplayName = (string)appKey.GetValue("DisplayName");
        appKey.Close();
        if(uninstallString.Contains(searchString))
            break;
    }
    uninstallKey.Close();
    return uninstallString;
}

I then launch the uninstaller, using the Process class, and use some Interop calls to the Win32 API to find the uninstaller window and automatically “push” the “OK” button (would have been nice if the was a /silent switch so I wouldn’t have to do this). Finally, I launch ClickOnce for the new version of the client, signed with the new certificate, to update the user’s workstation. A zip file with this source code can be found here: ClickOnceReinstall.zip Using these utility classes, I just insert the following code into the client’s startup routine to make it reinstall itself:

// Self-uninstall
Utils.DeploymentUtils.UninstallMe();
Utils.DeploymentUtils.AutoInstall(
  "http://host-name/deployment-folder/MyApp.application");
Application.Exit();
return;

Windows Explorer Periodically Hangs with High CPU Utilization

So my PC at work has been giving me trouble this week. Every so often things would just sort of hang for several seconds. Sometimes it would be when I tried to open an email message in Outlook, sometimes when I was working with a web application in IE, sometimes when killing the screen saver after being away from my desk. There just wasn’t any rhyme or reason to it. One thing I did notice was that each time it happened, the Windows Explorer process, explorer.exe, would be consuming 99 – 100% of the CPU.

My first fear was that Windows had been infected by some nefarious bit of code. I checked the system with a couple virus scanners and anti-spyware tools, but found nothing. I tried using Windows XP’s restore points to roll back to a time before this problem starting happening. All that did was screw up Visual Studio 2005 (which I then had to repair). I tried uninstalling that last couple of applications I had installed. I tried disabling IE add-ons. Nothing helped.

Finally I tried to make some sense of what was going on inside explorer.exe when it was gobbling up CPU cycles by using the Visual Studio debugger and Sysinternals’ Process Explorer. There were enough clues there that led me to think that whatever was happening was centered around the Windows desktop folder. Hmmm. Desktop. I had set my desktop to hide all icons months ago (FYI: right-click the desktop, click “Arrange Icons by”, and then uncheck “Show Desktop Icons) in an effort to unclutter things (and I got tired of fighting every little app that feels it’s important enough to put itself on my desktop). So, I made the desktop icons visible again to see what was there.

Bazillions of files. Covering both my displays and then some. XML files. I knew immediately where they had come from. The application I maintain and support has an option to save the XML requests/responses to and from the server. Where does it save them? To your “working directory”. Where’s the working directory? By default, the Windows desktop. Ugh. I had enabled this logging months ago and forgot to turn it off. It appears that things hit critical mass this week and was causing Explorer to choke (explorer.exe runs the XP desktop, taskbar, and start menu as well as the file explorer).

Things got much faster after nuking all those files. I also checked the utilization of the MFT using defrag c: -a -v and since it was over 90%, I ran this batch file to expand it a bit:

c:
md c:MFT-folder
md C:MFT-files
cd c:MFT-folder
FOR /L %%f in (1,1,5000) do md %%f
cd C:MFT-files
FOR /L %%f in (1,1,50000) do echo Creating files >%%f
cd c:
rd /s c:MFT-folder
rd /s C:MFT-files

It’s usually best to do this after defragging the drive. Basically, you’d like there to always be some space available in the MFT so you don’t have to wait for Windows to expand it, and if you use this method with Windows defrag it will also help prevent the MFT from becoming fragmented.

No Macs at Macworld

It’s official. Apple is no longer a “computer company”. Steve Jobs’s big keynote at Macworld 2007 introduced zero new Macs and no new software (OS X, iLife, iWork, etc.), but instead focused on a TV appliance and of course, the “iPhone”. This was underscored by the announcement that “Apple Computer, Inc.” would now be just “Apple, Inc.”.

This comes at a time when I was giving serious consideration to buying my first Mac. I was hoping there would be an announcement of upgraded Mac Minis, with Core 2 Duo CPUs. But no. Only a $300 streaming media box (obviously designed to drive more revenue to the iTunes store) and a $500-$600 cell phone. Ok, cell phone is an understatement, but I have no interest in it at that price (not to mention the monthly bill to go along with it).

Apple is now a consumer electronics company. Apple is the new Sony.

I think I’ll build a new PC to run Vista on instead.