I was experiencing a most perplexing issue yesterday and the day prior that I *finally* resolved late last night. In essence, I have a Windows Service that I wrote that hosts an adapted Cassini server which, in turn, hosts an ASP.NET website product running on the client's computer. The issue was the classic case of 'it works on my machine'.
I could install my service, configure it, and run an ASP.NET website on my computer. It was a beautiful and exciting thing to witness. When I went to execute it on another computer, however, the service would start up perfectly but when I made the first request to the website the service would crash (immediately stop) and log an error to the system's Application event log. The error was a FileNotFoundException.
After troubleshooting the obvious stuff for a bit (folder permissions (the Service runs as NETWORK SERVICE), executables in the proper places, comparing systems, etc), I resorted to more drastic means. :) Actually, this is technique I frequently employ when troubleshooting issues loading assemblies.
You can troubleshoot Fusion binding issues by simply setting up some registry keys on the machine in question. Fusion, in short, is the name given by Microsoft for their assembly loading and binding technology in .NET. Because I don't have the .NET SDK or Visual Studio on the target machine in question, I had to set it up manually. I added the following registry keys:
HKLM\SOFTWARE\Microsoft\Fusion\LogPath (string) with value pointing to my desired output folder (C:\(TEMP)\FusionLog).HKLM\SOFTWARE\Microsoft\Fusion\LogFailures (DWORD) with a value of 1.
I restarted the service and made another web request to witness the failure for the umpteenth time. Sure enough I got some output in my FusionLog folder:
*** Assembly Binder Log Entry (6/13/2008 @ 9:18:40 PM) *** The operation failed.Bind result: hr = 0x80070002. The system cannot find the file specified. Assembly manager loaded from: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscorwks.dllRunning under executable C:\Program Files\Devstone\WebHostSvc\WebHostSvc.exe--- A detailed error log follows. === Pre-bind state information ===LOG: User = NT AUTHORITY\NETWORK SERVICELOG: DisplayName = WebHostSvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxxxxxxxxxx (Fully-specified)LOG: Appbase = file:///C:/Documents and Settings/All Users/Application Data/Devstone/Web/LOG: Initial PrivatePath = NULLLOG: Dynamic Base = NULLLOG: Cache Base = NULLLOG: AppName = f9861834Calling assembly : (Unknown).===LOG: This bind starts in default load context.LOG: Using application configuration file: C:\Documents and Settings\All Users\Application Data\Devstone\Web\web.configLOG: Using machine configuration file from C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\config\machine.config.LOG: Post-policy reference: WebHostSvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxxxxxxxxxxLOG: GAC Lookup was unsuccessful.ERR: No codebases found to download from.ERR: Unrecoverable error occurred during pre-download check (hr = 0x80070002).
*** Assembly Binder Log Entry (6/13/2008 @ 9:18:40 PM) ***
The operation failed.Bind result: hr = 0x80070002. The system cannot find the file specified.
Assembly manager loaded from: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscorwks.dllRunning under executable C:\Program Files\Devstone\WebHostSvc\WebHostSvc.exe--- A detailed error log follows.
=== Pre-bind state information ===LOG: User = NT AUTHORITY\NETWORK SERVICELOG: DisplayName = WebHostSvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxxxxxxxxxx (Fully-specified)LOG: Appbase = file:///C:/Documents and Settings/All Users/Application Data/Devstone/Web/LOG: Initial PrivatePath = NULLLOG: Dynamic Base = NULLLOG: Cache Base = NULLLOG: AppName = f9861834Calling assembly : (Unknown).===LOG: This bind starts in default load context.LOG: Using application configuration file: C:\Documents and Settings\All Users\Application Data\Devstone\Web\web.configLOG: Using machine configuration file from C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\config\machine.config.LOG: Post-policy reference: WebHostSvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxxxxxxxxxxLOG: GAC Lookup was unsuccessful.ERR: No codebases found to download from.ERR: Unrecoverable error occurred during pre-download check (hr = 0x80070002).
The thing that immeidately stood out was the fact that the Initial PrivatePath, Dynamic Base, and Cache Base were all NULL. On my development machine these all referenced the /Web/bin folder. Why wasn't it looking in the \bin folder?
At this point I began debugging the service, actually stepping into the .NET Framework code itself to see if I could figure out the issue. Delving into the HttpRuntime.cs file I could see that the \bin folder gets appended to the app domain's private paths in the InitFusion() method, just as I had expected. However, somehow the code wasn't getting that far. Unfortunately, I couldn't step through some of the code leading up to this point so it was difficult to see exactly why it wasn't getting here. Actually, in retrospect, I should have looked at the code a little longer - the answer was right in front of me.
I noticed from the fusion log that it was performing a GAC Lookup for the assembly. Now the assembly isn't GAC-registered on my development machine but I was at my wit's end with the issue. So I GAC'd it:
gacutil /i WebHostSvc.exe
Immediately upon the next request I got the error I was looking for in the browser:
The current identity (NT AUTHORITY\NETWORK SERVICE) does not have write access to 'C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files'.
Aha! The one thing I didn't check but should have before. Granting NETWORK SERVICE rights to that folder fixed the problem straight away.
I then removed it from the GAC and removed the Temporary ASP.NET Files that were created, restarted the service, and ran it again to verify that it was running properly.
At this point I went back to the source code and sure enough I saw the line I was looking for. When initializing, it invokes a method called SetUpCodegenDirectory (which maps to the Temporary ASP.NET Files folder). This method ensures that the caller has write access to it.
So the moral of the story: make sure that the user account that you're using for ASP.NET has write access to the Temporary ASP.NET Files folder and you'll be a much happier developer.
Remember Me
a@href@title, b, i, strike
Powered by: newtelligence dasBlog 2.0.7226.0
Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.
© Copyright 2008R. Aaron Zupancic
E-mail