February, 2008

  • Never doubt thy debugger

    Version numbers in a compiled assembly


    A while ago I got a call from a customer who wanted to understand what is the meaning of the version number's in a compiled assembly and more than that, he wanted to understand how they are calculated. He expected that at least the build number were incremented every time a build is done, but that was not always happening...

    As I guess you know, what that customer was talking about are the Major Version, Minor Version, Build, Revision (see Assembly versioning). This version number is physically represented as a four-part string with the following format:
    <major version>.<minor version>.<build number>.<revision>. For example, version 1.5.1254.15632 indicates 1 as the major version, 5 as the minor version, 1254 as the build number, and 15632 as the revision number. The version number is stored in the assembly manifest along with other identity information, including the assembly name and public key, as well as information on relationships and identities of other assemblies connected with the application.

    Of course you can manually specify the assembly version you want to use by setting it in the AssemblyInfo.* file, but if you leave to Visual Studio the task to calculate it, here is what happens (as usual don't take this as granted forever, the algorithm might change in the future):

    • Build: the number of days since 1.1.2000 (you can modify this start date by setting Software\\Microsoft\\ALink\\VersionStartDate).  i.e. 1879 = 02.22.2005
    • Revision: the number of two second intervals since midnight in local time; i.e. 31308 =  17:23:36 local time.

    Regarding how Visual Studio updates the version number, the behavior is different depending is you are using Visual Basic .NET or C#: here is an excerpt from Team development with Visual Studio and Visual SourceSafe

    Note   For a Microsoft Visual Basic® .NET project with an AssemblyVersion set to "1.0.*", the assembly version is only updated the first time the project is rebuilt within the Visual Studio .NET integrated development environment (IDE). The version number remains constant for subsequent rebuilds within the same instance of Visual Studio .NET. This does not represent a problem because the assembly version is for information only in assemblies that do not have a strong name. For strong named assemblies, you should avoid the use of wild characters in the AssemblyVersion attribute, as explained in the following section.

    For C# projects with an AssemblyVersion set to "1.0.*", the assembly version is updated every time the project is rebuilt.


    Quote of the day:
    It is by universal misunderstanding that all agree. For if, by ill luck, people understood each other, they would never agree. - Charles Baudelaire
  • Never doubt thy debugger

    aspnet_wp.exe can not be started. Code: 80070003


    This was a Windows 2000 server with ASP.NET 1.1 installed, and for some reason the customer was unable to start his web applications, he was getting "Server Application Unavailable" messages on the client.

    We found the Application event log full of entries like this one:

    Event Type:    Error
    Event Source:    ASP.NET 1.1.4322.0
    Event Category:    None
    Event ID:    1084
    Date:        18/02/2008
    Time:        15.59.43
    User:        N/A
    Computer:    <computername>
    aspnet_wp.exe could not be started. The error code for the failure is 80070003.
    This error can be caused when the worker process account has insufficient rights to read the .NET Framework files.
    Please ensure that the .NET Framework is correctly installed and that the
    ACLs on the installation directory allow access to the configured account.

    The next logical step is to use Process Monitor to try to spot any "Access denied" errors, but there weren't.

    Interestingly within the procmon trace we instead found quite a few entries similar to the following

    inetinfo.exe:1112 OPEN C:\Debuggers\cdb.exe PATH NOT FOUND
    inetinfo.exe:1112 QUERY INFORMATION C:\Debuggers\cdb.exe -server PATH NOT FOUND
    inetinfo.exe:1112 QUERY INFORMATION C:\Debuggers\cdb.exe -server.exe PATH NOT FOUND
    inetinfo.exe:1112 OPEN C:\Debuggers\cdb.exe -server PATH NOT FOUND
    inetinfo.exe:1112 QUERY INFORMATION C:\Debuggers\cdb.exe -server tcp:port=8090 PATH NOT FOUND
    inetinfo.exe:1112 QUERY INFORMATION C:\Debuggers\cdb.exe -server tcp:port=8090.exe PATH NOT FOUND
    inetinfo.exe:1112 OPEN C:\Debuggers\cdb.exe -server tcp:port=8090 PATH NOT FOUND

    As you may know, cdb.exe usually ships with the Debugging Tools for Windows, and C:\Debuggers is a folder we usually use (at least in CSS) to install the debugger... This made me think to a problem I had a while ago with my notepad (see here), so I asked the customer to check if he had a this registry key:

    hklm\software\microsoft\windows nt\currentversion\image file execution options\aspnet_wp.exe

    This is usually created by gflags, and those Path not found errors means the customer actually had the Debugging Tools for Windows installed in C:\Debuggers and then for some reason someone moved (or deleted) the folder without first removing the gflags entry from the registry: if cdb.exe cannot be found, then the target process (in this case aspnet_wp.exe) cannot be started.

    We just deleted the registry key and the site was back online.




    Quote of the Day:
    Sharing money is what gives it its value. -- Elvis Presley, singer
  • Never doubt thy debugger

    Want to persist your properties as markup?


    Last week I got a request from a customer whom was developing a custom ASP.NET control targeted for other developers, and one of his requirements was to persist the control's properties in the html markup, even for the default values which normally Visual Studio removes: since those are default values the assumption is that we don't need to persist them in the markup (what usually happens is that if you change the value of a property through the property grid this is reflected in the html markup, but if you later set the value back to its default the designer removes the corresponding markup since it's no longer needed), but the requirement here was to persist them anyway.

    To be honest I had never thought to this possibility (who knows, maybe I'm too used to do things in the Microsoft way smile_wink) and despite my researches I've not been able to find this answer anywhere in our internal docs and samples, so we started working on a sample together (well, me from my office in Milan and the customer in his office in Crecchio, central Italy) and we also involved an Escalation Engineer (Radomir Zaric, thanks for your help!) to clarify a few weird behaviors and internals we encountered during our tests.

    To make a long story short, basically we came up with a solution based on two fundamentals:

    • Use the ToolboxData attribute which creates the markup when the control is first dragged from the toolbox on the page
    • Use the DesignerSerializationVisibility attribute (set to Content) which generates initialization code for each public not hidden property of our control

    If you can read Italian, Andrea (the customer) posted the solution to his blog: Valori di default per un ASP.Net WebControl; he promised to publish an English translation soon, add a comment to the post if you're eager to read it smile_regular

    By the way, here is another interesting posts in February (I did not have time to read the past month yet): La persistenza delle proprietà di un WebControl senza utilizzare il Postback (How to persist a control property without using the postback).


    UPDATE (18/02/2008): here's Andrea's first translation: How to persist a control property without using PostBack



    Quote of the Day:
    Civilization begins with order, grows with liberty, and dies with chaos. --Will Durant
  • Never doubt thy debugger

    LogParser scripts for various occasions...


    I've been working again with LogParser lately to extract some statistics for an IIS server which was facing a suspicious activity from the outside world: they were getting literally thousands of requests from a bunch if IP addresses to their page to request a "forgot password" for their online services. For this post is not important how we resolved the problem, but rather that of the occasion I had to create a few LogParser scripts to extract some statistics from the IIS logs, so I through those might be useful for other people too... Of course you're free to change them to adapt to your needs. Before you proceed, a couple of words on the scripts: those are meant to be "generic" and run with a batch file which accepts some input arguments, but you can run them from a command prompt directly replacing the "%x" placeholders; also, I print them on multiple lines to be easier to read, but you must run then on a single line.


    This is to count how many requests you got to a specific page with a specific value in the query string (we were extracting data for the "change password" page with a "reset password" switch):

    logparser "SELECT COUNT(*) INTO %1 FROM *.log WHERE EXTRACT_FILENAME(cs-uri-stem) = '%2' AND INDEX_OF(cs-uri-query, '%3') > 0" -i:IISW3C


    Do you need some more details such as the return code, User-Agent etc...?

    logparser "SELECT date, time, c-ip, cs-uri-query, sc-status, cs(User-Agent) 
        INTO %1 FROM *.log WHERE EXTRACT_FILENAME(cs-uri-stem) = '%2' AND INDEX_OF(cs-uri-query, '%3') > 0 
        ORDER BY c-ip" -i:IISW3C -RTP:-1


    Since we noted that we were receiving a very high number or requests from specific IP addresses, here is the script to get this statistic:

    logparser "SELECT DISTINCT c-ip, COUNT(*) as Hits INTO %1 FROM *.log 
        WHERE EXTRACT_FILENAME(cs-uri-stem) = '%2' AND INDEX_OF(cs-uri-query, '%3') > 0 
        GROUP BY c-ip ORDER BY Hits DESC" -i:IISW3C -RTP:-1


    To get the average executing time for your pages (or web services) you must first of all enable extended logging in IIS, then you can run this script:

    logparser "SELECT AVG(time-taken) As AverageTimeTaken, MAX(time-taken) As MaxTimeTaken, COUNT(*) As Hits, TO_LOWERCASE(cs-uri-stem) 
        INTO %1 FROM *.log
        WHERE EXTRACT_EXTENSION(TO_LOWERCASE(cs-uri-stem)) = '%2' 
        GROUP BY cs-uri-stem ORDER BY AverageTimeTaken DESC"  -i:IISW3C -RTP:-1


    If you want information about requests getting a specific IIS status code, here's what you need:

    logparser "SELECT date, time, cs-uri-stem, cs-uri-query, sc-status, cs(User-Agent) 
        INTO %1 FROM *.log WHERE sc-status = '%2'" -i:IISW3C -RTP:-1


    If you want a nice pie graphic to summarize the distribution of bytes served for file type (you need extended logging to have the sc-bytes property):

    LogParser "SELECT TO_UPPERCASE(EXTRACT_EXTENSION(cs-uri-stem)) AS PageType, MUL(PROPSUM(sc-bytes),100.0) AS Bytes 
        INTO %1 FROM *.log GROUP BY PageType ORDER BY Bytes DESC" 
        -chartType:PieExploded -chartTitle:"Bytes per page type" -categories:off -i:IISW3C -o:CHART


    On the Event Viewer now... this is if you need to extract all the events for a specific SourceName:

    logparser "SELECT TimeWritten, SourceName, EventID, Message INTO %2 FROM %2 
        WHERE SourceName = '%3'" -i:EVT -o:CSV


    A very simple way to convert a .evt file into a csv one, if you prefer to use Excel for your filters:

    logparser "SELECT * INTO %1 FROM %2" -i:EVT -o:CSV


    That's it for today, I'll likely add new ones if you're interested (or if you have specific requests smile_wink).



    Quote of the day:
    O Lord, help me to be pure, but not yet. - Saint Augustine
  • Never doubt thy debugger

    The message received from the server could not be parsed


    Again on the Ajax-compression subject (see here and here), here's another error I got recently:

    Sys.WebForms.PageRequestManagerParserErrorException: The message received from the server could not be parsed. Common causes for this error are when the response is modified by calls to Response.Write(), response filters, HttpModules, or server trace is enabled.

    The customer was using the GZipStream class within an HttpModule to compress the ASP.NET output; everything was working except in pages where he was using some UpdatePanel controls, where he was getting error messages like the above. As you can guess if you've read my previous posts, with http compression the response stream was being truncated.

    There are a couple of solutions available to this error: do not use the HttpModule and rely on IIS compression, or move the application to the .NET Framework 3.5.

    1. Remove the HttpModule from web.config
    2. Assure ScriptResource is not compressed: <scriptResourceHandler enableCompression="false" enableCaching="true" />
    3. Open the IIS Manager
    4. Right click on "Web Sites" folder > Properties
    5. Click the "Services" tab
    6. Check "Compress application files" and "Compress static files"
    7. Apply and confirm all dialogs
    8. Stop IIS
    9. Open a command prompt and go to C:\Inetpub\AdminScripts
    10. Run the following commands to add .js, .aspx and .axd to the compression list:
      • cscript adsutil.vbs set W3SVC/Filters/Compression/GZIP/HcFileExtensions "htm" "html" "txt" "js"
      • cscript adsutil.vbs set W3SVC/Filters/Compression/Deflate/HcFileExtensions "htm" "html" "txt" "js"
      • cscript adsutil.vbs set W3SVC/Filters/Compression/GZIP/HcScriptFileExtensions "asp" "dll" "exe" "aspx"
      • cscript adsutil.vbs set W3SVC/Filters/Compression/Deflate/HcScriptFileExtensions "asp" "dll" "exe" "aspx"
    11. Restart IIS

    You can also move the application to .NET Framework 3.5 instead, because the UpdatePanel control has been changed internally and now this problem should not happen anymore.



    Quote of the day:
    There are painters who transform the sun to a yellow spot, but there are others who with the help of their art and their intelligence, transform a yellow spot into the sun. - Pablo Picasso
Page 1 of 1 (5 items)