سؤال

I am writing a C# winform application, that controls a service. I tried to control this service with System.Diagnostics.Process and System.ServiceProcess.ServiceController.

Because of the required AdminRights to make changes at a service and a few post i read about make this happen with the "runas"-verb, I decided to use System.Diagnostics.Process.

System.Diagnostics.ProcessStartInfo startInfo = 
    new System.Diagnostics.ProcessStartInfo();
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.FileName = "cmd.exe";
startInfo.Arguments = "/C net start " + SERVICE_NAME;
startInfo.RedirectStandardError = true; //// <<<<<<<<<<<<<<<<<
startInfo.UseShellExecute = true;
startInfo.Verb = "runas";

Process process = new Process();
process.StartInfo = startInfo;
process.ErrorDataReceived += cmd_DataReceived;
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();

But it seems that the runas-Verb and the redirection of the StandardErrorOutput doesn't work. It works if I comment out the line, but I need this to decide if the command was executed successfully or not.

Is there a way how to start/ stop a service (executed with temporary admin rights) and to get a result of success?

هل كانت مفيدة؟

المحلول

Your best option is to start the service yourself, inside your own application.

The problem of course is that your application normally isn't run as an administrator (nor do you want it to be). That means you can't start or stop services. That is why you should temporarily elevate, perform the task needed, then exit.

In other words, launch a second copy of your app, passing a command line option instructing yourself to start the service. The second copy does only that, then exits.

The Code

First we have a button the user can click to start the service. We first check if the user already is an administrator, in which case we don't have to do anything special:

private void button1_Click(object sender, EventArgs e)
{
    //If we're an administrator, then do it
    if (IsUserAnAdmin())
    {
        StartService("bthserv"); //"Bluetooth Support Service" for my sample test code
        return;
    }

    //We're not running as an administrator.
    //Relaunch ourselves as admin, telling us that we want to start the service
    ExecuteAsAdmin(Application.ExecutablePath, "/serviceStart");
}

//helper function that tells us if we're already running with administrative rights
private Boolean IsUserAnAdmin()
{
    //A user can be a member of the Administrator group, but not an administrator.
    //Conversely, the user can be an administrator and not a member of the administrators group.

    //Check if the current user has administrative privelages
    var identity = WindowsIdentity.GetCurrent();
    return (null != identity && new WindowsPrincipal(identity).IsInRole(WindowsBuiltInRole.Administrator));
}

private void ExecuteAsAdmin(string Filename, string Arguments)
{
   //Launch process elevated
   ProcessStartInfo startInfo = new ProcessStartInfo(Filename, Arguments);
   startInfo.Verb = "runas"; //the runas verb is the secret to making UAC prompt come up
   System.Diagnostics.Process.Start(startInfo);
}

Then, during startup, we just need to check if we're being called with the command line option. If so, start the service:

public Form1()
{
    InitializeComponent();

    //Ideally this would be in program.cs, before the call to Application.Run()
    //But that would require me to refactor code out of the Form file, which is overkill for a demo
    if (FindCmdLineSwitch("serviceStart", true))
    {
        StartService("bthserv"); //"Bluetooth Support Service"
        Environment.Exit(0);
    }
}

private bool FindCmdLineSwitch(string Switch, bool IgnoreCase)
{
    foreach (String s  in System.Environment.GetCommandLineArgs())
    {
        if (String.Compare(s, "/" + Switch, IgnoreCase) == 0)
            return true;
        if (String.Compare(s, "-" + Switch, IgnoreCase) == 0)
            return true;
    }
    return false;
}

And finally to start the service:

private void StartService(String ServiceName)
{
    TimeSpan timeout = TimeSpan.FromMilliseconds(30000); //30 seconds

    using (ServiceController service = new ServiceController(ServiceName))
    {
        try
        {
            service.Start();
        }
        catch (Exception e)
        {
            MessageBox.Show(e.Message, "Error starting service");
            return;
        }
        service.WaitForStatus(ServiceControllerStatus.Running, timeout);
    }
}

Extra fancy

You can add arbitrary amounts of fancy code. Detect if the user is not an admin, and if not then use the proper Windows API (BCM_SETSHIELD message) to add a UAC shield to your button:

enter image description here

That way users know to expect UAC to appear; because you followed the Microsoft UI guidelines:

Guidelines

UAC shield icon

Display controls with the UAC shield to indicate that the task requires immediate elevation when UAC is fully enabled, even if UAC isn't currently fully enabled. If all paths of a wizard and page flow require elevation, display the UAC shield at the task's entry point. Proper use of the UAC shield helps users predict when elevation is required.

Bonus Reading

Note: Any code is released into the public domain. No attribution required.

نصائح أخرى

You could use MT.exe which is a utility provided by the framework ( In the SDK sub-directory ) to generate a manifest that forces your application to run in administrator mode. In which case you shouldn't have problem dealing with your protected service. The trade off of course is that ... you're running in admin mode.

mt.exe -manifest "your-path\your-app.exe.manifest" -updateresource:"$(TargetDir)$(TargetName).exe;#1

Hopefully this helps.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top