Link to home
Start Free TrialLog in
Avatar of richtelieu88
richtelieu88Flag for United States of America

asked on

Launch Process with NOT as Administrator

Is it possible, in Windows 10, for an application WITH Administrator Permissions to launch a process WITHOUT Administrator Permissions?

I'm using C#.  To keep the discussion straightforward, let's say I launch Visual Studio with Administrator Permissions (dvenv.exe).  Here is the code:

          System.Diagnostics.Process.Start(@"C:\Windows\System32\cmd.exe");

cmd is always launched with "Run as administrator".

If I launch Visual Studio Not as Admnistrator, cmd is launched Not as Administrator.

Is there a way for dvenv.exe when launched As Administrator to launch cmd NOT as administrator?

Thanks in advance.
Avatar of McKnife
McKnife
Flag of Germany image

Sure, simply use psexec or runas.
You are wanting to de-elevate the child process from the parent process.  One way this can be accomplished is by using Explorer.exe to launch the process:

http://mdb-blog.blogspot.com/2013/01/nsis-lunch-program-as-user-from-uac.html
https://blogs.msdn.microsoft.com/oldnewthing/20131118-00/?p=2643

Unfortunately, this is not a bullet-proof solution as this uses a bug which the Windows Shell team has said will be addressed and may not work in future updates/versions of Windows.

The next viable solution is to use the Username and Password properties of the ProcessStartInfo class in order to specify the credentials to launch the process as.

And one more solution is to use code injection to start your process:

http://www.codeproject.com/Articles/18946/High-elevation-can-be-bad-for-your-application-How

-saige-
Seems like someone created some User Account Control Helper methods.  Because this library was written in 2009, i cannot guarantee the codes current relevance nor reliability.

http://uachelpers.codeplex.com/

-saige-
Avatar of richtelieu88

ASKER

I'm trying:
          ProcessStartInfo info = new ProcessStartInfo();
          info.WorkingDirectory = @"C:\Windows\System32";
          info.FileName = @"cmd.exe";
          info.UseShellExecute = false;
          info.CreateNoWindow = false;
          . . .
          Process whatever = Process.Start( info );

"McKnife", this does not work:
          info.Verb = "run as";

Am I supposed to put something other than simply "run as"?

"it_saige", this doesn't work, probably because I have administrator permissions:
          info.Verb = "runas /user: [MY USERNAME]";

I'll take a look at the other links provided.
Sorry, I don't know c sharp. But since we can launch a program from an elevated command prompt with different (non-admin) credentials using runas or psexec, I am sure, there will be a way.
The "runas" verb tells the process to run as an elevated user, the proper usage is without spaces; e.g. -
ProcessStartInfo info = new ProcessStartInfo() { Verb = "runas" };

Open in new window

This does not help with your request because you want to de-elevate the process, not elevate it.  As for the username and password, these are also properties of the ProcessStartInfo class.  An example of their proper usage is:
ProcessStartInfo info = new ProcessStartInfo() { UserName = "SomeUserName", Password = "SomePassword" };

Open in new window


-saige-
"it_saige",

I am trying your "code injection" recommendation.

It works fine when launching VS NOT As Administrator.

However, when I exit VS, then relaunch VS *AS Administrator*, it does nothing . . . it does not launch cmd.exe.

To see what I'm seeing, create a new C# Windows application project.

Code the Form's Load() function as:

      private void Form1_Load(object sender, EventArgs e)
        {
                string sVista, sCmd;
                string sTotal;

                sVista = @"C:\[LOCATION OF YOUR NEW WINDOWS C# PROJECT]\WindowsFormsApplication1\bin\Debug\VistaLib32.dll";

                sCmd = @"C:\WINDOWS\system32\cmd.exe";

                sTotal = [DOUBLE QUOTE] + sVista + [DOUBLE QUOTE] + "," + "RunNonElevated" + " " + [DOUBLE QUOTE] + sCmd + [DOUBLE QUOTE];

                ProcessStartInfo Prog = new ProcessStartInfo();
                Prog.UseShellExecute = true;
                Prog.FileName = @"C:\WINDOWS\system32\RUNDLL32.EXE";
                Prog.Arguments = sTotal;
                Process p = Process.Start(Prog);

        }

Where:
          [LOCATION OF YOUR NEW WINDOWS C# PROJECT]   is the location of your new C# project.

          [DOUBLE QUOTE] refers to a function I wrote which simply returns a double quote character.

This should result in "sTotal" looking like this in the debugger:

sTotal      "\"C:\\[LOCATION OF YOUR NEW WINDOWS C# PROJECT]\\WindowsFormsApplication1\\bin\\Debug\\VistaLib32.dll\",RunNonElevated \"C:\\WINDOWS\\system32\\cmd.exe\""      string

Then put "VistaLib32.dll" in the same directory as the C# project's "Debug" directory.

Create a class in the C# project called, let's say, "Native.cs".

Put this in "Native.cs":

        [DllImport(@"VistaLib32.dll")]
        public static extern Boolean RunNonElevated(IntPtr hWnd,
                    string pszPath,
                    string pszParameters,
                    string pszDirectory,
                    ref IntPtr phProcess);

Change the Platform Target from "Any CPU" to "x86".

Execute this C# project.

Nothing happens.

If I can get this to work, I'll accept this as the solution.

Please help.
The reason why this fails to work is that you put the file into the build location for an AnyCPU build configuration and then change the configuration to an x86 build.

You can either distribute the dll with your application, by adding it to the project and setting the 'Copy to Output Directory' property to either 'Copy always' or 'Copy if newer' -User generated image
Or you can build your program with the associated dll embedded and have a resolver distribute the needed dll into the application directory.  Something like this:

Create a folder in the project to contain your resources, I named my folder 'Resources' and copied the 'VistaLib32.dll' file into it -User generated image
Include the 'Resources' folder (and it's contents), into your project -User generated image
Select the 'VistaLib32.dll' file and change the 'Build Action' property to 'Embedded Resource' (leave the 'Copy to Output Directory' as 'Do not copy'; yes this is different than above) -User generated image
After you do that, you now need a way to retrieve the embedded resource and save it to the disk;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;

/// <summary>Class EmbeddedAssembly.</summary>
class EmbeddedAssembly
{
	/// <summary>The assembly dictionary</summary>
	static Dictionary<string, byte[]> fAssemblyDictionary = null;

	/// <summary>Load Assembly, DLL from Embedded Resources into memory.</summary>
	/// <param name="resourceString">A <see cref="String"/> value that represents the embedded resource string.</param>
	/// <param name="fileName">A <see cref="String"/> value that represents the file name of the resource.</param>
	/// <example>EmbeddedAssembly.Load("WindowsFormsApplication1.SomeTools.dll", "SomeTools.dll")</example>
	public static void Load(string resourceString, string fileName)
	{
		Console.WriteLine("Attempting to load the following embedded resource {0} ({1}).", fileName, resourceString);
		if (fAssemblyDictionary == null)
			fAssemblyDictionary = new Dictionary<string, byte[]>(StringComparer.OrdinalIgnoreCase);

		byte[] buffer = null;
		var current = Assembly.GetExecutingAssembly();

		foreach (var name in current.GetManifestResourceNames())
			Console.WriteLine("Embedded resource {0} of '{1}'.", name, current.GetName());

		using (var stream = current.GetManifestResourceStream(resourceString))
		{
			// Either the file does not exist or it is not mark as an embedded resource.
			if (stream == null)
			{
				Console.WriteLine("WARNING!!!  Embedded resource {0} ({1}) was not found in '{2}'.", fileName, resourceString, current.GetName());
				return;
			}

			Console.WriteLine("Found embedded resource {0} ({1}) in '{2}'.", fileName, resourceString, current.GetName());
			// Get byte[] from the file from embedded resource
			buffer = new byte[(int)stream.Length];
			stream.Read(buffer, 0, (int)stream.Length);
			try
			{
				Console.WriteLine("Loading embedded resource {0} ({1}) from '{2}'.", fileName, resourceString, current.GetName());
				// Add the assembly/dll into dictionary
				Console.WriteLine("Adding embedded resource {0} ({1}) to local dictionary '{2}'.", fileName, resourceString, current.GetName());
				fAssemblyDictionary.Add(fileName, buffer);
				return;
			}
			// Purposely do nothing.  An unmanaged dll or assembly cannot be loaded directly from byte[].  Let the process fall through for next part
			catch { /* Eat the exception */ ;}
		}
	}

	/// <summary>Saves the specified name.</summary>
	/// <param name="name">The name.</param>
	/// <param name="target">The target.</param>
	/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
	public static bool Save(string name, string target)
	{
		bool results = false;
		try
		{
			if (!Directory.Exists(target))
				Directory.CreateDirectory(target);

			if (fAssemblyDictionary.ContainsKey(name))
				File.WriteAllBytes(Path.Combine(target, name), fAssemblyDictionary[name]);
			results = true;
		}
		catch (Exception)
		{
			results = false;
		}
		return results;
	}
}

Open in new window


Now we can put it all together -

Program.cs -
using System;
using System.IO;
using System.Windows.Forms;

namespace EE_Q28977165
{
	static class Program
	{
		static Program()
		{
			EmbeddedAssembly.Load("EE_Q28977165.Resources.VistaLib32.dll", "VistaLib32.dll");
			EmbeddedAssembly.Save("VistaLib32.dll", Path.GetDirectoryName(Application.ExecutablePath));
		}

		/// <summary>
		/// The main entry point for the application.
		/// </summary>
		[STAThread]
		static void Main()
		{
			Application.EnableVisualStyles();
			Application.SetCompatibleTextRenderingDefault(false);
			Application.Run(new Form1());
		}
	}
}

Open in new window


Form1.cs -
using System;
using System.Diagnostics;
using System.Windows.Forms;

namespace EE_Q28977165
{
	public partial class Form1 : Form
	{
		public Form1()
		{
			InitializeComponent();
		}

		private void OnLoad(object sender, EventArgs e)
		{
			ProcessStartInfo info = new ProcessStartInfo() { Arguments = "VistaLib32.dll,RunNonElevated cmd.exe", FileName = "RUNDLL32.EXE", UseShellExecute = false };
			Process p = Process.Start(info);
		}
	}
}

Open in new window


NativeMethods.cs -
using System;
using System.Runtime.InteropServices;

namespace EE_Q28977165
{
	static class NativeMethods
	{
		[DllImport("VistaLib32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		public static extern bool RunNonElevated(IntPtr hWnd, string pszPath, string pszParameters, string pszDirectory, ref IntPtr phProcess);
	}
}

Open in new window


-saige-
Sorry everybody, I got busy with other work related activity.  I need time to build this potential solution from "it_saige".
it_saige,

I tried your recommendation:
          "You can either distribute the dll with your application, by adding it to the project and setting the 'Copy to Output Directory' property to either 'Copy always' or 'Copy if newer' -"

But, as before, when I launch VS as ADMIN, then call "RunNonElevated" it does nothing; that is, cmd.exe is not launched.  Nothing happens.

(And, like I mentioned, when I launch VS NOT as admin, cmd.exe does get launched just fine, but it is launched as Administrator.  I can see "Administrator" in the cmd's Title Bar.)

I think there is some other underlying problem here.

Please help, anyone.

Thanks in advance.
ASKER CERTIFIED SOLUTION
Avatar of it_saige
it_saige
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
it_saige,

Damn, I hope your code works.  I will try to find time to test it tomorrow.

Thanks
Good work it_saige !
Good job
Glad to be of assistance

-saige-