My Training Period: xx hours. Before you begin, read someinstruction here. This is a continuation from previousWindows User Accounts & Groups Programming 3.
The expected skills are:
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 theAdministrators group as a result of the previous program example.
A sample output:
Then log off and log on as restricted usermyuser#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>

Figure 5:user#1,user#2 anduser#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.

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 theNetLocalGroupAdd() function. To add a user to that group, call theNetLocalGroupAddMembers() 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) isjmti_st_00.jmtibm.com and the Domain Controller’s (DC) FQDN ismawar.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 theOK button. |

Figure 7: Adding 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.

Figure 9:mytestuser user andmytestgroup 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 theNetUserAdd() function. The following are considerations for managing computer accounts:
The computer account name should be all uppercase for consistency with Windows NT or later account management utilities.
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$.
The maximum computer name length isMAX_COMPUTERNAME_LENGTH (15). This length does not include the trailing dollar sign ($).
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.
The maximum password length isLM20_PWLEN (14). The password should be truncated to this length if the computer account name exceeds this length.
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.

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:
Check the best selling C, C++ and Windows books at Amazon.com.
Microsoft C references, online MSDN.
Microsoft Visual C++, online MSDN.
ReactOS - Windows binary compatible OS - C/C++ source code repository, Doxygen.
Linux Access Control Lists (ACL) info can be found atAccess Control Lists.
For Multi bytes, Unicode characters and Localization please refer to Locale, wide characters & Unicode (Story) and Windows users & groups programming tutorials (Implementation).
Structure, enum, union and typedef story can be foundC/C++ struct, enum, union & typedef.
Notation used in MSDN is Hungarian Notation instead of CamelCase and is discussedWindows programming notations.
Windows data type information is inWindows data types used in Win32 programming.