views:

164

answers:

4

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:

  1. Download new version in temp dir
  2. Restart itself under admin rights
  3. Rename old exe-file and copy new exe-file from temp dir
  4. Restart itself under non-admin rights
+1  A: 

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:

  1. Check for new version and download if necessary
  2. Alert user that a new version is available and request elevation
  3. Rename / copy action
  4. Restart normally

There is no restart necessary for requesting elevation. You might want to still use this way when working on pre-Vista environments.

Roald van Doorn
My program reinstall itself. I don't want distribute any additional programs for it. So my steps are: 1. Download new version in temp dir.2. Restart itself under admin rights3. Rename old exe-file and copy new exe-file from temp dir.4. Restart itself under non-admin rights
LionSoft
See my edit with suggestions.
Roald van Doorn
> "..request elevation when you need.." - How do you do that?
Sertac Akyuz
>> 4. Restart normallyHOW? It was may question! :)
LionSoft
+3  A: 

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.

Kate Gregory
I know how solve my problem using two program. But it was interesting to me make it in this way. And I just want to know can I run in admin process another process with non-admin rights. If it is impossible - just confirm it.
LionSoft
Launching a non elevated process from an elevated one can be done. Links at http://www.gregcons.com/KateBlog/NonElevatedFromElevatedManagedThisTime.aspx
Kate Gregory
@Kate Gregory - Yes!!!! That's really work!!! Big thanks to U.
LionSoft
@LionSoft, you're welcome. You can help the site work by accepting the answer (look for a checkmark) so others know it worked.
Kate Gregory
A: 

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;
Sertac Akyuz
I have already tried this method, but ShellExecute(0, 'open', PChar(ParamStr(0)), PChar(Params), '', SW_SHOW) don't work propery: in process runned with admin rights this command run process also with admin rights.
LionSoft
@LionSoft - indeed.
Sertac Akyuz
@Sertac Akyuz - "indeed" - This was question or approval? :)
LionSoft
@LionSoft - No questions, it seems that 'ShellExecute' cannot be used for restarting as relegated. Down-vote at will.. :P
Sertac Akyuz
+1  A: 

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;
LionSoft