AD computername/JSS record comparision extension attribute

flojo10
New Contributor

Hello All,

This is probably a simple one, but I'm pretty new to bash scripting..
I created an EA to compare a computer's JSS record and AD to ensure there aren't any mismatches. This works properly when the names match and returns a mismatch if the dsconfigad commands returns something that doesn't matches. However, the problem is that if the machine isn't bound to AD, this still returns a match. I tried doing a null string test, but still NG...

#!/bin/bash
jssName=`jamf getComputerName | awk '{print tolower($0)}'`
adinfo=`dsconfigad -show | grep 'Computer Account' && dsconfigad -show | grep 'Active Directory Domain'`
computerName=`scutil --get ComputerName`
if [[ $adinfo = *"${jssname}"* ]]; 
then
echo "<result>JSS & AD records match.
JSS Record          = $computerName
$adinfo</result>"
elif [ -z $adinfo ]; 
then 
echo "<result>Not bound to AD...attempting to rebind. If this isn't auto-corrected in about 15 minutes, I'll need to be connected to the LAN to be fixed.</result>"
else
echo "<result>Mismatch.... help please!
JSS Record         = $jssName"
    if [ -z $adinfo ];
    then 
    echo "I'm not bound to AD either! This should be fixed shortly, though</result>"
    else
echo "$adinfo</result>"
    fi
fi

Just a little background on why this is needed -- we're using DEP to initially enroll Macs as their serial number, then manually renaming in the JSS later during initial setup. This then triggers a policy to unbind the computer, remove the dummy AD record and rebind it under our naming convention.

16 REPLIES 16

joshuasee
Contributor III

Hmm. I didn't test, but I'm noticing the single equals sign on line 5, the first if statement. Wouldn't that do an assignment rather than a comparison, which would succeed, thus always returning true?

If all else fails, you could create a smart group containing computers whose Active Directory Status is Not Bound, scope the policy to exclude those machines and ignore the flaw in the EA.

mm2270
Legendary Contributor II

I didn't look over this too closely, but I wanted to point out that, one of the variables you are defining as 'jssName' may not be giving you what you think it is. jamf getComputerName will give you the same results as scutil --get ComputerName which I see you're using for the 'computerName' variable. The jamf binary is only querying the local system for its name, its not going to grab the name from the JSS record, in case that's what you were thinking it would do. And I do think that's what you were thinking since in your post you mention comparing the computer's JSS record name with the AD name.

The only way to get the computers name from the JSS would be to look it up using the API and the Mac's serial number or UUID or something like that as the string to search for.

flojo10
New Contributor

thanks for the clarification @mm2270

so since i have a config profile that sets the computername to the jss record, this happens immediately? the reason i thought that jamf getComputerName was returning the JSS record is because i tested by renaming a computer in the JSS, then running that command no more than 5 seconds later, and it returns the xml tags as well, unlike the scutil command.

but in any case, what do i need to fix my case for when dsconfigad -show returns nothing? as of now, it's returning a false positive. i realize also that the wildcards in my string comparison are also returning false positives, so that'll have to be tweaked..

@joshuasee - we have a smart group for not bound to AD, but this would be separate, targeted to computers that are bound, but bound under the wrong computername

mm2270
Legendary Contributor II

I'd have to look over your script and see what needs to be changed to get it to work as you want. Give me a few and I can do that and post something back.

As for the jamf getComputerName command, I did some testing to confirm what I was stating. If you run that command while completely off of any network, it still returns a computer name, so its definitely pulling that from the local system, not the JSS itself. I also tested manually renaming a Mac with scutil --set ComputerName, then ran that command again and it immediately returned the new name. Yet, querying the JSS via the API to get the Computer Name brings back the name before I renamed it. So that command isn't getting the name from the JSS for sure.

mm2270
Legendary Contributor II

Ok, so circling back here, I'm a little confused on exactly what you need to verify in your EA. I see you're getitng the local computer name with scutil, but then you seem to only be using that for the purpose of output in the EA, with the following line:

JSS Record         = $jssName"

I'm not sure what that's supposed to be telling you since that's the local computer name that would show up in the Sharing Preference Pane for example. Its not the JSS' computer record name, though hopefully they would match up.

So here's my take on doing this with an EA. Keep in mind as I stated before the only way I'm aware of to get the actual JSS computer record name is to use the API, which means in the case of an EA script, you have to hardcode in an API username & password with read privileges. Please don't use an API account with write privileges there as its risky since its sitting there in plain text.
Many people even have a problem with needing to include an API Read only account name in an EA script, so there's that to consider too.

#!/bin/bash

## API Read account credentials (edit these)
apiuser="apiuser"
apipass="apipass"
jssurl=$(defaults read /Library/Preferences/com.jamfsoftware.jamf.plist jss_url)

UDID=$(ioreg -rd1 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformUUID/{print $4}')

JSSComputerName=$(curl -H "Accept: text/xml" -sfku "${apiuser}:${apipass}" "${jssurl}JSSResource/computers/udid/$UDID/subset/general" | xmllint --format - | awk -F'>|<' '/<name>/{print $3; exit}')

ADCompName=$(dsconfigad -show 2>/dev/null | awk -F'= ' '/Computer Account/{print $NF}' | sed 's/$$//')

if [[ "$ADCompName" != "" ]]; then
    echo "This computer has an Active Directory bind"
    ## Turn on case insensitive matching
    shopt -s nocasematch
    if [[ "$ADCompName" == "$JSSComputerName" ]]; then
        result="Match"
    else
        result="Mismatch"
    fi
    ## Turn off case insensitive matching
    shopt -u nocasematch
else
    echo "This computer is not bound to Active Directory"
    result="Not bound"
fi

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

So what's going on here? The script is getting the JSS address from the local jamf.plist, since that should always be populated with the JSS the Mac is under management with.
Its using that in combination with the API info you supply to get the JSS Computer Name. It does this by grabbing the Mac's UUID (or UDID as JAMF refers to it as) and using that in the API call. it pulls just the Mac name. It checks dsconfigad -show, which only is populated when a Mac has been previously bound to AD. It won't be there at all if the Mac was never bound, but there are cases of Macs falling out of domain trust and dsconfigad -show will still get you a result since it reads from a local plist file, not from AD directly. (just something to keep in mind)

It then compares the AD name (minus the trailing $ character) using case insensitive matching with the JSS computer name and will create a result of either "Match" or "Mismatch"
If there is nothing returned from the dsconfigad -show command, it means the Mac has no AD bind and instead creates a result of "This computer is not bound to Active Directory"

It finally prints the result variable between the result tags.

Lastly, if you'd rather not do the whole API call in this, you could just use the local computer name and compare to the AD computer account name to see if they match. Since generally speaking, if your Macs are submitting regular inventory to the JSS, the JSS computer name should match the local name, you could do that. I only included a way to do it via the API b/c in your post you mentioned wanting to compare the JSS name with the AD name, and this would be the only real way to do that that I know of.

Give this a try and see if it works for you.

flojo10
New Contributor

@mm2270 I just got back to looking at this...
It doesn't work quite right as of yet. The problem seems to be this part; when I attempt to run it manually (of course removing the variables and replacing them with real values), I receive -:1: parser error : Document is empty

curl -H "Accept: text/xml" -sfku "${apiuser}:${apipass}" "${jssurl}JSSResource/computers/udid/$UDID/subset/general" | xmllint --format - | awk -F'>|<' '/<name>/{print $3; exit}'

If I just run curl -H "Accept: text/xml" -sfku "${apiuser}:${apipass}" "${jssurl}JSSResource/computers/udid/$UDID/subset/general" it returns nothing. I've confirmed that the machine is bound to AD and that the API URL returns the computer record data.

Thanks again for the help

mm2270
Legendary Contributor II

@flojo10 The parser error comes from xmllint, which is expecting xml formatted input. Since your curl command isn't returning anything, its generating an error as it didn't receive valid input it could work on.
I suspect there's an issue with your API account credentials or the permissions on the account. I would go back and double check that the account you're using has access to read Computer objects in the JSS.
Did you change the apiuser="apiuser" & apipass="apipass" strings to match the username and password for an account in your JSS with those privileges?

flojo10
New Contributor

@mm2270

I did change the apiuser and apipass strings to match a JSS account with those privileges. I also tested with my own (administrator) account, which had the same blank results...

mm2270
Legendary Contributor II

So the next step will be to test that account in a browser to access an API record. Open up a browser on a Mac that can reach your JSS, and put in something like

https://your.jss.address:8443/api

This will get you to the API page for your Casper server. From there, locate the Computers section and open that up. Then click on any of the ones labeled with a blue "GET" next to them, which is the Read API call. You will be prompted to enter an API name/password when you do that. Enter the one you're using to see if it lets you access those computer records or do a lookup by Serial number, UDID, etc. If it fails, the issue is with the account. As I mentioned, it may not have appropriate permissions to read Computer objects.

If it allows you in using those credentials, post back and we'll see about taking it step by step to see where the issue is.

flojo10
New Contributor

@mm2270

The API page works with my account perfectly, so it seems it's something else. Once we get this working with my account, I'll try again with our separate service account, which is enabled for this as we have it authenticating and pulling JSS records through the API using our in-house app and a few of our internal web pages.

mm2270
Legendary Contributor II

Can you use the following script and post back with what gets returned. If necessary, scrub out anything sensitive. I just want to see if we're getting the correct address and other info to do the API call. I had run my exact script above on my Mac after swapping in the actual API username/password and it worked perfectly for me.

Let me know how this turns out.

#!/bin/bash

## API Read account credentials (edit these)
apiuser="apiuser"
apipass="apipass"
jssurl=$(defaults read /Library/Preferences/com.jamfsoftware.jamf.plist jss_url)

UDID=$(ioreg -rd1 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformUUID/{print $4}')

## Debugging echo lines
echo "JSS URL:  ${jssurl}"
echo "UUID:  ${UDID}"
echo "Full API URL:  ${jssurl}JSSResource/computers/udid/$UDID/subset/general"

flojo10
New Contributor

@mm2270

Everything looks good there. I even created a new JSS user account with the same privileges as our service account (auditor), which I tested and confirmed works on the API page. Script and results (specifics changed, but confirmed UDID and jss url are correct)

#!/bin/bash

## API Read account credentials (edit these)
apiuser="apitest"
apipass="12345"
jssurl=$(defaults read /Library/Preferences/com.jamfsoftware.jamf.plist jss_url)

UDID=$(ioreg -rd1 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformUUID/{print $4}')

## Debugging echo lines
echo "JSS URL:  ${jssurl}"
echo "UUID:  ${UDID}"
echo "Full API URL:  ${jssurl}JSSResource/computers/udid/$UDID/subset/general"

JSS URL:  https://myssurl.com:8443/
UUID:  jsscomputerudidrecordmatch
Full API URL:  https://myjssurl.com:8443/JSSResource/computers/udid/jsscomputerudidrecordmatch/subset/general

mm2270
Legendary Contributor II

So the "jsscomputerudidrecordmatch" is showing up as the actual UUID string, right? Not that text? I assume that was just replaced by you, but wanted to be sure.

flojo10
New Contributor

@mm2270

Correct. The actual UUID shows; I just replaced it in the results to confirm that the two are the same

flojo10
New Contributor

@mm2270

I know the UUID is the most authoritative identifier, but I tested going the serial number route and confirmed it worked. Any idea as to why the other doesn't? Here's the difference from the original script:

SerialNum=$(ioreg -c IOPlatformExpertDevice -d 2 | awk -F" '/IOPlatformSerialNumber/{print $(NF-1)}')

JSSComputerName=$(curl -H "Accept: text/xml" -sfku "${apiuser}:${apipass}" "${jssurl}JSSResource/computers/serialnumber/$SerialNum/subset/general" | xmllint --format - | awk -F'>|<' '/<name>/{print $3; exit}')

mm2270
Legendary Contributor II

Hi @flojo10 Ok, just getting back to this. Still want to help you get this working.
If the UDID is showing up in that script, and the URL is being properly generated, then the next step would be to manually try a curl against the API using what the script returned.
For example, take the string returned as the Full API URL, what you show above as "https://myjssurl.com:8443/JSSResource/computers/udid/jsscomputerudidrecordmatch/subset/general"
and use that in the following command in Terminal:

curl -H "Accept: text/xml" -u apiuser:apipass "https://myjssurl.com:8443/JSSResource/computers/udid/jsscomputerudidrecordmatch/subset/general"

Obviously, replace apisuer:apipass with the actual apiusername and passwords. Keep the : between them as shown. Also replace the url string with what you saw returned in the script.
Note also that I left out the -sfk flags in the curl command, so if there are any errors or failures, we should see it returned at the command prompt.

Let's see what output you get from that. Its not going to be formatted, so if it returns anything it will be one lone string of text all mashed together, but that's OK. We're just testing to see if you get some data back first.