问题
I am trying to do something that, now after much banging my head against the screen, am not sure it can be done.
The scenario is as follows:
- a windows network with a domain controller where the normal users of the PC's don't have administrative privileges.
- A program that, when it finds an update (MSI) in a share of the network (UNC path), will run the update.
- Because the user cannot do installations. The update must be run with some other user with admin rights.
The theory is sound but:
- it only works if the admin user is a local administrator in the given PC. I cannot make it work with a domain administrator that does not have a local account in the PC.
I have tried with:
- User impersonation with a token from advapi32.dll LogonUser.
- Process->Start() install providing the Domain administrator user credentials.
- Process->Start() Advertise with the Domain administrator and then Process->start() install with the normal user.
As mentioned above, if the admin user has a Local account in the PC, it will work. However, if I use a Domain administrator, the PC will prompt a UAC screen asking for valid administrator credentials.
If I check with task manager, the process is started with the given credentials. It just doesn't have the rights to install.
Is it possible to do what I am trying here? If not, any way around?
The problem is that the network is administered from a domain and the PC's do not necessarily have the admin user account locally created.
回答1:
As mentioned above, if the admin user has a Local account in the PC, it will work. However, if I use a Domain administrator, the PC will prompt a UAC screen asking for valid administrator credentials.
What is it you want to happen? In order to impersonate a user, you have to know their password.
The user needs to know the password of the domain administrator either way, so they type it into the Consent dialog, and the install can proceed.
Are you envisioning a system where an application can be run as an administrator without anyone having to enter the domain admin credentials? That is simply not allowed.
Is it possible to do what I am trying here?
i'm not sure what it is you want to have happen here. Which is why i was asking what exactly you want to have happen.
I get the sense that you want to launch a process as an account with Administrative privileges; so this separate process can perform an update. The issue, i gather, is that the logged in user is not an administrator.
I cannot make it work with a domain administrator that does not have a local account in the PC.
What, in particular, are you trying to make work when using a domain administrator? What, precisely do you want to see happen? Are you thinking that that, through code, you can launch an elevated process as a domain user without a UAC prompt appearing? That's not going to happen.
But it is possible
All that being said, what you describe is possible. As Chris Jackson noted in a blog entry the trick is:
- Launch a program as the domain administrator using
CreateProcessWithLogonW
- From that application, use
ShellExecute
with therunas
verb to elevate to an administrator - Perform your updates
I simulated it with a quick Delphi app.
Initially the test app is running as a standard user (named Forest Gump). I detect that he is not an administrator, and show a UAC shield on the Update Software button:
Clicking the button causes the software to launch a copy of itself, but using the credentials of a domain administrator account (me, Ian). This is done using CreateProcessWithLogonW
:
if IsUserAdmin then
begin
//No need for hoops, we're already an admin. Do the update.
PerformUpdate();
Exit;
end;
//Relaunch ourselves as a particular domain admin user.
username := 'ian@redacted.com';
password := 'correct battery horse staple';
applicationName := ParamStr(0);
commandLine := applicationName+' /performUpdates';
ZeroMemory(@si, SizeOf(si));
si.cb := SizeOf(si);
CreateProcessWithLogonW(PWideChar(username), nil, PWideChar(password), 0, PWideChar(applicationName), PWideChar(commandLine), 0, nil, nil, si, {var} pi)
The trick is that we pass the /performUpdates
option. When our app starts up, it detects if it's being instructed to perform updates. If so, we use ShellExecute
with the runas
verb to launch an elevated copy of ourselves:
procedure TForm1.FormActivate(Sender: TObject);
begin
if FindCmdLineSwitch('performUpdates', True) then
begin
//If we're not an admin, then use ShellExecute to launch ourselves as one
if not IsUserAdmin then
begin
//Relaunch ourselves as an admin
Toolkit.RunAsAdmin(0, ParamStr(0), '/performUpdates'); //don't forget to pass the command option
Application.Terminate;
Exit;
end;
//We are an admin; do the updates.
PerformUpdates;
MessageDlg('Update complete.', mtINformation, [mbOk], 0);
Application.Terminate;
Exit;
end;
end;
The ShellExecute
with the runas
verb triggers the UAC elevation prompt, and then the elevated app running as me (Ian), a different admin user with admin privileges appears:
The RunAsAdmin
function is a simple wrapper around ShellExecute
:
function RunAsAdmin(hWnd: HWND; filename: string; Parameters: string): Boolean;
{
See Step 3: Redesign for UAC Compatibility (UAC)
http://msdn.microsoft.com/en-us/library/bb756922.aspx
}
var
sei: TShellExecuteInfo;
begin
ZeroMemory(@sei, SizeOf(sei));
sei.cbSize := SizeOf(TShellExecuteInfo);
sei.Wnd := hwnd;
sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI;
sei.lpVerb := PChar('runas');
sei.lpFile := PChar(Filename); // PAnsiChar;
if parameters <> '' then
sei.lpParameters := PChar(parameters); // PAnsiChar;
sei.nShow := SW_SHOWNORMAL; //Integer;
Result := ShellExecuteEx(@sei);
end;
In syntax is slightly easier, but UseShellExecute and Verb = "runas" are the critical points.
The function IsUserAdmin is much easier inside .NET:
//helper function that tells us if we're already running with administrative rights
private Boolean IsUserAnAdmin()
{
//A user can be a member of the Administrator group, but not an administrator.
//Conversely, the user can be an administrator and not a member of the administrators group.
//Check if the current user has administrative privelages
var identity = WindowsIdentity.GetCurrent();
return (null != identity && new WindowsPrincipal(identity).IsInRole(WindowsBuiltInRole.Administrator));
}
than it is for native code:
function IsUserAdmin: Boolean;
var
b: BOOL;
AdministratorsGroup: PSID;
begin
{
This function returns true if you are currently running with admin privelages.
In Vista and later, if you are non-elevated, this function will return false (you are not running with administrative privelages).
If you *are* running elevated, then IsUserAdmin will return true, as you are running with admin privelages.
Windows provides this similar function in Shell32.IsUserAnAdmin. But the function is depricated, and this code is lifted
from the docs for CheckTokenMembership: http://msdn.microsoft.com/en-us/library/aa376389.aspx
}
{
Routine Description: This routine returns TRUE if the callers
process is a member of the Administrators local group. Caller is NOT
expected to be impersonating anyone and is expected to be able to
open its own process and process token.
Arguments: None.
Return Value:
TRUE - Caller has Administrators local group.
FALSE - Caller does not have Administrators local group.
}
b := AllocateAndInitializeSid(
SECURITY_NT_AUTHORITY,
2, //2 sub-authorities
SECURITY_BUILTIN_DOMAIN_RID, //sub-authority 0
DOMAIN_ALIAS_RID_ADMINS, //sub-authority 1
0, 0, 0, 0, 0, 0, //sub-authorities 2-7 not passed
AdministratorsGroup);
if (b) then
begin
if not CheckTokenMembership(0, AdministratorsGroup, b) then
b := False;
FreeSid(AdministratorsGroup);
end;
Result := b;
end;
So, all that works, and does what you need. But the user will still be presented a UAC Consent dialog; although they will only have to continue. i gather that's not desirable to you. And even if it is:
Don't do it
The critical problem in the above code, and Chris Jackon mentions it in his blog:
The most common request is for people writing home-grown software deployment software, where they’d like to encode credentials right into the app and elevate their own process. The real problem here is not the lack of the API, it’s that you have admin credentials encoded in your app for the world to read. If you have that, what you want is a way to get to a place where you do not have that as quickly as possible, not make it easier to build on that design.
The horrible, horrible, offending code is:
//Relaunch ourselves as a particular domain admin user.
username := 'ian@redacted.com';
password := 'correct battery horse staple';
You've hard-coded the password of a user. That's no good. You don't want that.
You want an administrator to actually be required to walk to the machine and enter his credentials.
Much simpler option
You're in a world where any user is allowed to update the application. If that is true, if all users are allowed to modify the files and registry keys, then let them!
During application install, change the NTFS permissions on your application's folder so that all Users have Full Control:
This is the correct solution to your problem.
Even though none of the above answers your question.
What do you want to see?
And now we come full circle. What do you want to see?
Note: Any code is released into the public domain. No attribution required.
来源:https://stackoverflow.com/questions/19928161/process-start-as-domain-administrator-from-user-started-process-with-uac-activat