Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Administrating an NT-Server

97 views
Skip to first unread message

David Smith

unread,
Mar 10, 1997, 3:00:00 AM3/10/97
to


I have a Projekt, writing a program to administrate (add and delete users)
an NT 3.51 Server from another workstation. My client wants to be able to
have program that will do all settings for him (username, groups, profiles
etc.). He has prdefined groups to where all rights are set. He basicliy
just wants to enter the Username and cleck on what grou the user is in.
Is there a way over API-Calls to:

a) get status of users
b) add a user with all needed settings (also groups and profiles)
c) delete users

Thanks in advance

Dave

-----
David Smith
David...@frankfurt.netsurf.de


Cory Smith

unread,
Mar 24, 1997, 3:00:00 AM3/24/97
to

I am doing a similar project, except on NT4.0. I have tracked down
the location of what is called, I believe, the ported Lan Manager
functions. They are located in NetApi32.DLL. In order to use these
functions, you have to create a type library, due to VB not allowing
you to call UniCode functions natively. The function I believe you
are trying to use is NetUserGetInfo and NetUserSetInfo. If you have
MSDN you can do a search for NetUserChangePassword and that will allow
you to find the rest of the documentation on all of the Net*
functions. These only work on NT and are Unicode. The only function
that I have used, so far, is the NetUserChangePassword to allow users
on a domain to change their password, for when it expires or just for
the flop of it, via Netscape or Internet Explorer over the Intranet
thru a OLE DLL called from and Active Server Page on the Internet
Information Server machine.

There is one function that I'm having problems converting, due to the
fact it uses a (LPBYTE *)&ComputerName in the C sample code I have.
How does that convert to Unicode in VB???????

Anyways, hope this helps.

On 10 Mar 1997 12:16:59 GMT, "David Smith"

Hong YAN

unread,
Apr 2, 1997, 3:00:00 AM4/2/97
to

Edmund Davis wrote:
>
> If anyone has succeeded in using these NetUser* etc. API Calls from
> Visual Basic would it be possible for some kind person to post the relevant
> libraries DLLs so that we can all share the benefit of their toils.
>
> These questions have come up regularly and are either ignored or passed
> off with rather general replies involving MSDN but this doesn't help since
> some C++ is going to be needed at some stage and most of the people
> in this group, I assume, don't use it!
>
> TIA
> Yours Edmund
> ---
> edmund...@worc.ox.ac.uk

Edmund :

I have been working in that direction in darkness for sometime and I have uncracked some
dll functions. I have not posted them in any newsgroups but I did send my code to other
users individually.

The API functions that I succeeded at are some of the following categories:

NetUser*
NetServer*
NetWksta*
NetLocalGroup*
(for WinNT)
WNet*
(for Win95 and NT)

and some misc ones.

I clearly remember how painful it has been, the sleepless nights, no-go trials... . One
example: vbNull / vbNullChar / vbNullString / 0& are not identical when you pass a null
pointer to dll functions. I got stucked at this for a whole week before I reallized it.
I am really not sure how many of the guys requesting these functions are ready for this
pain.

I donot have ready-to-post solutions to many LAN programming questions, though the
method is implied in my code. In many cases, guys who got a piece of my code would
simply throw the questions back to me rather than try to figure it out by themselves.

I often receive 10 to 20 requests of my functions, but I found out that only 2-3 of the
guys who received my functions really tried them. Maybe they just gave up when they took
a glance at the horrible code.

My source is the following :

(1) 'Windows NT Network Programming' by Ralph Davis

(2) 'Network Programming in Windows NT' by Alok K. Sinha

(3) MSDN

(4) User Groups

The following is a sample of these functions. The job is to list all the machines on a
Windows NT domain according to a criterion. Believe me, this one is just moderately
complicated. I am going to interpret my code in my next posting. And the code will be
simplified (the current one is a straight translation of C code).

Option Explicit

Declare Function PtrToStr Lib "kernel32" Alias "lstrcpyW" _
(RetVal As Byte, ByVal Ptr As Long) As Long

Declare Sub RtlMoveMemory Lib "kernel32" _
(dest As Any, src As Any, ByVal size&)

Declare Function NetAPIBufferAllocate Lib "netapi32.dll" Alias _
"NetApiBufferAllocate" (ByVal ByteCount As Long, Ptr As Long) As Long

Declare Function PtrToInt Lib "kernel32" Alias "lstrcpynW" _
(RetVal As Any, ByVal Ptr As Long, ByVal nCharCount As Long) As Long

Declare Function NetApiBufferFree& Lib "Netapi32" (ByVal buffer&)

Type MungeInt
XLo As Integer
XHi As Integer
Dummy As Integer
End Type

Type MungeLong
X As Long
Dummy As Integer
End Type

Type SERVER_INFO_100
sv100_platform_id As Long
sv100_name As Long
End Type ' SERVER_INFO_100, *PSERVER_INFO_100, *LPSERVER_INFO_100;

Declare Function NetServerGetInfo Lib "netapi32.dll" (ByVal ServerName As String, level
As Long, BufPtr As Long)
Declare Function NetServerEnum Lib "netapi32.dll" (ByVal ServerName As String, _
ByVal level As Long, BufPtr As Long, ByVal prefmaxlen As Long, _
EntriesRead As Long, TotalEntries As Long, ByVal servertype As Long, _
ByVal domain As String, resume_handle As Long) As Long

Global Const SV_TYPE_WORKSTATION = &H1 'All LAN Manager workstations
Global Const SV_TYPE_SERVER = &H2 'All LAN Manager servers
Global Const SV_TYPE_SQLSERVER = &H4 'Any server running with Microsoft SQL
Server
Global Const SV_TYPE_DOMAIN_CTRL = &H8 'Primary domain controller
Global Const SV_TYPE_DOMAIN_BAKCTRL = &H10 'Backup domain controller
Global Const SV_TYPE_TIMESOURCE = &H20 'Server running the Timesource service
Global Const SV_TYPE_AFP = &H40 'Apple File Protocol servers
Global Const SV_TYPE_NOVELL = &H80 'Novell servers
Global Const SV_TYPE_DOMAIN_MEMBER = &H100 'LAN Manager 2.x Domain Member
Global Const SV_TYPE_LOCAL_LIST_ONLY = &H40000000 'Servers maintained by the browser
Global Const SV_TYPE_PRINT = &H200 'Server sharing print queue
Global Const SV_TYPE_DIALIN = &H400 'Server running dial-in service
Global Const SV_TYPE_XENIX_SERVER = &H800 'Xenix server
Global Const SV_TYPE_MFPN = &H4000 'Microsoft File and Print for Netware
Global Const SV_TYPE_NT = &H1000 'Windows NT (either Workstation or Server)
Global Const SV_TYPE_WFW = &H2000 'Server running Windows for Workgroups
Global Const SV_TYPE_SERVER_NT = &H8000 'Windows NT non-DC server
Global Const SV_TYPE_POTENTIAL_BROWSER = &H10000 'Server that can run the Browser
service
Global Const SV_TYPE_BACKUP_BROWSER = &H20000 'Server running a Browser service as
backup
Global Const SV_TYPE_MASTER_BROWSER = &H40000 'Server running the master Browser
service
Global Const SV_TYPE_DOMAIN_MASTER = &H80000 'Server running the domain master
Browser
Global Const SV_TYPE_DOMAIN_ENUM = &H80000000 'Primary Domain
Global Const SV_TYPE_WINDOWS = &H400000 'Windows 95 or later
Global Const SV_TYPE_ALL = &HFFFF 'All servers

'Remarks
'The SV_TYPE_LOCAL_LIST_ONLY flag returns the list of servers maintained by the
'browser internally. This has meaning only on the master browser (or on a computer that
'has been the master browser in the past). The master browser is the machine that
'currently has rights to determine which machines can be servers or workstations on the
'net.


Function GetMachineList(sMName As String, sDName As String, lMachineType As Long,
sReturn() As String) As Boolean

Dim Result As Long, BufPtr As Long, EntriesRead As Long, _
TotalEntries As Long, ResumeHandle As Long, BufLen As Long, _
i As Long, TempPtr As MungeLong, TempStr As MungeInt
Dim MNArray() As Byte
Dim sMNameBuff As String

ReDim MNArray(255)
BufLen = 255 ' Buffer size
ResumeHandle = 0 ' Start with the first entry

Dim swMName As String * 255 'Unicode string
Dim swGName As String * 255 'Unicode string

swMName = StrConv(sMName & vbNullChar, vbUnicode) 'server name
swGName = StrConv(sDName & vbNullChar, vbUnicode) 'domain name

GetMachineList = False
Do

Result = NetServerEnum(swMName, 100&, BufPtr, BufLen, EntriesRead, _
TotalEntries, lMachineType, swGName, ResumeHandle)
If Result <> 0 And Result <> 234 Then
Call NetErrorHandler(Result, "")
Exit Function
End If

For i = 1 To EntriesRead
Result = PtrToInt(TempStr.XLo, BufPtr + i * 4, 2)
Result = PtrToInt(TempStr.XHi, BufPtr + i * 4 + 2, 2)
LSet TempPtr = TempStr
Result = PtrToStr(MNArray(0), TempPtr.X)
sMNameBuff = Left(MNArray, StrLen(TempPtr.X))
ReDim Preserve sReturn(i)
sReturn(i) = sMNameBuff
Next i
Loop Until EntriesRead = TotalEntries

Result = NetApiBufferFree(BufPtr)
GetMachineList = True

End Function

This function would be useful when you build a network DDE application like netChat.
NetDDE itself is simple, and there are many books cover it, but locating the machine
name you want to chat with needs these NetServer* functions.

This function is designed for WInNT and not for Win95. Run it in VB4.0.

I welcome any critics and suggestions from anybody.

Rgds

Jeff Hong YAN

John Halsey

unread,
Apr 3, 1997, 3:00:00 AM4/3/97
to

I also spent months attempting to use the NET* API's. I finally got
several of them to work. However, Microsoft then released its Active
Directory Service Interface (ADSI), which made all of my coding
unnecessary...you can use ADSI to accomplish the same thing with less than
a dozen lines of code and no need for API calls!

Download the ADSI SDK from Microsoft's NT Server web page:
http://www.microsoft.com/ntserver/info/adsi.htm

John Halsey - MCSE
Enterprise Networking Consultant
MicroAge - Portland, OR


Hong YAN <HONG...@worldnet.att.net> wrote in article
<334302...@worldnet.att.net>...

< -- snip -- >


Joe Garrick

unread,
Apr 4, 1997, 3:00:00 AM4/4/97
to

"John Halsey" <jha...@teleport.com> wrote:

>I also spent months attempting to use the NET* API's. I finally got
>several of them to work. However, Microsoft then released its Active
>Directory Service Interface (ADSI), which made all of my coding
>unnecessary...you can use ADSI to accomplish the same thing with less than
>a dozen lines of code and no need for API calls!

Doesn't ADSI require NT 4.0?

Joe

Never underestimate the power of a WAG.
jgar...@citilink.com
http://www.citilink.com/~jgarrick/vbasic/

Hong YAN

unread,
Apr 5, 1997, 3:00:00 AM4/5/97
to

Hi all,

The following is the function I use to get the current logon domain name and machine
name. There could be many ways to get the current machine name (from registry, calling
WNet* function, etc..). This posting employs NetWksta* API function set.

I welcome any comments and suggestions.

Rgds
Jeff Hong YAN
(Hong...@WorldNet.ATT.Net)

Option Explicit

'Author: Jeff Hong YAN
'Date: 12/20/96
'Description: Pass in current user id to get the current domain name and machine name
'Works on Windows NT workstation on a Windows NT domain

Type WKSTA_INFO_101
wki101_platform_id As Long
wki101_computername As Long
wki101_langroup As Long
wki101_ver_major As Long
wki101_ver_minor As Long
wki101_lanroot As Long
End Type

Type WKSTA_USER_INFO_1
wkui1_username As Long
wkui1_logon_domain As Long
wkui1_logon_server As Long
wkui1_oth_domains As Long
End Type


Declare Sub lstrcpyW Lib "kernel32" (dest As Any, ByVal src As Any)

Declare Sub RtlMoveMemory Lib "kernel32" _
(dest As Any, src As Any, ByVal size&)

Declare Function NetWkstaGetInfo& Lib "Netapi32" _
(strServer As Any, ByVal lLevel&, pbBuffer As Any)

Declare Function NetWkstaUserGetInfo& Lib "Netapi32" _
(reserved As Any, ByVal lLevel&, pbBuffer As Any)

Declare Function NetApiBufferFree Lib "Netapi32" (ByVal buffer As Long) As Long

Function GetWorkstationInfo(sDomain As String, sMName As String) As Boolean

Dim lReturn As Long, buffer(512) As Byte
Dim i As Integer
Dim wk101 As WKSTA_INFO_101
Dim pwk101 As Long
Dim wk1 As WKSTA_USER_INFO_1
Dim pwk1 As Long
Dim sUserName As String
Dim sComputerName As String
Dim sLanGroup As String
Dim sLogonDomain As String

sComputerName = "": sLanGroup = "": sUserName = "": sLogonDomain = ""

'Call user defined function (in a another module)
sUserName = GetUserID()

lReturn = NetWkstaGetInfo(ByVal 0&, 101, pwk101)
RtlMoveMemory wk101, ByVal pwk101, Len(wk101)
lstrcpyW buffer(0), wk101.wki101_computername
i = 0
Do While buffer(i) <> 0
sComputerName = sComputerName & Chr(buffer(i))
i = i + 2
Loop
lstrcpyW buffer(0), wk101.wki101_langroup
i = 0
Do While buffer(i) <> 0
sLanGroup = sLanGroup & Chr(buffer(i))
i = i + 2
Loop
lReturn = NetApiBufferFree(pwk101)

lReturn = NetWkstaUserGetInfo(ByVal 0&, 1, pwk1)
RtlMoveMemory wk1, ByVal pwk1, Len(wk1)
lstrcpyW buffer(0), wk1.wkui1_logon_domain
i = 0
Do While buffer(i) <> 0
sLogonDomain = sLogonDomain & Chr(buffer(i))
i = i + 2
Loop
lReturn = NetApiBufferFree(pwk1)

Debug.Print sComputerName, sLanGroup, sUserName, sLogonDomain
sMName = sComputerName
sDomain = sLogonDomain

End Function

Hong YAN

unread,
Apr 5, 1997, 3:00:00 AM4/5/97
to Edmund Davis

Hi all,

The following is the function I use to trap error code returned by NetAPI32.dll function
calls.

I welcome any comments and suggestions.

Rgds
Jeff Hong YAN
(Hong...@WorldNet.ATT.Net)


'Author: Jeff Hong YAN
'Date: 11/21/96
'Description: This module is for handling the error code returned by NetAPI32.dll API
function calls
'Usage: pass the return value as the first argument, the second string argument is user
defined entity to be displayed as part of error msg.

Option Explicit

Const ERROR_ACCESS_DENIED = 5&
Const ERROR_ACCOUNT_DISABLED = 1331&
Const ERROR_ACCOUNT_EXPIRED = 1793&
Const ERROR_ACCOUNT_RESTRICTION = 1327&
Const ERROR_ADAP_HDW_ERR = 57&
Const ERROR_ALIAS_EXISTS = 1379&
Const ERROR_ALLOTTED_SPACE_EXCEEDED = 1344&
Const ERROR_ALREADY_ASSIGNED = 85&
Const ERROR_ALREADY_EXISTS = 183&
Const ERROR_ALREADY_RUNNING_LKG = 1074&
Const ERROR_ALREADY_WAITING = 1904&
Const ERROR_ARENA_TRASHED = 7&
Const ERROR_ARITHMETIC_OVERFLOW = 534&
Const ERROR_ATOMIC_LOCKS_NOT_SUPPORTED = 174&
Const ERROR_AUTODATASEG_EXCEEDS_64k = 199&
Const ERROR_BAD_ARGUMENTS = 160&
Const ERROR_BAD_COMMAND = 22&
Const ERROR_BAD_DESCRIPTOR_FORMAT = 1361&
Const ERROR_BAD_DEV_TYPE = 66&
Const ERROR_BAD_DEVICE = 1200&
Const ERROR_BAD_DRIVER = 2001
Const ERROR_BAD_DRIVER_LEVEL = 119&
Const ERROR_BAD_ENVIRONMENT = 10&
Const ERROR_BAD_EXE_FORMAT = 193&
Const ERROR_BAD_FORMAT = 11&
Const ERROR_BAD_IMPERSONATION_LEVEL = 1346&
Const ERROR_BAD_INHERITANCE_ACL = 1340&
Const ERROR_BAD_LENGTH = 24&
Const ERROR_BAD_LOGON_SESSION_STATE = 1365&
Const ERROR_BAD_NET_NAME = 67&
Const ERROR_BAD_NET_RESP = 58&
Const ERROR_BAD_NETPATH = 53&
Const ERROR_BAD_PATHNAME = 161&
Const ERROR_BAD_PIPE = 230&
Const ERROR_BAD_PROFILE = 1206&
Const ERROR_BAD_PROVIDER = 1204&
Const ERROR_BAD_REM_ADAP = 60&
Const ERROR_BAD_THREADID_ADDR = 159&
Const ERROR_BAD_TOKEN_TYPE = 1349&
Const ERROR_BAD_UNIT = 20&
Const ERROR_BAD_USERNAME = 2202&
Const ERROR_BAD_VALIDATION_CLASS = 1348&
Const ERROR_BADDB = 1009&
Const ERROR_BADKEY = 1010&
Const ERROR_BEGINNING_OF_MEDIA = 1102&
Const ERROR_BOOT_ALREADY_ACCEPTED = 1076&
Const ERROR_BROKEN_PIPE = 109&
Const ERROR_BUFFER_OVERFLOW = 111&
Const ERROR_BUS_RESET = 1111&
Const ERROR_BUSY = 170&
Const ERROR_BUSY_DRIVE = 142&
Const ERROR_CALL_NOT_IMPLEMENTED = 120&
Const ERROR_CAN_NOT_COMPLETE = 1003&
Const ERROR_CAN_NOT_DEL_LOCAL_WINS = 4001
Const ERROR_CANCEL_VIOLATION = 173&
Const ERROR_CANNOT_COPY = 266&
Const ERROR_CANNOT_FIND_WND_CLASS = 1407&
Const ERROR_CANNOT_IMPERSONATE = 1368&
Const ERROR_CANNOT_MAKE = 82&
Const ERROR_CANNOT_OPEN_PROFILE = 1205&
Const ERROR_CANT_ACCESS_DOMAIN_INFO = 1351&
Const ERROR_CANT_DISABLE_MANDATORY = 1310&
Const ERROR_CANT_OPEN_ANONYMOUS = 1347&
Const ERROR_CANTOPEN = 1011&
Const ERROR_CANTREAD = 1012&
Const ERROR_CANTWRITE = 1013&
Const ERROR_CHILD_MUST_BE_VOLATILE = 1021&
Const ERROR_CHILD_NOT_COMPLETE = 129&
Const ERROR_CHILD_WINDOW_MENU = 1436&
Const ERROR_CIRCULAR_DEPENDENCY = 1059&
Const ERROR_CLASS_ALREADY_EXISTS = 1410&
Const ERROR_CLASS_DOES_NOT_EXIST = 1411&
Const ERROR_CLASS_HAS_WINDOWS = 1412&
Const ERROR_CLIPBOARD_NOT_OPEN = 1418&
Const ERROR_CLIPPING_NOT_SUPPORTED = 2005
Const ERROR_CONNECTION_UNAVAIL = 1201&
Const ERROR_CONTROL_ID_NOT_FOUND = 1421&
Const ERROR_COUNTER_TIMEOUT = 1121&
Const ERROR_CRC = 23&
Const ERROR_CURRENT_DIRECTORY = 16&
Const ERROR_DATABASE_DOES_NOT_EXIST = 1065&
Const ERROR_DC_NOT_FOUND = 1425&
Const ERROR_DEPENDENT_SERVICES_RUNNING = 1051&

Sub NetErrorHandler(lErrorCode As Long, sEntity As String)

Dim sMsg As String

On Error Resume Next

If lErrorCode& = 0 Then Exit Sub
sMsg$ = "An error happend when " & sEntity$ & ", "
Select Case lErrorCode&
Case 5, 65
sMsg$ = sMsg$ + "you had insufficient privileges and the action failed."
Case 50
sMsg$ = sMsg$ + "a network request was not supported. Possibly :
NetStatisticsGet remoted to downLevel OS/2 server does not work for the service
LanmanWorkstation."
Case 53
sMsg$ = sMsg$ + "the domain controler name is wrong."
Case 71
sMsg$ = sMsg$ + "a network path was not found and the action failed."
Case 86
sMsg$ = sMsg$ + "an insufficient old password was used and the action failed."
Case 87
sMsg$ = sMsg$ + "an invalid parameter was specified and the action failed."
Case 123
sMsg$ = sMsg$ + "invalid computer name. (The computer name you gave is not NULL
or the client computer."
Case 124
sMsg$ = sMsg$ + "an invalid parameter was attempted to be passed and sMsged.
Possibly: wrong level specified."
Case 234
sMsg$ = sMsg$ + "too little memory (less than 40 bytes) was allocated to perform
the action and failed."
Case 2123
sMsg$ = sMsg$ + "too little memory was allocated to perform the action and
failed."
Case 2102
sMsg$ = sMsg$ + "a device driver was not installed to conduct the action."
Case 2106
sMsg$ = sMsg$ + "an action was attempted that can take place only from an NT
Server."
Case 2138
sMsg$ = sMsg$ + "NT Workstation services have not been started."
Case 2141
sMsg$ = sMsg$ + "either the NT Server is not configured for this transaction or
IPC is not shared."
Case 2202
sMsg$ = sMsg$ + "an invalid NT user name was specified and the action failed."
Case 2220
sMsg$ = sMsg$ + "the NT group name does not exist."
Case 2221
sMsg$ = sMsg$ + "the NT user account name was not found."
Case 2223
sMsg$ = sMsg$ + "the NT group name already exists."
Case 2224
sMsg$ = sMsg$ + "the NT user account name already exists."
Case 2226
sMsg$ = sMsg$ + "the specified NT Server is not the Primary Domain Controller
and the action failed."
Case 2227
sMsg$ = sMsg$ + "the NT Server is not running at user-level security."
Case 2228
sMsg$ = sMsg$ + "the accounts database became full. "
Case 2229
sMsg$ = sMsg$ + "a reserved error (code 2229) occurred in accessing the NT
accounts database."
Case 2245
sMsg$ = sMsg$ + "the password is too short."
Case 2247
sMsg$ = sMsg$ + "the NT accounts database file is corrupted."
Case 2320
sMsg$ = sMsg$ + "bad domain name."
Case 2351
sMsg$ = sMsg$ + "an invalid NT computer name was specified and the action
failed."
Case 2456
sMsg$ = sMsg$ + "the NT user accounts database cannot be enlarged because the NT
Server's hard disk is full."
Case Else
sMsg$ = sMsg$ + "an error code of " + Str(lErrorCode&) + " was generated."
End Select
MsgBox sMsg$ & " Error Code = " & lErrorCode&
End Sub

Hong YAN

unread,
Apr 5, 1997, 3:00:00 AM4/5/97
to

Hi all,

The following is the function I use to send a msg to a particular machine or all running
machines on a Windows NT domain.

This function could be useful if you develop a maintenance module in your application
for your network support guys.

I welcome any comments and suggestions.

Rgds
Jeff Hong YAN
(Hong...@WorldNet.ATT.Net)

Option Explicit

'Author: Jeff Hong YAN
'Date: 1/30/97
'Description: Send to msg to a machine on a Windows NT domain
' similar to that of the notification msg from printer spooler
'Note: Use with care to avoid a broadcasting to all workstations on the domain
' if you do not want to

Declare Function NetMessageBufferSend Lib "netapi32.dll" _
(ByVal sNullParam As String, ByVal pszServer As String, ByVal pszRecipient As
String, _
ByVal pbBuffer As String, ByVal cbBuffer As Long) As Long

Function SendMsg(sToDomn As String, sFromWho As String, sToWho As String, sTheMsg As

String) As Boolean
Dim lReturn As Long

Dim sToMachine As String * 256
Dim sToDomain As String
Dim sFrom As String * 256
Dim sMessage As String * 256
Dim lLength As Long

SendMsg = False
lLength = 256
sToMachine = StrConv(sToWho & vbNullChar, vbUnicode)
sToDomain = StrConv(sToDomn, vbUnicode) + vbNullChar
sFrom = StrConv(sFromWho & vbNullChar, vbUnicode)
sMessage = StrConv(Left$(sTheMsg, 256) & vbNullChar, vbUnicode)

lReturn = NetMessageBufferSend(sToDomain, sToMachine, sFrom, sMessage, lLength)

If lReturn = 0 Then
SendMsg = True
Else
Call NetErrorHandler(lReturn, "Sending message to " & sToWho & ". The message is
" & sTheMsg & ".")
End If
End Function

Hong YAN

unread,
Apr 5, 1997, 3:00:00 AM4/5/97
to

Hi all,

The following is the function I use to get the current domain
controller's machine name.

I welcome any comments and suggestions.

Rgds
Jeff Hong YAN
(Hong...@WorldNet.ATT.Net)


Option Explicit

'Author: Jeff Hong YAN
'Date: 12/4/96
'Description: The computer name of the primary domain
' controller on a Windows NT domain

Declare Function NetGetDCName Lib "netapi32.dll" (ByVal sServerName As
String, _
ByVal sDomainName As String, lPtr As Long) As Long

Declare Function PtrToStr Lib "kernel32" Alias "lstrcpyW" _

(bRet As Byte, ByVal lPtr As Long) As Long

Declare Function NetApiBufferFree Lib "Netapi32" (ByVal buffer As Long)
As Long

Function GetPrimaryDomainControllerName() As String

Dim sDCName As String
Dim bDCNArray() As Byte
Dim lReturn As Long
Dim lPtr As Long

ReDim bDCNArray(256)

lReturn = NetGetDCName(vbNullChar, vbNullChar, lPtr)

If lReturn <> 0 Then
Call NetErrorHandler(lReturn, "Calling NetGetDCName")
Exit Function
End If

lReturn = PtrToStr(bDCNArray(0), lPtr)

lReturn = NetApiBufferFree(lPtr)

sDCName = bDCNArray()
GetPrimaryDomainControllerName = sDCName

End Function

Hong YAN

unread,
Apr 5, 1997, 3:00:00 AM4/5/97
to Edmund Davis

Hi all,

The following is the function I use to validate user logon on Windows NT domain. User
accounts should be domain global accounts.

This function is useful if you want to implement an integrated user logon with the
Windows NT domain.

I welcome any comments and suggestions.

Rgds
Jeff Hong YAN
(Hong...@WorldNet.ATT.Net)


Option Explicit

'Author: Jeff Hong YAN
'Date: 1/21/97
'Description: Validate user id and password on Windows NT domain.
'This function works on NT Workstation
'If you want to crack a user account, this function is a start.

Private Declare Function LogonUserA Lib "advapi32.dll" _
(ByVal lpszUsername As String, ByVal lpszDomain As String, _
ByVal lpszPassword As String, ByVal dwLogonType As Long, _
ByVal dwLogonProvider As Long, phToken As Long) As Long

'Logon type
Const LOGON32_LOGON_INTERACTIVE = 2
Const LOGON32_NETWORK = 3
Const LOGON32_LOGON_BATCH = 4
Const LOGON32_LOGON_SERVICE = 5

'Provider
Const LOGON32_PROVIDER_WINNT35 = 1
Const LOGON32_PROVIDER_DEFAULT = 0

Function ValidateLogon(UserID As String, Pass As String, sDomainName As String) As
Boolean

Dim sUserID As String
Dim sPass As String
Dim sDName As String

Dim hToken As Long
Dim lReturn As Long

sUserID = Trim$(UserID)
sPass = Trim$(Pass)
ValidateLogon = False

lReturn = LogonUserA(sUserID, sDomainName, sPass, LOGON32_NETWORK ,
LOGON32_PROVIDER_DEFAULT, hToken)
If lReturn = 1 Then
ValidateLogon = True
Exit For
End If

End Function

Hong YAN

unread,
Apr 8, 1997, 3:00:00 AM4/8/97
to Edmund Davis

Hi all,

The following is the function I use to get the current user id. There could be many ways
to do this job and I will be back on this later.

This function has been tested on Windows NT wksta / svr and Win95.

I welcome any comments and suggestions.

Rgds
Jeff Hong YAN
(Hong...@WorldNet.ATT.Net)

Option Explicit

Declare Function WNetGetUser Lib "mpr" Alias "WNetGetUserA" (ByVal lpName As String,
ByVal lpUserName As String, lpnLength As Long) As Long

Function GetUser() As String

Dim sUserNameBuff As String * 255
Dim lbuff As Long
Dim lReturnValue As Long

sUserNameBuff = Space(255)
lbuff = 255

lReturnValue = WNetGetUser(vbNullString, sUserNameBuff, lbuff)

If lReturnValue = 0 Then
GetUser = sUserNameBuff
Else
GetUser = "Not Known"
End If

End Function

Hong YAN

unread,
Apr 8, 1997, 3:00:00 AM4/8/97
to Edmund Davis

Hi all,

The following is the function I use to get the complete infomation on any valid
global user account. Please note that on Windows NT, local groups and local users are
maintained with different API functions as global groups and group users. (If one gets
confused on this, he's got to take an NT course first.) I will come back on local user
info problem later.

I welcome any comments and suggestions.

Rgds
Jeff Hong YAN
(Hong...@WorldNet.ATT.Net)

Option Explicit

'===============================
'Author : Jeff Hong YAN
'Date : 12/4/97
'Description: Function to get complete user information
'Note : Assume current user has account operator permission
' Function works on WinNT workstation or server.
' (Tested on WinNT workstation / server 3.51, 4.0)
'===============================

'Type TUser0 ' Level 0
' ptrName As Long
'End Type

'Type TUser1 ' Level 1
' ptrName As Long
' ptrPassword As Long
' dwPasswordAge As Long
' dwPriv As Long
' ptrHomeDir As Long
' ptrComment As Long
' dwFlags As Long
' ptrScriptPath As Long
'End Type
'

'Type Level_2_User_Structure_Type
' ptrName As Long
' ptrPassword As Long
' dwPasswordAge As Long
' dwPriv As Long
' ptrHomeDir As Long
' ptrComment As Long
' dwFlags As Long
' ptrScriptPath As Long
' dwAuthFlags As Long
' ptrFullName As Long
' ptrComment2 As Long
' ptrParms As Long
' ptrWorkstations As Long
' dwLastLogon As Long
' dwLastLogoff As Long
' dwAccountExpires As Long
' dwMaxStorage As Long
' dwUnitsPerWeek As Long
' pbLogonHours As Long
' dwBadPasswordCount As Long
' dwNumLogons As Long
' ptrLogonServer As Long
' dwCountryCode As Long
' dwCodePage As Long
'End Type

'
' for dwPriv
'
'Const USER_PRIV_MASK = &H3
'Const USER_PRIV_GUEST = &H0
'Const USER_PRIV_USER = &H1
'Const USER_PRIV_ADMIN = &H2

'
' for dwFlags
'
'Const UF_SCRIPT = &H1
'Const UF_ACCOUNTDISABLE = &H2
'Const UF_HOMEDIR_REQUIRED = &H8
'Const UF_LOCKOUT = &H10
'Const UF_PASSWD_NOTREQD = &H20
'Const UF_PASSWD_CANT_CHANGE = &H40
'Const UF_NORMAL_ACCOUNT = &H200 ' Needs to be ORed with the
' other flags

Private Type USER_INFO_3
usri3_name As Long 'LPWSTR
usri3_password As Long 'LPWSTR
usri3_password_age As Long 'DWORD
usri3_priv As Long 'DWORD
usri3_home_dir As Long 'LPWSTR
usri3_comment As Long 'LPWSTR
usri3_flags As Long 'DWORD
usri3_script_path As Long 'LPWSTR
usri3_auth_flags As Long 'DWORD
usri3_full_name As Long 'LPWSTR
usri3_usr_comment As Long 'LPWSTR
usri3_parms As Long 'LPWSTR
usri3_workstations As Long 'LPWSTR
usri3_last_logon As Long 'DWORD
usri3_last_logoff As Long 'DWORD
usri3_acct_expires As Long 'DWORD
usri3_max_storage As Long 'DWORD
usri3_units_per_week As Long 'DWORD
usri3_logon_hours As Long 'PBYTE
usri3_bad_pw_count As Long 'DWORD
usri3_num_logons As Long 'DWORD
usri3_logon_server As Long 'LPWSTR
usri3_country_code As Long 'DWORD
usri3_code_page As Long 'DWORD
usri3_user_id As Long 'DWORD
usri3_primary_group_id As Long 'DWORD
usri3_profile As Long 'LPWSTR
usri3_home_dir_drive As Long 'LPWSTR
usri3_password_expired As Long 'DWORD
End Type

Declare Function MyPtrToStr Lib "kernel32" Alias "lstrcpyW" _
(RetVal As String, ByVal ptr As Long) As Long

Private Declare Function MyNetUserGetInfo Lib "netapi32.dll" Alias "NetUserGetInfo" ( _
ByVal strServerName As String, ByVal strUserName As String, ByVal dwLevel As Long, _
pBuffer As Long) As Long

Declare Sub RtlMoveMemory Lib "kernel32" (hpvDest As _
Any, ByVal hpvSource As Long, ByVal cbCopy As Long)

Function GetUserInfo(sUserID As String, sUserFullName As String, sUserDescription As
String)

'===============================
'Author : Jeff Hong YAN
'Date : 12/4/97
'Description:
' (1) sUserID: user id you pass in
' (2) sUserFullName: string buffer to return the full name of the
' specified user id
' (3) sUserDescription: string buffer to return the description of
' specified user id
'===============================


Dim swServer As String * 255
Dim swUserID As String * 255
Dim lReturn As Long
Dim ptmpBuffer As Long
Dim ptr As Long

Dim sUser As String
Dim sByte() As Byte
ReDim sByte(255)

swUserID = StrConv(sUserID & vbNullChar, vbUnicode)
swServer = StrConv(vbNullChar, vbUnicode)

Dim tmpBuffer As USER_INFO_3

lReturn = MyNetUserGetInfo(swServer, swUserID, 3&, ptmpBuffer)

RtlMoveMemory tmpBuffer, ptmpBuffer, LenB(tmpBuffer)

ptr = tmpBuffer.usri3_full_name
If ptr <> 0 Then
RtlMoveMemory sByte(0), ptr, 256
sUser = vbNullString
sUser = sByte
sUser = sUser & vbNullChar
Else
sUser = "(Unknown)"
End If
sUserFullName = sUser

ptr = tmpBuffer.usri3_comment
If ptr <> 0 Then
RtlMoveMemory sByte(0), ptr, 256
sUser = vbNullString
sUser = sByte
sUser = sUser & vbNullChar
Else
sUser = "(Unknown)"
End If
sUserDescription = sUser

Call NetAPIBufferFree(ptmpBuffer)
End Function

Hong YAN

unread,
Apr 8, 1997, 3:00:00 AM4/8/97
to

Hi all,

The following is the function I use to get the current machine name. There could be many
ways to get the current machine name (from registry, calling NetWksta* function, etc..).
In a previous posting, a similar function for Windows NT was proposed. This posting
employs Kernel32 API function and it works on Win95 / NT.

I welcome any comments and suggestions.

Rgds
Jeff Hong YAN
(Hong...@WorldNet.ATT.Net)

Option Explicit

Private Declare Function GetComputerName _
Lib "kernel32" Alias "GetComputerNameA" ( _
ByVal lpBuffer As String, _
nSize As Long) As Long

Public Function CurrentMachineName() As String
Dim lSize As Long
Dim sBuffer As String * 255

On Error GoTo CurrentMachineName_Err
lSize = 255
If GetComputerName(sBuffer, lSize) Then
lSize = InStr(sBuffer, Chr$(0)) - 1
CurrentMachineName = Left$(sBuffer, lSize)
Else
CurrentMachineName = "(Not Known)"
End If

CurrentMachineName_Exit:
Exit Function
CurrentMachineName_Err:
MsgBox "Program error in function CurrentMachineName. Error code = " & Err,
vbOKOnly, "Error"
Resume CurrentMachineName_Exit
Resume
End Function

Hong YAN

unread,
Apr 12, 1997, 3:00:00 AM4/12/97
to Edmund Davis

Hi all,

The following is the function I use to change user password. There could be many ways to
do the job. This function requires that you pass in the server name, user id, and new
password.

I welcome any comments and suggestions.

Rgds
Jeff Hong YAN
(Hong...@WorldNet.ATT.Net)


Option Explicit

'typedef struct _USER_INFO_1003 {
' LPWSTR usri1003_password;
'} USER_INFO_1003, *PUSER_INFO_1003, *LPUSER_INFO_1003;

Private Type TUser1003
ptrPassword As Long
End Type

Private Declare Function NetUserSetPassword Lib "NETAPI32.DLL" Alias _
"NetUserSetInfo" (ByVal ServerName As String, ByVal UserName As String, _
ByVal Level As Long, Buffer As TUser1003, ParmError As Long) As Long

Private Declare Function GetPointer Lib "kernel32.dll" Alias "lstrcpyW" _
(ByVal lpString1 As String, ByVal lpString2 As String) As Long

Private Declare Function NetAPIBufferFree Lib "NETAPI32.DLL" Alias "NetApiBufferFree" _
(ByVal ptr As Long) As Long

Function NetChangePassword( _
sServerName As String, _
sUserName As String, _
sNewPassword As String) As Boolean

Dim lReturnValue As Long
Dim swServerName As String
Dim swUserName As String
Dim tpUserInfo As TUser1003

Dim ptrNewPass As Long
Dim ptrError As Long

sNewPassword = StrConv(sNewPassword & vbNullChar, vbUnicode)
ptrNewPass = GetPointer(sNewPassword, sNewPassword)
If ptrNewPass = 0 Then
MsgBox "Error converting new password into pointer"
GoTo NetChangePassword_Exit
End If

tpUserInfo.ptrPassword = ptrNewPass

swServerName = StrConv(sServerName & vbNullChar, vbUnicode)
swUserName = StrConv(sUserName & vbNullChar, vbUnicode)

lReturnValue = NetUserSetPassword(swServerName, swUserName, 1003&, tpUserInfo,
ptrError)
If lReturnValue <> 0 Then
NetChangePassword = False

Call NetErrorHandler(lReturnValue, "change password for user: " & sUserName)
Debug.Print "Change password failed."
Else
NetChangePassword = True
Debug.Print "Change password succeeded."
End If

lReturnValue = NetAPIBufferFree(ptrNewPass)
NetChangePassword_Exit:
Exit Function
NetChangePassword_Err:
MsgBox "Program error happened. Error code = " & Err
Resume NetChangePassword_Exit
Resume
End Function

Hong YAN

unread,
Apr 13, 1997, 3:00:00 AM4/13/97
to Edmund Davis

4/13/97

Hi all,

The following is the function I use to enumerate global user groups using "NetGroupEnum"
API function. The way of wrapping this API function is exactly the same as I did for
the enumerating local user gropus in a previous posting.

In later postings, I will give wrapper functions for enumerating users and workstations
running on an NT domain.

Comments and suggestions are welcome. Thank you for reading.

Rgds
Jeff Hong YAN
(Hong...@WorldNet.ATT.Net)


Option Explicit

Declare Function NetAPIBufferFree Lib "netapi32.dll" Alias _
"NetApiBufferFree" (ByVal ptr As Long) As Long

Declare Function CopyMem Lib "kernel32" Alias "RtlMoveMemory" ( _
hpvDest As Any, ByVal hpvSource As Long, ByVal cbCopy As Long) As Long

Declare Function StrLen Lib "kernel32" Alias "lstrlenW" (ByVal ptr As Long) As Long

Private Declare Function MyNetGroupEnum0 Lib "netapi32.dll" Alias "NetGroupEnum" _


(ByVal ServerName As String, _

ByVal level As Long, _
Buffer As Long, _
ByVal PrefMaxLen As Long, _
lEntriesRead As Long, _
lTotalEntries As Long, _
lResumeHandle As Long) As Long

Function EnumerateGroups(ByVal sMName As String, ByRef sReturn() As String) As Long
Dim lResult As Long
Dim lBufPtr As Long
Dim lEntriesRead As Long
Dim lTotalEntries As Long
Dim lResumeHandle As Long
Dim lBufLen As Long

Dim sGName As String
Dim I As Integer
Dim swMName As String

Dim lTheWord As Long
Dim bTestArray() As Byte
Dim sTest As String

lBufLen = 255 ' Buffer size
lResumeHandle = 0 ' Start with the first entry
swMName = StrConv(sMName, vbUnicode)
ReDim bTestArray(255)
ReDim sReturn(0)

Do
lResult = MyNetGroupEnum0(swMName, 0, lBufPtr, lBufLen, _
lEntriesRead, lTotalEntries, lResumeHandle)

EnumerateGroups = lResult
If lResult <> 0 And lResult <> 234 Then ' 234 means multiple reads required
Call NetErrorHandler(lResult, "Error " & _
lResult & " enumerating group " & _
lEntriesRead & " of " & lTotalEntries)
Exit Function
End If

'Get the data into an array and then a string
lResult = CopyMem(bTestArray(0), lBufPtr, 256&)
sTest = bTestArray

For I = 0 To lEntriesRead - 1
'Munge 4 bytes into a long integer
lTheWord = CInt(bTestArray(I * 4 + 3)) * 2 ^ 8 + _
CInt(bTestArray(I * 4 + 2)) * 2 ^ 16 + _
CInt(bTestArray(I * 4 + 1)) * 2 ^ 8 + _
CInt(bTestArray(I * 4))
'Extract the substring we want
sGName = Mid(sTest, (lTheWord - lBufPtr) / 2 + 1, StrLen(lTheWord))

If I > UBound(sReturn) Then ReDim Preserve sReturn(UBound(sReturn) + 1)
sReturn(I) = sGName
Next I

Loop Until lEntriesRead = lTotalEntries

lResult = NetAPIBufferFree(lBufPtr) ' Don't leak memory

End Function

Hong YAN

unread,
Apr 13, 1997, 3:00:00 AM4/13/97
to

Enjoy

Hong YAN

unread,
Apr 13, 1997, 3:00:00 AM4/13/97
to

Enjoy

Hong YAN

unread,
Apr 13, 1997, 3:00:00 AM4/13/97
to Edmund Davis

4/13/97

Hi all,

The following is the function I use to enumerate local user groups using
"NetLocalGroupEnum" API function. The way of wrapping any Enum type NetAPI32.dll API
function is very sterotyped and in the following code, I have tried to make it as
self-explaining as possible.

In later postings, I will show that enumerating global group, enermerating users,
browsing NT domain for workstation according to a criteria are just similar. And I will
also simplify the NT domain browser function I posted in the first one of this series
using this method. Copy the wrapper function and do a cut-paste to replace the Enum API,
and that's it.

When dealing with an Enum type API wrapper, remember one thing, that is, check the total
entries value. If that value is more than 1, you have to run a loop to exhaust all the
entries, or you loss information.

As I remarked in a previous posting, local user accounts are very different from global
user accounts programmatically. One should use different sets of API functions. Check
this in the NT Resource Kit if you are confused.

Comments and suggestions are welcome. Thank you for reading.

Rgds
Jeff Hong YAN
(Hong...@WorldNet.ATT.Net)


Option Explicit

Type TGroup0


ptrName As Long
End Type

Declare Function NetAPIBufferFree Lib "NetAPI32.dll" Alias _


"NetApiBufferFree" (ByVal ptr As Long) As Long

Declare Function CopyMem Lib "kernel32" Alias "RtlMoveMemory" ( _
hpvDest As Any, ByVal hpvSource As Long, ByVal cbCopy As Long) As Long

Declare Function StrLen Lib "kernel32" Alias "lstrlenW" (ByVal ptr As Long) As Long

' Quotation from MSDN:
' Security Requirements
'Admin or account operator group membership is required to successfully execute
NetLocalGroupEnum.
'NET_API_STATUS NET_API_FUNCTION
' NetLocalGroupEnum (
' LPWSTR servername OPTIONAL,
' DWORD level,
' OUT LPBYTE * bufptr,
' DWORD prefmaxlen,
' OUT LPDWORD entriesread,
' OUT LPDWORD totalentries,
' OUT LPDWORD resumehandle OPTIONAL
' );

Private Declare Function MyNetGroupEnum0 Lib "NetAPI32.dll" _
Alias "NetLocalGroupEnum" ( _
ByVal ptrServerName As String, _


ByVal dwLevel As Long, _

ptrBuf As TGroup0, _
ByVal dwMaxLen As Long, _
ptrlEntriesRead As Long, _
ptrlTotalEntries As Long, _
ptrlResumeHandle As Long) As Long

Function EnumerateGroups(ByVal sMName As String, ByRef sReturn() As String) As Long
Dim lResult As Long

Dim BufPtr As TGroup0


Dim lEntriesRead As Long
Dim lTotalEntries As Long
Dim lResumeHandle As Long
Dim lBufLen As Long

Dim sGName As String
Dim I As Integer

Dim swMName As String

Dim lTheWord As Long
Dim bTestArray() As Byte
Dim sTest As String

lBufLen = 255 ' Buffer size
lResumeHandle = 0 ' Start with the first entry
swMName = StrConv(sMName, vbUnicode)
ReDim bTestArray(255)
ReDim sReturn(0)

Do 'or you loss information.
lResult = MyNetGroupEnum0(swMName, 0&, BufPtr, lBufLen, _
lEntriesRead, lTotalEntries, lResumeHandle)


If lResult <> 0 And lResult <> 234 Then ' 234 means multiple reads required

Call NetErrorHandler(lResult, "Enumerating Local Groups " & lEntriesRead & "

of " & lTotalEntries)
Exit Function
End If

'Get the data into an array and then a string

lResult = CopyMem(bTestArray(0), BufPtr.ptrName, 256&) '


sTest = bTestArray

For I = 0 To lEntriesRead - 1
'Munge 4 bytes into a long integer
lTheWord = CInt(bTestArray(I * 4 + 3)) * 2 ^ 8 + _
CInt(bTestArray(I * 4 + 2)) * 2 ^ 16 + _
CInt(bTestArray(I * 4 + 1)) * 2 ^ 8 + _
CInt(bTestArray(I * 4))
'Extract the substring we want

sGName = Mid(sTest, (lTheWord - BufPtr.ptrName) / 2 + 1, StrLen(lTheWord))



If I > UBound(sReturn) Then ReDim Preserve sReturn(UBound(sReturn) + 1)
sReturn(I) = sGName

Debug.Print sGName


Next I

Loop Until lEntriesRead = lTotalEntries

lResult = NetAPIBufferFree(BufPtr.ptrName) ' Don't leak memory

End Function

Hong YAN

unread,
Apr 13, 1997, 3:00:00 AM4/13/97
to Edmund Davis

4/13/97

Hi all,

The following is one of the functions I use to change user password. There could be many
ways to do the job. In a previous posting, it is done using NetUserSetInfo which enables
you to change a user's password w/o knowing his old password (which is one of the daily
duties of a network administrator), while it is done in this posting using
NetUserChangePassword which requires you to enter the correct old password of the user
to change his password.

This function requires the name of the domain where the user is currently logon, the old
password and the user id whose password you want to change, and the new password your
want to enforce for the specified user. You can change another user's password if you
have the proper permission.

Error handling notes:
(1) When lReturnValue = 86, you have entered a wrong old password.
(2) When lReturnValue = 2245, the new password is too long or too short.

I welcome any comments and suggestions.

Rgds
Jeff Hong YAN
(Hong...@WorldNet.ATT.Net)


Option Explicit

Private Declare Function GetPointer Lib "kernel32.dll" Alias "lstrcpyW" _
(ByVal lpString1 As String, ByVal lpString2 As String) As Long

Private Declare Function NetAPIBufferFree Lib "netapi32.dll" Alias "NetApiBufferFree" _


(ByVal ptr As Long) As Long

Private Declare Function NetUserChangePassword Lib "netapi32.dll" ( _
swDName As Byte, sUName As Byte, sOldPass As Byte, sNewPass As Byte) As Long
' C prototype
'NET_API_STATUS NetUserChangePassword(
' LPWSTR domainname, // pointer to server or domain name string
' LPWSTR username, // pointer to user name string
' LPWSTR oldpassword, // pointer to old password string
' LPWSTR newpassword, // pointer to new password string
' );

Sub ChangePass(sUserName As String, sOldPass As String, sNewPass As String)
Dim lReturnValue As Long

Dim bUserName() As Byte
Dim bDomainName() As Byte
Dim bOldPass() As Byte
Dim bNewPass() As Byte

bUserName = sUserName & vbNullChar
bDomainName = vbNullChar
bOldPass = sOldPass & vbNullChar
bNewPass = sNewPass & vbNullChar

lReturnValue = NetUserChangePassword(bDomainName(0), bUserName(0), bOldPass(0),
bNewPass(0))
Debug.Print lReturnValue

If lReturnValue <> 0 Then
Call NetErrorHandler(lReturnValue, "Changing password for user " & sUserName)
Else
MsgBox "Success"
End If

End Sub

Hong YAN

unread,
Apr 15, 1997, 3:00:00 AM4/15/97
to

4/15/97

Hi all,

The following are functions I use to include or exclude an existing user to/from a
global group on a Windows NT domain.

Rgds
Jeff Hong YAN
(Hong...@WorldNet.ATT.Net)

Option Explicit

Declare Function NetGroupDelUser Lib "netapi32.dll" (ServerName As Byte, _
GroupName As Byte, Username As Byte) As Long

Declare Function NetGroupAddUser Lib "netapi32.dll" (ServerName As Byte, _
GroupName As Byte, Username As Byte) As Long

Function AddUserToGroup(ByVal sSName As String, ByVal sGName As String, ByVal sUName As
String) As Boolean
'Arguments: sSName is server name or empty for default server
' sGName is global group name
' sUName is an existing user id

Dim SNArray() As Byte
Dim GNArray() As Byte
Dim UNArray() As Byte
Dim lResult As Long

AddUserToGroup = False
SNArray = sSName & vbNullChar 'Server machine name, null for default server.
GNArray = sGName & vbNullChar 'Global group name
UNArray = sUName & vbNullChar 'User Name
lResult = NetGroupAddUser(SNArray(0), GNArray(0), UNArray(0))

Select Case lResult
Case 2220
'You probably mistook a local group as a global group or mistyped group name
MsgBox "There is no such global group '" & sGName & "'.", vbOKOnly, ""
Exit Function
Case 2236
MsgBox "This user '" & sUName & "' is already in this group.", vbOKOnly, ""
Exit Function
End Select

If lResult <> 0 Then
Call NetErrorHandler(lResult, "Add New User " & sUName)
Else
AddUserToGroup = True
End If

End Function

Function DelUserFromGroup(ByVal sSName As String, ByVal sGName As String, ByVal sUName
As String) As Boolean
'Arguments: sSName is server name or empty for default server
' sGName is global group name
' sUName is a user id presumably in this group

Dim SNArray() As Byte
Dim GNArray() As Byte
Dim UNArray() As Byte
Dim lResult As Long

DelUserFromGroup = False
SNArray = sSName & vbNullChar 'Server machine name, null for default server.
GNArray = sGName & vbNullChar 'Global group name
UNArray = sUName & vbNullChar 'User Name
lResult = NetGroupDelUser(SNArray(0), GNArray(0), UNArray(0))

Select Case lResult
Case 2220
'You probably mistook a local group as a global group or mistyped group name.
MsgBox "There is no such global group '" & sGName & "'.", vbOKOnly, ""
Exit Function
Case 2234
'You probably tried to delete a user from domain user group.
MsgBox "The user '" & sUName & "' must be in this primary group.", vbOKOnly, ""
Exit Function
Case 2237
'You probably mistyped the user name or you had excluded it already.
MsgBox "This user '" & sUName & "' is not in this group.", vbOKOnly, ""
Exit Function
End Select

If lResult <> 0 Then
Call NetErrorHandler(lResult, " delele user " & sUName)
Else
DelUserFromGroup = True
End If

End Function

Hong YAN

unread,
Apr 18, 1997, 3:00:00 AM4/18/97
to Edmund Davis

4/18/97

Hi all,

The following is functions I use to get (1) SIDs of users, groups, domains when you give
the user id, group name, or domain name; (2) user id, group name, domain name when you
give the SID.

This is the first VB wrapper functions of SID APIs posted in user groups, as far as I
know.

Kev-:

Thank you for your info.

Rgds
Jeff Hong YAN
(Hong...@WorldNet.ATT.Net)

Option Explicit

Declare Function NetAPIBufferAllocate Lib "netapi32.dll" Alias _
"NetApiBufferAllocate" (ByVal ByteCount As Long, Ptr As Long) As Long

Private Declare Function NetAPIBufferFree Lib "netapi32.dll" Alias _
"NetApiBufferFree" (ByVal Ptr As Long) As Long



Declare Function CopyMem Lib "kernel32" Alias "RtlMoveMemory" ( _

hpvDest As Any, ByVal hpvSource As Long, ByVal cbCopy As Long) As Long

Private Declare Function LookupAccountSid Lib "advapi32.dll" Alias "LookupAccountSidA" _
(ByVal lpSystemName As String, _
SID As Any, _
ByVal name As String, _
cbName As Long, _
ByVal ReferencedDomainName As String, _
cbReferencedDomainName As Long, _
peUse As Integer) As Long

Public Declare Function LookupAccountName Lib "advapi32.dll" Alias "LookupAccountNameA"
_
(ByVal lpSystemName As String, _
ByVal lpAccountName As String, _
SID As Byte, _
cbSid As Long, _
ByVal ReferencedDomainName As String, _
cbReferencedDomainName As Long, _
peUse As Integer) As Long

Function GetMySID(sSName As String, sDName As String, sUname As String, bRet() As Byte,
iType As Integer) As String

'Author: Jeff Hong YAN
'Date : 4/17/97
'Arguments:
' sSName : current server name (In)
' sDName : current domain name (In)
' sUName : the account name (user id, global group name, etc..) (In)
' bRet() : SID of the account (Out)
' iType : Type of SID (Out)
'Return Value:
' SID in a string

Dim lReturn As Long
Dim iSIDTypeBuf As Integer
Dim ptrSIDBuf As Long
Dim sRet As String

Dim lSIDBufLen As Long
Dim lDNameLen As Long

Dim i As Long
Dim sSID As String

'I pass sDName string ByVal, to the dll function, the length of the string
'is increased by 1 byte. This is a trap that goes to GPF
lDNameLen = Len(sDName) + 1

lSIDBufLen = 0
iSIDTypeBuf = 0
ReDim bRet(0)
lReturn = LookupAccountName( _
sSName, _
sUname, _
bRet(0), _
lSIDBufLen, _
sDName, _
lDNameLen, _
iSIDTypeBuf)

Select Case Err.LastDllError
Case 1722
MsgBox "Bad server name - Jeff"
Case 1322, 1332
MsgBox "Bad user name - Jeff"
Case 122 'buffer size for sid is not large enough
lReturn = NetAPIBufferAllocate(lSIDBufLen, ptrSIDBuf)
If lReturn <> 0 Then
MsgBox "Failed in allocating space"
GoTo GetMySID_Exit
End If
sSID = Space(lSIDBufLen + 1)
sDName = Space(255)
lDNameLen = 255
ReDim bRet(lSIDBufLen)

lReturn = LookupAccountName( _
sSName, _
sUname, _
bRet(0), _
lSIDBufLen, _
sDName, _
lDNameLen, _
iSIDTypeBuf)
If lReturn <> 0 Then
For i = 1 To lSIDBufLen
sRet = sRet & bRet(i - 1)
Next i
Debug.Print "Type = " & iSIDTypeBuf & " SID = " & sRet & " Description = " &
sDName
GetMySID = sRet
iType = iSIDTypeBuf
Else
GetMySID = ""
MsgBox "Failed in obtaining the SID."
GoTo GetMySID_Exit
End If
Case Else
Call NetErrorHandler(Err.LastDllError, "get the SID of account '" & sUname &
"'.")
End Select

GetMySID_Exit:
lReturn = NetAPIBufferFree(ptrSIDBuf)
End Function

Function CheckMySID(sSName As String, sDName As String, bSID() As Byte, iAccountType As
Integer) As String

'Author: Jeff Hong YAN
'Date : 4/17/97
'Arguments:
' sSName : current server name (In)
' sDName : current domain name (In)
' bSID() : SID of the account (In)
' iAccountType : Type of SID (Out)
'Return Value:
' Account Name in a string

Dim lReturn As Long 'Hold return value of API calls
Dim sUNameBuf As String 'Buffer of account name returned by API
Dim lUNameBufLen As Long 'Length of the account name buffer
Dim lDNameLen As Long 'Length of domain name

'I pass sDName string ByVal, to the dll function, the length of the string
'is increased by 1 byte. This is a trap that goes to GPF
lDNameLen = Len(sDName) + 1

lUNameBufLen = 255
sUNameBuf = Space(lUNameBufLen)
lReturn = LookupAccountSid( _
sSName, _
bSID(0), _
sUNameBuf, _
lUNameBufLen, _
sDName, _
lDNameLen, _
iAccountType)


If lReturn = 0 Then

Call NetErrorHandler(Err.LastDllError, "")
Else
' MsgBox "Success!!!"
CheckMySID = Left$(sUNameBuf, InStr(sUNameBuf, Chr$(0)) - 1)
End If

End Function

Hong YAN

unread,
Apr 18, 1997, 3:00:00 AM4/18/97
to Edmund Davis

4/18/97

Hi all,

The following is a function I use to include an existing user (or global group) to a
local group on a Windows NT server. This function depends on GetMySID function I posted
earlier to run.

Rgds
Jeff Hong YAN
(Hong...@WorldNet.ATT.Net)


Option Explicit

'Author : Jeff Hong YAN
'Description: Include an account into a local group.
'Note: The account could be a user (then enter user id),
' or a global group (then enter group name)

'C prototype of add member API function
'NET_API_STATUS NET_API_FUNCTION
' NetLocalGroupAddMember (
' LPWSTR servername OPTIONAL,
' LPWSTR groupname,
' PSID membersid
' );

Private Declare Function NetLocalGroupAddMember Lib "netapi32.dll" ( _
sServerName As Byte, _
sGroupName As Byte, _
bMemberSID As Byte) As Long

Function AddUserToLGroup(ByVal sSName As String, ByVal sGName As String, bUSIDArray() As
Byte) As Boolean
'Arguments:
' sSName : server name
' sGName : Target local group name
' bUSIDArray(): Byte array of SID of the account
' you want to include into the target group
'Return Value:
' True : if success
' False : if failure

Dim SNArray() As Byte
Dim GNArray() As Byte

Dim lResult As Long

AddUserToLGroup = False



SNArray = sSName & vbNullChar 'Server machine name, null for default server.

GNArray = sGName & vbNullChar 'Local group name
lResult = NetLocalGroupAddMember(SNArray(0), GNArray(0), bUSIDArray(0))

Select Case lResult
Case 1337
MsgBox "There is no such account.", vbOKOnly, ""
Exit Function
Case 1378
MsgBox "This user is already in this group.", vbOKOnly, ""


Exit Function
End Select

If lResult <> 0 Then

Call NetErrorHandler(lResult, "add user into " & sGName & ".")
Else
AddUserToLGroup = True
End If

End Function

Pierre Fillion

unread,
Apr 18, 1997, 3:00:00 AM4/18/97
to

On Sat, 19 Apr 1997 12:00:16 -0300, Hong YAN
<HONG...@worldnet.att.net> wrote:

Do you know how to get the user domain in Win95 and WinNT4 without the
need to create a C++ dll ? I'm using vb5.

Thanks

>Hi all:
>
>This is another API wrapper to get user id on WinNT/Win95
>
>Tested on WinNT Server 4.0/Win95
>Jeff Hong YAN
>
>
>Option Explicit
>
>Private Declare Function GetUserName Lib "advapi32.dll" Alias "GetUserNameA" ( _
>ByVal lpBuffer As String, nSize As Long) As Long
>
>Function GetMyUserName()
> Dim sName As String
> Dim lNameLen As Long
> Dim lReturn As Long
>
> lNameLen = 255
> sName = Space(256)
> lReturn = GetUserName(sName, lNameLen)
> If sName = "" Then
> GetMyUserName = "(Unknown)"
> Else
> GetMyUserName = Left$(sName, InStr(sName, Chr$(0)) - 1)
> End If
>End Function


Hong YAN

unread,
Apr 19, 1997, 3:00:00 AM4/19/97
to

4/18/97

Hi all,

The following are functions I use to include / exclude an existing member (or global
group) to/from a local group on a Windows NTserver. These functions depend on GetMySID

function I posted earlier to run.

Rgds
Jeff Hong YAN
(Hong...@WorldNet.ATT.Net)

Option Explicit

'Author : Jeff Hong YAN
'Date : 4/18/97


'Description: Include an account into a local group.
'Note: The account could be a user (then enter user id),
' or a global group (then enter group name)

'C prototype of add member API function
'NET_API_STATUS NET_API_FUNCTION
' NetLocalGroupAddMember (
' LPWSTR servername OPTIONAL,
' LPWSTR groupname,
' PSID membersid
' );

Private Declare Function NetLocalGroupAddMember Lib "netapi32.dll" ( _
sServerName As Byte, _
sGroupName As Byte, _
bMemberSID As Byte) As Long

'C proto type for removing a member from a local group
'NET_API_STATUS NET_API_FUNCTION
' NetLocalGroupDelMember (


' LPWSTR servername OPTIONAL,
' LPWSTR groupname,
' PSID membersid
' );

Private Declare Function NetLocalGroupDelMember Lib "netapi32.dll" ( _


sServerName As Byte, _
sGroupName As Byte, _
bMemberSID As Byte) As Long

Function DelUserFromLGroup(ByVal sSName As String, ByVal sGName As String, bUSIDArray()
As Byte) As Boolean
'Author : Jeff Hong YAN
'Date : 4/18/97
'Description:
' (1)Arguments:


' sSName : server name
' sGName : Target local group name
' bUSIDArray(): Byte array of SID of the account

' (2)Return Value:


' True : if success
' False : if failure

Dim bSNArray() As Byte
Dim bGNArray() As Byte
Dim lResult As Long
Dim ptrSID As Long

DelUserFromLGroup = False

bSNArray = sSName & vbNullChar 'Server machine name, null for default server.
bGNArray = sGName & vbNullChar 'Local group name
lResult = NetLocalGroupDelMember(bSNArray(0), bGNArray(0), bUSIDArray(0))

Select Case lResult
Case 0 'Success
DelUserFromLGroup = True
Case 1337
MsgBox "Bad account SID.", vbOKOnly, "Error"
Case 1377
MsgBox "This user is not in this group.", vbOKOnly, "Error"
Case Else
Call NetErrorHandler(lResult, "remove user from " & sGName & ".")
End Select

End Function

Function AddUserToLGroup(ByVal sSName As String, ByVal sGName As String, bUSIDArray() As
Byte) As Boolean

'Author : Jeff Hong YAN
'Date : 4/17/97

'Description:
' (1)Arguments:


' sSName : server name
' sGName : Target local group name

' bUSIDArray(): Byte array of SID of the member
' (2) Return Value:


' True : if success
' False : if failure

Dim bSNArray() As Byte
Dim bGNArray() As Byte


Dim lResult As Long

AddUserToLGroup = False

bSNArray = sSName & vbNullChar 'Server machine name, null for default server.
bGNArray = sGName & vbNullChar 'Local group name
lResult = NetLocalGroupAddMember(bSNArray(0), bGNArray(0), bUSIDArray(0))

Select Case lResult
Case 0 'success
AddUserToLGroup = True
Case 1337
MsgBox "Bad SID.", vbOKOnly, "Error"
Exit Function
Case 1378
MsgBox "This user is already in this group.", vbOKOnly, "Error"
Exit Function
Case Else


Call NetErrorHandler(lResult, "add user into " & sGName & ".")

End Select

End Function

Hong YAN

unread,
Apr 19, 1997, 3:00:00 AM4/19/97
to

Hong YAN

unread,
Apr 19, 1997, 3:00:00 AM4/19/97
to

4/18/97

Hi all,

The following is a bunch of functions most well known to VB guys. I obtained them from
KB article (Number Q159498, and I made some minor modifications) and you can use them to
create a new user and destroy an existing user on a Windows NT domain. When a user is
created, I set him to be a member of Domain Users while you can specify that he goes
into Domain User, Domain Guests, Domain Admins

If you want to include this user to any other groups, local or global, go ahead with the
functions I posted earlier.

Rgds
Jeff Hong YAN
(Hong...@WorldNet.ATT.Net)

Posted directly to microsoft.public.vb.winapi.network

Option Explicit
'Jeff Hong YAN 11/20/96 modified on 4/18/97
'This module shows how to create / destroy a user account.
'Modified according to MS KB article Q159498
'You must have account operator's right to run

' for dwPriv
Const USER_PRIV_MASK = &H3
Const USER_PRIV_GUEST = &H0
Const USER_PRIV_USER = &H1
Const USER_PRIV_ADMIN = &H2

' for dwFlags
Const UF_SCRIPT = &H1
Const UF_ACCOUNTDISABLE = &H2
Const UF_HOMEDIR_REQUIRED = &H8
Const UF_LOCKOUT = &H10
Const UF_PASSWD_NOTREQD = &H20
Const UF_PASSWD_CANT_CHANGE = &H40
Const UF_NORMAL_ACCOUNT = &H200

Declare Function StrToPtr Lib "kernel32" Alias "lstrcpyW" ( _
ByVal Ptr As Long, Source As Byte) As Long

' Add using Level 1 user structure
Declare Function NetUserAdd1 Lib "NETAPI32.DLL" Alias "NetUserAdd" _
(ServerName As Byte, ByVal Level As Long, Buffer As TUser1, lParmError _
As Long) As Long

Declare Function NetUserDel Lib "NETAPI32.DLL" (ServerName As Byte, _
UserName As Byte) As Long

Type TUser1 ' Level 1
ptrName As Long
ptrPassword As Long
dwPasswordAge As Long
dwPriv As Long
ptrHomeDir As Long
ptrComment As Long
dwFlags As Long

ptrScriptHomeDir As Long
End Type

Declare Function NetAPIBufferFree Lib "NETAPI32.DLL" Alias _


"NetApiBufferFree" (ByVal Ptr As Long) As Long

Declare Function NetAPIBufferAllocate Lib "NETAPI32.DLL" Alias _
"NetApiBufferAllocate" (ByVal ByteCount As Long, Ptr As Long) As Long

Function DomainCreateUser( _
ByVal sSName As String, _
ByVal sUName As String, _
ByVal sPWD As String, _
ByVal sHomeDir As String, _
ByVal sComment As String, _
ByVal sScriptFile As String) As Long
'Create a new user to be a member of group Domain Users

Dim lResult As Long
Dim lParmError As Long

Dim lUNPtr As Long
Dim lPWDPtr As Long
Dim lHomeDirPtr As Long
Dim lCommentPtr As Long
Dim lScriptFilePtr As Long

Dim bSNArray() As Byte
Dim bUNArray() As Byte
Dim bPWDArray() As Byte
Dim bHomeDirArray() As Byte
Dim bCommentArray() As Byte
Dim bScriptFileArray() As Byte

Dim UserStruct As TUser1

' Move to byte arrays


bSNArray = sSName & vbNullChar

bUNArray = sUName & vbNullChar
bPWDArray = sPWD & vbNullChar
bHomeDirArray = sHomeDir & vbNullChar
bCommentArray = sComment & vbNullChar
bScriptFileArray = sScriptFile & vbNullChar

' Allocate buffer space
lResult = NetAPIBufferAllocate(UBound(bUNArray) + 1, lUNPtr)
lResult = NetAPIBufferAllocate(UBound(bPWDArray) + 1, lPWDPtr)
lResult = NetAPIBufferAllocate(UBound(bHomeDirArray) + 1, lHomeDirPtr)
lResult = NetAPIBufferAllocate(UBound(bCommentArray) + 1, lCommentPtr)
lResult = NetAPIBufferAllocate(UBound(bScriptFileArray) + 1, lScriptFilePtr)

' Copy arrays to the buffer
lResult = StrToPtr(lUNPtr, bUNArray(0))
lResult = StrToPtr(lPWDPtr, bPWDArray(0))
lResult = StrToPtr(lHomeDirPtr, bHomeDirArray(0))
lResult = StrToPtr(lCommentPtr, bCommentArray(0))
lResult = StrToPtr(lScriptFilePtr, bScriptFileArray(0))

With UserStruct
.ptrName = lUNPtr
.ptrPassword = lPWDPtr
.dwPasswordAge = 3
.dwPriv = USER_PRIV_USER
.ptrHomeDir = lHomeDirPtr
.ptrComment = lCommentPtr
.dwFlags = UF_NORMAL_ACCOUNT Or UF_SCRIPT
.ptrScriptHomeDir = lScriptFilePtr
End With

' Create the new user
lResult = NetUserAdd1(bSNArray(0), 1, UserStruct, lParmError)
DomainCreateUser = lResult
If lResult <> 0 Then
Call NetErrorHandler(lResult, " when creating new user " & sUName)
End If

' Release buffers from memory
lResult = NetAPIBufferFree(lUNPtr)
lResult = NetAPIBufferFree(lPWDPtr)
lResult = NetAPIBufferFree(lHomeDirPtr)
lResult = NetAPIBufferFree(lCommentPtr)
lResult = NetAPIBufferFree(lScriptFilePtr)

End Function
Public Function DomainDestroyUser(ByVal sSName As String, ByVal sUName As String)
'Destroy an existing user with user id sUName
'from current PDC with sSName

Dim lResult As Long
Dim lParmError As Long

Dim bSNArray() As Byte
Dim bUNArray() As Byte

' Move to byte arrays


bSNArray = sSName & vbNullChar

bUNArray = sUName & vbNullChar

lResult = NetUserDel(bSNArray(0), bUNArray(0))
If lResult = 0 Then
DomainDestroyUser = True
Else
Call NetErrorHandler(lResult, "delete user '" & sUName & "' from server '" &
sSName & "'.")
DomainDestroyUser = False
End If

End Function

Dave Quecke

unread,
Apr 19, 1997, 3:00:00 AM4/19/97
to

Worse. Under Win95 it requires DCOM for Win95.
I've installed numerous Betas and currently run several,
this is the only one I consider a "problem-child".

But most of my problems come from 16-bit software,

EX: Winword6 blows up the entire machine when you attempt to exit that
program.

I suspect this is an unadvertised feature of DCOM win95 to hasten my
purchase of Office97 <g>.

dj.

Hong YAN

unread,
Apr 25, 1997, 3:00:00 AM4/25/97
to pfil...@infobahnos.com

Pierre Fillion wrote:
>
> Hi,
>
> I looked at all your routines and could not find anything that would
> return me the domain name the user is currently on for VB5 Win95 &
> WinNT4.
>
> Your SID functions both takes the domain name as an input parameter.
>
> Can you help me out.
>
> Thanks a lot
> - Pierre

Pierre:

I am reposting my function. I did it some days ago.

Hi all,

The following is the function I use to get the current user id. There could be many ways

to do this job and I will be back on this later.

This function has been tested on Windows NT wksta / svr and Win95.

I welcome any comments and suggestions.

Rgds
Jeff Hong YAN
(Hong...@WorldNet.ATT.Net)

Option Explicit

Pierre Fillion

unread,
Apr 25, 1997, 3:00:00 AM4/25/97
to

Hi,

I looked at all your routines and could not find anything that would
return me the domain name the user is currently on for VB5 Win95 &
WinNT4.

Your SID functions both takes the domain name as an input parameter.

Can you help me out.

Thanks a lot
- Pierre

On Sat, 19 Apr 1997 16:17:57 -0300, Hong YAN
<HONG...@worldnet.att.net> wrote:

>Pierre Fillion wrote:
>>
>> On Sat, 19 Apr 1997 12:00:16 -0300, Hong YAN
>> <HONG...@worldnet.att.net> wrote:
>>
>> Do you know how to get the user domain in Win95 and WinNT4 without the
>> need to create a C++ dll ? I'm using vb5.
>>
>> Thanks
>

>Check my postings in microsoft.public.vb.winapi.network. Sure you can do that with my
>functions.
>
>rgds
>Jeff Hong YAN


Hong YAN

unread,
Apr 25, 1997, 3:00:00 AM4/25/97
to pfil...@infobahnos.com

Pierre Fillion wrote:
>
> Hi,
>
> I looked at all your routines and could not find anything that would
> return me the domain name the user is currently on for VB5 Win95 &
> WinNT4.
>
> Your SID functions both takes the domain name as an input parameter.
>
> Can you help me out.
>
> Thanks a lot
> - Pierre
>

Pierre:

The following is my posting #4 in March. I am reposting it.

Hi all,

The following is the function I use to get the current logon domain name and machine

name. There could be many ways to get the current machine name (from registry, calling

WNet* function, etc..). This posting employs NetWksta* API function set.

I welcome any comments and suggestions.

Rgds
Jeff Hong YAN
(Hong...@WorldNet.ATT.Net)

Option Explicit

'Author: Jeff Hong YAN


'Date: 12/20/96
'Description: Pass in current user id to get the current domain name and machine name
'Works on Windows NT workstation on a Windows NT domain

Type WKSTA_INFO_101
wki101_platform_id As Long
wki101_computername As Long
wki101_langroup As Long
wki101_ver_major As Long
wki101_ver_minor As Long

wki101_lanroot As Long
End Type

Type WKSTA_USER_INFO_1


wkui1_username As Long
wkui1_logon_domain As Long
wkui1_logon_server As Long

wkui1_oth_domains As Long
End Type


Hong YAN

unread,
Apr 25, 1997, 3:00:00 AM4/25/97
to pfil...@infobahnos.com

Here comes another one. (Reposting)

Jeff Hong YAN

PS: Tested on WinNT Server 4.0/Win95

Pierre Fillion

unread,
Apr 29, 1997, 3:00:00 AM4/29/97
to

On Fri, 25 Apr 1997 22:40:02 -0300, Hong YAN
<HONG...@worldnet.att.net> wrote:

Hi,

Thanks a lot.

Do you know how to get the domain name for a Win95 workstation?

Thanks

0 new messages