Reputation: 1509
Windows 7, Vista, Server 2008, UAC is activated
Program must be stated with admin rights to make some installation actions. After that I want my program to continue work with non-admin rights.
How can I restart it with not administrative rights?
P.S.
My program reinstall itself. I don't want distribute any additional programs for it. So my steps are:
Upvotes: 2
Views: 1399
Reputation: 1509
Thanx to Kate Gregory for help.
There is a working code on Delphi:
function RunAsUser(CommandLine, WorkDirectory: string; Wait: Boolean): Boolean;
const
TOKEN_ADJUST_SESSIONID = $0100;
dwTokenRights = TOKEN_QUERY or TOKEN_ASSIGN_PRIMARY or TOKEN_DUPLICATE or TOKEN_ADJUST_DEFAULT or TOKEN_ADJUST_SESSIONID;
var
WExe, WCmdLine, wCurrDir: WideString;
hProcessToken, dwLastErr, retLength, hwnd, dwPID, hShellProcess, hShellProcessToken, hPrimaryToken: Cardinal;
tkp: TOKEN_PRIVILEGES;
PI: TProcessInformation;
SI: TStartupInfoW;
begin
Result:= False;
hShellProcessToken:= 0;
hPrimaryToken:= 0;
hShellProcess:= 0;
if WorkDirectory = '' then WorkDirectory:= GetCurrentDir;
Wexe:= SeparateText(CommandLine, ' ');
WCmdLine:= CommandLine;
wCurrDir:= WorkDirectory;
// Enable SeIncreaseQuotaPrivilege in this process. (This won't work if current process is not elevated.)
if not OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, hProcessToken) then Exit;
tkp.PrivilegeCount:= 1;
LookupPrivilegeValueW(nil, SE_INCREASE_QUOTA_NAME, tkp.Privileges[0].Luid);
tkp.Privileges[0].Attributes:= SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hProcessToken, FALSE, tkp, 0, nil, retLength);
dwLastErr:= GetLastError();
CloseHandle(hProcessToken);
if (dwLastErr <> ERROR_SUCCESS) then Exit;
// Get an HWND representing the desktop shell.
// CAVEATS: This will fail if the shell is not running (crashed or terminated), or the default shell has been
// replaced with a custom shell. This also won't return what you probably want if Explorer has been terminated and
// restarted elevated.
hwnd:= GetShellWindow();
if hwnd = 0 then Exit;
// Get the PID of the desktop shell process.
GetWindowThreadProcessId(hwnd, dwPID);
if dwPID = 0 then Exit;
// Open the desktop shell process in order to query it (get the token)
hShellProcess:= OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwPID);
if hShellProcess = 0 then Exit;
// From this point down, we have handles to close, so make sure to clean up.
try
// Get the process token of the desktop shell.
if not OpenProcessToken(hShellProcess, TOKEN_DUPLICATE, hShellProcessToken) then Exit;
// Duplicate the shell's process token to get a primary token.
// Based on experimentation, this is the minimal set of rights required for CreateProcessWithTokenW (contrary to current documentation).
if not DuplicateTokenEx(hShellProcessToken, dwTokenRights, nil, SecurityImpersonation, TokenPrimary, hPrimaryToken) then Exit;
SI.cb:= SizeOf(SI);
FillChar(SI, SI.cb, 0);
SI.wShowWindow:= SW_SHOWNORMAL;
SI.dwFlags:= STARTF_USESHOWWINDOW;
// Start the target process with the new token.
Result:= CreateProcessWithTokenW(
hPrimaryToken,
0,
PWideChar(WExe),
PWideChar(wCmdLine),
0,
nil,
PWideChar(wCurrDir),
@si,
@pi);
if not Result then Exit;
if Wait then
while MsgWaitForMultipleObjects(1, PI.hProcess, False, INFINITE, QS_ALLINPUT) <> WAIT_OBJECT_0 do
ProcessMessages;
CloseHandle(PI.hProcess);
finally
// Clean up resources
CloseHandle(hShellProcessToken);
CloseHandle(hPrimaryToken);
CloseHandle(hShellProcess);
end;
end;
Upvotes: 4
Reputation: 54812
Here's a simple restart method;
procedure Restart(RunAs: Boolean);
var
i: Integer;
Params: string;
begin
// Close handle to Mutex or any such thing if only one inst. is allowed
// Prepare to re-pass parameters if the application uses them
Params := '';
for i := 1 to ParamCount do
Params := Params + ' "' + ParamStr(i) + '"';
Application.MainForm.Close;
Application.ProcessMessages;
if RunAs then
ShellExecute(0, 'runas', PChar(ParamStr(0)), PChar(Params), '', SW_SHOW)
else
ShellExecute(0, 'open', PChar(ParamStr(0)), PChar(Params), '', SW_SHOW);
end;
Upvotes: 0
Reputation: 1154
I think you are going the wrong way at this. In my opinion you should do one of the following:
or
Edit: So the steps would be:
There is no restart necessary for requesting elevation. You might want to still use this way when working on pre-Vista environments.
Upvotes: 1
Reputation: 18954
Under UAC, doing anything "on first run" is now strongly discouraged. Also, programs that update themselves using a roll-your-own technique will find it more difficult. You say you don't want to distribute additional programs, but under UAC you really have very little choice. Either your whole app runs elevated every time (annoying the user) in case it happens to need to do something administrative, or you split it into two parts, and run one elevated occasionally and the other non elevated all the time.
One way to split it is to write an installer, which elevates, and the regular app, which doesn't. That works for the people who install once, do some things on first run (you move those things to the installer) and then are done. You say your app updates itself. So you need to move that code to a separate exe and put a manifest on that exe that has requireAdministrator. Then your main app will launch (using ShellExecute) the updating exe when there is a new update available.
Upvotes: 4