The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 1,860 other subscribers

Windows authorization: Adding/Removing users from local groups

Posted by jpluimers on 2011/08/10

Though there are API ways to add users to local groups in Delphi, you need the JwaLmAccess unit from the JEDI API library project to import those.

Sometimes that is not feasible, and sometimes you want to just script things.

For those, you can use the Windows net localgroup command (if you have sufficient privileges, you can even apply it to the local groups on your domain controller by appending it with the /domain parameter, or use the net group /domain command to execute on global domain groups instead of local groups).

Sample of using this in a cmd script:

net localgroup Guests Me Myself I ACME\BugsBunny /delete

It will remove the local users Me, MySelf and I, and remove the domain user ACME\BugsBunny from the local group Guests.

Sample source of using this in Delphi:

program ADConsoleProject;

{$APPTYPE CONSOLE}

uses
  ShellUnit in '..\src\ShellUnit.pas',
  LocalGroupUnit in '..\src\LocalGroupUnit.pas';
begin
  RemoveUsersFromLocalGroup(GuestsLocalGroup, ['Me', 'Myself', 'I', 'ACME\BugsBunny']);
end.

These are the underlying Delphi units:

LocalGroupUnit: the implementation for AddUsersToLocalGroup and RemoveUsersFromLocalGroup.

unit LocalGroupUnit;

interface

const
  GuestsLocalGroup = 'Guests'; // do not translate
  AdministratorsLocalGroup = 'Administrators'; // do not translate

procedure AddUsersToLocalGroup(const LocalGroup: string; const Users: array of string);

procedure RemoveUsersFromLocalGroup(const LocalGroup: string; const Users: array of string);

implementation

uses
  SysUtils, ShellUnit, Windows;

const
  AddToLocalGroupComand = 'net localgroup %s %s /add';
  RemoveFromLocalGroupComand = 'net localgroup %s %s /delete';

// alternative: http://stackoverflow.com/questions/1584275/add-current-selected-user-to-a-group
procedure AddUsersToLocalGroup(const LocalGroup: string; const Users: array of string);
var
  Command: string;
  User: string;
begin
  for User in Users do
  begin
    Command := Format(AddToLocalGroupComand, [LocalGroup, User]);
    RunProcessAndWait(Command, SW_NORMAL, False, nil);
  end;
end;

procedure RemoveUsersFromLocalGroup(const LocalGroup: string; const Users: array of string);
var
  Command: string;
  User: string;
  Result: LongWord;
begin
  for User in Users do
  begin
    Command := Format(RemoveFromLocalGroupComand, [LocalGroup, User]);
    RunProcessAndWait(Command, SW_NORMAL, False, nil);
  end;
end;

end.

ShellUnit (it also contains some other parts I recently needed: shell folders):

unit ShellUnit;

interface

uses
  ShlObj, Windows;

function GetSystemPath(Folder: Integer): string;

type
  TShellFolders = array[1..39] of Integer;

const
  ShellFolders: TShellFolders = (
    CSIDL_DESKTOP,
    CSIDL_INTERNET,
    CSIDL_PROGRAMS,
    CSIDL_CONTROLS,
    CSIDL_PRINTERS,
    CSIDL_PERSONAL,
    CSIDL_FAVORITES,
    CSIDL_STARTUP,
    CSIDL_RECENT,
    CSIDL_SENDTO,
    CSIDL_BITBUCKET,
    CSIDL_STARTMENU,
    CSIDL_DESKTOPDIRECTORY,
    CSIDL_DRIVES,
    CSIDL_NETWORK,
    CSIDL_NETHOOD,
    CSIDL_FONTS,
    CSIDL_TEMPLATES,
    CSIDL_COMMON_STARTMENU,
    CSIDL_COMMON_PROGRAMS,
    CSIDL_COMMON_STARTUP,
    CSIDL_COMMON_DESKTOPDIRECTORY,
    CSIDL_APPDATA,
    CSIDL_PRINTHOOD,
    CSIDL_LOCAL_APPDATA,
    CSIDL_ALTSTARTUP,
    CSIDL_COMMON_ALTSTARTUP,
    CSIDL_COMMON_FAVORITES,
    CSIDL_INTERNET_CACHE,
    CSIDL_COOKIES,
    CSIDL_HISTORY,
    CSIDL_PROFILE,
    CSIDL_CONNECTIONS,
    CSIDL_COMMON_MUSIC,
    CSIDL_COMMON_PICTURES,
    CSIDL_COMMON_VIDEO,
    CSIDL_CDBURN_AREA,
    CSIDL_COMPUTERSNEARME,
    CSIDL_PROFILES
  );

function RunProcessAndWait(const FileName: string; const ShowCmd: DWORD; const wait: Boolean; const ProcID: PDWORD): Longword;

implementation

uses
  SysUtils,
  Forms,
  ActiveX,
  ComObj;

// http://www.scalabium.com/faq/dct0106.htm
// http://www.delphipages.com/forum/showthread.php?t=158704
function GetSystemPath(Folder: Integer): string;
var
  ItemIDList: PItemIDList;
  AMalloc: IMalloc;
begin
  if SHGetMalloc(AMalloc) = NOERROR then
  begin
    ItemIDList := nil;
    OleCheck(SHGetSpecialFolderLocation(Application.Handle, Folder, ItemIDList));
    try
      SetLength(Result, MAX_PATH);
      SHGetPathFromIDList(ItemIDList, PChar(Result));
      SetLength(Result, Length(PChar(Result)));
    finally
      AMalloc.Free(ItemIDList);
    end;
  end;
end;

// http://www.codeguru.com/forum/archive/index.php/t-235106.html
// http://sites.google.com/site/delphibasics/home/delphibasicssnippets/createprocessandwaitforexit
// http://stackoverflow.com/questions/268208/delphi-gracefully-closing-created-process-in-service-using-tprocess-createp
// http://www.codeproject.com/KB/winsdk/runsilent.aspx
function RunProcessAndWait(const FileName: string; const ShowCmd: DWORD; const wait: Boolean; const ProcID: PDWORD): LongWord;
var
  StartupInfo: TStartupInfo;
  ProcessInfo: TProcessInformation;
begin
  FillChar(StartupInfo, SizeOf(StartupInfo), #0);
  StartupInfo.cb := SizeOf(StartupInfo);
  StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
  StartupInfo.wShowWindow := ShowCmd;
  if not CreateProcess(nil,
    @Filename[1],
    nil,
    nil,
    False,
    NORMAL_PRIORITY_CLASS,
    nil,
    nil,
    StartupInfo,
    ProcessInfo)
  then
    Result := WAIT_FAILED
  else
  begin
    if wait = FALSE then
    begin
      if ProcID <> nil then
        ProcID^ := ProcessInfo.dwProcessId;
      result := WAIT_FAILED;
      exit;
    end;
    WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
    GetExitCodeProcess(ProcessInfo.hProcess, Result); // should always succeed as the process has exited
  end;
  if ProcessInfo.hProcess <> 0 then
    CloseHandle(ProcessInfo.hProcess);
  if ProcessInfo.hThread <> 0 then
    CloseHandle(ProcessInfo.hThread);
end;

end.

As an added bonus, this will list the local groups on your domain controller if you have enough rights to read those:

net localgroup /domain

–jeroen

PS: As a reminder to self, some other handy commands like NET, DSTOOLS, NETSH, NETDOM, WMIC, PSTOOLS, et cetera are described by Rob van der Woude.

One Response to “Windows authorization: Adding/Removing users from local groups”

  1. Robert H's avatar

    Robert H said

    Very useful. Thanks for posting.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.