Determining what ActiveDirectory OU a Mac computer is located in?

dstranathan
Valued Contributor II

Can Casper Suite determine what ActiveDirectory OU a Mac computer is located in?

I can't think of a native OS X command line tool that can do this.

Tools such as dsconfigad, dscl, etc are unable to glean this info to my satisfaction. dscl and Directory Utility.app can see the OrganizationalUnit (top-level), but it can't drill too deep, regardless of authentication/rights.

Please enlighten me if there is an efficient (scriptable) way for a Mac to "see" what computer OU it is located in.

Can the Casper Suite tell what computer OU a specific Mac computer is located in?

16 REPLIES 16

chriscollins
Valued Contributor

You would have to use ldapsearch:

ldapsearch -LLL -h domain.company.com -x -D domain.user@domain.company.com -w "domainuserpassword" -b "DC=Domain,DC=company,DC=com" "name=thecomputername"

In the output the dn: / distinguishedName attribute would be what OU the machine is in.

The only problem with this is in the output if your OU path is extremely long ldapsearch splits it onto two lines making it hard to grep.

mm2270
Legendary Contributor III

If the Mac is properly joined to and can talk to AD its possible to pull it.

#!/bin/sh

CompName=$(dsconfigad -show | awk '/Computer Account/{print $NF}' | sed 's/$$//')

## Replace "DOMAIN" in the command below with your actual domain name
OU=$(dscl "/Active Directory/DOMAIN/All Domains" read /Computers/${CompName}$ dsAttrTypeNative:distinguishedName | tail -1 | awk -F"${CompName}," '{print $2}')

echo "$OU"

The above should print something along the lines of:

OU=Computers,OU=Macs,DC=com,DC=inc,DC=acme

davidacland
Honored Contributor II

Looks like its stored in the computer record in the distinguishedName attribute so you could read it with:

dscl /Active Directory/SHORTDOMAINNAME/All Domains -read /Computers/computername distinguishedName

dbrodjieski
New Contributor III

We have had luck with dscl to get this info. Our extension attribute looks like this:

#!/bin/bash

computerID=$(hostname)
computerOU=$(dscl /Search read /Computers/$computerID dsAttrTypeNative:distinguishedName 2> /dev/null | sed -e 's/dsAttrTypeNative:distinguishedName://g' | tr -d "
" | cut -d "," -f 2-)

echo "<result>$computerOU</result>"

This gives us just the OU as a result.

jhalvorson
Valued Contributor

Maybe I have it all wrong, but is @chriscollins ldapsearch function asking Active Directory which OU has this hostname. Where as, the other two commands are asking the Mac OS what it believes is the OU it is in.

When things go bad, aren't there times the Mac thinks it is in one OU and AD has a record in another OU? Would it be better practice to report both and compare?

dstranathan
Valued Contributor II

Great info everyone, thanks. I always appreciate how fast people share info and ideas here.

Got my wheels turning now.

Each dept here at my company has a computer OU dedicated to each specific department. Very clean. Great for policies, reporting, security etc.

I'm asking this question because I'd like to force my IT team into move our newly-deployed Macs into a computer OU that matches the department that the Mac belongs to. This is how we deploy Windows PCs and I want to be consistent with both platforms in terms of OU location.

On the Windows side (using GPO), any Windows PC in the default "Computers" OU automatically gets denied certain functionality/access because the computer is in an unsupported OU. Thus, our IT team MUST move a new PC into the correct AD computer OU at deployment time. Standard operating procedure. But my IT team forgets the Macs in the "Computers" ghetto, because there is no consequence for leaving them in "Computers".

If my Macs or Casper can logically determine where the Macs are located (or better yet - where they are NOT located - in this case the generic MS "Computers" OU) then I can act on this information in some useful way. What that is exactly I don't know yet. Regardless, this will encourage the IT team to start putting our new Macs into the correct dept OUs like we do on the Windows side.

nessts
Valued Contributor II

@jhalvorson when you use dscl /Active Directory that is similar to a ldapsearch, its reading directly from AD in the OS X way.

dstranathan
Valued Contributor II

@jhalvorson I use dscl all the time for user/group information, but wasn't able to drill into the specific Computer OUs that I was looking for. I need to examine this again.

mm2270
Legendary Contributor III

Exactly what @nessts said. The dscl commands above are pulling the OU info directly from AD. The main difference between ldapsearch and dscl are that ldapsearch is doing an 'on the fly' bind, so it's possible to use it to do lookups against AD on a Mac that isn't actually bound to your AD environment. That can be useful in some cases.
Otherwise they should pull the same information.

davidacland
Honored Contributor II

An issue with ldapsearch is the need to supply the password with the command. It can be hard to protect the password in that case.

jhbush
Valued Contributor II

@dstranathan this is what I use if you're interested:

#!/bin/sh
ad_computer_name=`dsconfigad -show | grep "Computer Account" | awk '{print $4}'`
ad_computer_ou=`dscl /Search read /Computers/$ad_computer_name | 
grep -A 1 dsAttrTypeNative:distinguishedName | 
cut -d, -f2- | sed -n 's/OU=//gp' | 
sed -n 's/(.*),DC=/1./gp' | 
sed -n 's/DC=//gp' | 
awk -F, '{
N = NF
while ( N > 1 )
{
printf "%s/",$N
N--
}

printf "%s",$1
}'`

echo "<result>$ad_computer_ou</result>"

the result looks like this: domain.com/Workstations/USA/Building/Macs

dstranathan
Valued Contributor II

@ jhalvorson: Good point. On occasion I find Macs that computer name and the hostname differ. Usually a human error on IT's fault.

@ jhbush1973 and @mm2270 uses the AD computer record name rather that computer name/hostname, which prevents avoids this problem.

@ jhbush1973 and @ dbrodjieski Any reason you are marking-up (tagging) the final output with <result> tags? Is this a JAMF thing? (I don't have Casper purchased yet...)

mm2270
Legendary Contributor III

@dstranathan
When script code echoes back items contained in <result> </result> tags, this is for an Extension Attribute. Extension Attributes (otherwise known as "EAs") are custom database fields that can be added to the JSS to help capture any extra information outside of the normal inventory the JSS collects. Usually these 'EAs' are in the form of scripts that run on the Macs at the time of inventory collection, grab some pieces of data and then echo back the data contained in those tags. The jamf binary captures the contents between the tags and enters that into the computer record back on the JSS within the custom database field.

So for example, you can create an EA that would be called "Computer OU" and have the script run on each Mac at inventory collection and then be able to see their AD OU inside each respective Mac's record.

Hope that makes sense.

cvgs
Contributor II

If you have clients sometimes being off the corporate network (i.e. laptops), you might also think about additionally caching the result of the dscl call and using the previously cached response if the AD cannot be reached. This prevents empty EA results when a recon is done off-network.
This might not be strictly necessary for the OU record (haven't tested), but we use the same method to pull different AD attributes into the JSS inventory.

Here is an EA script doing a cached dscl lookup for OU. Well, it really should be pythonized, but for now it works.

#!/bin/bash

# adapted from "AD Attribute generic", returns CN without the first part ("CN=computername,")
# which translate to the OU of the record

# Set this to the desired AD Attribute
dsclAttribute="dsAttrTypeNative:distinguishedName"
dsclAttributeCacheName="dsAttrTypeNative:distinguishedName_OU"

# Nothing to change below
prefCacheDomain="local.extensionattribute.ds"
dsclValue=""

# we need our own computername for the OD search
# so parse response of dsconfigad -show
computerAccount=$( /usr/sbin/dsconfigad -show 2>/dev/null |
    /usr/bin/sed -ne 's|^Computer Account.*= (.*)$|1|p' )
if [[ ${computerAccount} == "" ]]; then
    # no computer account found
    dsclValue=""
else
    # look up value for computer account
    dsclResponse=$(/usr/bin/dscl -q -plist "/Search" -read "/Computers/${computerAccount}" "${dsclAttribute}" 2>/dev/null)
    dsclRC=$?
    if [[ $dsclRC -ne 0 ]]; then
        # query failed, so get cached value
        dsclValue=$( defaults read "${prefCacheDomain}" "${dsclAttributeCacheName}" )
    else
        # query succeeded, cut away the part up to the first comma from DN (should be the OU)
        dsclValue=$( echo "${dsclResponse}" |
            /usr/bin/grep -v '^<!DOCTYPE.*>$' |
            /usr/bin/xpath "//key[.="${dsclAttribute}"]/following-sibling::*[1]/string/text()" 2>/dev/null |
            /usr/bin/cut -d "," -f 2-100 
        )
        # cache current value
        defaults write "${prefCacheDomain}" "${dsclAttributeCacheName}" -string "${dsclValue}"
    fi
fi
echo "<result>${dsclValue}</result>"
exit 0

dwandro92
Contributor III

Here is the function that I created to do AD lookups more easily. This function also includes parsing to resolve the line-split issue that @chriscollins mentioned:

#!/bin/bash

# Function for querying LDAP
# Parameters:
# (1) Filter to be used for querying LDAP.
# (2) Optional - Value(s) that you are searching for within LDAP.
# (3) Optional - User to perform query with
# (4) Optional - Password for user to perform query with
ldapQuery() {
    # Set default return code to 0 (Success)
    local iRet=0

    # If an LDAP user's credentials were passed to the function, use them to query LDAP. Otherwise, use a service account
    [ -n "$3" -a -n "$4" ] && { ldapUser="domain\${3}"; ldapPass="$4"; } || { ldapUser='domainuser'; ldapPass='password'; }

    # Set environment-specific variables
    domain="domain.company.com"; searchBase="dc=domain,dc=company,dc=com"

    # Set path to temp file and create it
    tmpFile=`mktemp -t ''`

    # If a return value was specified
    if [ -n "$2" ]; then
        # Query LDAP and get desired value
        result=`ldapsearch -xLLL -H "ldap://$domain" -b "$searchBase" -D "$ldapUser" -w "$ldapPass" "$1" "$2" 2> $tmpFile | perl -p00e 's/
?
 //g' | grep -v -e '#' -e '^$' | grep -i "$2" | awk -F ': ' '{ print $2 }'`

        # If the value is an integer that is 18 characters in length, convert to UNIX timestamp in "seconds" format
        aNumber='^[0-9]+$'; [[ $result =~ $aNumber ]] && [ ${#result} -eq 18 ] && result=`expr ${result} / 10000000 - 11644473600`
    # If a return value was not specified
    else
        # Query LDAP and get all values
        result=`ldapsearch -xLLL -H "ldap://$domain" -b "$searchBase" -D "$ldapUser" -w "$ldapPass" "$1" 2> $tmpFile | perl -p00e 's/
?
 //g' | grep -v -e '#' -e '^$'`
    fi

    # If credentials are invalid, set return code to 1 (Failure). Otherwise, use 0 (Success)
    [ "$(cat $tmpFile | grep 'Invalid credentials')" != "" ] && iRet=1

    # Remove temp file
    rm -f "$tmpFile"

    # Output result and send back return code 
    echo "$result"; return $iRet
}

To get the full path to the OU which the computer resides in, I use the following command:

ldapQuery "cn=$computerName" dn | sed -E -e 's/.*CN=.{0,15},//g'

Hopefully this helps!

Tigerhaven
Contributor

if the machine is non ADbind, is there a way to leverage LDAP which is being used to enroll machine in Jamf

Kunal V