# Friday, December 11, 2009

198 - Copy I received this question this morning from the Microsoft Online Concierge. We have a Concierge? Who knew?! Can you set me a wake-up time please, for 10AM? Oh, and a full breakfast would be excellent too.

The specific question was:
“I want to check the assembly residing in GAC, previously we use to check using gacutil, but now it is not a part of .net framework so is there any replacement for gacutil?”

I’ve seen this same question many times before, almost verbatim. Clearly, time to get something on the InterWebs to slow down the frequency of it.

The short answer is that gacutil is still your answer, but that it lives in the SDK (v2, v3.5, v3.5). It doesn’t ship with the redist product, and since gacutil is intended as a development time scenario only, that makes sense. End-users (the ones using your apps) shouldn’t be messing with the GAC.

From what I understand, Gacutil *did* end up on customer machines in a service pack of .NET 1.1. Apparently, the SP relied on gacutil.exe to do GACing, and did not delete the exe as part of its final cleanup. So, there were two mistakes there (in order): using a different set of tools/APIs than what we guide customers to use, and leaving behind turds on end-user machines. Bad, bad, bad. In any case, gacutil.exe was never intended to be part of the end-user install.

Gacutil also ships with most (maybe all) SKUs of VS. So, if you open the VS command-prompt, you just need to type “gacutil” you’ll see it print a bunch of command-line switch help.

To check if an assembly is in the GAC, you can type one of the following two forms:

  • gacutil /l [simple name]
  • gacutil /l [full strong name]

Here are two examples, using the .NET 4 gacutil:

C:\Users\rlander\Documents\Visual Studio 2010\Projects>gacutil /l System
Microsoft (R) .NET Global Assembly Cache Utility.  Version 4.0.21201.0
Copyright (c) Microsoft Corporation.  All rights reserved.

The Global Assembly Cache contains the following assemblies:
  System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, pro
cessorArchitecture=MSIL
  System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, pro
cessorArchitecture=MSIL

Number of items = 2

C:\Users\rlander\Documents\Visual Studio 2010\Projects>gacutil /l "System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
Microsoft (R) .NET Global Assembly Cache Utility.  Version 4.0.21201.0
Copyright (c) Microsoft Corporation.  All rights reserved.

The Global Assembly Cache contains the following assemblies:
  System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, pro
cessorArchitecture=MSIL

Number of items = 1

C:\Users\rlander\Documents\Visual Studio 2010\Projects>

Notice that specifying “System” (simple name only) returns both the v4 and v2 version of the assembly, but specifying the “System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089” (full strong name) returns just the v4 version of the assembly. That makes sense, for a general query (the first one) and a specific query (the second one).

If I had passed in mscorlib, System.Web or System.Data, I would have seen 4 assemblies, as those assemblies are bit-specific, and require a different compiled assembly (using the platform switch) for each processor type (X86, X64). See:

C:\Users\rlander\Documents\Visual Studio 2010\Projects>gacutil /l System.Web
Microsoft (R) .NET Global Assembly Cache Utility.  Version 4.0.21201.0
Copyright (c) Microsoft Corporation.  All rights reserved.

The Global Assembly Cache contains the following assemblies:
  System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a,
processorArchitecture=AMD64
  System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a,
processorArchitecture=x86
  System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a,
processorArchitecture=AMD64
  System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a,
processorArchitecture=x86

Number of items = 4

If you do need to check whether assemblies are in the GAC on end-user machines, we do have a set of APIs for doing that. Being that they are APIs, you need to code to them, and they are native. A good question is whether a set of first-class managed APIs would be generally useful to developers. You can do searches for samples, which would likely help you.

MSI has support for installing and uninstalling assemblies. For that scenario, you should just rely on that support. That’s what Visual Studio does, for example. I should note that MSI supports installing .NET 4 assemblies; no updates are required for that.

Looking back at the history of this blog, it looks like I take a few moments out to answer this question every 2 years. It appears that I’m right on target again, in late 2009. See you again (on this topic) in late 2011! Hopefully not.

Friday, December 11, 2009 6:15:52 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Tuesday, December 08, 2009

I posted an article on the CLR Team blog on a new scenario that we enabled for sharing assemblies between Silverlight and .NET. This is one of the features that I worked on in the .NET 4 cycle.

http://blogs.msdn.com/clrteam/archive/2009/12/01/sharing-silverlight-assemblies-with-net-apps.aspx

 

Tuesday, December 08, 2009 11:53:02 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 

I'm doing a little project right now that requires me to know (among other things) the number of hardlinks for a given file. I have a tool that already will tell me the number of hard links for a given file, but I need to collect a bunch more information, and I really hate calling out to an exe and collecting and parsing its output, as part of a larger app. So, I set out to write that slightly larger app.

Having never written an app that cares about hardlinks, I didn't know whether or not the BCL had APIs that exposed this info. It doesn't. Whether it should expose this info is a whole other question. So, I resorted to p/invoke, which is fine. I did a few searches and found that there isn't too much info out there on doing this from .NET, hence my motivation for writing this post.

I started out with NtQueryInformationFile. I'm pretty sure that the p/invoke signature on p/invoke.net is wrong. I wasn't able to get this function working to my satisfaction, but can share where I ended up, in the hope that it might help someone:

        [DllImport("ntdll.dll", SetLastError=true)] 
        static extern Int32 NtQueryInformationFile(IntPtr fileHandle, ref IO_STATUS_BLOCK IoStatusBlock, [MarshalAs(UnmanagedType.LPArray)] Byte[] arrayBuffer, uint length, FILE_INFORMATION_CLASS fileInformation); 

FWIW, I allocated a byte[2048], and passed in (uint)array.Length as the length paramter. 2k was overkill for what I needed, but at least got me a bunch of bytes. I wasn't too sure what to do next, so decided to look for something easier. I was guessing that I needed to write a struct who fields mapped over that byte[], but I wasn't sure what the meaning of those fields was going to be. I couldn't find any docs that would have that easier, and didn't see any code samples either.

The good news is that I found another function -- GetFileInformationByHandle -- that is much easier to use, and that did work for me.

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool GetFileInformationByHandle(IntPtr hFile, out BY_HANDLE_FILE_INFORMATION lpFileInformation);

You can see my app below. For now, it just contains calculates the hard-links, which is the part that I tackled first. I'll now move to writing the rest of my experiment.

using System;
using System.Runtime.InteropServices;
using System.IO;

namespace banff
{
    class Program
    {
        static void Main(string[] args)
        {

            if (args == null || args.Length == 0)
            {
                Console.WriteLine("Please specify a file to check");
                return;
            }
            else if (!File.Exists(args[0]))
            {
                Console.WriteLine("File doesn't exist");
            }

            

            FileStream fs = null;

            fs = new FileStream(args[0],FileMode.Open,FileAccess.Read);
            BY_HANDLE_FILE_INFORMATION fileInfo;

            Boolean result = GetFileInformationByHandle(fs.Handle, out fileInfo);

            Console.WriteLine(fileInfo.NumberOfLinks);

            fs.Close();

        }

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool GetFileInformationByHandle(IntPtr hFile, out BY_HANDLE_FILE_INFORMATION lpFileInformation);


        struct BY_HANDLE_FILE_INFORMATION
        {
            public uint FileAttributes;
            public FILETIME CreationTime;
            public FILETIME LastAccessTime;
            public FILETIME LastWriteTime;
            public uint VolumeSerialNumber;
            public uint FileSizeHigh;
            public uint FileSizeLow;
            public uint NumberOfLinks;
            public uint FileIndexHigh;
            public uint FileIndexLow;
        }

    }
}

 

 

Tuesday, December 08, 2009 7:19:57 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  |