Searching the JSS for specified users

iholland
New Contributor

So the task that's been laid out before me is straight forward but not really simple: take a list of up to 400 user names, search the JSS for computers those users have logged into and produce a legible list of those computers. I've started working in the API and it looks completely possible to PUT into an existing advanced computer search then GET the results and move them into a static computer group. Unfortunately I'm not good enough with scripting to fill in all the gaps before the deadline I've been given.

If anyone out there has some suggestions about how I can get the names out of a list and how I can get the script to do each name one at a time the help would be much appreciated!

Ian

12 REPLIES 12

iholland
New Contributor

Bump...

mm2270
Legendary Contributor III

@iholland Do you need the final result of what you're doing to be in a JSS group of some kind, or would it be sufficient to just have a local csv file written as it locates systems that match the usernames?

Also, for clarity, when you say you need to search for Macs that the user has logged in to, what criteria are you assuming for this? What I mean is, if you were doing this search manually, would you simply be using the simple search in the JSS and just entering the username and hitting return? Or would it be more adding a criteria (if so, which criteria?) in an Advanced Search and entering it there?

edit: meant @iholland, not jholland, sorry.

iholland
New Contributor

Technically a simple search would work, but at the moment I'm tinkering with creating an advanced search for Local User Accounts and having the specific username filled in via API and the search run. The output can really be anything, though a static group is preferable in this case.

mm2270
Legendary Contributor III

OK, I see. I asked the question because its possible to feed a simple text file with all the usernames in it to a script that would do a simple search against the JSS for Macs that have that username associated with them and then add the output into a csv file that it can continue to build as it loops over each name entry. In the end you'd have a csv file with the first column being the username and any additional columns on the same row showing matching Mac names. Technically, it doesn't have to be the Mac names, could be the serial numbers or other identifying data, like the JSS ID or something.

If you require it to be in a static group instead, that could be done as well, but would take a bit more work. The script would also run considerably slower in that case, since Static Groups require a few specific data items in them to correctly populate Macs into them when they're being created in the API. Still, its possible to do. I just don't know how realistic is it and how long it would take to build a group of potentially 400 Macs or more into the group.

iholland
New Contributor

Honestly because of time constraints I'd be more than happy with the .csv version. I just need to get an idea of how to get started on this...

mm2270
Legendary Contributor III

Base script below to get you started. Edit the relevant bits for your environment, like the JSS URL and the API Username and Password. The API account would need Read privileges to Computer objects at a minimum, maybe a few other things, but I don't know for sure.
Also change the variable for sourceFileAndPath to whatever you are using as a source file. It should be a simple plain text document with a list of the names. Although it would be possible to use another csv file as the source, I've found that I often need to resave them from a text editor like TextWrangler to get it to work, or make sure to include more code in my script to read it correctly. Plain text is just easier to deal with. Actual Excel files often have too much extraneous junk formatting in the file and tend to foul things up.

#!/bin/bash

loggedInUser=$(stat -f%Su /dev/console)

jssURL="https://your.jss.url:8443"     ## Don't include the trailing slash at the end of the URL
jssAPIuser="apiuser"
jssAPIpass="apipass"
sourceFileAndPath="/Users/$loggedInUser/Desktop/mynamesourcelist.txt"
finalFileAndPath="/Users/$loggedInUser/Desktop/resultslist.csv"

echo "Username,Computer Names" > "$finalFileAndPath"

while read -r LINE || [[ -n "$LINE" ]]; do
    results=$(curl -sfku ${jssAPIuser}:${jssAPIpass} ${jssURL}/JSSResource/computers/match/$LINE | xmllint --format - | awk -F'>|<' '/<name>/{print $3}' | sed 's/^/"/g;s/$/"/g' | tr '
' ',')
    echo "${LINE},${results}" >> "$finalFileAndPath"
done < <(cat "$sourceFileAndPath")

As I mentioned, the file you feed it (the "mynamesourcelist.txt" in my script) should be a plain list of usernames with hard returns after each name.
Run the script and it should (after a while of running) produce a csv file on your Desktop of whatever name you use for the finalFileAndPath variable name. I would test it on a small batch of names first, like 10 or something, just to ensure it works for you. No sense in sitting there watching the script run for 30 or 40 minutes only to produce something unusable.

Let me know if that helps. You can also use the above as the basis for creating a Static Group, but it would need to do individual queries for each computer name it finds and grab stuff like the JSS ID, MAC Address(es) and Computer Name and drop them into an xml file as it goes, then close up the xml file appropriately when its all done (to make sure its a valid xml) and finally, use that with a POST command to create the Static Group. As I said, all possible, but would be a longer more involved script.

Finally, keep in mind this is using the "match" functionality of searching for computers via the API. This may end up getting Macs that happen to have part of the username in the Computer name or some other element, so just bear in mind this isn't going to be as accurate as doing a specific Advanced Search for 'Local Users Accounts has' and plugging the name in there. There isn't an easy way to simulate that type of search in the API other than to do what you were speculating - create an advanced search on the fly, or edit an existing one and changing the name string each time, over and over again, up to 400 times. That doesn't sound like a viable approach to me though.

mm2270
Legendary Contributor III

@iholland Later on, I may post another script that can build a Static Computer Group from the same basic search items as above, but in a much faster way than I originally was imaging it. It could prove useful. I just need to do some checking on a few items to make sure it will work correctly.

iholland
New Contributor

This is amazing work. I'm just starting scripting so... yeah half of this I get and half of it is in a foreign language.

Is there any way we can search for what computers the users have been logged into as well as which they are already logged into? For example running a PUT command via API on an existing blank advanced search, run the search and then pull the results into the csv. Is that possible?

I've been talking with JAMF support and we're slowly piecing some things together but any assistance on this is appreciated.

mm2270
Legendary Contributor III

@iholland OK, so here's another script, similar to what I posted above, that will create a Static Group. How this works is, like the other script, it loops over the input file one line at a time, runs an API query using the wildcard matching function and gets any results back from the JSS. Basically, its the equivalent of plunking a name into the simple search field in the JSS. If any of the default fields that this function searches against match the string, it will pull the Mac up, or possibly several Macs, if any. Hopefully this will pull any machines the user has logged in to, but I can't be certain about that in your case. It works for me because we assign the User Info fields to the primary user that logs into the Mac, and that's one of the items the simple search runs a query against.
The script cleans up the output in a function by stripping out the irrelevant lines from the xml output and then adds what's left (basically, JSS ID, Computer Name, Serial Number and MAC Addresses) into a new xml file that it continues to build up as it loops over the file.
At the end, it closes the xml file and then uses it with a POST API command to create the group. The group name is defined in the script with a variable. The group JSS ID is automatically assigned by the JSS when you use "0" as the ID to create, since there is no such thing as a JSS ID of 0 for anything.

Here's the script.

#!/bin/bash

## Script name: create_JSS_staticgroup.sh
## Author:      mm2270 (Mike Morales)

## Assign the JSS URL, API Username and API Password's here.
## Note: To create Computer Groups, the API account must have Read privileges for Computer objects
## and Create privileges for Computer Groups. Additional privileges may be necessary.

jssURL="https://your.jss.address.com:8443"
jssAPIuser="apiusername"
jssAPIpass="apipassword"

sourceFileAndPath="$1"

## Check to make sure we got a source file passed to the script and that it exists
if [ "$1" == "" ]; then
    echo "Error. Source file was not passed to the script"
    echo "Usage: ./createstaticgroup.sh /path/to/source_file.txt"
    exit 1
elif [ ! -e "$1" ]; then
    echo "Error. The source file passed cannot be found or is not readable."
    exit 1
fi

## Assign a static group name to be created
finalGroupName="Group Name"

dataFound="no"

## Start the xml file that will be used to create the computer group
echo "<computer_group><name>$finalGroupName</name><is_smart>false</is_smart><computers>" > "/tmp/$finalGroupName.xml"

## Function to parse the API result into a data string to be added to the xml file
buildGroup ()
{

dataFound="yes"

## Clean up the data by removing unneeded lines
data=$(echo "$results" | sed -e '/<asset_tag/d;/<bar_code_1/d;/<bar_code_2/d;/<username/d;/<realname/d;/<email/d;/<room/d;/<position/d;/<building_name/d;/<department_name/d;/<udid/d')

## Send the cleaned up data into the xml file
echo "$data" >> "/tmp/$finalGroupName.xml"

}

## Loop over each line (string) from the source file
while read -r LINE || [[ -n "$LINE" ]]; do
    ## Search the JSS using the search string, and parse the results
    results=$(curl -H "Accept: text/xml" -sfku ${jssAPIuser}:${jssAPIpass} ${jssURL}/JSSResource/computers/match/*{$LINE}* | xmllint --format - | sed '1,3d;$d')

    ## If we got a non null result, run the function to add the results to the xml file
    if [ "$results" != "" ]; then
        buildGroup
    fi
done < <(cat "$sourceFileAndPath")

## Finally, close off the xml file so we can create the Static Computer Group
echo "</computers></computer_group>" >> "/tmp/$finalGroupName.xml"

if [ "$dataFound" == "yes" ]; then
    ## Add (POST) the computer group using the xml file
    postGroup=$(curl -X POST -skfu ${jssAPIuser}:${jssAPIpass} ${jssURL}/JSSResource/computergroups/id/0 -T "/tmp/$finalGroupName.xml")

    newGroupID=$(echo "$postGroup" | xmllint --format - | awk -F'>|<' '/<id>/{print $3}')

    if [ "$newGroupID" != "" ]; then
        echo "Computer group "$finalGroupName" successfully created with JSS ID $newGroupID. Cleaning up..."
        rm "/tmp/$finalGroupName.xml"
        exit 0
    else
        echo "Computer group creation failed. Check the curl man page for more specific information. The xml file created was left in place in /tmp/"
        exit 0
    fi
else
    echo "No computers were found from the source file. Nothing was created."
    exit 0
fi

To use this properly, first ensure your API account can create computer groups, and also read computer objects at a minimum since it will be doing both of those actions.
When you run the script in Terminal, supply the path to the source text file after the script name, so something like:

~/Desktop/create_JSS_staticgroup.sh ~/Desktop/sourcefile.txt

You'll see an error if you don't add the full path to the source file after the script name.

What's nice about this is, the script can be modified to fairly quickly create a Static Computer Group from a list of computer names. Instead of using /computers/match/ as the resource url, it could be changed to /computers/name/ and it would search for computer names supplied in the file and add each one it finds into the xml file and ultimately into the static group.

EDIT: I also meant to say that, although its technically possible to do what you proposed, namely, editing an existing advanced search criteria string, running the search and extracting the data, I don't know if I would personally do it that way for up to 400 user accounts. That seems like an awful lot of editing of a search, repeatedly. It may not be an issue, but I'd be concerned about corrupting the advanced search and it becoming unusable with that many change iterations in a short time period.

iholland
New Contributor

When I try to run the script above I get the following:

line 57: syntax error near unexpected token `<'

mm2270
Legendary Contributor III

@iholland Make sure you aren't running it by doing something like:

sh /path/to/script.sh

Because its a bash script and uses a bash only function done < <(some command), which won't work when run in the older Bourne shell (sh). Just make sure the script is executable by doing a chmod +x against it, and run it like:

/path/to/script.sh

Let me know if that still doesn't work for you.

tsylwest
Contributor

Hi :-) sorry for reviving an oldie here, but I have a semi-related question...

@mm2270 we don't use the username in the computer name, so if I understand this script right, it won't help me... What I'm looking for is a script that will, given a list of users, return a list of Mac's associated with each user.

Is that even possible?

Currently it's very manual, in that I download the whole list, and then use the other list of users to find duplicates, and delete the rest... not efficient when the full list is about 1500 macs and the other list is 150 strong :-( 

Any suggestions would be super helpful!