I have some small questions...I have a program that stores a list of users in a database and compares on program startup if the user is in the list or is an administrator before letting them use it. At the moment, the way I'm using to check if the user is an administrator is simply by comparing the username to a string constant called 'ADMINISTRATOR'. Will this work on a non-Engish system? I.E. does Windows use a language specific version of 'administrator'? Or maybe is there an enumerated version of the Admin user that I can use to check with instead of my 'ADMINISTRATOR' string? (you know, just like how Windows folders are enumerated). I'm using Delphi 2009 by the way. Thanks in advance!
-
The administrator name changes, and it can even be disabled on some systems where policies requires that each administrator logins with his/her own account. – Oct 26 '10 at 07:12
-
Good you asked this question! – ChristianWimmer Oct 26 '10 at 09:24
5 Answers
No, don't do it that way. It will surely break. You could get a list of all the groups the user is a member of and check if one of the SIDs is S-1-5-32-544
, which is the SID of the Administrators group. There is a list of well known SIDs. There is also an SID for the original administrator account.
Here is the list:

- 28,337
- 7
- 52
- 74
-
1-1: A simple search of a membership will return an incorrect result in >= Vista because the membership in Administrators is usually restricted in the token. The membership is available but Windows will check the DENY_ONLY_SID flag of the membership in the token and still deny access. Restricted Tokens have been available since Windows 2000 but were rarely used. – ChristianWimmer Oct 26 '10 at 09:15
-
You suggestion is implemented here: http://www.techtricks.com/delphi/isadmin.php is incorrect for >= VISTA (and works on < Vista by accident). CreateRestrictedToken Function: msdn.microsoft.com/en-us/library/aa446583%28VS.85%29.aspx Minimum supported client: Windows 2000 Professional – ChristianWimmer Oct 28 '10 at 19:20
NEWS
In 2010, @ChristianWimmer criticized my coding style. Now, two years afterward, I have to use the function again in my program. So, I decided to improve the coding style of the function.
Overview
I pick out a small portion of my private library for your convenience. To test whether user account of the access token is a member of the local administrator's group, pass WinBuiltinAdministratorsSid
of JwaWinNT
to eWellKnownSidType parameter. Note, it requires JEDI API Libray because Delphi Windows.pas
unit didn't define CreateWellKnownSid()
.
Implementation
//------------------------------------------------------------------------------
// Purpose: Tests whether user account of the access token is a member of the
// specified well known group, and report its elevation type.
// Parameter:
// hToken [in,opt]
// A handle to an access token having TOKEN_QUERY and TOKEN_DUPLICATE
// access. If hToken is 0: if it is an impersonation token, the access token
// of the calling thread is used; otherwise, the access token associated
// with the process is used.
// eWellKnownSidType [in]
// Member of the WELL_KNOWN_SID_TYPE enumeration that specifies what Sid the
// function will identify.
// pDomainSid [in,opt]
// A pointer to a SID that identifies the domain to use when identifying the
// Sid. Pass nil to use the local computer.
// peElevType [out,opt]
// A pointer to a variable that receives the following elevation type of the
// access token:
// - TokenElevationTypeDefault: The access token does not have a linked
// token. This value is reported under Windows prior to Windows Vista.
// - TokenElevationTypeFull: The access token is an elevated token.
// - TokenElevationTypeLimited: The access token is a limited token.
// Return value:
// - True if user account of the access token is a member of the well known
// group specified in eWellKnownSidType parameter.
// - False, otherwise. To get error information, call GetLastError().
// Remarks:
// To test whether user account of the access token is a member of local
// administrators group, pass JwaWinNT.WinBuiltinAdministratorsSid to
// eWellKnownSidType parameter.
// References:
// - How To Determine Whether a Thread Is Running in User Context of
// Local Administrator Account [MSDN]
//------------------------------------------------------------------------------
function Inu_IsMemberOfWellKnownGroup(const hToken: Windows.THandle;
const eWellKnownSidType: JwaWinNT.WELL_KNOWN_SID_TYPE;
const pDomainSid: JwaWinNT.PSID=nil;
peElevType: PTokenElevationType=nil): Boolean;
var
hAccessToken: Windows.THandle;
rOSVerInfo: Windows.OSVERSIONINFO;
eTET: Windows.TTokenElevationType;
iReturnLen: Windows.DWORD;
hTokenToCheck: Windows.THandle;
iSidLen: Windows.DWORD;
pGroupSid: JwaWinNT.PSID;
bMemberOfWellKnownGroup: Windows.BOOL;
begin
Result := False;
hAccessToken := 0;
hTokenToCheck := 0;
pGroupSid := nil;
try
if hToken = 0 then begin // If the caller doesn't supply a token handle,
// Get the calling thread's access token
if not Windows.OpenThreadToken(Windows.GetCurrentThread(),
Windows.TOKEN_QUERY or Windows.TOKEN_DUPLICATE,
True, hAccessToken) then begin
if Windows.GetLastError() <> Windows.ERROR_NO_TOKEN then
Exit();
// If no thread token exists, retry against process token
if not Windows.OpenProcessToken(Windows.GetCurrentProcess(),
Windows.TOKEN_QUERY or Windows.TOKEN_DUPLICATE, hAccessToken) then
Exit();
end;
end
else // If the caller supplies a token handle,
hAccessToken := hToken;
// Determine whether the system is running Windows Vista or later because
// because they support linked tokens, previous versions don't.
rOSVerInfo.dwOSVersionInfoSize := SizeOf(Windows.OSVERSIONINFO);
if not Windows.GetVersionEx(rOSVerInfo) then
Exit();
if rOSVerInfo.dwMajorVersion >= 6 then begin
// Retrieve information about the elevation level of the access token
if not Windows.GetTokenInformation(hAccessToken,
Windows.TokenElevationType, @eTET,
SizeOf(Windows.TTokenElevationType), iReturnLen) then
Exit();
// If the access token is a limited token, retrieve the linked token
// information from the access token.
if eTET = Windows.TokenElevationTypeLimited then begin
if not Windows.GetTokenInformation(hAccessToken,
Windows.TokenLinkedToken, @hTokenToCheck,
SizeOf(Windows.TTokenLinkedToken), iReturnLen) then
Exit();
end;
// Report the elevation type if it is wanted
if Assigned(peElevType) then
peElevType^ := eTET;
end
else begin // if rOSVerInfo.dwMajorVersion < 6
// There is no concept of elevation prior to Windows Vista
if Assigned(peElevType) then
peElevType^ := Windows.TokenElevationTypeDefault;
end;
// CheckTokenMembership() requires an impersonation token. If we just got a
// linked token, it is already an impersonation token. Otherwise, duplicate
// the original as an impersonation token for CheckTokenMembership().
if (hTokenToCheck = 0) and (not Windows.DuplicateToken(hAccessToken,
Windows.SecurityIdentification, @hTokenToCheck)) then
Exit();
// Allocate enough memory for the longest possible Sid
iSidLen := JwaWinNT.SECURITY_MAX_SID_SIZE;
pGroupSid := JwaWinNT.PSid(Windows.LocalAlloc(Windows.LMEM_FIXED, iSidLen));
if not Assigned(pGroupSid) then
Exit();
// Create a Sid for the predefined alias as specified in eWellKnownSidType
if not JwaWinBase.CreateWellKnownSid(eWellKnownSidType, pDomainSid,
pGroupSid, iSidLen) then
Exit();
// Now, check presence of the created Sid in the user and group Sids of the
// access token. In other words, it determines whether the user is a member
// of the well known group specified in eWellKnownSidType parameter.
if not JwaWinBase.CheckTokenMembership(hTokenToCheck, pGroupSid,
bMemberOfWellKnownGroup) then
Exit();
Result := bMemberOfWellKnownGroup;
finally
// Close the access token handle
if hAccessToken <> 0 then
Windows.CloseHandle(hAccessToken);
// Close the new duplicate token handle if exists
if (hTokenToCheck <> 0) then
Windows.CloseHandle(hTokenToCheck);
// Free the allocated memory for the Sid created by CreateWellKnownSid()
if Assigned(pGroupSid) then
Windows.LocalFree(Windows.HLOCAL(pGroupSid));
end;
end; // endfunction Inu_IsMemberOfWellKnownGroup
//==============================================================================
-
3+1: For using JEDI API -1: for using goto: This is really not necessary here and it seems you converted a c source one to one? It makes the code really hard to read. (I'm a try/finally follower) +1: for adding a lot of comments. You check the return values of LocalFree and CloseHandle! This does not make sense imho. If you already have a result bMemberOfWellKnownGroup then you can just call the free and close handle functions and you can be happy. If they fail then there is a real problem which is disguised nontheless. Usually we had to check every Free and Close call with raiseLastOsError. – ChristianWimmer Oct 26 '10 at 09:11
-
I always use `try..finally/except` in my *pure/mixed* pascal code, but not when dealing with merely Winapi functions because `try..except` only understand its own exceptions. In regard to your `LocalFree()` & `CloseHandle()` comment, IMHO, it's matter of preference. I checked their return values because I might want to know why they fails in a strange circumstance; some people might don't care about it. For `goto`, I never use it in my *pure/mixed* pascal code. I use it sometimes in Winapi code to reduce indentation and a lot of same code repetition, but ONLY in a simple (noncomplex) function. – Vantomex Oct 26 '10 at 10:18
-
I'm not talking about try/except because they are useless in pure API calls. Instead try/finally can be used to close handles or free memory even if you call exit. In this way you don't need "IF" clauses all over your code just for this. Checking CloseHandle and such is only usuable in debug mode or if you take extra effort into it. However, in your code you create an incorrect result even if the result was computed correctly shortly before. – ChristianWimmer Oct 26 '10 at 10:37
-
No, I didn't converted a C source one to one. You can compare my function with my original reference `http://msdn.microsoft.com/en-us/windows/ff420334.aspx`, didn't they much differ? – Vantomex Oct 26 '10 at 10:39
-
@Christian, in pure Winapi code (without any RTL/VCL code), we even don't need a `try/finally` just to ensure everything closed. About checking CloseHandle, I assume you didn't notice that MSDN has many such a code, because by doing that, end-users could also notice closing failure when it happens, so that they can debug it and/or report it to its programmer. BTW, could you mention explicitly where is the incorrect result you meant? – Vantomex Oct 26 '10 at 15:01
-
I don't blame you! I just say that it seems like you converted it from c. The goto exit strategies are really used often in MS c code in MSDN. But try/finally makes the code much more clear to read in Delphi imho. – ChristianWimmer Oct 26 '10 at 15:07
-
1if not CloseHandle(hAccessToken) then Exit(); Result := bMemberOfWellKnownGroup; This CloseHandle will alter the result value of the function even if bMemberOf... is true. CloseHandle failed, you cannot do anything about it, but the function returns FALSE nonetheless. Try/finally is not for the WinAPI call but for EXIT() calls if they fail. In this way you can just exit and finally will be called though. No need for goto here. – ChristianWimmer Oct 27 '10 at 08:46
-
1In addition, if any of the normal winapi calls fail, you just exit with false. In a way, you map all errors to FALSE which is also a correct return value. How can you distinguish between them? So the solution is to use exceptions. if not wincall then raiseLastOsError. There also comes try/finally quite handy. – ChristianWimmer Oct 27 '10 at 08:47
-
Your link (http://msdn.microsoft.com/en-us/windows/ff420334.aspx) even makes it correct: if (wincall) {lastErr = ERROR_SUCCESS;}else{lastErr = GetLastError();} and then if (ERROR_SUCCESS != lastErr) { throw (lastErr);} return (fIsAdmin); The return values of all CloseHandle calls are irgnored. – ChristianWimmer Oct 27 '10 at 11:27
-
+1 for the telling the wrong place of Result (I missed that, and I'll correct it) :-). However, `Try/Finally` never knows whether an API function calls success or not. Without any `if` as well as `try..finally` in a pure Winapi code, remaining code will be always executed even if an API function fails. As you see, there was no even an RTL/VCL statement in the above code. So, `Try/Finally` is just a mess. – Vantomex Oct 27 '10 at 11:31
-
My low level functions were designed to work just like Winapi functions works. To get the error detail, user can call `GetLastError()`. – Vantomex Oct 27 '10 at 11:35
-
As I said before, checking the return value of `CloseHandle()` or `LocalFree()` has its use, MSDN does it in many places, e.g. see `http://msdn.microsoft.com/en-us/library/ms724892%28VS.85%29.aspx`; Again, it is just a matter of preference. – Vantomex Oct 27 '10 at 11:52
-
There a so many coding style out there, a code may sometimes emphasize on its eligibility, and sometimes emphasize on its speed (e.g. Fastcode project). Do you think MSDN people are not able to not use `goto` in their code? Readability sometimes a relative view, someone might say it is easier to read deep nested `IF` and the same code repetition instead of use `goto` to reduce them; and someone might say the latter is more readable. – Vantomex Oct 27 '10 at 12:17
-
"My low level functions were designed to work just like Winapi functions works. To get the error detail, user can call GetLastError()". No you did not! The user cannot call GetLastError on your function because the ERROR return value FALSE is part of a successfull return value. And since GetLastError is never resetted to 0 your code can return FALSE on success but GetLastError contains the error code from a failure 10 stack calls before your function. So how can a caller know whether the given member is a member of admins or the function failed? WinAPI uses out Parameter to return the result! – ChristianWimmer Oct 28 '10 at 19:28
-
"As I said before, checking the return value of CloseHandle() or LocalFree() has its use, MSDN does it in many places, e.g. see"... of course, you can also find as many examples where they don't. But why do you just check one CloseHandle but leave 2 others alone? Under a debugger, you don't need to do this at all because CloseHandle throws an exception as MSDN reads. Also AFAIK Application Verifier can check this handle, too. – ChristianWimmer Oct 28 '10 at 19:39
-
1"However, Try/Finally never knows whether an API function calls success or not. Without any if as well as try..finally in a pure Winapi code, remaining code will be always executed even if an API function fails." Did you even read my comments at all? I've already said that. IMHO, goto should be replaced in Delphi with exit in combination with try/finally. Old plain C does not know exceptions and therefore many codes don't use this technique which is okay for them. In my career I could read lots of such c codes and I was always glad to read a Delphi code. – ChristianWimmer Oct 28 '10 at 19:44
-
Converting these codes to Delphi without adapting them leaves Delphi features unused which are there to make code better. Checkout JEDI Windows Security Code Library. There are many WinAPI functions called in this way using try/finally (and also exit). And it works great because errors are propagated using Delphi exceptions with lots of additional information which GetLastError cannot provide. They even can't fail silently as your function where a caller don't know if FALSE is a valid or error value. – ChristianWimmer Oct 28 '10 at 19:53
-
-1 for bad error handling and ignoring comments/suggestions to improve them. – Remko Oct 28 '10 at 19:56
-
@Remko, I'd already admitted the bug in my comment before, but I didn't have enough time to fix it, but now it is. Don't expect every people to fix as soon as possible, we are busy people. BTW even if I didn't fix it, the questioner already got what steps to achieve his/her purpose, and everybody can fix such a simple code. – Vantomex Oct 29 '10 at 03:12
-
@Christian, you toke a chance to criticize the code result while it was not fixed yet at the time. In regard to your comment about `try..finally`, I want to say, I didn't hate it, I like it, I use it in my programs as I said before, but that is a small function containing pure API calls, `try..finally` just make the function slower a bit. I know that the function is not a critical one to optimize, but I have other low-level functions in the same library which are critical. So, the coding style is just for style-consistency in that library. – Vantomex Oct 29 '10 at 04:54
-
@Remko, -1 for voting down action just because you find a flaw or you don't agree with how something is implemented. – Vantomex Oct 29 '10 at 05:00
-
@Vantomex: Don't take me wrong. I hate to criticize without showing ways for solutions or improvements. I try not to make everything bad. But be aware that by posting source code people will take it for granted that your code as an expert is also working for them (otherwise the code that checks for admin wouldn't be copied wrong over and over again ... see link in one of the answers here again). However, you seem to know the problems in your code already but didn't comment on them. This makes it more problematic. IMO, you did not help but just made it worse. – ChristianWimmer Oct 29 '10 at 09:10
-
try/finally makes code slower? Hmm, do you have proof of that? I can give a counter proof from Dr. Bob from http://www.drbob42.com/delphi/perform.htm "Finally Measurements of the try-finally block that also was introduced with Delphi exceptions don't indicate any significant performance loss either. This, combined with the fact that a try-finally block is a very helpful way to safeguard resources and prevent any leakages, makes the try-finally-block my preferred way of programming!" This is what I was talking about the whole time! – ChristianWimmer Oct 29 '10 at 09:12
-
"Delphi exceptions don't indicate any significant performance loss either." Yes, that's true, that's why I said "a bit slower". You might said it's just nano/micro/miliseconds difference, but it's still slower. I saw the compiler add some extra code when `try..finally` was applied. Nevertheless, I never said `try..finally` was bad. Please forgive me, it's just for style-consistency in that library. – Vantomex Oct 29 '10 at 09:43
-
@Christian, however, I must say thank for your explanation about *try-exit-finally* trick, I missed such a thought. :-) – Vantomex Oct 30 '10 at 11:10
-
@Christian, I have just updated the coding style as you suggested. Thank you. :-) – Vantomex Sep 24 '12 at 04:15
This is an excerpt from JwsclToken.pas from the JEDI API&WSCL. Both functions do the same check but in different ways. You see how little code is used? The same code in plain WinAPI would be at least 5 times bigger. Of course you can just call these functions from the unit itself. No need to copy here!
function JwCheckAdministratorAccess: boolean;
var
SD: TJwSecurityDescriptor;
begin
if not Assigned(JwAdministratorsSID) then
JwInitWellKnownSIDs;
SD := TJwSecurityDescriptor.Create;
try
SD.PrimaryGroup := JwNullSID;
SD.Owner := JwAdministratorsSID;
SD.OwnDACL := True;
SD.DACL.Add(TJwDiscretionaryAccessControlEntryAllow.Create(nil,
[], STANDARD_RIGHTS_ALL, JwAdministratorsSID, False));
Result := TJwSecureGeneralObject.AccessCheck(SD, nil,
STANDARD_RIGHTS_ALL, TJwSecurityGenericMapping);
finally
FreeAndNil(SD);
end;
end;
function JwIsMemberOfAdministratorsGroup: boolean;
var
Token: TJwSecurityToken;
begin
Token := TJwSecurityToken.CreateTokenEffective(TOKEN_READ or
TOKEN_DUPLICATE);
try
Token.ConvertToImpersonatedToken(SecurityImpersonation, MAXIMUM_ALLOWED);
Result := Token.CheckTokenMembership(JwAdministratorsSID)
finally
FreeAndNil(Token);
end;
end;

- 1,039
- 8
- 16
-
1Shouldn't you do the `Assigned(JwAdministratorsSID)` check in the second function? Or is it initialised in one of the previous statements? – The_Fox Oct 26 '10 at 10:29
-
1Good point. It is done by user but there should be a check nontheless. I think I added a check in another revision. I just see that I catched an older revision. Now, you see why copy&paste shouldn't done without thinking or using the library directly. I will fix it in the Subversion branches repository but leave it here. – ChristianWimmer Oct 26 '10 at 15:03
-
Jedi API uses lots of inline asm and which is not supported by Delphi's win64 compiler. EMB only provides a subset of the Windows API (I have XE7 only). Anyone any suggestion? – Sender Sep 17 '17 at 06:53
It varies from windows version to windows version... in pre-vista... administrator username is in the primary windows language... for example, in spanish it is Administrador.
In post-vista, there's no administrator user. You shall store and check for user privileges.
I found this IsAdmin function and you may find it useful too...

- 16,976
- 3
- 57
- 98
-
1-1 : In post vista there is an Administrator, but it is deactivated. Every user with the membership in group Administrators is de facto an administrator. The IsAdmin function is incorrect, it does not take into account restricted token and just checks for the availability of the group Administrators in the token but ignores the DENY_ONLY flag. These sources are floating around on the internet a lot because they are just copied from an old, wrong and revised MSDN article. – ChristianWimmer Oct 26 '10 at 08:59
There is the CreateWellKnownSid function.
But explicit check for admin account may be not a good idea. Just do the operation and ask for elevation, if you got 'access denied' error.

- 5,477
- 2
- 36
- 56