Posted on 09-18-2012 09:43 AM
We've been having an issue at our institution where our Macs have changed their Computer Name on their own (DNS Problems?). In an effort to stop this from happening because our printing solution relies on the AD name and Computer Name to match, I put together an on-going startup script to check the hostname against Active Directory and change it if needed. I also have the computer set a preference in a plist, then have an extended attribute check that. If it exists, then it joins a smart group and finally the JSS email me if a computer joins that group so I can follow up with computer.
I'd appreciate any feedback, comments or a better way to implement a solution. I got a little crazy with the "if-then" statements.
#!/bin/sh
############################################################################
## Script Name: check_hostname.sh
## Author: Steven Russell
############################################################################
## Variables:
## ________________________________________________________________________
## Active Directory Name
udn="ad_username_here"
password="ad_p@ssw0rd_here"
## Computers Hostname Set in System Preferences
host_name=`/bin/hostname -s | tr '[:lower:]' '[:upper:]'`
domain_name=".domain_name.org"
domain="domain_name.org"
## Company plist file to set preference
company_plist="/Library/Preferences/org.company.preferences.plist"
## Active Directory Name Stored in "ActiveDirectory.plist" Presumably same name in Directory Utility
ad_plist_name=`/usr/libexec/PlistBuddy -c "Print AD Computer ID" /Library/Preferences/DirectoryService/ActiveDirectory.plist | tr '[:lower:]' '[:upper:]'`
echo "[ check_hostname ] :: AD-Plist Name: $ad_plist_name"
## SCUTIL Name Lookups
scutil_name=`scutil --get ComputerName | tr '[:lower:]' '[:upper:]'`
echo "[ check_hostname ] :: Computer Name: $scutil_name"
scutil_localname=`scutil --get LocalHostName | tr '[:lower:]' '[:upper:]'`
echo "[ check_hostname ] :: Local Host Name: $scutil_localname"
scutil_hostname=`scutil --get HostName | tr '[:lower:]' '[:upper:]' | sed 's/.$domain$//'`
echo "[ check_hostname ] :: Host Name: $scutil_hostname"
## Count the length of the Computer Name
## We have a standard 14 character length for our Computers
## But sometimes because of typos they are longer or shorter
name_length=`echo "$scutil_name" | tr -d '
' | wc -m | sed -e 's/^[ ]*//'`
echo "[ check_hostname ] :: Name Length: $name_length of 14"
## Check-File location
check_file="Library/Application Support/some_directory/Resources/check_hostname.txt"
## Create Query for AD
## Case for Length of Computer Name
## This will do a wildcard search in AD and try and use the last five
## characters because that is where our asset tag is
case $name_length in
10)
asset_tag1=`scutil --get ComputerName | tr '[:lower:]' '[:upper:]' | cut -c1-2`
asset_tag2=`scutil --get ComputerName | tr '[:lower:]' '[:upper:]' | cut -c5-9`
;;
11)
asset_tag1=`scutil --get ComputerName | tr '[:lower:]' '[:upper:]' | cut -c1-2`
asset_tag2=`scutil --get ComputerName | tr '[:lower:]' '[:upper:]' | cut -c6-10`
;;
12)
asset_tag1=`scutil --get ComputerName | tr '[:lower:]' '[:upper:]' | cut -c1-2`
asset_tag2=`scutil --get ComputerName | tr '[:lower:]' '[:upper:]' | cut -c7-11`
;;
13)
asset_tag1=`scutil --get ComputerName | tr '[:lower:]' '[:upper:]' | cut -c1-2`
asset_tag2=`scutil --get ComputerName | tr '[:lower:]' '[:upper:]' | cut -c8-12`
;;
14)
asset_tag1=`scutil --get ComputerName | tr '[:lower:]' '[:upper:]' | cut -c1-2`
asset_tag2=`scutil --get ComputerName | tr '[:lower:]' '[:upper:]' | cut -c9-13`
;;
15)
asset_tag1=`scutil --get ComputerName | tr '[:lower:]' '[:upper:]' | cut -c1-2`
asset_tag2=`scutil --get ComputerName | tr '[:lower:]' '[:upper:]' | cut -c10-14`
;;
*)
echo "[ check_hostname ] :: Name Length is Invalid. Aborting Script..."
exit 1
esac
echo "[ check_hostname ] :: SearchQuery: $asset_tag1 * $asset_tag2*"
## Has this script run before?
if [ ! -f $checkfile ]; then
touch "$checkfile"
echo "[ check_hostname ] :: This is the first time this script is running on this computer. Setting Preferences..."
defaults write $company_plist OrphanedADObjects -bool false
defaults write $company_plist BindPending -bool false
defaults write $company_plist NameChanged -bool false
fi
## Query Active Directory
## ----------------------------------
ad_name=`ldapsearch -x -LLL -h $domain -D cn=$udn,cn=users,dc=put_domain_here,dc=org -w $password -b dc=put_domain_here,dc=org cn=$asset_tag1*$asset_tag2* | grep sAMAccountName | awk '{print$2}' | sed 's/[$]//g'| tr '[:lower:]' '[:upper:]'`
## Count how many results from AD
if [ "$ad_name" = "." -o "$ad_name" = "" -o "$ad_name" = " " ]; then
ad_name_count="0"
else
ad_name_count=`echo "$ad_name" | wc -l | sed -e 's/^[ ]*//'`
fi
echo "[ check_hostname ] :: # of AD Objects: $ad_name_count"
if [ "$ad_name_count" -eq "0" ]; then
## Lets try to Query AD with just the Asset Tag without the Location Code...
## The first two characters of our naming scheme specifies the location
ad_name=`ldapsearch -x -LLL -h $domain -D cn=$udn,cn=users,dc=put_domain_here,dc=org -w $password -b dc=put_domain_here,dc=org cn=*$asset_tag2* | grep sAMAccountName | awk '{print$2}' | sed 's/[$]//g'| tr '[:lower:]' '[:upper:]'`
ad_name_count=`echo "$ad_name" | wc -l | sed -e 's/^[ ]*//'`
echo "[ check_hostname ] :: # of AD Objects: $ad_name_count"
fi
echo "[ check_hostname ] :: AD Name: $ad_name"
if [ "$ad_name" = "." -o "$ad_name" = "" -o "$ad_name" = " " ]; then
echo "[ check_hostname ] :: ERROR: Cannot Find, or Read an Active Directory Name..."
echo "[ check_hostname ] :: Adding this computer to ' 2012-2013 Orphaned AD Objects' Group..."
defaults write $company_plist OrphanedADObjects -bool true
echo "[ check_hostname ] :: Forcing Computer to do inventory with JSS to update Smart Group and Preferences..."
jamf recon
echo "[ check_hostname ] :: Aborting Script..."
exit 1
fi
## Check plist for bind status...
bind_status=`defaults read $company_plist BindPending`
orphaned_status=`defaults read $company_plist OrphanedADObjects`
## Main:
## ________________________________________________________________________
## Test to see if Bind is pending...
if [ $bind_status = "1" ]; then
echo "[ check_hostname ] :: Resuming script after Reboot..."
echo "[ check_hostname ] :: Binding to Active Directory with current Computer Name: '$scutil_name'"
jamf policy -trigger SnowLeopardBind
echo "[ check_hostname ] :: Removing Preference for Reboot..."
defaults write $company_plist BindPending -bool false
echo "[ check_hostname ] :: Forcing Computer to do inventory with JSS to update Smart Group and Preferences..."
jamf recon
echo "[ check_hostname ] :: Binding complete, Script done!"
echo "[ check_hostname ] :: Rebooting..."
shutdown -r now
else
## Test to see if AD has multiple Results
if [ "$ad_name_count" -gt "1" -a "$orphaned_status" = "0" ]; then
echo "[ check_hostname ] :: There are more than one entry for this computer in Active Directory"
echo "[ check_hostname ] :: Adding this computer to ' 2012-2013 Orphaned AD Objects' Group..."
defaults write $company_plist OrphanedADObjects -bool true
sleep 2
echo "[ check_hostname ] :: Unbinding from Active Directory..."
echo "[ check_hostname ] :: Removing existing AD-Binding to '$scutil_name'"
dsconfigad -r -u "$udn" -p "$password"
echo "[ check_hostname ] :: Removing Search Path entries..."
dscl /Search -delete / CSPSearchPath /Active Directory/"$domain"
dscl /Search/Contacts -delete / CSPSearchPath /Active Directory/"$domain"
dscl /Search -delete / CSPSearchPath /Active Directory/"$domain"
dscl /Search -delete / CSPSearchPath "/Active Directory/All Domains"
dscl /Search/Contacts -delete / CSPSearchPath "/Active Directory/All Domains"
dscl /Search -delete / CSPSearchPath "/Active Directory/All Domains"
sleep 5
echo "[ check_hostname ] :: Setting Preference to Bind on next Reboot..."
defaults write $company_plist BindPending -bool true
sleep 2
echo "[ check_hostname ] :: Rebooting..."
shutdown -r now
else
## If the Name in the AD Plugin DOESN'T match the AD name AND the AD Name Count (number of matches in AD) is "1" then...
## If there were more than one match from AD, we wouldn't want the script trying to rename the computer with multiple names, that could get ugly.
if [ "$ad_plist_name" != "$ad_name" -a "$ad_name_count" -eq "1" ];then
echo "[ check_hostname ] :: The AD PLIST Name of: $ad_plist_name DOESN'T match the AD Name of: $ad_name"
/usr/libexec/PlistBuddy -c "Set AD Computer ID $ad_name" /Library/Preferences/DirectoryService/ActiveDirectory.plist
echo "[ check_hostname ] :: Fixed!"
echo "[ check_hostname ] :: Adding Computer to ' 2012-2013 Named Changed'"
defaults write $company_plist NameChanged -bool true
echo "[ check_hostname ] :: Forcing Computer to do inventory with JSS to update Smart Group and Preferences..."
jamf recon
else
## Additional Checks for the name on the Mac to match Active Directory
if [ "$scutil_name" != "$ad_name" -o "$scutil_localname" != "$ad_name" -o "$scutil_hostname" != "$ad_name" -a "$ad_name_count" -eq "1" ]; then
echo "[ check_hostname ] :: We have to fix the Computer Name to Match AD Name of: $ad_name"
scutil --set ComputerName $ad_name
scutil --set LocalHostName $ad_name
scutil --set HostName $ad_name$domain_name
echo "[ check_hostname ] :: Adding Computer to ' 2012-2013 Named Changed'"
defaults write $company_plist NameChanged -bool true
echo "[ check_hostname ] :: Forcing Computer to do inventory with JSS to update Smart Group and Preferences..."
jamf recon
else
if [ "$ad_name_count" -eq "1" -a "$orphaned_status" = "1" ]; then
echo "[ check_hostname ] :: Only one AD Object in AD; Clearing Orphaned Objects Group"
defaults write $company_plist OrphanedADObjects -bool false
echo "[ check_hostname ] :: Forcing Computer to do inventory with JSS to update Smart Group and Preferences..."
jamf recon
echo "[ check_hostname ] :: Names Match. Exiting..."
else
echo "[ check_hostname ] :: Names Match. Exiting..."
fi
fi
fi
fi
fi
Posted on 09-18-2012 03:34 PM
I initially set the ComputerName, LocalHostName, and HostName with scutil for each system and they don't change their computer names anymore. You are right, it is a DNS thing, where the Mac OS decides to set the local computer name based on what was previously registered for that IP in DNS. Explicitly setting the names overrides that behavior.
Also, why are you doing all the AD stuff? I would just have a simple script that runs dsconfigad -show and greps the system name it is joined as, compare with the three names in scutil, and --set them if they are different.
Posted on 09-18-2012 03:43 PM
We've had some issues where Computer Names were being changed (which doesn't change the AD name) without unbinding and rebinding the Mac. It results in the Computer Name not matching the AD Name, then our printing system breaks (Kerberos Auth Printers). My thought of querying AD directly was to make sure I get the right name for the computer. Because the Mac likes to rename itself. I couldn't trust any name it provided.
Since my script uses scutil to set the name. Hopefully that'll stop this behavior. Thanks for that tip.
I'll test your suggestion of using the dsconfigad -show. If I had to guess, I imagine that dsconfigad -show command could be using the same value set in that /Library/Preferences/DirectoryService/ActiveDirectory.plist file.
I tried to be as detailed as possible in the script to make sure the Computer Name matches the Active Directory Name. it's been a frustration in our environment to print because of the Computer Naming.
Posted on 09-21-2012 10:02 AM
ditto what alex said but I also set it in /etc/hostconfig just to be super sure.
# This file is going away
AFPSERVER=-NO-
AUTHSERVER=-NO-
TIMESYNC=-NO-
QTSSERVER=-NO-
HOSTNAME=MIS-13-RMANLY
I do this in my bootscript to set it:
name=$(scutil --get ComputerName)
printf "%s
" "HOSTNAME=${name}" >> /etc/hostconfig
P.S. It has said "This file is going away" for like 4 versions now... :)
Posted on 09-22-2012 12:47 PM
Great idea Ryan :)
I've been using Thomas Larkins method here successfully for quite some time. Thanks Thomas
[[http://www.tlarkin.com/tech/2-shell-scripts-maintain-standard-naming-conventions]([http://www.tlarkin.com/tech/2-shell-scripts-maintain-standard-naming-conventions)