Q. How to test in a locale-independent way if a user has administrative rights?
A. The short answer is: use the “Well Known Sid.” For ready to use code see below.
Introduction
Once in a while one might need to test if a user has administrative rights or not.
Except for applications specially developed for network or system management, very few applications have to deal with users, groups, or security rights. As a result, most developers are not familiar with the network and user management API. And even fewer are aware of the fact that the user/group names are localized on some versions of Windows.
The code
You can download from here all the code below, including headers, and a small test application.
There is not much explaining to do. The main idea is to use the a well known SID. But getting all the pieces together might be tricky.
So here is the code, ready to use. It works with everything from Visual Studio 6 to Visual Studio 2005 SP1. For some reasone CreateWellKnownSid (working with VS 6) does not work anymore with VS 2005, so I had to replace it with AllocateAndInitializeSid.
// IsAdminAPI.cpp : Tests if user is Administrator using plain Win32 API// Copyright (c) April 2007, Mihai Nita//#include<wtypes.h>#include<Lm.h>// for ASSERT#include<crtdbg.h>#include"IsAdminAPI.h"boolIsAdminAPI( WCHARconst*szUserName ) { _ASSERT(szUserName);boolbAdmin = FALSE; LOCALGROUP_USERS_INFO_0* localGroups; DWORD entriesread, totalentries; NET_API_STATUS nts = NetUserGetLocalGroups( NULL, szUserName, 0, 0, (unsignedchar**)&localGroups, MAX_PREFERRED_LENGTH, &entriesread, &totalentries);if( nts != NERR_Success ) { NetApiBufferFree(localGroups);returnFALSE; }// Retrieve the Administrators group well-known SID// For some reason CreateWellKnownSid generates error C3861 on Developer Studio .NET:// error C3861: 'CreateWellKnownSid': identifier not found, even with argument-dependent lookupBYTE SidAuth[] = SECURITY_NT_AUTHORITY; PSID pAdminSid; AllocateAndInitializeSid( (PSID_IDENTIFIER_AUTHORITY)SidAuth, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, NULL, NULL, NULL, NULL, NULL, NULL, &pAdminSid );// Will use this to retrieve the SID of the groupBYTE buffSid[SECURITY_MAX_SID_SIZE];wchar_tbuffDomain[DNLEN+1]; DWORD dwSidSize; DWORD dwDomainSize; SID_NAME_USE m_sidnameuse;for( DWORD i = 0; i < entriesread; ++i ) { dwSidSize = sizeof(buffSid); dwDomainSize = DNLEN;// Although in general is a bad idea to call directly the W or A versions of API// we do it here to avoid converting the localGroups[i].lgrui0_name back to ANSI// This kind of security API is only present on NT/2000/XP family only, so// the W version is present and safe to useif( LookupAccountNameW( NULL, localGroups[i].lgrui0_name, buffSid, &dwSidSize, (LPWSTR)buffDomain, &dwDomainSize, &m_sidnameuse) )// no sid for the actual groupif( EqualSid( buffSid, pAdminSid ) ) { bAdmin = TRUE;break; } } FreeSid( pAdminSid ); NetApiBufferFree(localGroups);returnbAdmin; }
Starting with Visual Studio .NET 2003 (or maybe 2002, I am not sure), the ATL added support for user and network administration.
It is cleaner, it easyer to read and maitain. Just compare the ATL predefined Sids::Admins() with the AllocateAndInitializeSid above.
// IsAdminATL.cpp : Tests if user is Administrator using ATL// Copyright (c) April 2007, Mihai Nita//#include<wtypes.h>#include<Lm.h>#include"IsAdminATL.h"#ifdef__DEV_STUDIO_NET__// This works with VS .NET, much cleaner#include<atlsecurity.h>boolIsAdminATL( WCHARconst*szUserName ) { _ASSERT(szUserName);boolbAdmin = FALSE; LOCALGROUP_USERS_INFO_0* localGroups; DWORD entriesread, totalentries; NET_API_STATUS nts = NetUserGetLocalGroups( NULL, szUserName, 0, 0, (unsignedchar**)&localGroups, MAX_PREFERRED_LENGTH, &entriesread, &totalentries);if( nts == NERR_Success ) {for( DWORD i = 0; i < entriesread; ++i ) { NonATLSid( localGroups[i].lgrui0_name );// You can find the list of all well-known SIDs in atlsecurity.htry{ CSid gsid( localGroups[i].lgrui0_name );if( gsid == Sids::Admins() ) { bAdmin = TRUE;break; } }catch( CAtlException e ) {// we just go ahead with the next group} } NetApiBufferFree(localGroups); }returnbAdmin; }#endif// __DEV_STUDIO_NET__
Ok, this is it!
Not much to add, and it is late, so “good night!”
