|< Windows User Accounts & Groups Programming 3 | Main | Windows Registry Tutorial 1 >| Site Index | Download |


 

 

 

 

MODULE N1

WINDOWS OS

.::USER ACCOUNTS AND GROUPS:  STORY & PROGRAM EXAMPLES::.

PART 4

 

 

 

 

What do we have in this Module?

  1. Privilege and User Management

  2. Creating a Local Group and Adding a User

  3. Creating a New Computer Account

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

My Training Period: xx hours. Before you begin, read some instruction here. This is a continuation from previous Windows User Accounts & Groups Programming 3.

 

The expected skills are:

  • Able to understand users and groups as implemented in Windows OSes.

  • Able to understand and use functions to manipulate users, groups and machine account.

  • Able to gather and understand the required information in order to use those functions.

  • Able to understand, appreciate and apply how the Unicode/wide character implemented in Microsoft C programs.

Privilege and User Management

 

As a restricted user, to run your program in debug mode you must be a member of the Debugger Users group.  The following example will try to shows how a privilege is required to accomplish our task.  First of all by using the previous program example, let add restricted users as a member of Debugger Users group so that he can run programs in debug mode.  Make sure you remove mytestgroup from the Administrators group as a result of the previous program example.

 

A sample output:

F:\myproject\win32prog\Debug>myaddmember mypersonal "Debugger Users" mypersonal\myuser#1

mypersonal\myuser#1 has been added successfully to Debugger Users on mypersonal machine.

 

F:\myproject\win32prog\Debug>myaddmember mypersonal "Debugger Users" mypersonal\myuser#2

mypersonal\myuser#2 has been added successfully to Debugger Users on mypersonal machine.

 

F:\myproject\win32prog\Debug>myaddmember mypersonal "Debugger Users" mypersonal\myuser#3

mypersonal\myuser#3 has been added successfully to Debugger Users on mypersonal machine.

 

F:\myproject\win32prog\Debug>

 

Then log off and log on as restricted user myuser#1 and start using Visual C++ .Net.  First of all let try running our previous, first program example, creating users.

 

 

//********* myuserprog.cpp **********

// For WinXp

#define _WIN32_WINNT 0x0501

// Wide character/Unicode based program

#ifndef UNICODE

#define UNICODE

#endif

 

#include <windows.h>

#include <stdio.h>

#include <lm.h>

 

// This program accept 3 arguments: servername, username and password.

// It is run on local WinXp machine so the servername is the

// local WinXp machine name or you can use NULL for the 1st parameter

// of the NetUserAdd() and arguments, should be without the servername.

int wmain(int argc, wchar_t *argv[ ])

{

   USER_INFO_1 ui;

   DWORD dwLevel = 1;

   DWORD dwError = 0;

   NET_API_STATUS nStatus;

 

   if(argc != 4)

   {

      fwprintf(stderr, L"Usage: %s ServerName UserName Password.\n", argv[0]);

      // or use fwprintf(stderr, L"Usage: %s UserName Password.\n", argv[0]);

      // for local machine and adjust other argc and argv[] array element appropriately.

      exit(1);

   }

 

   // Set up the USER_INFO_1 structure.

   // USER_PRIV_USER: name identifies an normal user

   // UF_SCRIPT: required for LAN Manager 2.0 and Windows NT and later.

   ui.usri1_name = argv[2];                      // Username entered through command line

   ui.usri1_password = argv[4];              // Password

   ui.usri1_priv = USER_PRIV_USER; // As a normal/restricted user

   ui.usri1_home_dir = NULL;                // No home directory

   ui.usri1_comment = L"This is a test normal user account using NetUserAdd";  // Comment

   ui.usri1_flags = UF_SCRIPT;            // Must be UF_SCRIPT

   ui.usri1_script_path = NULL;             // No script path

 

   // Call the NetUserAdd() function, specifying level 1.

   nStatus = NetUserAdd(argv[1],

                        dwLevel,

                        (LPBYTE)&ui,

                        &dwError);

 

   // If the call succeeds, inform the user.

   if(nStatus == NERR_Success)

   {

     fwprintf(stderr, L"%s user has been successfully added on %s machine.\n", argv[2], argv[1]);

     fwprintf(stderr, L"Username: %s password: %s.\n", argv[2], argv[3]);

   }

   // Otherwise, print the system error.

   else

      fprintf(stderr, "A system error has occurred: %d\n", nStatus);

 

   return 0;

}

 

A sample output:

F:\myuserprog\myuserprog\Debug>myuserprog mypersonal user#1 12345678

user#1 user has been successfully added on mypersonal machine.

Username: user#1 password: 12345678.

 

F:\myuserprog\myuserprog\Debug>myuserprog mypersonal user#2 12345678

user#2 user has been successfully added on mypersonal machine.

Username: user#2 password: 12345678.

 

F:\myuserprog\myuserprog\Debug>myuserprog mypersonal user#3 12345678

user#3 user has been successfully added on mypersonal machine.

Username: user#3 password: 12345678.

 

F:\myuserprog\myuserprog\Debug>

 

Create new users

 

Figure 5: user#1, user#2 and user#3 have been created.

 

Well, we can create a user account, just being a member of the Debugger Users group.  Then let test the previous program that creates a local group.

 

//********* myuserproglg.cpp **********

// For WinXp

#define _WIN32_WINNT 0x0501

// Wide character/Unicode based program

#ifndef UNICODE

#define UNICODE

#endif

 

#include <windows.h>

#include <stdio.h>

#include <lm.h>

 

// This program accept 3 arguments: servername, GroupName and Comment.

int wmain(int argc, wchar_t *argv[ ])

{

   LOCALGROUP_INFO_1 lgi1;

   DWORD dwLevel = 1;

   DWORD dwError = 0;

   NET_API_STATUS nStatus;

 

   if(argc != 4)

   {

      fwprintf(stderr, L"Usage: %s ServerName GroupName Comment\n", argv[0]);

      // Just exit, no further processing

      exit(1);

   }

 

   // Set up the LOCALGROUP_INFO_1 structure.

   // Assign the group name and comment

   lgi1.lgrpi1_name = argv[2];    // Local group name

   lgi1.lgrpi1_comment = argv[3]; // Comment

 

   // Call the NetLocalGroupAdd() function, specifying level 1.

   nStatus = NetLocalGroupAdd(argv[1],

                        dwLevel,

                        (LPBYTE)&lgi1,

                        &dwError);

 

   // If the call succeeds, inform the user.

   if(nStatus == NERR_Success)

     fwprintf(stderr, L"%s local group has been created successfully on %s machine.\n", argv[2], argv[1]);

 

   // Otherwise, print the system error.

   else

      fprintf(stderr, "A system error has occurred: %d\n", nStatus);

 

   return 0;

}

 

A sample output:

F:\myuserprog\myuserprog\Debug>myuserproglg

Usage: myuserproglg ServerName GroupName Comment

 

F:\myuserprog\myuserprog\Debug>myuserproglg mypersonal normalusergroup "Created by restricted user"

normalusergroup local group has been created successfully on mypersonal machine.

 

F:\myuserprog\myuserprog\Debug>

Verify our task.

 

Create a local group

 

Figure 6: normalusergroup group has been created.

 

Also successful.  Then test adding a user to a group program example.  When running the previous program example to add user as a member of built-in Users and Power Users groups, from the output, the following error code displayed.

 

A sample output:

Microsoft Windows XP [Version 5.1.2600]

(C) Copyright 1985-2001 Microsoft Corp.

 

C:\Documents and Settings\myuser#1>f:

 

F:\myuserprog\myuserprog\Debug>mynewaddmember

Usage: mynewaddmember ServerName GroupName MemberAccountName-(DomainName\AccountName)

 

F:\myuserprog\myuserprog\Debug>mynewaddmember mypersonal Users mypersonal\user#1

A system error has occurred: 5

 

F:\myuserprog\myuserprog\Debug>mynewaddmember mypersonal "Power Users" mypersonal\user#1

A system error has occurred: 5

 

F:\myuserprog\myuserprog\Debug>

This error code means: 5 - Access is denied (ERROR_ACCESS_DENIED).  So as a restricted user, we don’t have privilege, because of the access token that we are carrying doesn’t has permission to complete the task.

 

Creating a Local Group and Adding a User

 

Windows Server 2003 family, Windows XP, Windows 2000, and Windows NT use the same functions that Microsoft LAN Manager uses to create and maintain user and local group-account information.  For example, to create a new local group, call the NetLocalGroupAdd() function.  To add a user to that group, call the NetLocalGroupAddMembers() function.  The following program allows you to create a user and a local group and then add the user to the local group.

The following program example has been run on Windows 2000 member server of jmtibm.com domain.  Its Fully Qualified Domain Name (FQDN) is jmti_st_00.jmtibm.com and the Domain Controller’s (DC) FQDN is mawar.jmtibm.com.  The member server was logged on as Domain Administrator.  Compiler used is Visual C++ 6.0.  The steps to include the netapi32.lib library (or other library) to the project in Visual C++ 6.0 are shown below. 

 

Project menu Setting... sub menu.  Then click the Link tab on the right page.  Under Object/Library modules: type manually the library name at the end of the list separated by a space.  Click the OK button.

 

Adding dependencies i.e library to Visual C++ project

 

Figure 7: Adding library to Visual C++ project.

 

Manually adding the library to Visual C++ project

 

Figure 8: Another step of adding library to Visual C++ project.

 

The let run our program example that create a new user and a group, then include the user as a member of that group.

 

//********* myusrgrp.cpp ************

// Network management functions have their own

// error codes...

#define WIN32_WINNT 0x0500

#define UNICODE 1

#include <windows.h>

#include <lmcons.h>

#include <lmaccess.h>

#include <lmerr.h>

#include <lmapibuf.h>

#include <stdio.h>

#include <stdlib.h>

 

NET_API_STATUS MyTestNet(LPWSTR lpszDomain,

                         LPWSTR lpszUser,

                         LPWSTR lpszPassword,

                         LPWSTR lpszLocalGroup )

{

    USER_INFO_1                          user_info;

    LOCALGROUP_INFO_1         localgroup_info;

    LOCALGROUP_MEMBERS_INFO_3 localgroup_members;

    LPWSTR                                    lpszPrimaryDC = L"mawar";

    NET_API_STATUS                  err = 0;

    DWORD                                     parm_err = 0;

 

   // First get the name of the primary domain controller. Make sure to free the returned buffer.

    err = NetGetDCName(L"mawar",                // Local machine

                   lpszDomain,                                    // Domain name, if NULL use lpszPrimaryDC

                   (LPBYTE *)&lpszPrimaryDC );     // Returned PDC

 

    if(err != 0)

    {

        printf("Error getting DC name: %d\n", err);

        return(err);

    }

 

    // Set up the USER_INFO_1 structure.

    user_info.usri1_name = lpszUser;

    user_info.usri1_password = lpszPassword;

    user_info.usri1_priv = USER_PRIV_USER;

    user_info.usri1_home_dir = TEXT("");

    user_info.usri1_comment = TEXT("This is just a sample user lol!");

    user_info.usri1_flags = UF_SCRIPT;

    user_info.usri1_script_path = TEXT("");

 

    err = NetUserAdd(lpszPrimaryDC,        // PDC name

                      1,                                              // Level, use other level for more information

                      (LPBYTE)&user_info,            // Input buffer

                      &parm_err);                            // Parameter in error

 

    switch (err)

    {

    case 0:

        printf("%ls user successfully created.\n", user_info.usri1_name);

        break;

    case NERR_UserExists:

        printf("%ls user already exists.\n", user_info.usri1_name);

        err = 0;

        break;

    case ERROR_INVALID_PARAMETER:

       {

        printf("Invalid Parameter Error adding user: Parameter Index = %d\n", parm_err);

        NetApiBufferFree(lpszPrimaryDC);

       return(err);

       }

    default:

        printf("Error adding %ls user: %d\n", user_info.usri1_name, err);

        NetApiBufferFree(lpszPrimaryDC);

        return(err);

    }

 

    // Set up the LOCALGROUP_INFO_1 structure.

    localgroup_info.lgrpi1_name = lpszLocalGroup;

    localgroup_info.lgrpi1_comment = TEXT("This is just a sample Local group.");

 

    err = NetLocalGroupAdd(lpszPrimaryDC,    // PDC name

                  1,                                                         // Level

                  (LPBYTE)&localgroup_info,            // Input buffer

                  &parm_err);                                      // Parameter in error

 

    switch (err)

    {

    case 0:

        printf("%ls Local Group successfully created.\n", localgroup_info.lgrpi1_name);

        break;

    case ERROR_ALIAS_EXISTS:

          printf("%ls Local Group already exists.\n", localgroup_info.lgrpi1_name);

          err = 0;

          break;

    case ERROR_INVALID_PARAMETER:

       {

         printf("Invalid Parameter Error adding Local Group: Parameter Index = %d\n", err, parm_err);

         NetApiBufferFree(lpszPrimaryDC);

         return(err);

       }

    default:

        printf("Error adding %ls Local Group: %d\n", localgroup_info.lgrpi1_name, err);

        NetApiBufferFree(lpszPrimaryDC);

        return(err);

    }

 

    // Now add the user to the local group.

    localgroup_members.lgrmi3_domainandname = lpszUser;

 

    err = NetLocalGroupAddMembers(lpszPrimaryDC,       // PDC name

                           lpszLocalGroup,                                           // Group name

                           3,                                                                   // Name

                           (LPBYTE)&localgroup_members,             // Buffer

                           1);                                                                  // Count

 

    switch(err)

    {

    case 0:

        printf("%ls user successfully added to %ls Local Group.\n", user_info.usri1_name, localgroup_info.lgrpi1_name);

        break;

    case ERROR_MEMBER_IN_ALIAS:

        printf("User %ls already in %ls Local Group.\n", user_info.usri1_name, localgroup_info.lgrpi1_name);

        err = 0;

        break;

    default:

        printf("Error adding %ls user to %ls Local Group: %d\n", user_info.usri1_name, localgroup_info.lgrpi1_name, err);

        break;

    }

 

    NetApiBufferFree(lpszPrimaryDC);

    return (err);

}

 

// This program run at command prompt, receives 4 arguments: The domain name,

// user name (user account), user password and the group name.

int wmain(int argc, wchar_t *argv[])

{

    NET_API_STATUS err = 0;

 

       if(argc != 5)

       {

         printf("Usage: %ls <domain_name> <user_name> <password> <group_name>\n", argv[0]);

         exit (-1);

       }

 

    printf("Calling MyTestNet(): Create a user and a group then,\n");

    printf("add the user to the group.\n");

    printf("===================================================.\n");

    err = MyTestNet(argv[1],    // domain name

                    argv[2],    // user account

                    argv[3],    // password for the user

                    argv[4]);   // group name

    printf("MyTestNet() returned %d\n", err);

    return (0);

}

 

A sample output:

C:\myproject\win32prog\Debug>myusrgrp

Usage: myusrgrp <domain_name> <user_name> <password> <group_name>

 

C:\myproject\win32prog\Debug>myusrgrp jmtibm mytestuser 12345678 mytestgroup

Calling MyTestNet(): Create a user and a group then,

add the user to the group.

===================================================.

mytestuser user successfully created.

mytestgroup Local Group successfully created.

mytestuser user successfully added to mytestgroup Local Group.

MyTestNet() returned 0

Rerun the program with same arguments.

 

A sample output:

C:\myproject\win32prog\Debug>myusrgrp jmtibm mytestuser 12345678 mytestgroup

Calling MyTestNet(): Create a user and a group then,

add the user to the group.

===================================================.

mytestuser user already exists.

mytestgroup Local Group already exists.

User mytestuser already in mytestgroup Local Group.

MyTestNet() returned 0

 

It looks OK.  Then verify our task.

Create user and group and add the user to the group

 

Figure 9: mytestuser user and mytestgroup group have been created.

 

The mytestuser user still not usable because there is no login name setting etc.   Use NetUserSetInfo() function with different level to set other properties of the user account as demonstrated in the previous program example.  For domain user account, when you try to delete the account there is a message prompted whether the mailbox of that user also need to be deleted.  This means email account also has been created for that account.

 

Creating a New Computer Account

 

The following program example demonstrates how to create a new computer account using the NetUserAdd() function.  The following are considerations for managing computer accounts:

  1. The computer account name should be all uppercase for consistency with Windows NT or later account management utilities.

  2. A computer account name always has a trailing dollar sign ($).  Any functions used to manage computer accounts must build the computer name such that the last character of the computer account name is a dollar sign ($).  For interdomain trust, the account name is TrustingDomainName$.

  3. The maximum computer name length is MAX_COMPUTERNAME_LENGTH (15).  This length does not include the trailing dollar sign ($).

  4. The password for a new computer account should be the lowercase representation of the computer account name, without the trailing dollar sign ($).  For interdomain trust, the password can be an arbitrary value that matches the value specified on the trust side of the relationship.

  5. The maximum password length is LM20_PWLEN (14).  The password should be truncated to this length if the computer account name exceeds this length.

  6. The password provided at computer-account-creation time is valid only until the computer account becomes active on the domain.  A new password is established during trust relationship activation.

 

The program example has been run on Windows 2000 member server of jmtibm.com domain same as the previous example.

 

//********* machineacct.cpp *********

// For Win 2000

#define _WIN32_WINNT 0x0500

// Wide character/Unicode based program

#ifndef UNICODE

#define UNICODE

#endif

 

#include <windows.h>

#include <stdio.h>

#include <lm.h>

 

BOOL AddMachineAccount(LPWSTR wTargetComputer, LPWSTR MachineAccount, DWORD AccountType)

{

    LPWSTR wAccount;

    LPWSTR wPassword;

    USER_INFO_1 ui;

    DWORD cbAccount;

    DWORD cbLength;

    DWORD dwError;

 

    // Ensure a valid computer account type was passed.

    if(AccountType != UF_WORKSTATION_TRUST_ACCOUNT &&

       AccountType != UF_SERVER_TRUST_ACCOUNT &&

       AccountType != UF_INTERDOMAIN_TRUST_ACCOUNT)

    {

        SetLastError(ERROR_INVALID_PARAMETER);

        return FALSE;

    }

    else

       printf("Computer account type is valid.\n");

 

    // Obtain the number of chars in computer account name.

    cbLength = cbAccount = lstrlenW(MachineAccount);

 

    // Ensure computer name doesn't exceed maximum length.

    if(cbLength > MAX_COMPUTERNAME_LENGTH)

    {

        SetLastError(ERROR_INVALID_ACCOUNT_NAME);

        return FALSE;

    }

    else

       printf("Computer name length is valid.\n");

 

    // Allocate storage to contain Unicode representation of

    // computer account name + trailing $ + NULL.

    wAccount = (LPWSTR)HeapAlloc(GetProcessHeap(), 0,

        (cbAccount + 1 + 1) * sizeof(WCHAR)  // Account + '$' + NULL

        );

 

    if(wAccount == NULL) return FALSE;

       else printf("Memory allocation is OK.\n");

 

    // Password is the computer account name converted to lowercase;

    // you will convert the passed MachineAccount in place.

    wPassword = MachineAccount;

 

    // Copy MachineAccount to the wAccount buffer allocated while

    // converting computer account name to uppercase.

    // Convert password (in place) to lowercase.

    while(cbAccount--) {

        wAccount[cbAccount] = towupper(MachineAccount[cbAccount]);

        wPassword[cbAccount] = towlower(wPassword[cbAccount]);

    }

 

    // Computer account names have a trailing Unicode '$'.

    wAccount[cbLength] = L'$';

    wAccount[cbLength + 1] = L'\0'; // terminate the string

 

    // If the password is greater than the max allowed, truncate.

    if(cbLength > LM20_PWLEN) wPassword[LM20_PWLEN] = L'\0';

       else printf("No truncation was done to the password, the length is OK, max is 14.\n");

 

    // Initialize the USER_INFO_1 structure.

    ZeroMemory(&ui, sizeof(ui));

 

    ui.usri1_name = wAccount;

    ui.usri1_password = wPassword;

    ui.usri1_flags = AccountType | UF_SCRIPT;

    ui.usri1_priv = USER_PRIV_USER;

    ui.usri1_comment = L"A virtual machine created by NetUserAdd()...";

 

    dwError = NetUserAdd(

                wTargetComputer,    // target computer name

                1,                  // info level

                (LPBYTE) &ui,       // buffer

                NULL

                );

 

    // Release the allocated memory.

    if(wAccount) HeapFree(GetProcessHeap(), 0, wAccount);

 

    // Indicate whether the function was successful.

   if(dwError == NO_ERROR)

   {

        printf("%ls computer account successfully created on %ls DC.\n", MachineAccount, wTargetComputer);

        return TRUE;

   }

   else

   {

        SetLastError(dwError);

        return FALSE;

   }

}

 

// This program run at command prompt, receives 2 arguments: The target server and the machine account name.

int wmain(int argc, wchar_t *argv[])

{

       if(argc != 3)

       {

              printf("Usage: %s <TargetComputer> <MachineAccount/Password>.\n", argv[0]);

              exit (-1);

       }

 

       DWORD AccountType = UF_WORKSTATION_TRUST_ACCOUNT;

 

       BOOL Test = AddMachineAccount(argv[1], argv[2], AccountType);

       printf("The return value is: %u\n", Test);

 

       return 0;

}

 

A sample output:

C:\myproject\win32prog\Debug>machineacct

Usage: machineacct <TargetComputer> <MachineAccount/Password>.

 

C:\myproject\win32prog\Debug>machineacct Mawar mymachine

Computer account type is valid.

Computer name length is valid.

Memory allocation is OK.

No truncation was done to the password, the length is OK, max is 14.

mymachine computer account successfully created on Mawar DC.

The return value is: 1

 

Verify our task.

 

Add machine account

 

Figure 10: MYMACHINE computer account has been created.

 

The user that calls the account management functions must have Administrator privilege on the target computer.  In the case of existing computer accounts, the creator of the account can manage the account, regardless of administrative membership.

The SeMachineAccountPrivilege can be granted on the target computer to give specified users the ability to create computer accounts.  This gives non-administrators the ability to create computer accounts.  The caller needs to enable this privilege prior to adding the computer account.

 

 

------------------------User Accounts and Groups: Story and Program Examples, Part II-----------------------

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Further reading and digging:

 

  1. Check the best selling C, C++ and Windows books at Amazon.com.

  2. Microsoft Visual C++, online MSDN.

  3. MSDN library.

  4. For Multibytes, Unicode characters and Localization please refer to Locale, wide characters & Unicode (Story) and Windows users & groups programming tutorials (Implementation).

  5. Structure, enum, union and typedef story can be found C/C++ struct, enum, union & typedef.

  6. Notation used in MSDN is Hungarian Notation instead of CamelCase and is discussed Windows programming notations.

  7. Windows data type information is in Windows data types used in Win32 programming.

 

 

 

 

 

 

 

 

|< Windows User Accounts & Groups Programming 3 | Main | Windows Registry Tutorial 1 >| Site Index | Download |