Run my program asUser
开发者_JAVA技巧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:
- Download new version in temp dir
- Restart itself under admin rights
- Rename old exe-file and copy new exe-file from temp dir
- Restart itself under non-admin rights
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.
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;
I think you are going the wrong way at this. In my opinion you should do one of the following:
- Do the installation actions during the installation of the software and require the installation to have administrator rights
or
- Start as non-administrator and request elevation when you need to perform some actions. That way you don't have to restart the program.
Edit: So the steps would be:
- Check for new version and download if necessary
- Alert user that a new version is available and request elevation
- Rename / copy action
- Restart normally
There is no restart necessary for requesting elevation. You might want to still use this way when working on pre-Vista environments.
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;
精彩评论