oleg-shilo / wixsharp

Framework for building a complete MSI or WiX source code by using script files written with C# syntax.
MIT License
1.08k stars 171 forks source link

"Per User" installer, AfterInstall is not elevated. #1567

Closed albertmata closed 1 week ago

albertmata commented 1 month ago

Hi Oleg,

we are moving our old installer project created from a visual studio vdproj to WixSharp.

I run into a problem, the old installer was installing "perUser" and required elevation to execute some custom actions in a .net assembly, and this was working fine, the custom actions executed elevated.

I have created a new ManagedProject, Scope= "perUser" and in the AfterInstall event I do the necessary actions, but I run into the problem that this execution is not elevated ( e.IsElevated == false ).

If I set"perMachine" then the AfterInstall is elevated ( e.IsElevated == true )

Is there any way in a "perUser" installer to run custom actions elevated?

I need it to be "perUser" so that the new msi it generates will do the upgrade of old installs well.

I have tried with ManagedCustomAction but also the same thing happens, they run without elevation when it is "perUser".

oleg-shilo commented 1 month ago

Hi Albert,

Unfortunately, WiX/MSI does not have a mechanism for elevating custom actions. Period. Thus developers have to use an indirect mechanism that is that PerMachine+DeferredAction triggers the elevation.

This trick cannot help you as you need perUser. The only things that is left for you is to:

  1. Ensure that the whole msi session is elevated. See this sample: image

  2. Ensure the routine of your custom action / event is elevated. Right now I do not have a ready-to-go mechanism for that but it should fork an external elevated process. Kinda like in the sample above.

    I am planning to do the release on the weekend and I will see if I can build a QWixSharp tool for that. I would like to achieve syntax like this:

    project.AfterInstall += (SetupEventArgs e) =>
    {
        // ...
    };
    
    project.AfterInstallExecution = EventExecution.ExternalElevatedProcess;
albertmata commented 1 month ago

Hello Oleg, thanks for your quick response.

I will take a look at this sample and will consult next week with my team if this could be an acceptable solution. However, if you finally build this tool to run the custom action as elevated regardless of the scope of the installation, it would be super helpful, please let me know if you manage to do this.

Thanks for creating this library and answering everyone's questions.

oleg-shilo commented 1 month ago

...tool to run the custom action as elevated regardless of the scope of the installation, it would be super helpful, please...

I am optimistic about that. The most difficult part of this work is serialization of a session object that can be transferred to another (elevated) process context. The biggest problem with that that WiX team has designed the Session in such a way that it can have no abstraction whatsoever. You cannot instantiate it. If you manage to bypass this, the object must have a live connection to the unmanaged session object, which is 100% incompatible with serialization and external process context.

But I just solved this problem yesterday. So we are on the projection of making it happen.

albertmata commented 1 month ago

But I just solved this problem yesterday. So we are on the projection of making it happen.

That's good news! Do you think you will have it by the next release?

oleg-shilo commented 1 month ago

I think so

oleg-shilo commented 1 month ago

Yes it will be. See this post

albertmata commented 1 month ago

Hi Oleg,

in the meantime, I've been testing the "RestartElevated" solution, I've copied from your sample. I found that this was working ok when I install, but it didn't work when uninstalling from "Add/Remove programs", the UIInitalized event is not fired when uninstalling.

But I've noticed that the BeforeInstallEvent is fired during uninstall, so I've done a workaround to overcome this issue, here is my code, in case you want to update your sample.

project.BeforeInstall += ( SetupEventArgs e ) =>
{
    if ( e.IsUninstalling && !e.IsElevated )
    {
        e.Result = ActionResult.Failure;

        var startInfo = new ProcessStartInfo();
        startInfo.UseShellExecute = true;
        startInfo.WorkingDirectory = Environment.CurrentDirectory;
        startInfo.FileName = "msiexec.exe";
        startInfo.Arguments = $"/x \"{e.MsiFile}\"";
        startInfo.Verb = "runas";

        Process.Start( startInfo );
    }
};
project.UIInitialized += ( SetupEventArgs e ) =>
{
    if ( e.IsInstalling && !e.IsElevated )
    {
        e.Result = ActionResult.Failure;

        var startInfo = new ProcessStartInfo();
        startInfo.UseShellExecute = true;
        startInfo.WorkingDirectory = Environment.CurrentDirectory;
        startInfo.FileName = "msiexec.exe";
        startInfo.Arguments = $"/i \"{e.MsiFile}\"";
        startInfo.Verb = "runas";

        Process.Start( startInfo );
    }
};
oleg-shilo commented 1 month ago

Done. The latest release v2.2.0 allows controlling the hosting environment of the managed events (custom actions). You can now specify the execution model for all three MSI events:

project.BeforeInstallEventExecution = EventExecution.MsiSessionScopeImmediate; 
project.BeforeInstallEventExecution = EventExecution.MsiSessionScopeDeferred;
project.BeforeInstallEventExecution = EventExecution.ExternalElevatedProcess;

// the same for Load and AfterInstall events

This is the code sample in the repository: https://github.com/oleg-shilo/wixsharp/blob/wix-v4-master/Source/src/WixSharp.Samples/Wix%23%20Samples/InstallEventElevation/setup.cs

albertmata commented 1 month ago

Thank you very much Oleg, much appreciated.

I've upgraded the nuget package to 2.2.0 and tested it but when it compiles the msi an error is shown: (my path)\bin\WixSharp.MsiEventHost.exe is not found. Elevated events will not be executed.

I've looked at the packages\WixSharp_wix4.bin.2.2.0\lib folder and I don't see this exe file, maybe you forgot to add it to the nuget package?

oleg-shilo commented 1 month ago

Oh my, Sorry. You are right. Accidently it was not included in the package. Will fix it asap.

oleg-shilo commented 1 month ago

Done. Please update the package. It was a packaging problem that is now addressed by publishing a new package version for the already released binaries.

NuGet package v2.2.1 contains WixSharp v2.2.0 binaries. The release notes are updated to reflect this fact.

albertmata commented 1 month ago

Now it compiles the msi ok, however in my tests the installation has failed, it seems that if you use the session object in the custom actions it throws an exception:

Calling custom action WixSharp!WixSharp.ManagedProjectActions.WixSharp_AfterInstall_Action
WixSharp aborted the session because of the error:
System.Exception: WixToolset.Dtf.WindowsInstaller.InstallerException: Se produjo una excepción de tipo 'WixToolset.Dtf.WindowsInstaller.InstallerException'.
   en WixToolset.Dtf.WindowsInstaller.Session.Message(InstallMessage messageType, Record record) en <hidden content>
   en WixToolset.Dtf.WindowsInstaller.Session.Log(String msg) en <hidden content>
   en WixSharp.ManagedProject.InvokeClientHandlersInternally(Session session, String eventName, IShellView UIShell)
   en MsiEventHost.Program.Main(String args)
   en WixSharp.ManagedProject.InvokeClientHandlersExternally(Session session, String eventName)
CustomAction WixSharp_AfterInstall_Action returned actual error code 1603 (note this may not be 100% accurate if translation happened inside sandbox)

I've tried removing the calls to the session.Log inside my custom action but it also fails, I use the sesion object also to retrieve the INSTALLDIR from CustomActionData or directly from session object.

oleg-shilo commented 1 month ago

All elevated CAs, regardless of whether either via an external event host or by making it deferred, are executed outside of MSI session. Thus session object is always disconnected. Thus if you try to access session attributes will always throw an exception.

Note, this is MSI/WiX limitation and it's not connected any WixSharp functionality.

Though WixSharp goes the extra mile and preserves some of the session properties for accessing from deferred CA. It is described here within the context of deferred actions.

Thus you can access the properties by using an alternative syntax (extension method):

var test = session.Property("TEST_PROPERTY");

You can easily check what properties are available for your action by displaying them all from the elevated event:

project.AfterInstall += msi_AfterInstall;
...
static void msi_AfterInstall(SetupEventArgs e)
{
    MessageBox.Show(e.ToString(), "AfterExecute");
}
oleg-shilo commented 1 month ago

I have added an extra wiki section to describe the new event execution model: https://github.com/oleg-shilo/wixsharp/wiki/Managed-Setup-Model#elevating-msi-events