Posted on 09-29-2015 11:43 AM
Over the last year, I have been working on a workflow that would allow me to deploy software via policies without having to maintain packages within Casper Imaging and having multiple imaging configurations. In our environment, we push software based on an end user's department. Sometimes other user variables can come into effect such as the Site and Building as well.
However there is no way to currently do that within Casper Imaging. This led me to work on a script that would run as a postinstall script in a package. Just to be clear, this is a postinstall script so it needs to be added as a postinstall script in Composer or Packages or pkgbuild or whatever your favorite packaging tool is. This package is to be run during Casper Imaging (not on restart/firstboot). What this package does is it will pull information from your JSS using the API and determine all users, departments, buildings, and sites. Based on that information it will then put xml and txt files on the target drive. The idea here is that you now have user/location information on the drive which you could then read from on a first boot script or at any point later in the computer's deployment.
The goal here was to stick to built-in OS X tools so I used bash and made use of the little I know of AppleScript. You will need to create an api user in the JSS that has API access to at least read Buildings, Departments, Sites and Users.
There were two things I did want to improve:
One was the parsing of the Site XML. In the XML, if you upload information for Site you have to include the Site ID and Site Name. I had trouble getting rid of the tags so that it looks nice in Apple Script. But since it was cosmetic and not a showstopper, I never spent any more time on it.
The second issue was that I wanted to find a better way of taking the info generated from the API in bash into arrays that could be generate a list in AppleScript. That's why I resorted to writing to temp files in bash and reading from it in AppleScript.
I should probably put this on github or on a blog that would explain further how to make use of the XML or TXT files on firstboot. And maybe I will at some point, but until then perhaps someone can make use of this. I sincerely hope this helps others. I see very frequently people ask here how can I assign a user to a computer. And this is just one of the many approaches you can take.
Anyways, if anyone makes any improvements to it, feel free to give give me a holler. It will probably help me and others.
#!/bin/bash
#######
#This script has been created by Balmes Pavlov on 08/27/15
#Version 1.0
##
#Purpose:
#The purpose of this postinstall script is aide in getting a username assigned to a computer in the JSS
#Here is a rough outline of the script:
#The XML files are downloaded and generated from the JSS for Users, Buildings, and Departments.
#The postinstall will popup with a dialog requesting to have the tech enter a username.
#The entered username will then be compared to a list of users from the JSS.
#If user is found then an XML file is created on the harddrive based of user info.
#If user is not found then the tech is prompted to either re-try entering the username or to continue anyway.
#If the tech continues without a valid username then they will be prompted for department and building.
#The departments and buildings are generated live from the previously downloaded XML files from the JSS.
#The department and building information is then copied into an XML file created on the harddrive.
######
#This script does not upload information but rather generates an XML that will be copied to
#the harddrive of the computer which can then be uploaded on firstboot.
#Note that there will be an XML file copied to the HDD but also text files.
#The hope is to make it flexible so that you can use the API to upload the XML file
#Or make use of the jamf binary if you want to just read off the text files
#using /bin/cat or the tool of your choice.
#Working directory for script to reference resources
declare -x install_dir=`dirname $0`
#Enter in the URL of the JSS we are are pulling info from. (NOTE: We will need https:// and :8443 (or whatever port you use). Example: https://jss.company.com:8443 )
jssURL="https://jss.company.com:8443"
#Enter in a username and password that has the correct permissions to the JSS API for what data we need. Read Access to Buildings, Departments, Sites and Users.
jssUser="apiuser"
jssPass="apiuser"
#Default file path we will use to place XML file for JSS API submission
#Feel free to edit these to the location of your choice
declare -x FirstbootXML="$3/Library/Scripts/Firstboot/location.xml"
declare -x UsernameTxtFile="$3/Library/Scripts/Firstboot/user.txt"
declare -x DeptTxtFile="$3/Library/Scripts/Firstboot/dept.txt"
declare -x BuildingTxtFile="$3/Library/Scripts/Firstboot/building.txt"
#Name for XML files pulled from JSS
declare -x BuildingXML="$install_dir/buildings.xml"
declare -x DeptXML="$install_dir/departments.xml"
declare -x UserXML="$install_dir/users.xml"
declare -x SiteXML="$install_dir/sites.xml"
#Create directory for firstboot upload
#Feel free to edit these to the location of your choice
/bin/mkdir -p "$3/Library/Scripts/Firstboot/"
##You shouldn't have to edit anything below. Proceed with caution.
#Get list of departments from JSS
/usr/bin/curl -k -v -u "$jssUser":"$jssPass" "$jssURL"/JSSResource/departments -X GET -o "$DeptXML"
#Get list of users from JSS
/usr/bin/curl -k -v -u "$jssUser":"$jssPass" "$jssURL"/JSSResource/users -X GET -o "$UserXML"
#Get list of buildings from JSS
/usr/bin/curl -k -v -u "$jssUser":"$jssPass" "$jssURL"/JSSResource/buildings -X GET -o "$BuildingXML"
#Get list of buildings from JSS
/usr/bin/curl -k -v -u "$jssUser":"$jssPass" "$jssURL"/JSSResource/sites -X GET -o "$SiteXML"
######################
######################
#Function to gather username from an AppleScript dialog
function UserName(){
username=`/usr/bin/osascript <<EOT
tell application "System Events"
with timeout of 43200 seconds
activate
set myReply to text returned of (display dialog "Please enter AD username:" default answer "" buttons {"Continue"})
end timeout
end tell
EOT`
/bin/echo "$username"
}
######################
######################
#Function to determine whether to re-try UserName function
function retryUserName(){
retryUserName=`/usr/bin/osascript <<EOT
tell application "System Events"
with timeout of 43200 seconds
activate
-- A reminder to improve this so that the actual username that was typed in by the tech is displayed.
set Retry to display dialog "The username does not exist." buttons {"Retry", "Continue Anyway"}
set buttonPressed to button returned of Retry
end timeout
end tell
EOT`
echo "$retryUserName"
}
######################
######################
#Function to determine building from an AppleScript dialog
function Building(){
#install_dir=`dirname $0`
#BuildingXML="$install_dir/buildings.xml"
tmpBuildingsFile="$install_dir/BuildingsList.txt"
#Generate a variable from information in XML to use in an array
Buildings=$(xpath "$BuildingXML" '/buildings/building/name' 2>&1| sed 's/-- NODE --//g' | sed 's/<name>//g' | sed 's/</name>//g' | sed 's/Found.*nodes://g')
IFS=$'
'
#Iterate through XML array
for i in $Buildings; do
BuildingsArray+=($i)
echo $i >> "$tmpBuildingsFile"
chmod 777 "$tmpBuildingsFile"
done
building=`/usr/bin/osascript <<EOT
tell application "System Events"
with timeout of 43200 seconds
activate
-- Create an empty list called BuildingsList
set BuildingsList to {}
-- Populate list with contents read from a file
set BuildingsFile to paragraphs of (read POSIX file "$tmpBuildingsFile")
-- Iterate through each line in file to add to BuildingsList
repeat with i in BuildingsFile
if length of i is greater than 0 then
copy i to the end of BuildingsList
end if
end repeat
-- For testing to make sure the right number of items are counted in the list
-- display dialog count of BuildingsList
choose from list BuildingsList with title "Building List" with prompt "Please select a building to associate to computer:"
end timeout
end tell
EOT`
echo "$building"
if [ -f "$tmpBuildingsFile" ]; then
rm -f "$tmpBuildingsFile"
fi
}
######################
######################
#Function to determine Dept from an AppleScript dialog
function Dept(){
tmpDeptFile="$install_dir/DeptList.txt"
Dept=$(xpath "$DeptXML" '/departments/department/name' 2>&1| sed 's/-- NODE --//g' | sed 's/<name>//g' | sed 's/</name>//g' | sed 's/Found.*nodes://g')
IFS=$'
'
for i in $Dept; do
DeptsArray+=($i)
echo $i >> "$tmpDeptFile"
chmod 777 "$tmpDeptFile"
done
department=`/usr/bin/osascript <<EOT
tell application "System Events"
with timeout of 43200 seconds
activate
-- Create an empty list called DepartmentsList
set DepartmentsList to {}
-- Populate list with contents read from a file
set DepartmentsFile to paragraphs of (read POSIX file "$tmpDeptFile")
-- Iterate through each line in file to add to DepartmentsList
repeat with i in DepartmentsFile
if length of i is greater than 0 then
copy i to the end of DepartmentsList
end if
end repeat
-- For testing to make sure the right number of items are counted in the list
-- display dialog count of DepartmentsList
choose from list DepartmentsList with title "Department List" with prompt "Please select a department to associate to computer:"
end timeout
end tell
EOT`
echo "$department"
if [ -f "$tmpDeptFile" ]; then
rm -f "$tmpDeptFile"
fi
}
######################
######################
#Function to determine Site from an AppleScript dialog
function Site(){
tmpSiteFile="$install_dir/SiteList.txt"
Site=$(xpath "$SiteXML" '/sites/site' 2>&1| sed 's/-- NODE --//g' | sed 's/<name>//g' | sed 's/</name>//g' | sed 's/Found.*nodes://g' | sed 's/<site>//g' | sed 's/</site>//g') #| sed 's/<id>//g' | sed 's/</id>/ /g')
IFS=$'
'
for i in $Site; do
SitesArray+=($i)
echo $i >> "$tmpSiteFile"
chmod 777 "$tmpSiteFile"
done
site=`/usr/bin/osascript <<EOT
tell application "System Events"
with timeout of 43200 seconds
activate
-- Create an empty list called SitesList
set SitesList to {}
-- Populate list with contents read from a file
set SitesFile to paragraphs of (read POSIX file "$tmpSiteFile")
-- Iterate through each line in file to add to SitesList
repeat with i in SitesFile
if length of i is greater than 0 then
copy i to the end of SitesList
end if
end repeat
-- For testing to make sure the right number of items are counted in the list
-- display dialog count of SitesList
choose from list SitesList with title "Site List" with prompt "Please select a site to associate to computer:"
end timeout
end tell
EOT`
echo "$site"
if [ -f "$tmpSiteFile" ]; then
rm -f "$tmpSiteFile"
fi
}
######################
######################
#Function to check whether username exists in JSS
function checkUserName(){
#install_dir=`dirname $0`
#UserXML="$install_dir/users.xml"
#FirstbootXML="/Library/Scripts/Firstboot/location.xml"
UserName
#Variable to get list of usernames from JSS to compare to input from dialog prompt
SearchUserName=$(xpath "$UserXML" '/users/user/name' 2>&1| sed 's/-- NODE --//g' | sed 's/<name>//g' | sed 's/</name>//g' | sed 's/Found.*nodes://g' | grep "$username")
if [ "$username" = "$SearchUserName" ]; then
# /bin/echo "$username exists in the JSS"
usernameTag="<username>$username</username>"
#Write the username into XML on harddrive for JSS to upload on first boot
/bin/echo "<computer><location>$usernameTag</location></computer>" > "$1"
/bin/echo "$username" > "$2"
/usr/bin/osascript <<EOT
tell application "System Events"
with timeout of 43200 seconds
activate
display dialog "Username exists. Location.xml file has been created successfully." buttons {"Continue"}
end timeout
end tell
EOT
if [ -f "$1" ]; then
Site
#Need to improve code so that <id> and </id> tags are stripped from the AppleScript dialog
#but still show up in xml file for uploading
if [[ -n "$site" || "$site" != "" ]]; then
siteTag="<site>$site</site>"
/bin/echo "<computer><general>$siteTag</general><location>$usernameTag</location></computer>" > "$1"
fi
fi
exit 0
else
echo "$username does not exist in the JSS"
retryUserName
if [ "$retryUserName" = "Retry" ]; then
checkUserName "$FirstbootXML" "$UsernameTxtFile" "$DeptTxtFile" "$BuildingTxtFile"
else
# echo "I'm going to ask for department and building at this point which is going to be yet another function!"
/usr/bin/osascript <<EOT
tell application "System Events"
with timeout of 43200 seconds
activate
display dialog "Assigned username for this computer will be blank. However for imaging purposes the department and building information will be requested to determine what software gets pushed and made available through Self Service." buttons {"Continue"}
end timeout
end tell
EOT
Dept
Building
Site
if [[ -n "$department" || "$department" != "" ]]; then
departmentTag="<department>$department</department>"
/bin/echo "$department" > "$3"
fi
if [[ -n "$building" || "$building" != "" ]]; then
buildingTag="<building>$building</building>"
/bin/echo "$building" > "$4"
fi
if [[ -n "$site" || "$site" != "" ]]; then
siteTag="<site>$site</site>"
fi
/bin/echo "<computer><general>$siteTag</general><location>$departmentTag$buildingTag</location></computer>" > "$1"
fi
fi
}
checkUserName "$FirstbootXML" "$UsernameTxtFile" "$DeptTxtFile" "$BuildingTxtFile"
exit 0