1

I have a winforms app that installs other apps in a loop. This works properly on an administrator account in Windows 7, but I have serious issues in a standard account - the app requires elevation in order to write to "Program Files(x86)" folder.

Therefore I am trying to ask for elevation for a specific method (the one that runs the installers) in a winforms c# app, using this code:

[System.Security.Permissions.PrincipalPermission(System.Security.Permissions.SecurityAction.Demand, Role = @"BUILTIN\Administrators")]

After receiving an error, I learned from the web that before calling the method which carries the above attribute, I need to write this:

AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);

I did this, and the method still throws the following error:

Request for principal permission failed.

Step by step debugging passes the SetPrincipalPolicy line but, when it reaches the method with the Demand atribute, it just throws the same error, as if the SetPrincipalPolicy never existed.

Am I doing something wrong in setting the Demand attribute properly?

Thank you in advance.

LATER EDIT: as requested here is the code that is supposed to trigger the elevation request when installing the app silently (but does not work):

 WindowsPrincipal principal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
        bool hasAdministrativeRight = principal.IsInRole(WindowsBuiltInRole.Administrator);
        if (!hasAdministrativeRight)
        {
            ProcessStartInfo psi = new ProcessStartInfo(file);
            psi.WindowStyle = ProcessWindowStyle.Hidden;
            psi.UseShellExecute = true;
            psi.Verb = "runas";

            //psi.CreateNoWindow = true;
            psi.Arguments = modifiers;
            try
            {
                using (Process process = Process.Start(psi))
                {
                    process.WaitForExit();
                    if (process.HasExited)
                        return process.ExitCode;
                }
            }
            catch (Win32Exception wex)
            {

            }
        }

What I need, is for that process to pop a dialog asking for username and password for admin, if the app was ran under a Windows Standard User. Only the process started programmatically above should run as admin, the main app itself can remain as a standard user.

Amc_rtty
  • 3,221
  • 9
  • 40
  • 69
  • You need to do something so the user can enter an administrator password. – MrFox Aug 13 '11 at 21:32
  • Thanks - are you suggesting I should not use the above code at all, with the attribute? Also regarding your comment I have read about the Consent UI which asks for the username and password, but could not find out anywhere how to call that - if you could point me to a link I would much appreciate – Amc_rtty Aug 13 '11 at 21:52
  • 1
    Should `process.HasExited` always be true after `process.WaitForExit();`? – Oskar Kjellin Aug 14 '11 at 12:12
  • True, I think that is redundant in there. I will remove HasExited. – Amc_rtty Aug 14 '11 at 12:33

2 Answers2

3

This is just not the way UAC works. It is process based, the user only ever gets the "please let me mess with your machine" prompt when you start a new process. With the proper incantation of "I need the user's consent to mess with the machine, please say Yes" signal embedded in the program. Which you do by this answer.

Death to the idea of making it method based. Unreasonable to a programmer, makes sense to a user. User wins.

Community
  • 1
  • 1
Hans Passant
  • 873,011
  • 131
  • 1,552
  • 2,371
  • Thank you very much for your insight. If you are suggesting that I should include a manifest to make the whole app run with admin rights from the start - isn't that unsafe to do? Also could you tell me a reason why I should not do this method based? Please elaborate on how it is unreasonable. – Amc_rtty Aug 13 '11 at 22:27
  • 1
    @Andrei: He's not saying that you "should not" do it for individual methods, he's saying that you *cannot* do it for individual methods. User Account Control (or UAC), the mechanism you're trying to outsmart here, is *process-based*, not method-based. As far as what you *should* do, you shouldn't be doing this at all. Standard applications *never* need to write to the system directories; they're protected for a reason. Yes, it's unsafe to grant a process access to these directories. That's why you shouldn't do it unless absolutely required. – Cody Gray Aug 14 '11 at 12:20
  • Thank you for your comment I understand now. – Amc_rtty Aug 14 '11 at 12:32
2

You can either force your app to always run as an admin. This is how you do that. It is not recommended however for your app to need admin privileges to run.

If you start a Process to run the installer, you can check here how to run the process as an admin.

A third option which Visual Studio uses is that when you do something where you need admin privileges you are prompted to restart the app and it then restarts the app as an admin and you can perform the tasks. Just use the code from the second way to start your app.

The method you've posted to run as admin will check if the user is admin and then start the process as an admin. If the user doesn't have admin rights the app won't even start. A better solution is to always try to run the process as an admin. Then the user will get an UAC prompt with password and username, which an admin can fill in.

public static int RunAsAdmin(string fileName)
{

        ProcessStartInfo psi = new ProcessStartInfo(fileName);
        psi.WindowStyle = ProcessWindowStyle.Hidden;
        psi.UseShellExecute = true;
        psi.Verb = "runas";
        psi.Arguments = modifiers;

        try
        {
            using (Process process = Process.Start(psi))
            {
                process.WaitForExit();
                if (process.HasExited)
                    return process.ExitCode;
            }
        }
        catch (Win32Exception wex)
        {

        }

    return 0;
}
Oskar Kjellin
  • 19,439
  • 10
  • 50
  • 92
  • Indeed, I start a process to run the installer. I have used ProcessStartInfoVerb = runas, as the link suggests, however with no success - my process still ran as a standard user. Maybe I was doing it wrong - when I use the runas option, do I also have to pop the Consent UI, to ask for the username and password? Thank you – Amc_rtty Aug 13 '11 at 22:36
  • @Andrei are you using ShellExecute as well? – Oskar Kjellin Aug 14 '11 at 11:25
  • I am using the attribute UseShellExecute = true as per instructions in the link you have provided (the one with starting a process). – Amc_rtty Aug 14 '11 at 11:29
  • I have posted, please see edit in the first post. Also my intent is to not restart the main app, but only to run the process that is created programmatically as admin. The main app itself can run as standard user. Thank you – Amc_rtty Aug 14 '11 at 11:56
  • Sorry I may miss something obvious here, but as I see now, the only difference between what you wrote and what I wrote is that you ommited the arguments. Does that make the difference? The thing is that I need the arguments also to be passed to the process. – Amc_rtty Aug 14 '11 at 12:12
  • @Andrei you missed something. The arguments should be there, I just missed them. I remove the `if (!hasAdministrativeRight)`. Try it – Oskar Kjellin Aug 14 '11 at 12:16
  • I have tried as you have suggested and also removed the if(!hasAdministrativeRight) check as instructed. I test with notepad++ latest installer on a Windows standard user test account. However I do not get the UAC prompt to enter admin details. The method appears to work as it returns exit code 0; but the application does not install - if I try to run the app as non silent to see what happens, it throws an error when trying to write to Program Files. Thank you for your patience. Probably my problem resides somewhere else. – Amc_rtty Aug 14 '11 at 12:39
  • Your solution worked PERFECTLY. All this time, I had UAC turned off- this is why I have not received the admin login window for that specific process. Now all is left to be done, is to determine programatically if UAC is off, and abort installation if it is. Thank you very much. – Amc_rtty Aug 14 '11 at 14:13
  • 1
    Glad to hear that it worked. As for if UAC is turned off or not (might even be an older OS where UAC does not exist), you could just inform the user that he needs to login as an admin – Oskar Kjellin Aug 14 '11 at 14:58