This question came in the newsgroups today and I thought it was a good one to show off an AdFind feature that you won’t find duplicated in many places… The ability to specify dates for those nasty Int8 fields…
The question was (paraphrased)….
How do I find all accounts that have a specific Account Expires Date?
Right off I need to explain something. This isn’t asking what accounts will have expired passwords on such and such a day, instead it is asking what accounts are configured to expire on a specific day. Account expiration and account password expiration are two entirely separate things. So now that that is out of the way…
AdFind as of, I think, V1.31.00 got two -binenc option modifiers added to it, one called LOCAL and the other called UTC. You can find the info on these modifiers and that switch when you do adfind /??? and look at [QUERY OPTIONS]. It says the following:
  -binenc      Transform filter elements to proper format:
                   {{GUID:guid value}} converts to LDAP format of binary.
                   {{SID:sid value}} converts to LDAP format of binary.
                   {{BIN:hex string}} converts to LDAP format of hex binary.
                   {{UTC:YYYY/MM/DD-HH:MM:SS}} converts to int8 of UTC date/time.
                   {{LOCAL:YYYY/MM/DD-HH:MM:SS}} converts to int8 of Local date/time.
A little secret that isn’t documented is that you don’t have to specify the time if you just want to specify a date. So if you need to find all accounts set to expire on 8/26/2006 for instance, the following query will do it.
adfind -default -tdcs -binenc -f “&(objectcategory=person)(accountexpires>={{LOCAL:2006/08/26}})(accountexpires< ={{LOCAL:2006/08/27}})" samaccountname accountexpires
 This query has the following switches
- -default : look at the default NC of the default domain controller
- -tdcs : Decode int8 time attributes into a sortable time format
- -binenc : Encode binary attributes marked for replacement in the query
The filter itself is
“&(objectcategory=person)(accountexpires>={{LOCAL:2006/08/26}})(accountexpires< ={{LOCAL:2006/08/27}})" The & says take all of these separate pieces of the query and return to me the intersection of the results
- (objectcategory=person) : contacts or users – you do this so you use an indexed attribute. You don’t have to worry about contacts as they don’t expire so they won’t have the attribute accountexpires set to anything…
- (accountexpires>={{LOCAL:2006/08/26}}) : Convert the value 2006/08/26 to an int8 format, BTW, that is in my localtime, not UTC (GMT) and then give me a list of all accounts that have a value of that or greater.
- (accountexpires< ={{LOCAL:2006/08/26}}) : Convert the value 2006/08/26 to an int8 format and then give me a list of all accounts that have a value of that or less.
- sAMAccountName : Pre-W2K UserID
- accountExpires: Date and time that the account expires (this is translated from int8 because of -tdcs)
Note, it should be obvious but that filter will not work in any other LDAP query tool alive. I don’t think anyone has started using my “language” for specifying filter metadata.
The query returns the following attributes
Â
Here is a sample run:
F:\>adfind -default -tdcs -binenc -f “&(objectcategory=person)(accountexpires>={{LOCAL:2006/08/26}})(accountexpires< ={{LOCAL:2006/08/27}})" samaccountname accountexpires -e AdFind V01.31.00cpp Joe Richards (joe@joeware.net) March 2006 Transformed Filter: &(objectcategory=person)(accountexpires>=128010384001650000)(accountexpires< =128011248001650000) Using server: 2k3dc02.joe.com:389 Directory: Windows Server 2003 Base DN: DC=joe,DC=com dn:CN=joe,OU=MailUsers,OU=joeware2,OU=Exchange,DC=joe,DC=com >accountExpires: 2006/08/26-01:00:00 Eastern Standard Time
>sAMAccountName: joe
1 Objects returned
If you want that in CSV format, simply tack on the CSV switch
F:\>adfind -csv -default -tdcs -binenc -f “&(objectcategory=person)(accountexpires>={{LOCAL:2006/08/26}})(accountexpires< ={{LOCAL:2006/08/27}})" samaccountname accountexpires -e "dn","samaccountname","accountexpires" "CN=joe,OU=MailUsers,OU=joeware2,OU=Exchange,DC=joe,DC=com","joe","2006/08/26-01:00:00 Eastern Standard Time"
Oh you don’t want the DN, tack on the -nodn switch
F:\>adfind -nodn -csv -default -tdcs -binenc -f “&(objectcategory=person)(accountexpires>={{LOCAL:2006/08/26}})(accountexpires< ={{LOCAL:2006/08/27}})" samaccountname accountexpires -e "samaccountname","accountexpires" "joe","2006/08/26-01:00:00 Eastern Standard Time"
 Oh, if you are paying really close attention, you probably notices the -e switch in there too…. This is one of my favorite switches that I use in a vast majority of my queries. It is the environment switch, what it does is that it looks at the environment variables currently defined and imports them into the current switches. My number one purpose in doing this is to point at various different Active Directory and ADAM directories… So to see what it did to the command above, use the -po switch which Prints Out the switches and parameters specified… Added to the command above in my current command prompt Window and it outputs the following extra info
Selected Switches
   -binenc
   -csv
   -default
   -e
   -f &(objectcategory=person)(accountexpires>={{LOCAL:2006/08/26}})(accountexpires< ={{LOCAL:2006/08/27}})    -h 2k3dc02    -nodn    -po    -tdcs Selected Attributes    samaccountname    accountexpires
Notice the -h 2k3dc02 that I didn’t specify on the command line? That is the only extra switch being picked up here.
The -e switch is documented in adfind /??? under the [MISC OPTIONS] section and this is what it says:
  -e xxx       Load switches from environment. Will read env vars with prefix
                and dash (adfind-) by default and load them in. Any switches
                specified explicitly on the command line will override. To
                specify a different prefix, specify string after -e. For
                example to specify the host switch create an env var of
                adfind-h. To specify properties specify the env var adfind-
                or adfind-props. To specify a switch that doesn’t take a
                a value, specify a value of {~} because you can’t set a
                an environment variable to blank.
                   Ex: Queries ADAM on localhost port 5000 for subnets.
                      set adam1-h=.:5000
                      set adam1-config={~}
                      set adam1-f=objectcategory=subnet
                      set adam1-props=name siteobject
                      adfind -e adam1
By default, I always have my primary AD/Exchange environment info set in the ADFIND-* variables. That way if I add a simple -e I don’t have to enter the hostname to get to that environment or even the authentication info if it is different[1]. So if you look at the adfind- variables right now you see
F:\>set adfind-
adfind-h=2k3dc02
I also usually have A1 through A9 environment variables set for various ADAM instances I have running on different machines; these are used by specify -e A1 through -e A9. I also have an AB environment variable set that point at an Address Book ADAM instance which is used with -e AB.
There is also a -ef option if you rather put the settings in a file.
Like I said, I love the -e option, it makes for much less typing when I need to…
  joeÂ
 Â
[1] I actually use CPAU to fire up a command prompt running in the security context of that environment as well so I don’t have to specify the userid/password in the environment variables.Â
This is, in fact, a very useful feature of the tool. It is so useful that I would actually be tempted to use it just for the convenience of the filter conversion alone, although I do have a goofy little program that does this for me.
As usual, nice job thinking about the needs of the users. I think the biggest challenge for ADFind is the “discoverability” of all the features. You may need to start shipping a CHM file to go with it with all these tidbits wrapped up in it. 🙂
Great article Joe!! I notice on some boards people ask how they can learn LDA queries. One great way is to read your blog and read your message boards. I learn a ton from those two resources.
That is one hell of a command line… I can see how this would intimidate someone that only worked on the GUI.
JoeK: Yep, I understand, it does a lot and there isn’t a lot of help. But I look at it this way, I could work hard on the tool and make it do what I need and then share it or I could spend the time on making the help better which I don’t need myself and have less capability. 🙂
Mike: The thing with command lines is to break them up in your head and look at them, then they aren’t quite so intimidating. That is why I broke the one above down. I am still a world of difference short of the command lines that I used to enter on UNIX or on DEC PDP’s and VAXes in the day… The hardest part I run into is coming up with good logical and flexible ways to represent functionality that isn’t necessarily innate in the LDAP mechanism such as handling the binary stuff. I would like it if the directory handled either format and if you wanted the int8 you specified the binary modifier otherwise it gave you a string just like the replication metadata attributes do. I guess the issue then though is just like what I have, what string do you give? This is expecially crucial for time which can be represented in multiple ways and the actual values will vary based on if you want local TZ or not. There are a lot of possible issues there so I understand why the AD Dev folks avoided the issues. Though SIDs and GUIDs I think they could handle better natively. Well SIDs anyway, GUIDs are tougher because their isn’t a directory type specific to them.
joe
Good blog!
I’m just getting into the time stuff with ADFIND. Here’s one example I used earlier this week to find unused account (Yes, I know about OldCMP, but it doesn’t give me all the options I need).
adfind -csv -default -s subtree -tdc -utc -binenc -f “(&(samaccounttype=805306368)(|(accountexpires
Bummer – it cut off my previous comment. I think I’m being censored by Joe. 🙁
As usual Joe, this tool is incredible. You make me look good. When I get to use your hard work.
This account expiration conversion is so slick.
Paul Bergson
Hi,
great blog but when I type this request for example I always have an error.
The request is : adfind -default -tdcs -binenc -f “&(objectcategory=person)(accountexpires>={{LOCAL:2006/08/26}})(accountexpires
SOrry,
I dont know why the post sent has been modified.
The adfind request I typed was : adfind -default -tdcs -binenc -f “&(objectcategory=person)(accountexpires>={{LOCAL:2006/08/26}})(accountexpires
Dude…thank you for this. I spent all day trying to put together a friggin VB Script and nothing worked!! You rock.