JohnQ
(Starting to like KiXtart)
2003-05-16 02:35 PM
Help with Active Directory Query

I'm using the following script to compile a list of users and their login scripts. This helps us identify which login scripts are being used and also finding any users who don't have a login script assigned. Here's the script:
code:
Break ON
$target = GetObject("LDAP://OU=Users,OU=USA,DC=mycompany,DC=net")
If ReDirectOutput("c:\logons.xls") = 0
For Each $user in $target
$script = $user.loginscript
If $Script = ""
? "Current Script for"+Chr(09)+$user.name+Chr(09)+ "NO SCRIPT ASSIGNED"
Else
? "Current Script for "+Chr(09)+$user.name+Chr(09)+$script
EndIf
Next
EndIf

The script works great to retrieve the users from the OU specified. The problem is, under the OU "USA" there are sub OU's for almost every state and then each of those has one or more sub OU's containing user IDs. Is there anyway to have the script search all sub OU's underneath any given "root" OU and return the same info? I know that I could hard code an array of target OU's and loop through that, but we're talking about possibly 100's of OU's and 1000's of users.

Thanks.


Howard Bullock
(KiX Supporter)
2003-05-16 02:39 PM
Re: Help with Active Directory Query

When you GETOBJECT on the users OU, your enumeration should also enumerate the other OU objects. You should check the "class" of each object. If the class is "user" then get and report your properties. Otherwise, You should perform another GETOBJECT on the OU to enumerate it. This would be a good use for a recursive UDF.

JohnQ
(Starting to like KiXtart)
2003-05-16 04:09 PM
Re: Help with Active Directory Query

Howard, if it's not too much to ask could you elaborate a little bit? A lot of this is new to me and I'm now lost [Frown] . If it's too much trouble, or too detailed to go into I understand.

Thanks.


Howard Bullock
(KiX Supporter)
2003-05-16 04:21 PM
Re: Help with Active Directory Query

I will provide you a code sample later today. I am currently working on some time sensitive items.

Kdyer
(KiX Supporter)
2003-05-16 08:03 PM
Re: Help with Active Directory Query

JohnQ,

Hate to "steal Howard's thunder!" [Big Grin]

Here is another way to do this, no LDAP needed. [Smile]

Borrowing from the follwing topics:
Re-Write of the KIX32.EXE Deployment tool

MS-Tech Article findings- 318689

We can take the code to modify the users from NTLOGON and change it to look for Domain Users that have no script. [Smile]

code:
CLS
BREAK ON
$DomainString='DOMAIN' ;Replace with your domain
$GroupString='Domain Users'
$GroupObj = GetObject('WinNT://' + $DomainString + '/' + $GroupString)
For each $UserObj in $GroupObj.Members
IF $UserObj.AccountDisabled<>'True' AND $UserObj.LoginScript=''
?$UserObj.Name
?$UserObj.FullName
$error=@error
$logshare='H:\' ;Change to an available drive or UNC path
$logfile=$logshare+$DomainString+'NOSCRIPT.CSV'
$logdata=$UserObj.Name+','+$UserObj.FullName+','+$error+@CRLF
LOGGER($logfile,$logdata)

ENDIF
Next
?'--'
?'Script complete'
SLEEP 4

FUNCTION LOGGER($logfile,$logdata)
DIM $n
WHILE Open(1, $logfile, 5) OR $n=5
IF $n
'.'
ELSE
?'Please wait'
ENDIF
$n=$n+1
SLEEP 3
LOOP
$n=WriteLine(1, $logdata)
$n=Close(1)
ENDFUNCTION

HTH,

Kent


Howard Bullock
(KiX Supporter)
2003-05-16 08:04 PM
Re: Help with Active Directory Query

Shame on you for trying that! [Eek!]

$count = EnumOUs("LDAP://NetbiosDomain/DC=us, DC=MyCompany, DC=com", "computer")
or
$count = EnumOUs("LDAP://NetbiosDomain/DC=us, DC=MyCompany, DC=com", "user")


Function EnumOUs($LDAP, $Filter)
;$Filter = 'computer' | 'user'
dim $aFilter[0], $pos, $objOU, $i, $j
$i = 0
$j = 0
$aFilter[0] = $Filter

$objOU = GetObject($LDAP)
if VarTypeName($objOU)='Object'
? ucase(Left($Filter,1)) + substr($Filter,2) + "(s) in ($LDAP)"
$objOU.Filter = $aFilter
for each $item in $objOU
$Name = $item.Name
? " " + substr($Name,4)
$i = $i +1
next
?
$aFilter[0] = "organizationalUnit"
$objOU.Filter = $aFilter
for each $item in $objOU
$Name = $item.Name
$pos = instrrev($LDAP,"/")
$DN = Left($LDAP,$pos) + $Name + ", " + substr($LDAP, $pos+1)
$j = EnumOUs($DN, $Filter);
$i = $i + $j
next
else
"GetObject COM error: " + @error + " " + @serror
endif
$EnumOUs = $i
Endfunction


[ 16. May 2003, 20:07: Message edited by: Howard Bullock ]


Howard Bullock
(KiX Supporter)
2003-05-16 08:10 PM
Re: Help with Active Directory Query

Radimus already has a UDF in the UDF Library:

EnumOUs() - Enumerates OUs containing Users or Computers


Kdyer
(KiX Supporter)
2003-05-16 08:26 PM
Re: Help with Active Directory Query

The basis of the code I provided is from -
setting a user's login script.

Thanks,

Kent


JohnQ
(Starting to like KiXtart)
2003-05-16 09:34 PM
Re: Help with Active Directory Query

Howard,
Your function works great but I have two questions. 1. When using the "user" filter, it still returns computers as well.
2. If I wanted to spit out the user's login script (or some other attribute), where the heck would I put that in your function?

Thanks for all of your help from the "scripting challenged".


Howard Bullock
(KiX Supporter)
2003-05-17 01:50 AM
Re: Help with Active Directory Query

There seems to be a problem with the IADsContainer FILTER property. In this function the filter works properly for "group", "computer", and "container", but returns computer objects of both 'user' and 'computer' classes when the FILTER is set to "user". This may be related to the fact that computer accounts are indeed hidden user account "computer$", but that should not be the issue since the object definitely return the class "computer" when the FILTER is to "user".

I have added additional code to validate that the object class is indeed the specified and desired class of object the function was sent to enumerate. See the "if $Class = $Filter" construct.

Also I have an oversight when enumerating the complete domain for users. The object called "Users" is in fact of class "container". So the code was modified to recurse on objects of both classes: 'organizationalUnit' and 'container'. The $aFilter array was Redim'ed and the extra filter added.


Function EnumOUs($LDAP, $Filter)
;$Filter = 'computer' | 'user' | 'group'
dim $aFilter[0], $pos, $objOU, $i, $j, $Class
$i = 0
$j = 0
$aFilter[0] = $Filter

$objOU = GetObject($LDAP)
if VarTypeName($objOU)='Object'
? ucase(Left($Filter,1)) + substr($Filter,2) + "(s) in ($LDAP)"
$objOU.Filter = $aFilter
for each $item in $objOU
$Name = $item.Name
$Class = $item.Class
if $Class = $Filter
? " " + substr($Name,4) + " " + $Class
$i = $i +1
endif
next
?
redim $aFilter[1]
$aFilter[0] = "organizationalUnit"
$aFilter[1] = "container"
$objOU.Filter = $aFilter
for each $item in $objOU
$Name = $item.Name
$pos = instrrev($LDAP,"/")
$DN = Left($LDAP,$pos) + $Name + ", " + substr($LDAP, $pos+1)
$j = EnumOUs($DN, $Filter);
$i = $i + $j
next
else
? "GetObject COM error: " + @error + " " + @serror
? "Bad path: " + $LDAP
endif
$EnumOUs = $i
Endfunction


[ 17. May 2003, 14:47: Message edited by: Howard Bullock ]


Howard Bullock
(KiX Supporter)
2003-05-17 01:52 AM
Re: Help with Active Directory Query

Oh, for your second question: you would add a line to get the loginscript property and place the value into a variable with $Name = $item.Name $Class = $item.Class and then add the variable to the out line.

[ 17. May 2003, 01:52: Message edited by: Howard Bullock ]


Howard Bullock
(KiX Supporter)
2003-05-17 03:43 AM
Re: Help with Active Directory Query

Please see More on perceived FILTER problem for some insights into the problem where Computers are returned when the FILTER is set to "user".

JohnQ
(Starting to like KiXtart)
2003-05-20 09:23 PM
Re: Help with Active Directory Query

Thanks Howard. One last question...If you wanted to get the value of a custom optional object property within a class, how would you go about getting that? Example:
code:
$ecode = $item.abc-emplCode  

The problem here is the dash in abc-emplCode. And $item.'abc-emplCode' returns nothing. Any suggestions?


LonkeroAdministrator
(KiX Master Guru)
2003-05-20 09:28 PM
Re: Help with Active Directory Query

that sounds like kixtart problem.

as once you write a operator there kixtart assumes it has a math sentence...


JohnQ
(Starting to like KiXtart)
2003-05-20 09:36 PM
Re: Help with Active Directory Query

Is there any way that you know of to get around it? Unfortunately, I can't remove the dash.

ShawnAdministrator
(KiX Supporter)
2003-05-20 09:47 PM
Re: Help with Active Directory Query

A long-shot, no AD here:

$Value = $Object.Get("Property-Name")


Howard Bullock
(KiX Supporter)
2003-05-20 09:55 PM
Re: Help with Active Directory Query

Try:

$ecode = $item.get("abc-emplCode")
or
$ecode = $item.getex("abc-emplCode")

{edit}
[Mad] I hate when people stop at my desk and ask questions. It makes my posts late.

{edit again} Lonkero, Your post has made me feel all warm and fuzzy now. [Big Grin]

[ 21. May 2003, 01:26: Message edited by: Howard Bullock ]


LonkeroAdministrator
(KiX Master Guru)
2003-05-20 10:24 PM
Re: Help with Active Directory Query

hoby, does it make you stop hating if I say I love you? [Frown]

JohnQ
(Starting to like KiXtart)
2003-05-21 03:05 AM
Re: Help with Active Directory Query

Thanks Howard and Shawn.

Using $item.get("abc-emplCode") worked like a champ.


JohnQ
(Starting to like KiXtart)
2003-05-21 05:05 AM
Re: Help with Active Directory Query

There is one other user property that I would like to be able to retrieve from AD and this is if the "password never expires" checkbox is checked. I have tried using $item.PasswordExpirationDate but it returns nothing. Am I searching for the wrong thing.

Howard Bullock
(KiX Supporter)
2003-05-21 05:21 AM
Re: Help with Active Directory Query

You need to perform bitwise operations for that data.


$usr = GetObject("LDAP://CN=Jsmith,OU=Sales,DC=ArcadiaBay,DC=Com")
$flags = $usr.Get("UserAccountControl")
if $flags & 65536
? "Password does not expire"
else
? "Password CAN expire"
endif


See the documetation for AdminMisc.DLL for some additional data.

http://mywebpages.comcast.net/habullock/Win32Admin.htm#User

More data: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/netdir/adsi/modifying_user_properties.asp

[ 21. May 2003, 05:24: Message edited by: Howard Bullock ]


NTDOCAdministrator
(KiX Master)
2003-05-21 08:06 AM
Re: Help with Active Directory Query

Here are some good script examples that can be converted to KiXtart

Script Center - Users and Groups
http://www.microsoft.com/technet/treeview/default.asp?url=/technet/scriptcenter/user/default.asp

TechNet Script Center
http://www.microsoft.com/technet/treeview/default.asp?url=/technet/scriptcenter/Default.asp


Kdyer
(KiX Supporter)
2003-05-21 08:26 AM
Re: Help with Active Directory Query

Howard's DLL is probably the easiest way to deal with PasswordNeverExpires property.

However, you can work with it, but you need to plug in values to set/remove it.

http://userpages.umbc.edu/~kbradl1/wsz/ref/ADSIref.html

HTH,

Kent


JohnQ
(Starting to like KiXtart)
2003-05-22 01:02 PM
Re: Help with Active Directory Query

Howard, thanks for the info on obtaining the "password never expires" setting. One question about that though...Why is $flags set in the following manner: $flags & 65536 instead of $flags = 65536?

Also, in one of your previous posts, you mentioned using GetEx instead of Get. What's the diff?

Thanks so much for all of your help.


Howard Bullock
(KiX Supporter)
2003-05-22 01:33 PM
Re: Help with Active Directory Query

The documentation states:
quote:
The IADs::GetEx method retrieves from the property cache property values of a given attribute. The returned property values can have single or multiple values. Unlike the IADs::Get method, the property values are returned as a variant array of VARIANT, or a variant array of bytes for binary data. A property with a single value is then represented as an array of a single element
quote:
The IADs::Get method retrieves a property of a given name from the property cache. The property can be single-valued, or multi-valued. The property value is represented as either a variant for a single-valued property or a variant array (of VARIANT or bytes) for a property that allows multiple values.
The difference:
quote:
You can also use IADs::GetEx to retrieve property values from the property cache. However, the values are returned as a variant array of VARIANTs, regardless of whether they are single- or multi-valued. This means that ADSI makes an extra effort to package the returned property values in consistent data formats. This saves you, as a caller, some efforts to validate the data types when you are not sure whether the returned data has single or multiple values.
quote:
The IADs::Get and IADs::GetEx methods return a different variant structure for a single-valued property value. If the property is a string, IADs::Get will return a variant of string (VT_BSTR), whereas IADs::GetEx will return a variant array of a VARIANT type string with a single element. Thus, if you are not sure that a multi-valued attribute will return a single value or multiple values, you should use IADs::GetEx. As it does not require you to validate the result's data structures, you may want to use IADs::GetEx to retrieve a property of which you are not sure whether it is single-valued or multi-valued. The following table compares the difference in calling the two methods.
The User_Flags value is one of those items that stores many different properties. Using an equal sign to set the value as in your example would change the settings of all the other properties.

The syntax ($flags & 65536) check the current setting it does not set the value or flag. You would have to do a bitwise OR $flags = $flags | 65536 to set the value.

Please read my Win32Admin.DLL help or search the MSDN for more detail on User_Flags. In short each bit of the number represents a flag. It can be a "1" or a "0". The postion of the bit gives it its decimal value. I think that User_Flags is a 3 byte (24 bit) field. The example below uses 2 bytes (16 bits).


bit Value = Value
1 1 1 1
2 2 0
3 4 1 4
4 8 0
5 16 1 16
6 32 0
7 64 0
8 128 0
9 256 0
10 512 0
11 1024 0
12 2048 0
13 4096 1 4096
14 8192 0
15 16384 0
16 32768 0
-------------------------
User_Flags = 4117


As you can see above changing the value of 4117 to some other multiple of 2 would set the other bits to "0" changing more than you wanted to change.

Read up on Binary operation if necessary.

[ 22. May 2003, 14:33: Message edited by: Howard Bullock ]


Jtel
(Fresh Scripter)
2003-05-22 04:52 PM
Re: Help with Active Directory Query

Staying with the subject here...

Does anyone know what the class property is to determin if an account is disabled? I looked at all of the properties in Howard's EnumObjProps function, but there are so many it's hard to tell which one it might be. I just thought that maybe someone had searched this one out before.


ShawnAdministrator
(KiX Supporter)
2003-05-22 04:58 PM
Re: Help with Active Directory Query

I always thought it was:

code:
if $user.accountdisabled   
? "ja - disabled!"
endif



Les
(KiX Master)
2003-05-22 04:58 PM
Re: Help with Active Directory Query

That was discussed here yesterday.
http://www.kixtart.org/board/ultimatebb.php?ubb=get_topic;f=1;t=007210


Howard Bullock
(KiX Supporter)
2003-05-22 05:09 PM
Re: Help with Active Directory Query

I you are using the LDAP:// provider then the property and access is somewhat different.

$Flags = $objUser.Get("userAccountControl")

Then the bitwise operation must be performed on this value.
code:
if $Flags & 2
? Account is DISABLED"
endif



[ 22. May 2003, 17:10: Message edited by: Howard Bullock ]


JohnQ
(Starting to like KiXtart)
2004-03-12 04:04 PM
Question for Howard

Howard, the EnumOUs() UDF that you provided works great and I have modified it for various things and use the heck out of it.

My question is, and forgive me for my ignorance, what is the purpose of $i and $j?

$J obviously causes recursion after each container is exhausted, but I don't understand bumping the count of $i and then later resetting $i to $i + $j. It doesn't seem to be used anywhere and seems to work fine without using $i.

Could you briefly explain if you have a moment?

Thanks


Howard Bullock
(KiX Supporter)
2004-03-12 04:44 PM
Re: Question for Howard

It is simply a counter. $i is incremented by one for each name that is printed. the final value of $i is then returned to the calling script (recursion) ($EnumOUs = $i). This value is placed into $j of the calling function. $j which represents the total count of all recursions for a particular bracnch is then added to $i in the current loop. After all recursion is exhausted and the original function call exists to the script $i will return a count of all names seen.

JohnQ
(Starting to like KiXtart)
2004-03-15 01:58 PM
Re: Question for Howard

Howard, I certainly don't mean to question you, but how can $i ultimately return the total number of items seen if $i and $j are both reset to 0 each time recursion occurs?

Sealeopard
(KiX Master)
2004-03-15 07:46 PM
Re: Question for Howard

Because the function returns the number of elements each time it's called.

Howard Bullock
(KiX Supporter)
2004-03-15 10:52 PM
Re: Question for Howard

The variables are SCOPED within the function:

Function EnumOUs($LDAP, $Filter)
;$Filter = 'computer' | 'user'
dim $aFilter[0], $pos, $objOU, $i, $j


This means that the variables $i and $j exists multiple times independently in each recursively called instance of the EnumOUs function. Each value is used within that instance of the function and returned to parent instance via "$EnumOUs = $i" statement. If this post does not clarify the issue, please let me know and I will try to explain it better.