Scott Hanselman

ClickOnce and FireFox with a custom setup equals ClickThrice and be disappointed

November 11, '05 Comments [11] Posted in ASP.NET | Javascript | XML | Bugs
Sponsored By

ClickoncescreencastthumbnailChris Sells via Michael Weinhardt ported Wahoo (a Tetris Clone) to ClickOnce/.NET2.0. I happily visited and attempted to download, expecting to click once. I run Firefox as my default browser and am currently running FireFox 1.5 RC2. However, Brian Noyes via Greg Robinson says

"ClickOnce does not have a dependency on browser flavour, so it should work with Firefox or IE (5.0.3 and up), provided the FX 2.0 is installed."

so I figured I'd be OK. But, just to be careful, I loaded up IE and ran it from there. Here's the result as a screencast.

There are two ways to launch this particular app. You can launch the setup.exe that confirms you have Windows Installer 3.1 (a requirement of MicrosoftUpdate.com) or you can launch it directly via the .application file. The application's manifest gets sent based on it's mime/type to the ClickOnce engine, dfsvc.exe. This application is responsible for reading the manifest and downloading the application into a per-user*per-application cache in C:\Documents and Settings\<you>\local settings\Apps\2.0. The application.manifest mime-type is application/x-ms-application and if you look in Windows (not Internet) Explorer's FileTypes dialog you see that it's open command is "rundll32.exe dfshim.dll,ShOpenVerbApplication %1" which tells rundll32.exe to call the ShOpenVerbApplication method on dfshim.dll also known as the Application Deployment Support Library. Since the setup.exe is calling ShellExecuteEx, it seems that Firefox as the default browser gets first dibs.

Note that the setup.exe isn't a requirement of ClickOnce, just a decision that MichaelW made to ensure a certain prereqs are available. (UPDATE: This has since been updated on the site but still doesn't work in FireFox) The ClickOnce deployment creates a .html file that does some interesting javascript in order to "do the right thing." The Javascript parses the UserAgent, looking for the presence of the CLR. This works on IE because IE's UserAgent string is modified when you install the .NET CLR and various other things. For example, my IE UserAgent is "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; InfoPath.1; .NET CLR 2.0.50727; Avalon 6.0.5070; WinFX RunTime 3.0.50727)" while my FireFox one is "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8) Gecko/20051107 Firefox/1.5".

UseragentmodifiedNOTE: These User Agents are added in a list in the registry under HKEY_LOCAL_MACHINE\SOFTWARE\
Microsoft\Windows\CurrentVersion\Internet Settings\5.0\User Agent\Post Platform
. See screenshot at next.

THOUGHT: Seems to me that FireFox on Windows could save us a hassle if it would respect and use this list in its own User-Agent string.

function Initialize()
{
  if (HasRuntimeVersion(runtimeVersion))
  {
    InstallButton.href = directLink;
    BootstrapperSection.style.display = "none";
  }
}
Opening Wahoo.applicationfunction HasRuntimeVersion(v)
{
  var va = GetVersion(v);
  var i;
  var a = navigator.userAgent.match(/\.NET CLR [0-9.]+/g);
  if (a != null)
    for (i = 0; i < a.length; ++i)
      if (CompareVersions(va, GetVersion(a[i])) <= 0)
		return true;
  return false;
}

Nutshell is, one page is generated by the deployment wizard, but different browsers get different client-side-modified pages based on this (and other) Javascript.

OperaClickOnceInterestingly the identical problem occurs when I use Opera 8.0. It downloads to a temp folder and runs, failing in the exact same way. Both Opera and Firefox download to the Local Settings\Temp, while IE downloads to the bowels of Temporary Internet Files\schmutz.

Hacking

I opened the XML-based .application file and noted this:

<dependentAssembly dependencyType="install" codebase="Wahoo_1_0_0_16\Wahoo.exe.manifest" size="10640">

which was mentioned in the error log file

 * Activation of C:\DOCUME~1\shanselm\LOCALS~1\Temp\wahoo-5.application resulted in exception. Following failure messages were detected:
  + Downloading
http://www.hanselman.com/blog/content/binary/Wahoo.exe.manifest did not succeed.
  + Could not find file 'C:\Documents and Settings\shanselm\Local Settings\Temp\Wahoo_1_0_0_16\Wahoo.exe.manifest'.

So, I downloaded http://www.sellsbrothers.com/wahoo2/Wahoo_1_0_0_16\Wahoo.exe.manifest and saved it to C:\Documents and Settings\shanselm\Local Settings\Temp\Wahoo_1_0_0_16 manually. Then I tried to run Wahoo.application again from FireFox and everything worked. Seems to me that could be a security hole. Dfsvc.exe shouldn't be thrilled to find a file it was responsible for downloading already present in the folder it was responsible for populating. I would have expected a failure to download, not success just because I did its work for it. However, what do I know about security? I tried to edit the .application file and of course was greeted with a tamper proof label, as expected:   

System.Deployment.Application.InvalidDeploymentException (SignatureValidation) - Manifest XML signature is not valid.

That's comforting, though.

No conclusion here yet, folks. I was unable to get either the setup.exe version or the .application version to work any on of three machines, all running FireFox as the default browser. Of course, if I switch to IE as the default, everything works great.

Seems to me that I should be able to download and click on a .application file anywhere and have dfsvc.exe just handle it, copying things to wherever it needs to. It doesn't seem to expect to have .application downloaded to other locations.

Good news is, it's been reported as a bug and I expect we will here something soon. I'll post as soon as I get more info. Feel free to comment if I've gotten something wrong.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Request.Browser.Version may not be a double

November 11, '05 Comments [7] Posted in ASP.NET
Sponsored By

Avoid writing code like this to do browser detection:

if(Double.Parse(Request.Browser.Version) < 5.2) { // 5 or less

Why might this be a problem? Well, Request.Browser.Version is typed in the Base Class Library as a string. It's a string for a reason - because it may not necessarily be a double. When one writes Double.Parse(someString) they're basically saying that the folks who chose the type for Request.Browser.Version were mistaken, and that a double would have been a better chose.

This can be a problem when a browser like IE7.0 beta comes out and the value of Request.Browser.Version is "7.0b." This also applies to Mac's Safari and its User agent version which changed recently from 2.0 to 2.0.1 or 2.0.2.

Exception: Input string was not in a correct format.
Exception: System.FormatException
Message: Input string was not in a correct format.
Source: mscorlib
at System.Number.ParseDouble(String s, NumberStyles style, NumberFormatInfo info)
at System.Double.Parse(String s, NumberStyles style, IFormatProvider provider)
at System.Double.Parse(String s)

Unexpected exceptions tend to cramp one's style. I like the philosophy that "if you ever get an unexpected exception, you didn't test your code well enough." Why? Because clearly it wasn't anticipated, otherwise there'd have been code to handle it. This line (and the code around it you can't see) never expected a non-double to be in Request.Browser.Version, but the property's string type was the first tip that other things could happen.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

When .NET 2.0 Applications Attack - Debugging weirdness after installing the .NET Runtime

November 10, '05 Comments [10] Posted in Learning .NET | Bugs
Sponsored By

WeirdLogitechRemotingBugI've been developing using the Logitech io2 Pen SDK recently and also, coincidentally installed .NET 2.0 and Visual Studio 2005 around the same time.

I got this error a few days ago upon docking the pen: "Ticks must be between DateTime.MinValue and DateTime.MaxValue" and noticed that remoting was involved in the stack dump:

System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
   at Logitech.Pen.Device.IPen.get_LastSynchronizationTime()
   at Logitech.Pen.TrayIcon.PenBalloon.DoRefresh()

So, two assemblies were chatting each other up via remoting and something went wrong. Logitech has a number of EXEs that all work together to make io2 Pen Services available in multiple contexts. Don't worry, they don't all run at the same time; they come and go as needed.

Io2Software

I knew that Logitech used .NET 1.1 for their development and I knew that on systems that have both .NET 2.0 and .NET 1.1 that 1.1 EXEs would get the 1.1 CLR loaded by default. However, the only thing that changed was the installation of 2.0.

Which of these was running .NET 2.0? Using Sysinternal's glorious ProcExp that can highlight processes that contain a .NET runtime as well as show exactly what DLLs are loaded in a process's memory space. Looks like Pen.LplsHost.exe gets 2.0 loaded, while EVERY other .EXE gets 1.1.

LplsHost

That's weird, and it would make sense that if a .NET 1.1 application remoted into a .NET 2.0 application that something odd might happened (Honest question: Doesn't it?).

When you install .NET 2.0, does it changed any 1.1 DLLs? Yes and no. The system-wide CLR loader, mscoree.dll changes when you install a new version of the .NET Framework. It's always the last version. This is necessary because the loader ultimately makes the decision of which CLR to load into that process. So, mscoree was updated to make the 1.0, 1.1 or 2.0 decision. 

Now, the real question is: What is it about Pen.LplsHost.exe that causes it to load .NET 2.0?

Here's why! It's NOT a .NET application! Reflector reminds me that it's an unmanaged C++ application. It does load the CLR though, so it's likely a CLR Host that does something like this to load the CLR:

LPWSTR pszFlavor = L"wks";
ICorRuntimeHost *pHost = NULL;
hr = CorBindToRuntimeEx(
                      //version
                      null,      
                      // svr or wks                       
                      pszFlavor,   
                      //domain-neutral"ness" and gc settings
                      STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN_HOST |       
                      STARTUP_CONCURRENT_GC,
                      CLSID_CorRuntimeHost,
                      IID_ICorRuntimeHost,
                      (void **)&pHost);

However, if you don't pass in a value for version, MSDN says:

"If the caller specifies null for pwszVersion, the latest version of the common language runtime is loaded. Passing null gives the host no control over which version of the runtime is loaded. While this may be appropriate in some scenarios, it is strongly recommended that the host supply a specific version to load."

So instead, they should note http://msdn.com/library/en-us/cpgenref/html/grfuncorbindtoruntimeex.asp and try this instead: 

LPWSTR pszVer = L"v1.1.4322"; // <!---- Oy!
LPWSTR pszFlavor = L"wks";
ICorRuntimeHost *pHost = NULL;
hr = CorBindToRuntimeEx(
                      //version
                      pszVer,  // <!---- Oy!
                      // svr or wks                       
                      pszFlavor,   
                      //domain-neutral"ness" and gc settings
                      STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN_HOST |       
                      STARTUP_CONCURRENT_GC,
                      CLSID_CorRuntimeHost,
                      IID_ICorRuntimeHost,
                      (void **)&pHost);

Speculation: I'd bet $100 that the devs aren't asking for .NET 1.1 in Pen.LplsHost.exe and they should have if they were following best practices. As I said, you can override it with the .config file, but the RIGHT thing to do (in the short term) is to ask for the version you want.

I'm chatting with Logitech Support about this, so until I update this post everything I've said so far is just my opinion and speculation and in no way means to impugn they or there developers. It's a difficult thing to anticipate these things and to include version-specific information, especially as a CLR Host.

Short term fix: You can easily force Pen.LplsHost.exe (or any application for that matter) to load the .NET 1.1 framework by dropping the attached file into C:\Program Files\Logitech\io2Software. I have done this and haven't see any problems since and noted with ProcExp that 1.1 is now correctly loaded in this process's space.

<configuration>
    <startup>
        <requiredRuntime version="v1.1.4322"  />
    </startup>
</configuration>

File Attachment: Pen.LplsHost.exe.config (118 bytes)

UPDATE: Matt Davis (see the comments for this post) nailed it. I was mistaken. I spoke to the team at Logitech and it was, in fact, C++ calling a .NET Assembly via COM Interop. This  could be controlled via the RunTimeVersion key for the InproServer32 key for this RCW within the Registry. However, everything in this post up to the CorBindToRuntimeEx conclusion holds, and the .exe.config solution is also workable one. Logitech is exploring ways to make this easier. My question is, what about Explorer.exe and hosting multiple shell extensions each written in different versions of .NET?

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Windows Clippings - A Cropper Contender

November 10, '05 Comments [2] Posted in Reviews | ASP.NET
Sponsored By

I dig Cropper in a big way. It's a great, elegant and lightweight way to do screenshot. However, lately I've been using Kenny Kerr's Windows Clippings 1.0 for posting to my blog.

It goes in the tray and when you double click on it the screen dims. You single click on the window you want to get a screenshot of and just that window brightens. A double-click then will send the screenshot to the clipboard, OneNote, or a Folder. It's even smart enough to pick a good filename based on the Window's Title.

Not only that, but it will create transparency in all the appropriate places, which is very useful when you consider that even Windows Explorer uses irregular regions and curved edges.

Kudos to Kenny for another killer util!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Blogging directly from a Logitech IO2 Pen

November 9, '05 Comments [9] Posted in Coding4Fun
Sponsored By

UPDATE: In case this seemed like a non sequitur, this is in support of an upcoming article for Coding4Fun on the Logitech io2 pen.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.