Archive: Register .Net Assembly in Global Assembly Cache


Register .Net Assembly in Global Assembly Cache
Here is a .nsh with some magic inside.

I've established this works on my PC but I'd happily have feedback from others.

What does it do?

It registers and un-registers MSIL Assemblies in the GAC.

How does it do it?

I monitored what gacutil did and tried to replicate it. Assemblies are copied to "$windir\assembly\<assembly>\<strong_version>\" and an entry added to the Registry (I particularly want feedback on whether that's needed).

What else do I need to know or do?

As a library developer, you will need to use gacutil to get the information (and sign your assemblies) but you can now deploy without additional tools.


This is my first time writing stuff for nsis, so comments on coding style gratefully received.


This is clearly not the way to do it

I don't care about .NET myself, so I don't know if there is sample code that already does it, but you really should call the official API with the system plugin, see http://blogs.msdn.com/junfeng/articles/229648.aspx and http://kobyk.wordpress.com/2008/04/1...sis-installer/ to maybe get you started


If someone would like to translate the C++ into English, I'd be happy to have another go. I'm not a Windows C++ programmer. I just want something that works.


you don't need to use C++, you could call the COM like functions with the system plugin. http://nsis.sourceforge.net/WinSxS_Q...y_is_installed almost does it, you would have to change it (call CreateAssemblyCache and not QueryAssemblyInfo, passing in a FUSION_INSTALL_REFERENCE struct pointer)

allocate the FUSION_INSTALL_REFERENCE struct with something like System::Call '*(i SIZEINBYTESHERE,i0,g FUSION_REFCOUNT_XXX,w "something",w "somethingElse")i.s'
pop $R1

also note that the COM method index is not 4 like it is in that sample, I could not find a typelib listing on google but I'm thinking its 7

so calling InstallAssembly would look something like:
System::Call `$R0->7(i 1,w "\path\to\manifest",i $R1)i.r0`
assuming that the IAssemblyCache pointer is in $R0 and FUSION_INSTALL_REFERENCE struct is in $R1

also note that you need to use the real value for FUSION_REFCOUNT_*, not a string, the guids are:

// {8cedc215-ac4b-488b-93c0-a50a49cb2fb8}
EXTERN_GUID(FUSION_REFCOUNT_UNINSTALL_SUBKEY_GUID, 0x8cedc215, 0xac4b, 0x488b, 0x93, 0xc0, 0xa5, 0x0a, 0x49, 0xcb, 0x2f, 0xb8);

// {b02f9d65-fb77-4f7a-afa5-b391309f11c9}
EXTERN_GUID(FUSION_REFCOUNT_FILEPATH_GUID, 0xb02f9d65, 0xfb77, 0x4f7a, 0xaf, 0xa5, 0xb3, 0x91, 0x30, 0x9f, 0x11, 0xc9);

// {2ec93463-b0c3-45e1-8364-327e96aea856}
EXTERN_GUID(FUSION_REFCOUNT_OPAQUE_STRING_GUID, 0x2ec93463, 0xb0c3, 0x45e1, 0x83, 0x64, 0x32, 0x7e, 0x96, 0xae, 0xa8, 0x56);
// {25df0fc1-7f97-4070-add7-4b13bbfd7cb8} // this GUID cannot be used for installing into GAC.
EXTERN_GUID(FUSION_REFCOUNT_MSI_GUID, 0x25df0fc1, 0x7f97, 0x4070, 0xad, 0xd7, 0x4b, 0x13, 0xbb, 0xfd, 0x7c, 0xb8);
// {d16d444c-56d8-11d5-882d-0080c847b195}
EXTERN_GUID(FUSION_REFCOUNT_OSINSTALL_GUID, 0xd16d444c, 0x56d8, 0x11d5, 0x88, 0x2d, 0x00, 0x80, 0xc8, 0x47, 0xb1, 0x95);

which GUID to use, I don't know, if you wanted to use the filepath option, it would look something like:

!define FUSION_REFCOUNT_FILEPATH_GUID {b02f9d65-fb77-4f7a-afa5-b391309f11c9}


System::Call '*(i 32 ,i0,g "${FUSION_REFCOUNT_FILEPATH_GUID}",w "$instdir\yourfile.dll",w "somethingElse")i.s'
pop $R1

as far as SIZEINBYTESHERE goes, I think its 32

edit: I understand that getting the system plugin code working is almost as hard as learing C, but I don't want to deal with .NET so its the best I can do, no complete sample from me, sorry

edit2: but since you probably know C#, the code at the bottom of http://nsis.sourceforge.net/Register...DLL_in_the_GAC looks like maybe something you could use


(Why do I get the feeling M$ make it deliberately hard..? :))

Thanks. I've finally found the doc at M$! :) I've been searching for ages.

Your http://nsis.sourceforge.net/WinSxS_QueryAssemblyInfo_to_check_if_assembly_is_installed page will be helpful.

I'm trying to keep this "cleanly" inside NSIS, so I'm avoiding calling out to an external program.

So... what do we find? (Sorry about the long URLs, otherwise it won't display them as I'm too new, I guess...)

Sxs.dll lives in $windir\system32 and has these. (I checked, it's on my WinXPSP3/.Net3.5 machine. Is it there on .Net2?)

http://msdn.microsoft.com/en-us/library/aa375127(VS.85).aspx (CreateAssemblyCache) gets an http://msdn.microsoft.com/en-us/library/aa375157(VS.85).aspx (IAssemblyCache) instance.

http://msdn.microsoft.com/en-us/library/aa375162(VS.85).aspx (IAssemblyCache::InstallAssembly) gets passed three things:[list=1][*]flags[*]location of assembly[*](optional) referrer to assembly[/list=1]
where that last one is http://msdn.microsoft.com/en-us/library/aa375151(VS.85).aspx (FUSION_INSTALL_REFERENCE). I think I can pass null on initial installation of a shared DLL assembly into the cache. I guess I'm meant to use it when I install an application that uses a shared DLL assembly (repeating the disk location of the DLL and adding the disk location of the EXE??).

One clueless point: once I have the IAssemblyCache, how do I get the InstallAssembly reference? You've used 4 (QueryAssemblyInfo) and 2 (Release); how do I check 7 is InstallAssembly? (If I just count through IUnknown and IAssemblyCache, I reckon it's 6, rather than 7. Does that mean I have a clue after all? ;) 3 for UninstallAssembly?)

If I can get it working, it should be nicer than what I've got at the moment.


if you want to pass 0 for FUSION_INSTALL_REFERENCE and it works, I'm sure that's fine, I don't even know what its for, maybe for uninstall tracking or something

the vtable offset starts @ 0, I counted to 7 based on https://svn.origo.ethz.ch/scoop/es_s...clude/fusion.h Note that MSDN does not display the methods in vtable order on "90%" of the COM interface pages, when they are in order, the table looks different, I have only seen it a couple of times


Ah, I see -- a sneaky "CreateAssemblyScavenger" (Reserved for internal use by the fusion technology), which wasn't listed on the page I looked on. OK, thanks - that's helped some more. I'll start hacking code when I get some time.


I've tried to replicate your WinSxS QueryAssemblyInfo to check my understanding. However, it fails EINVALIDARG, so I'm clearly missing something! Could you have a look at the attached and see if you can explain what's wrong?

Thanks!


I'm getting ERROR_SXS_INVALID_ASSEMBLY_IDENTITY_ATTRIBUTE, so 'Accessibility,Version="2.0.0.0",Culture="*",PublicKeyToken="b03f5f7f11d50a3a",processorArchitecture="MSIL"' is probably wrong, using 'Microsoft.VC80.CRT,version="8.0.50727.42",type="win32",processorArchitecture="x86",publicKeyToken="1fc8b3b9a1e18e3b"' works fine on my system

It crashes later on when you extract from the struct because you used just a . in some places, when you don't need a param, you MUST specify the type (i,l,etc) so it knows the size and gets the correct offset

last thing, IntCmpU $0 0x80070057 Match NoMatch is broken, remember, IntCmpU has 3 jumps and my error was 0x800736C1

if you wanted to check errors like that, you should use a switch with the known errors and a default case for the ones you don't handle


Its very picky about that stupid string, PublicKeyToken will not work, must be publicKeyToken. no space after "," so 'foo="bar",bar="baz"' will work, 'foo="bar", bar="baz"' will not etc. But the strings in the registry or from gacutil.exe /lr seems to be in pretty version


fighting the system plugin is never fun, maybe you could get it working in C# first, base it on http://blogs.msdn.com/junfeng/articles/229649.aspx or whatever

Or maybe use MSI, its probably a easy thing to do, or maybe thats just the .NET/XML/registry combo bloat hater in me talking


OK, I've spotted all the System:: bugs and got it working with your example.

However, this is searching in $windir\WinSxS. I'm expecting my assemblies to be in $windir\assembly (based on where gacutil puts them), so I don't think it's doing the same thing.

Here's the test code, anyway.

BTW, I never could get "ERROR_SXS_INVALID_ASSEMBLY_IDENTITY_ATTRIBUTE" -- only "EINVALIDARGS". Simply changing the assembly name (in my test) from "VC90" to "VC91" caused this. Am I looking in the wrong place -- is there some extended error information?


I've given up on the idea of installing to GAC. I'm going to try to generate a .Config file that can be used by apps that want to use the .DLLs. /sigh/ I hate being beaten.