2

I have a small command that allows me to query information from CMD, however it needs admin privileges (not at my application level but at the CMD level) Only reason I am going this route is because I couldn't get WMI to query bitlocker settings for the life of me and this project needs to get off of my desk.

if (bitA.Text == "Bitlocker Available")
{               
    Process cmd2 = new Process();
    cmd2.StartInfo.Verb = "runas";
    cmd2.StartInfo.FileName = "cmd.exe";
    cmd2.StartInfo.Arguments = "/c ping 8.8.8.8";
    cmd2.StartInfo.UseShellExecute = false;
    cmd2.StartInfo.RedirectStandardOutput = true;
    cmd2.StartInfo.RedirectStandardError = true;
    cmd2.Start();
    //* Read the output (or the error)
    string output2 = cmd2.StandardOutput.ReadToEnd();
    bitB.Text = output2;
    cmd2.WaitForExit();
}
David L
  • 32,885
  • 8
  • 62
  • 93
Joe Pearson
  • 149
  • 1
  • 2
  • 13
  • 2
    Have you tried running the application as an admin? That may enable CMD to run as an admin (under the running account's priveledges). – Tim Jun 04 '15 at 22:05
  • 2
    The issue is that UseShellExecute must be true for you to elevate. However UseShellExecute must be false to redirect. Therefore, you cannot elevate and redirect process output at the same time. You need another IPC mechanism. For some arbitrary command you will probably have to redirect output to a file and then read that in your calling process. – Mike Zboray Jun 04 '15 at 22:15
  • possible duplicate of [Running CMD as administrator with an argument from C#](http://stackoverflow.com/questions/16529713/running-cmd-as-administrator-with-an-argument-from-c-sharp) – Ken White Jun 04 '15 at 22:20
  • Did you ever ask a question about how to do the WMI bitlocker lookup on Stack Overflow, showing what you tried and what error you got? – Scott Chamberlain Jun 04 '15 at 22:37
  • 1
    Why does everyone always use `FileName = "cmd.exe"` then `Arguments = "/c Whatever.exe whateverArg"` as the arguments, why not just do `FileName = "Whatever.exe"` and `Arguments = "whateverArg"`? – Scott Chamberlain Jun 04 '15 at 22:49

3 Answers3

2

You can indicate the new process should be started with elevated permissions by setting the Verb property of your startInfo object to 'runas', as follows:

startInfo.Verb = "runas";

This will cause Windows to behave as if the process has been started from Explorer with the "Run as Administrator" menu command. The user will be prompted with the UAC to confirm they want to do it.

Edit: I see you have that verb set already. Are you asking if you can circumvent the UAC? That would be kind of silly, otherwise virus-writers and so forth could get around the security check with one line of code.

Cortright
  • 1,164
  • 6
  • 19
  • 2
    You must have UseShellExecute set to true when using the runas verb, however this prevents you from redirecting as the OP desires. – Mike Zboray Jun 04 '15 at 22:17
1

As I said in my comment, the issue is that the "runas" verb requires UseShellExecute to be true, but redirection requires UseShellExecute to be false. This makes the problem sort of tricky, but the key is start a process as admin that you can communicate with via some kind of IPC, then this process starts whatever process you want to redirect output from. It can even be the same executable just switching on the arguments received. If I was writing a library, I would probably embed a shim executable as an assembly resource. Here is a simple example using named pipes:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using System.IO.Pipes;
using System.IO;

namespace AdminRedirect
{
    class Program
    {
        private static readonly int ProcessId = Process.GetCurrentProcess().Id;

        static void Main(string[] args)
        {
            bool isAdmin = IsAdministrator();
            Console.WriteLine("Id = {0}, IsAdmin = {1}", ProcessId, isAdmin);
            if (!isAdmin)
            {
                Console.WriteLine("Press any key to spawn the admin process");
                Console.ReadKey(intercept: true);
                string pipeName = "mypipe-" + Guid.NewGuid();
                Process cmd = new Process()
                {
                    StartInfo =
                    {
                        Verb = "runas",
                        Arguments = pipeName,
                        FileName = typeof(Program).Assembly.Location,
                        UseShellExecute = true
                    }
                };

                using (var pipeStream = new NamedPipeServerStream(pipeName))
                {
                    cmd.Start();
                    Console.WriteLine("Started {0}", cmd.Id);
                    pipeStream.WaitForConnection();
                    Console.WriteLine("Received connection from {0}", cmd.Id);
                    using (var reader = new StreamReader(pipeStream))
                    {
                        string line;
                        while((line = reader.ReadLine()) != null)
                        {
                            Console.WriteLine(line);
                        }
                    }                
                }

                Console.WriteLine("Hit any key to end");
                Console.ReadKey(intercept: true);
            }
            else
            {
                if (args.Length > 0)
                {
                    string pipeName = args[0];
                    Console.WriteLine("Opening client pipe: {0}", pipeName);
                    using (var pipeStream = new NamedPipeClientStream(pipeName))
                    {
                        pipeStream.Connect();
                        using (var writer = new StreamWriter(pipeStream))
                        {
                            StartChildProcess(writer);
                        }
                    }
                }
                else
                {
                    Console.WriteLine("We are admin and not piping, so just run");
                    StartChildProcess(Console.Out);
                    Console.WriteLine("Hit any key to end");
                    Console.ReadKey(intercept: true);
                }
            }
        }

        private static bool IsAdministrator()
        {
            WindowsIdentity identity = WindowsIdentity.GetCurrent();
            WindowsPrincipal principal = new WindowsPrincipal(identity);
            return principal.IsInRole(WindowsBuiltInRole.Administrator);
        }

        private static void StartChildProcess(TextWriter output)
        {
            var cmd = new Process()
            {
                StartInfo =
                {
                    FileName = "cmd.exe",
                    Arguments = "/c ping 8.8.8.8",
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true
                }
            };
            cmd.Start();
            string procOutput = cmd.StandardOutput.ReadToEnd();
            output.Write("Id: {0}, Output:{1}", cmd.Id, procOutput);
        }
    }
}
Mike Zboray
  • 39,828
  • 3
  • 90
  • 122
0

To use runas you must have UseShellExecute be true, however to use any of the redirect methods you must have UseShellExecute be false. You can not have both at the same time.

If you need to redirect the output and elevate at the same time you must do the following steps:

  1. Start another process you control1 using runas and UseShellExecute = true to generate a elevated process.
  2. The new process starts ping.exe with UseShellExecute = false and redirects the outputs.
  3. Use a form of IPC, like WCF over named pipes, to forward the output from your elevated 2nd process to your non elevated first process.

1: it could be your same EXE but passing some special command line arguments to put it in this new mode

Community
  • 1
  • 1
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431