Wixsharp: Question: GAC + InstallUtil

Created on 28 Jul 2020  路  8Comments  路  Source: oleg-shilo/wixsharp

Hello,
I'm writing a WMI Provider in C#. For this, the way to install it manually is to record it in the GAC and call installutil to have the C# WMI Provider Installer creating the MOF File and compile it.
I've looked at the GAC example and I manage to register the DLL in the GAC.
However when I try to a an ElevatedManagedAction to call Tasks.InstallServic to run InstallUtil it fails.
I'm therefore not sure at which stage of the WiX process the DLL is considered to be registered in the GAC and what parameter should I pass to Tasks.InstallService as a step.
The default comes with InstallFiles, but I'm not sure for that compared to InstallService or other?
Is there an example with GAC then DA somewhere?

question

All 8 comments

What you can do is to us an AfterInstall event (it is already elevated) and spawn the GAC registration process:

```C#

project.Before install += e =>
{
if (e.IsUninstalling)
Process.Start("gacutil", $"-u \"{e.InstallDir.PathJoin("my_asm.dll")}\"");
};

project.AfterInstall += e =>
{
if (e.IsInstalling)
Process.Start("gacutil", $"-i \"{e.InstallDir.PathJoin("my_asm.dll")}\"");
};
```

Just don't forget to add the error handling to the pseudo-code above

AfterInstall seems elevated but not BeforeInstall, am I right?

Correct.
The elevation may have an undesirable side effects so it's not administered unless it's truly needed.

I have just added extension methods to allow an easy elevated execution. But you do not need o waith for the release. You can just add ProcessExtensions class as below and then use in tn both event handlers:

```C#
public static class ProcessExtensions
{
public static Process StartElevated(this string fileName, string args = "")
{
bool isAdmin = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);

    return Process.Start(new ProcessStartInfo
        {
            WorkingDirectory = Path.GetDirectoryName(Path.GetFullPath(fileName)),
            FileName = fileName,
            Arguments = args,
            UseShellExecute = true,
            Verb = isAdmin ? "" : "runas"
        });
    }

}
. . .
project.Before install += e =>
{
if (e.IsUninstalling)
"gacutil".StartElevated($"-u \"{e.InstallDir.PathJoin("my_asm.dll")}\"");
};

project.AfterInstall += e =>
{
if (e.IsInstalling)
"gacutil".StartElevated($"-i \"{e.InstallDir.PathJoin("my_asm.dll")}\"");
};
```

Thanks
I'll try this for installutil in https://github.com/1Dimitri/InventoryWMIProvider/blob/master/ProviderInstaller/Program.cs .

However I didn't find any proper solution to elevate non-external binaries, aka parts of the .NET Code in BeforeInstall when using 3rd party libraries(Example in https://github.com/1Dimitri/VSCELicense/blob/master/VSCELicInstallers/VSCELicense_WixSharpInstaller/Program.cs ).

You can add an ElevartedAction but then you will need to ensure that the action is scheduled properly. Not always easy but doable.

Though there is another not so obvious approach. The assembly that your event handler is implemented in is also an executable. So there is no dependency on any external executable:

```C#
static int Main(string[] args)
{
if (args.FirstOrDefault() == "elevated_before_install_action")
{
try
{
bool isUninstall = args.Contains("-u");
string installDir = args[1];
ElevatedBeforeInstall(installDir, isUninstall);
}
catch(Exception e)
{
return 1; // error
}
return 0;
}

var project = new ManagedProject(. . .
. . . 
project.Before install += e =>
{
    System.Reflection.Assembly
                     .GetExecutingAssembly()
                     .Location
                     .StartElevated($"elevated_before_install_action \"{e.InstallDir}\" {e.IsUninstalling? "-u"}");
 };
 . . .

}

static void ElevatedBeforeInstall(string installDir, bool isUninstall)
{
. . .
}
```

I made some tests about GAC & InstallUtil order

  1. As far as the GAC is concerned, the gacutil installations and uninstallations can be managed using the Assembly tag.
new Dir(@"%ProgramFiles%\Whatever",
                    new Assembly(@"MyAssembly.dll", true,
                        new NativeImage { Platform = NativeImagePlatform.all, Priority = 1 }))
            },
  1. For installutil, there are 2 issues:

    • InstallFiles as a step does not guarantee the assembly has been copied to the GAC. MsiPublishAssemblies is the right step in an ElevatedManagedAction

    • When your assembly is in the GAC, you must either retrieve the path in the GAC down to subfolder whose named is based on the public token, or you can use /AssemblyName with the StrongName. I do not see how to retrieve that property from the WixSharp.Assembly type so I used reflection to get the strong name:

System.Reflection.Assembly asm = System.Reflection.Assembly.LoadFrom(@"MyAssembly.dll");
string strongname = asm.GetName().FullName;

and then passed it as a property:

   project.Properties = new Property[]
                {
                    new Property("StrongName",strongname)
                }

The actions can be carried out as follows:

    Actions = new[] {
                new ElevatedManagedAction(CustomActions.InstallService, Return.check, When.After, Step.MsiPublishAssemblies, Condition.NOT_Installed) {
                    UsesProperties ="StrongName"
                },
                new ElevatedManagedAction(CustomActions.UnInstallService, Return.check, When.Before, Step.MsiUnpublishAssemblies, Condition.BeingUninstalled)
                {
                    UsesProperties = "StrongName"
                }
                }

CustomActions can be defined as:

 public class CustomActions
    {
        [CustomAction]
        public static ActionResult InstallService(Session session)
        {
            return session.HandleErrors(() =>
            {
                     ProcessExtensions.StartInstallUtilElevated(session.Property("StrongName"), true, "/AssemblyName");        
            });
        }

        [CustomAction]
        public static ActionResult UnInstallService(Session session)
        {
            return session.HandleErrors(() =>
            {         
                ProcessExtensions.StartInstallUtilElevated(session.Property("StrongName"),false,"/AssemblyName");
            });
        }
    }

With the ProcessExtensions you publish above used to create StartInstallUtilElevated re-using also the InstallService code from the CommonTasks:

  public static Process StartInstallUtilElevated(string assemblyFilePathOrName, bool isInstalling, string args = null)
        {
            string InstallUtilExePath = System.IO.Path.Combine(Tasks.CurrentFrameworkDirectory, "InstallUtil.exe");
            string InstallUtilArguments = string.Format("{0} {1} \"{2}\"", isInstalling ? "" : "/u", args ?? "", assemblyFilePathOrName);
            return StartElevated(InstallUtilExePath, InstallUtilArguments);
        }

Great.
An excellent solution.

I have also put an extra comment to the sample referencing your solution:

image

For reference, I've created a tag in my WMIProvider project where is (almost) nothing as far as the project goal is concerned.
It just creates a one class WMI Provider returning the version the assembly beneath.
But it includes the complete WixSharp code in a dedicated project ("ProviderInstaller")

https://github.com/1Dimitri/InventoryWMIProvider/releases/tag/1.0.0.1

Was this page helpful?
0 / 5 - 0 ratings

Related issues

meytes picture meytes  路  5Comments

mattias-symphony picture mattias-symphony  路  3Comments

Chrissyoung1223 picture Chrissyoung1223  路  5Comments

ltemimi picture ltemimi  路  5Comments

milhousemanastorm picture milhousemanastorm  路  4Comments