there are a number of ways by which an application can gather information about the current user, whether this is a local computer account, a domain account, or an active directory account. workstations using windows nt or 9x need to have the active directory client installed - download from here.

there are two protocols available for accessing directory information:

  • winnt:// is required to access users on nt domains or for active directory queries from a pc which isn't a member workstation in the active directory.
  • ldap:// gives full access to the active directory.

[test ldap:// with local accounts on winxp, 2k etc]

this code illustrates how to enumerate the property collections of adsi objects to show what is available and how to access the values (each property type has an associated method to return the value). it can use the winnt or ldap. useful references are the active directory user class, the list of properties not supported by winnt: and the extra properties added. - the latter list is a lot shorter than the former. schema extensions such as microsoft exchange can be accessed via ldap which can drastically increase the amount of information available.. each schema has a helper object - winntsysteminfo and adsysteminfo - to provide the information required to access user information.

the code in this article illustrates how to access the local user information (and, if connected to a domain, workstation information) using winnt and ldap. it shows how to extract the individual properties from the useraccountcontrol property (userflags in winnt), and how to access property values via the properties collection. it also illustrates how to calculate the password expiry date under each protocol.

clear

local owinntsysteminfo, cdomain, cusername, ccomputer, ;
odomain, ouser, ocomputer, oadsysteminfo, lldap, ;
nuserflags, texpirydatetime, ogroup

* use winnt:// by default - this should work on any system with the ad client.
owinntsysteminfo = createobject("winntsysteminfo")

cdomain = owinntsysteminfo.domainname
cusername = owinntsysteminfo.username
ccomputer = owinntsysteminfo.computername

* if winntsysteminfo doesn't work as expected in all environments
* cdomain = "." && local machine
* cusername = alltrim(substr(sys(0), at("#", sys(0)) + 1))
* ccomputer = alltrim(left(sys(0), at("#", sys(0)) - 1))

* note: these monikers are case-sensitive:
* winnt: and winnt: and ldap: and ldap: won't work

odomain = getobject("winnt://" + cdomain)
ouser = getobject("winnt://" + cdomain + "/" + cusername + ",user")

try && error if not connected to domain
ocomputer = getobject("winnt://" + cdomain + "/" + ccomputer + ",computer")
catch
endtry

* try to use ldap://
oadsysteminfo = createobject("adsysteminfo")

try
* pcs not connected to a domain will error as soon as any property is accessed
cdomain = oadsysteminfo.domainshortname
cusername = oadsysteminfo.username
ccomputer = oadsysteminfo.computername

odomain = getobject("ldap://" + cdomain)
ouser = getobject("ldap://" + cusername)
ocomputer = getobject("ldap://" + ccomputer)

lldap = .t.
catch
lldap = .f.
endtry

* the rest of the code works whether using ldap or winnt.

* the accountdisabled property doesn't appear in the property list
* but it's available without calling getinfo which would save
* resources when enumerating a list of users.

* test local guest user is disabled with
* ouser = getobject("winnt://./guest,user")

if ouser.accountdisabled
? "account is disabled"
return
endif

? "user flags"
? "--------------"
if lldap
nuserflags = ouser.useraccountcontrol
else
nuserflags = ouser.userflags
endif
getuserflags(nuserflags)

?
? "user properties"
? "---------------------"

* populate the properties (the data may be there but the propertycount
* isn't until this has been called)
ouser.getinfo()
* pass true as second parameter to see all the properties and values
showproperties(ouser, .f.)

* groups, workstation
?
? "group memberships"
? "---------------------"

for each ogroup in ouser.groups
? ogroup.name
next

?
? "first group's properties"
? "---------------------"
for each ogroup in ouser.groups
ogroup.getinfo()
showproperties(ogroup, .f.)
exit
next


?
? "workstation properties"
? "---------------------"

if vartype(ocomputer) = "o"
ocomputer.getinfo()
showproperties(ocomputer, .f.)
else
? "not available"
endif

?
? "password expiry"
? "---------------------"

if lldap
texpirydatetime = getpasswordexpiryldap(odomain, ouser)
else
texpirydatetime = getpasswordexpirywinnt(odomain, ouser)
endif

if not empty(texpirydatetime)
? "password expires " + ttoc(texpirydatetime)
endif

procedure getpasswordexpiryldap

lparameters odomain, ouser
* firstly determine the password policy:
local omaxpwdage, dexpirydate, nnumdays

omaxpwdage = odomain.get("maxpwdage")
if omaxpwdage.lowpart = 0
* domain policy doesn't expire passwords
dexpirydate = {--}
else
* number of 100 nanosecond intervals since 1.1.1601!
nnumdays = ((omaxpwdage.highpart * 2 ^ 32) + ;
omaxpwdage.lowpart) / -864000000000
* convert the policy to seconds to match passwordlastchanged
* and calculate expiry date and time.
dexpirydate = ouser.passwordlastchanged + (nnumdays * 24 * 60 * 60)
endif


return dexpirydate

endproc

procedure getpasswordexpirywinnt

lparameters odomain, ouser

* firstly determine the password policy:
local nmaxpasswordage, nactualpasswordage, texpiry
try
* errors for local accounts which don't have maxpasswordage set
nmaxpasswordage = odomain.maxpasswordage && in seconds
nactualpasswordage = ouser.passwordage && in seconds
* calculate expiry date and time.
texpiry = datetime() + nmaxpasswordage - nactualpasswordage
catch
texpiry = {--::}
endtry

return texpiry

endproc

procedure showproperties

lparameters oobject, lverbose
local oproperty, ncount, ovalue

if lverbose
for ncount = 0 to oobject.propertycount - 1

* access the properties collection by item
oproperty = oobject.item[ncount]

if not isnull(oproperty.values)
for each ovalue in oproperty.values
? oproperty.name + " : " + transform(getpropertyvalue(ovalue))
next
else
? oproperty.name + " is null"
endif
next
else
? "object has " + ltrim(str(oobject.propertycount)) + " properties"
endif

endproc

procedure getpropertyvalue

lparameters ovalue

local ntype
ntype = ovalue.adstype

* each type has it's own method to return the value..?
* see below for the full list.

do case
case ntype = 1
return ovalue.dnstring()

case ntype = 2
return ovalue.caseexactstring()

case ntype = 3
return ovalue.caseignorestring()

case ntype = 4
return ovalue.printablestring()

case ntype = 5
return ovalue.numericstring()

case ntype = 6
return ovalue.boolean()

case ntype = 7
* reserved word
return ovalue.integer()

case ntype = 8
return ovalue.octetstring()

case ntype = 9
return ovalue.utctime()

case ntype = 10
return ovalue.largeinteger()

case ntype = 25
return ovalue.securitydescriptor()

otherwise
* other types can't be obtained through this interface.
return .null.

endcase

endproc

procedure getuserflags

lparameters nflags

if bittest(nflags, 0) && ads_uf_script, 1
? "the logon script is executed."
endif

if bittest(nflags, 1) && ads_uf_accountdisable, 2
? "this user account is disabled."
endif

if bittest(nflags, 3) && ads_uf_homedir_required, 8
? "the home directory is required."
endif

if bittest(nflags, 4) && ads_uf_lockout, 16
? "this account is currently locked out."
endif

if bittest(nflags, 5) && ads_uf_passwd_notreqd, 32
? "no password is required for this account."
endif

if bittest(nflags, 6) && ads_uf_passwd_cant_change, 64
? "this user cannot change their password."
endif

if bittest(nflags, 7) && ads_uf_encrypted_text_password_allowed, 128
? "this user can send an encrypted password."
endif

if bittest(nflags, 8) && ads_uf_temp_duplicate_account, 256
? "this is an account for users whose primary account is in another domain."
endif

if bittest(nflags, 9) && ads_uf_normal_account, 512
? "this is a default account type that represents a normal user."
endif

if bittest(nflags, 16) && ads_uf_dont_expire_passwd, 65536
? "the password will not expire on this account."
endif

if bittest(nflags, 23) && ads_uf_password_expired, 8388608
? "this user's password has expired."
endif

* full list of flags at
* http://msdn.microsoft.com/library/en-us/adsi/adsi/ads_user_flag_enum.asp

endproc


* property type constants

#define adstype_invalid 0 && the data type is invalid.
#define adstype_dn_string 1 && the string is a distinguished name (path) of a directory service object.
#define adstype_case_exact_string 2 && the string is the case-sensitive type.
#define adstype_case_ignore_string 3 && the string is the case-insensitive type.
#define adstype_printable_string 4 && the string is displayable on screen or in print.
#define adstype_numeric_string 5 && the string is a numeral to be interpreted as text.
#define adstype_boolean 6 && the data is a boolean value.
#define adstype_integer 7 && the data is an integer value.
#define adstype_octet_string 8 && the string is a byte array.
#define adstype_utc_time 9 && the data is the universal time as expressed in universal time coordinate (utc).
#define adstype_large_integer 10 && the data is a long integer value.
#define adstype_prov_specific 11 && the string is a provider-specific string.
#define adstype_object_class 12 && not used.
#define adstype_caseignore_list 13 && the data is a list of case insensitive strings.
#define adstype_octet_list 14 && the data is a list of octet strings.
#define adstype_path 15 && the string is a directory path.
#define adstype_postaladdress 16 && the string is the postal address type.
#define adstype_timestamp 17 && the data is a time stamp in seconds.
#define adstype_backlink 18 && the string is a back link.
#define adstype_typedname 19 && the string is a typed name.
#define adstype_hold 20 && the data is the hold data structure.
#define adstype_netaddress 21 && the string is a net address.
#define adstype_replicapointer 22 && the data is a replica pointer.
#define adstype_faxnumber 23 && the string is a fax number.
#define adstype_email 24 && the data is an e-mail message.
#define adstype_nt_security_descriptor 25 && the data is windows nt/windows 2000 security descriptor as represented by a byte array.
#define adstype_unknown 26 && the data is an undefined type.
#define adstype_dn_with_binary 27 && the data is used for mapping a distinguished name to a non varying guid.
#define adstype_dn_with_string 28 && the data is used for mapping a distinguished name to a non-varying string value.

* ads name types
#define ads_name_type_1779 1
#define ads_name_type_canonical 2
#define ads_name_type_nt4 3
#define ads_name_type_display 4
#define ads_name_type_domain_simple 5
#define ads_name_type_enterprise_simple 6
#define ads_name_type_guid 7
#define ads_name_type_unknown 8
#define ads_name_type_user_principal_name 9
#define ads_name_type_canonical_ex 10
#define ads_name_type_service_principal_name 11
#define ads_name_type_sid_or_sid_history_name 12


One Response to Accessing user information from Foxpro with ADSI

Leave a Reply

Your email address will not be published. Required fields are marked *