How to Run Application Which Requires Admin Rights from One That Doesn't Have Them

How to run application which requires admin rights from one that doesn't have them

Real problem: (from Wikipedia: http://en.wikipedia.org/wiki/User_Account_Control)

An executable that is marked as "requireAdministrator" in its manifest cannot be started from a non-elevated process using CreateProcess(). Instead, ERROR_ELEVATION_REQUIRED will be returned. ShellExecute() or ShellExecuteEx() must be used instead.

(BTW, ERROR_ELEVATION_REQUIRED error == 740)

Solution: (same site)

In a native Win32 application the same "runas" verb can be added to a ShellExecute() or ShellExecuteEx() call.

ShellExecute(hwnd, "runas", "C:\\Windows\\Notepad.exe", 0, 0, SW_SHOWNORMAL);

This may be also helpful: (source: http://mark.koli.ch/2009/12/uac-prompt-from-java-createprocess-error740-the-requested-operation-requires-elevation.html)

2 - Basic UAC Flow

Ok, so before you dig into it, I thought it might be helpful to explain the basic flow of a UAC aware application and how everything fits together. Normally, your application runs as an unprivileged user. But, sometimes it needs to be an Administrator (to do whatever). So, here's the basic idea, in pseudo code:

int main (int argc, char **argv) {

HRESULT operation = tryToDoSomethingPrivileged();

if (operation == ACCESS_DENIED && !alreadyElevated) {

// Spawn a copy of ourselves, via ShellExecuteEx().
// The "runas" verb is important because that's what
// internally triggers Windows to open up a UAC prompt.
HANDLE child = ShellExecuteEx(argc, argv, "runas");

if (child) {
// User accepted UAC prompt (gave permission).
// The unprivileged parent should wait for
// the privileged child to finish.
WaitForSingleObject(child, INFINITE);
CloseHandle(pid);
}
else {
// User rejected UAC prompt.
return FAILURE;
}

return SUCCESS;

}

return SUCCESS;

}

Finally, this is how I've done it:

if(0 == CreateProcess(argv[2], params, NULL, NULL, false, 0, NULL, NULL, &si, &pi)) {
//runas word is a hack to require UAC elevation
ShellExecute(NULL, "runas", argv[2], params, NULL, SW_SHOWNORMAL);
}

And just for completness's sake - MSDN links to ShellExecute and CreateProcess:

http://msdn.microsoft.com/en-us/library/bb762153%28v=vs.85%29.aspx

http://msdn.microsoft.com/en-us/library/ms682425%28VS.85%29.aspx

Are admin rights required for installing an app for all users?

You cannot install apps for all users without certain write privileges since you generally need to modify %ProgramFiles%, the "common" start menu and HKEY_LOCAL_MACHINE to install an app. A normal user cannot write to these locations. A member of the now deprecated power-users group can install most applications for all users even though they are not a full administrator unless the installer specifically checks for administrator rights.

A non-admin user can however install patches to an app installed for all users if the .MSI is signed, this is called User Account Control (UAC) Patching.

Chrome and Firefox use an alternative approach with a Windows service and scheduled tasks. This also requires an administrator to perform the initial installation.

Force a program to run without admin privileges?

I tried to create a small C# app to modify the embedded manifest so it wouldn't request admin privileges. This is the solution I finally came up with, making a bunch of Win32 calls to extract the manifest and replace an existing manifest. It's already long enough, so I omitted the part where I actually modify the manifest (just some basic XML operations).

There are two static methods here: LoadManifestResource, which loads the string representation of the embedded manifest of an executable and SaveManifestResource, which saves the string representation of a manifest resource in the specified executable, overwriting the old one.

This is a quick and dirty solution which worked just fine for me, but might very well not work in every case.

public static class Library
{
[DllImport("kernel32.dll")]
static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hReservedNull, LoadLibraryFlags dwFlags);

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool FreeLibrary(IntPtr hModule);

[DllImport("kernel32.dll")]
static extern IntPtr FindResource(IntPtr hModule, int lpName, int lpType);

[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo);

[DllImport("kernel32.dll")]
static extern IntPtr LockResource(IntPtr hResData);

[DllImport("Kernel32.dll", EntryPoint = "SizeofResource", SetLastError = true)]
private static extern uint SizeofResource(IntPtr hModule, IntPtr hResource);

[System.Flags]
enum LoadLibraryFlags : uint
{
DONT_RESOLVE_DLL_REFERENCES = 0x00000001,
LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010,
LOAD_LIBRARY_AS_DATAFILE = 0x00000002,
LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 0x00000040,
LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020,
LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008
}

public static unsafe string LoadManifestResource(string fileName)
{
// load library to retrieve manifest from
var libraryHandle = LoadLibraryEx(fileName, IntPtr.Zero, LoadLibraryFlags.LOAD_LIBRARY_AS_DATAFILE);
if (libraryHandle.ToInt32() == 0)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "couldn't load library");
}
try
{
// find manifest
var resource = FindResource(libraryHandle, 1, 24);
if (resource.ToInt32() == 0)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "couldn't find manifest resource");
}

// load manifest
var loadedManifest = LoadResource(libraryHandle, resource);
if (loadedManifest.ToInt32() == 0)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "couldn't load manifest resource");
}

// lock manifest
var lockedManifest = LockResource(loadedManifest);
if (lockedManifest.ToInt32() == 0)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "couldn't lock manifest resource");
}

// calculate size of manifest, copy to byte array and convert to string
int manifestSize = (int)SizeofResource(libraryHandle, resource);

byte[] data = new byte[manifestSize];
Marshal.Copy(lockedManifest, data, 0, manifestSize);
var manifest = Encoding.UTF8.GetString(data);

return manifest;
}
finally
{
FreeLibrary(libraryHandle);
}
}

[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr BeginUpdateResource(string pFileName,
[MarshalAs(UnmanagedType.Bool)]bool bDeleteExistingResources);

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool UpdateResource(IntPtr hUpdate, string lpType, string lpName, ushort wLanguage, IntPtr lpData, uint cbData);

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool UpdateResource(IntPtr hUpdate, int lpType, int lpName, ushort wLanguage, IntPtr lpData, uint cbData);

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool EndUpdateResource(IntPtr hUpdate, bool fDiscard);

public static unsafe void SaveManifestResource(string file, string manifest)
{
var hUpdate = BeginUpdateResource(file, false);

byte[] bytes = Encoding.UTF8.GetBytes(manifest);
IntPtr ptr = Marshal.AllocHGlobal(bytes.Length);
try
{
Marshal.Copy(bytes, 0, ptr, bytes.Length);

if (!UpdateResource(hUpdate, 24, 1, 0, ptr, (uint)bytes.Length))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}

if (!EndUpdateResource(hUpdate, false))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
finally
{
Marshal.FreeHGlobal(ptr);
}
}
}

How do we create an installer than doesn't require administrator permissions?

ClickOnce is a good solution to this problem. If you go to Project Properties > Publish, you can setup settings for this. In particular, "Install Mode and Settings" is good to look at:

  • The application is available online only -- this is effectively a "run once" application
  • The application is avaiable offline as well (launchable from Start Menu) -- this installs the app on the PC

You don't actually have to use the ClickOnce web deployment stuff. If you do a Build > Publish, and then zip up the contents of the publish\ folder, you can effectively distribute that as an installer. To make it even smoother, create a self-extracting archive from the folder that automatically runs the setup.exe file.

Even if you install this way, if you opt to use it, the online update will still work for the application. All you have to do is put the ClickOnce files online, and put the URL in the project's Publish properties page.

Is it possible for the executable to ask for Administrator rights? (Windows 7)

You cannot acquire elevated privileges after the process has started. Your options are:

  1. Put the part of your application that requires elevated privileges into a separate process and manifest that with requireAdministrator.
  2. Run the part of your application that requires elevated privileges as an out-of-proc COM object.

How to start a new process without administrator privileges from a process with administrator privileges?

What you are trying to achieve cannot be done very easily and is not supported. However, it is possible using a modicum of hacking. Aaron Margosis wrote an article describing one technique.

To quote the pertinent section, you will need to carry out these steps:

  1. Enable the SeIncreaseQuotaPrivilege in your current token
  2. Get an HWND representing the desktop shell (GetShellWindow)
  3. Get the Process ID (PID) of the process associated with that window (GetWindowThreadProcessId)
  4. Open that process (OpenProcess)
  5. Get the access token from that process (OpenProcessToken)
  6. Make a primary token with that token (DuplicateTokenEx)
  7. Start the new process with that primary token (CreateProcessWithTokenW)

The article contains a download link for some demo C++ source from which it should be simple enough to translate to C#.

Visual Studio 2015 requires admin rights

Yes, this is a bug in VS2013. The problem is that your program is running with the wrong manifest. It uses the manifest that is embedded in the yourapp.vshost.exe file, it doesn't match the manifest you created. The bug was indeed fixed in VS2015. There are several bug reports about it, this one is probably best. Big blunder, an agile problem, and not addressed in the updates.

It has a simple workaround, beyond elevating VS up front, use Project > Properties > Debugging and untick the "Enable the Visual Studio hosting process" checkbox. Now the manifest embedded in your EXE is active and VS knows to prompt you to elevate. This setting is not critical unless you have a very unusual setup, like building to an untrusted network share.



Related Topics



Leave a reply



Submit