Suggestions for Re-deploying Legacy (Non-DEP) Macs

dstranathan
Valued Contributor II

Now that I have finally adopted DEP for all new Macs, Im examining my legacy Jamf Imaging workflows for existing (non-DEP) Macs.

I'm sure many of you are way farther ahead of when in regards to phasing-out legacy Imaging while maintaining a method to re-deploy older Macs. I'd appreciate your thoughts and workflows on this process. Im looking for pitfalls to avoid, tips, etc.

For the next couple years, I will need to provide a robust method for re-deploying under the following situations:

-Employees leave and their Mac need to be wiped and reassigned to another employee.
-Macs gets repaired/upgraded and need to be re-deployed to production.

I don't want to spend much time or energy into maintaining legacy Jamf/Capser Imaging Configurations, so Im doing some brainstorming on how to re-deploy non-DEP Macs though a "DEP-ish" workflow (re-deploy Macs in a DEP-like manner - but without a PreStage Enrollment of course). I want something that will look and feel similar to DEP so my desktop techs won't be confused by multiple complicated deployment methods.

Id like to replace my Jamf Imaging Configurations with something 'lean n mean' that can ultimately bootstrap them into the last stages my DEP/Jamf enrollment workflow.

Here is my basic workflow idea:

-Boot legacy Mac into Recovery Mode and erase disk, then install the latest supported OS.

-NetBoot Mac into Jamf Imaging to install QuickAdd package (and local admin account) and a single script that will run at next reboot that will trigger my DEP enrollment workflow.

-Reboot Mac. Script triggers an event that kickstarts my DEP enrollment policy (starts DEPNotify, installs some core policies for packages, scripts, AD, etc.)

-Done! Reboot and assign to employee, etc.

Thoughts?

11 REPLIES 11

Look
Valued Contributor III

If your putting down 10.13 or later you probably want to be using the enrollment URL to avoid MDM profile approval errors.
Our current work flow is simply.
Wipe and get macOS on there anyway you want
Step through setup
Enrol using URL
Everything is automatic from there

The biggest gotcha for us was to make sure that machines that were simply re-enrolling didn't fall into the new machines policies, but this can be avoided with smart groups, EA's etc... I actually went with a very simple, renamed the HDD to something quite specific prior to enrollment, but this is a pretty temporary measure.

robertliebsch
Contributor

Somehow our DEP and /enrol fall into the same workflow. Which is great for us.

I have a thumbdrive with the latest OS install. So today for Mojave, I boot to USB, hit disk utility then wipe the drive. Then install the latest OS. Go through setup and create the new user and password. Then I hit the /enrol site and run the quickadd.

Comes out the same as a DEP machine, for us anyway...

The JSS end of it Smart Group leveled at PreStage enrollment is (OUR DEP). Then we have a Policy scoped to that Smart Group (which somehow captures ALL DEP --which it should-- but also anything using /enrol) that has all packages, scripts, printers, dock, profiles, preferences, etc.

dstranathan
Valued Contributor II

Thanks. Ill play with user-inited enrollments. Never used it production before (other than testing it during my Jump Start a few years ago).

I sort of forgot about the PIA of user-approved MDM which is indeed a major factor in the process.

My challenge will be similar to your 'gotcha' - figuring out a way to scope the manually-enrolled Macs that won't interfere with DEP emrollments and legacy imaging workflows.

Eyoung
Contributor

also using a simple re-install of the OS and a /enroll rebuild. I have a bunch of (now disowned from DEP) macbook airs to be used for a loaner fleet.

boot from USB to a 10.13 restore
pre-populate the serial number in a smart list in JAMF
step through setup screens, including an admin account (tedious)
login, hit the /enroll url
install the MDM profe
PROFIT

once enrolled it is much like any DEP style workflow.

takes all of a few minutes (minus baking time while the OS restores)

stevewood
Honored Contributor II
Honored Contributor II

@dstranathan Similar to others:

  • boot to recovery, wipe, install OS or boot from external device, wipe, and use asr or Disk Utility to push a DMG created by AutoDMG.
  • step through Setup Assistant
  • Enroll using Enrollment URL (using an invitation)
  • Open Self Service and run a Provision Computer policy

We use two policies: one for DEP and one for Manual (Self Service). The policies are identical except for scoping and trigger. The DEP policy is scoped to "Enrolled via Prestage" and the Manual policy is scoped to "Last Enrolled Less than 1 day" (although we are starting to change this, more below). The DEP policy is triggered by "Enrollment Complete" and the Manual has no trigger and set to Ongoing, since it is only available via Self Service.

We are moving the Manual process to be behind a login in Self Service so that we can scope the policy to All Computers, but limit visibility to just our techs. That keeps the provisioning policy invisible to users, and it allows us to scope to All instead of a "Last Enrolled" Smart Group.

Hopefully that makes sense.

vuho
New Contributor II

Hi @stevewood,

Would you mind sharing an outline of what you include in that Provision Computer policy in Self Service? We still have a lot of non-DEP devices to manage and are slowly transitioning over to DEP.

Thanks a bunch!

stevewood
Honored Contributor II
Honored Contributor II

@vuho gladly! We create a policy with set as a Self Service policy, set to Ongoing. The policy has one package, DEP Notify, and two or three scripts, depending on the agency (we are an ad agency holding company with several hundred ad agencies under our umbrella). The policy is scoped to a Smart Group that's only criteria is "Last Enrollment Less Than 1 Day". We have Restart set to immediately.

The scripts:

  • name computer
  • base provision
  • agency provision (if needed)

If an agency doesn't require more than our base load of software, it's just the first two scripts.

That's basically it. The only differences between that policy and a DEP policy for us is: frequency set to Once Per Computer, trigger set to Enrollment Complete, no Self Service, and scope is set to "Enrollment Method: PreStage enrollment=agency prestage".

Hopefully that makes sense. I can ellaborate on anything that isn't clear.

vuho
New Contributor II

@stevewood That does make sense. Can you please elaborate on the base provision script and what that entails?

Thanks!

stevewood
Honored Contributor II
Honored Contributor II

@vuho the base script simply installs our base load of software: Office, Skype for Biz, Chrome, Firefox, VLC, Java, AnyConnect, HP/Xerox/Canon drivers. We also set things like time server, time zones, etc. Here's a sanitized version of the script:

#!/bin/sh

###############################################################################
#
# Name: all-provisioning-base.sh
# Version: 2.2
# Date:  29 Apr 2018
# Modified: changed to use DEP Notify
# Author:  Steve Wood (steve.wood@omnicomgroup.com)
# Purpose:  provisioning script used to put base layer of apps on a machine.
# 
###############################################################################

## Set global variables

LOGPATH='/path/to/logs'
LOGFILE=$LOGPATH/all-base-provisioning-$(date +%Y%m%d-%H%M).log
VERSION=2.2
DNLOG='/var/tmp/depnotify.log'
defaults='/usr/bin/defaults'
surveyPlist='/path/to/survey/plist/com.company.survey.plist'
serial=$(system_profiler SPHardwareDataType | awk '/Serial Number (system)/ {print $NF}');

## setup number of DEP Notify stages for the progress bar
if [[ ! $6 ]]; then

    dnStages=15

else

    dnStages=$6

fi

## Setup logging
if [[ ! -d $LOGPATH ]]; then

    mkdir -p $LOGPATH
    chmod -R 777 $LOGPATH

fi
set -xv; exec 1> $LOGFILE 2>&1

## setup Caffeinate to stay awake
/bin/echo "Loads of Coffee Now!!"
/bin/date
caffeinate -d -i -m -u &
caffeinatepid=$!


## setup command file
echo "Command: Image: /path/to/image" >> ${DNLOG}
echo "Command: Determinate: $dnStages" >> ${DNLOG}
echo "Command: MainText: We are installing software on your machine. This process could take up to 40 minutes to complete so please make sure your computer is plugged in to power, and please do not restart until we are finished. Your computer will restart when we are done." >> ${DNLOG}
echo "Status: Starting installations" >> ${DNLOG}

echo "Creating DEPNotify LaunchAgent"
/bin/echo "<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 
<plist version="1.0"> 
<dict>
    <key>Label</key>
    <string>com.corp.launchdepnotify</string>
    <key>ProgramArguments</key>
    <array>
        <string>/tmp/DEPNotify.app/Contents/MacOS/DEPNotify</string>
        <string>-fullScreen</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
</dict> 
</plist>" > /Library/LaunchAgents/com.company.launchdepnotify.plist
##Set the permission on the file just made.
/usr/sbin/chown root:wheel /Library/LaunchAgents/com.company.launchdepnotify.plist
/bin/chmod 644 /Library/LaunchAgents/com.company.launchdepnotify.plist

## Now launch DEP Notify
/bin/launchctl load /Library/LaunchAgents/com.company.launchdepnotify.plist

######################################################################################
# 
#       Tasks that do not require access to the JSS
# 
######################################################################################

####
# grab the OS version and Model, we may need it later
####

modelName=`system_profiler SPHardwareDataType | awk -F': ' '/Model Name/{print $NF}'`

######################################################################################
# Dummy package with image date and computer Model
# - this can be used with an ExtensionAttribute to tell us when the machine was last imaged
######################################################################################
/bin/echo "Creating provisioning receipt..."
/bin/date
TODAY=`date +"%Y-%m-%d"`
touch /Library/Application Support/JAMF/Receipts/$modelName_born_on_$TODAY.pkg
defaults write "$surveyPlist" ProvisionDate -string ${TODAY}

###############################################################################
#
#   S Y S T E M   P R E F E R E N C E S
#
# This section deals with system preference tweaks
#
###############################################################################
/bin/echo "Setting system preferences"
/bin/date

# Disable Time Machine's pop-up message whenever an external drive is plugged in
defaults write /Library/Preferences/com.apple.TimeMachine DoNotOfferNewDisksForBackup -bool true

### time machine off
/bin/echo "Disable Time Machine"
/bin/date
/usr/bin/defaults write com.apple.TimeMachine 'AutoBackup' -bool false

# Disable “Application Downloaded from the internet” message
defaults write /System/Library/User Template/English.lproj/Library/Preferences/com.apple.LaunchServices LSQuarantine -bool NO
defaults write com.apple.LaunchServices LSQuarantine -bool NO

# enable network time
systemsetup -setusingnetworktime on

# set the time server
systemsetup -setnetworktimeserver time.apple.com

#### below courtesy https://www.jamf.com/jamf-nation/discussions/6835/time-zone-using-current-location-scriptable#responseChild148977
/usr/bin/defaults write /Library/Preferences/com.apple.timezone.auto Active -bool YES
/usr/bin/defaults write /Library/Preferences/com.apple.locationmenu ShowSystemServices -bool YES

#Python code snippet to reload AutoTimeZoneDaemon
/usr/bin/python << EOF
from Foundation import NSBundle
TZPP = NSBundle.bundleWithPath_("/System/Library/PreferencePanes/DateAndTime.prefPane/Contents/Resources/TimeZone.prefPane")
TimeZonePref          = TZPP.classNamed_('TimeZonePref')
ATZAdminPrefererences = TZPP.classNamed_('ATZAdminPrefererences')

atzap  = ATZAdminPrefererences.defaultPreferences()
pref   = TimeZonePref.alloc().init()
atzap.addObserver_forKeyPath_options_context_(pref, "enabled", 0, 0)
result = pref._startAutoTimeZoneDaemon_(0x1)
EOF

sleep 1
#Get the time from time server
/usr/sbin/systemsetup -getnetworktimeserver

#Detect the newly set timezone
/usr/sbin/systemsetup -gettimezone

# disable the save window state at logout
/usr/bin/defaults write com.apple.loginwindow 'TALLogoutSavesState' -bool false

###########
#  AFP
###########

# enforce clear text passwords in AFP
/bin/echo "Setting AFP clear text to disabled"
/bin/date
/usr/bin/defaults write com.apple.AppleShareClient "afp_cleartext_allow" 0

# Turn off DS_Store file creation on network volumes
/bin/echo "Turn off DS_Store"
/bin/date
defaults write /System/Library/User Template/English.lproj/Library/Preferences/com.apple.desktopservices 
    DSDontWriteNetworkStores true


###  Expanded print dialog by default
# <http://hints.macworld.com/article.php?story=20071109163914940>
#
/bin/echo "Expanded print dialog by default"
/bin/date
# expand the print window
defaults write /Library/Preferences/.GlobalPreferences PMPrintingExpandedStateForPrint2 -bool TRUE

##########################################
# /etc/authorization changes
##########################################

security authorizationdb write system.preferences allow
security authorizationdb write system.preferences.datetime allow
security authorizationdb write system.preferences.printing allow
security authorizationdb write system.preferences.energysaver allow
security authorizationdb write system.preferences.network allow 
security authorizationdb write system.services.systemconfiguration.network allow

## add users to lpadmin
/usr/sbin/dseditgroup -o edit -n /Local/Default -a everyone -t group lpadmin
/usr/sbin/dseditgroup -o edit -n /Local/Default -a everyone -t group _lpadmin
/usr/sbin/dseditgroup -o edit -n /Local/Default -a 'Domain Users' -t group lpadmin

# check for jamf binary
/bin/echo "Checking for JAMF binary"
/bin/date

 if [[ "$jamf_binary" == "" ]] && [[ -e "/usr/sbin/jamf" ]] && [[ ! -e "/usr/local/bin/jamf" ]]; then
    jamf_binary="/usr/sbin/jamf"
 elif [[ "$jamf_binary" == "" ]] && [[ ! -e "/usr/sbin/jamf" ]] && [[ -e "/usr/local/bin/jamf" ]]; then
    jamf_binary="/usr/local/bin/jamf"
 elif [[ "$jamf_binary" == "" ]] && [[ -e "/usr/sbin/jamf" ]] && [[ -e "/usr/local/bin/jamf" ]]; then
    jamf_binary="/usr/local/bin/jamf"
 fi

${jamf_binary} flushPolicyHistory
${jamf_binary} recon

sleep 5

### Installing base image software
echo "Status: Begining software installations" >> $DNLOG

## Common Resources
/bin/echo "Installing Common Resources"
/bin/date
${jamf_binary} policy -trigger commonResources --forceNoRecon # AnyConnect, dockutil, cocoaDialog, VLC

echo "Status: Common resources installed" >> $DNLOG

## Internet Plug-Ins
/bin/echo "Installing Internet Plug-ins"
/bin/date
${jamf_binary} policy -trigger installJava --forceNoRecon #Java, Silverlight

echo "Status: Installing printer drivers." >> $DNLOG

## Printer Drivers
/bin/echo "Installing Printer Drivers"
/bin/date
${jamf_binary} policy -trigger xeroxGeneric --forceNoRecon # Xerox Driver
${jamf_binary} policy -trigger hpDriver --forceNoRecon # HP Driver
${jamf_binary} policy -trigger canonGeneric --forceNoRecon # Canon PS Driver

echo "Status: Printer drivers installed" >> $DNLOG
echo "Status: Installing web browsers." >> $DNLOG

## Web Browsers
/bin/echo "Installing Web Browsers"
/bin/date
${jamf_binary} policy -trigger firefox --forceNoRecon # Firefox
${jamf_binary} policy -trigger chrome --forceNoRecon # Chrome

echo "Status: Web browsers installed" >> $DNLOG
echo "Status: Installing Office 2016." >> $DNLOG

## Office 2016
/bin/echo "Installing Office 2016"
/bin/date
${jamf_binary} policy -trigger office --forceNoRecon # Full Office Suite

echo "Status: Office 2016 installed" >> $DNLOG
echo "Status: Installing Skype for Business." >> $DNLOG

## Skype for Biz
/bin/echo "Installing Skype for Business"
/bin/date
${jamf_binary} policy -trigger sfb --forceNoRecon # Skype for Biz

echo "Status: Skype for Business installed" >> $DNLOG

echo "Status: Updating computer inventory." >> $DNLOG
# recon
${jamf_binary} recon

## upload log to JPS
## https://github.com/jamfit/Encrypted-Script-Parameters
#Decypt string 
function DecryptString() {
    echo "${1}" | /usr/bin/openssl enc -aes256 -d -a -A -S "${2}" -k "${3}"
}

apiUser=$(DecryptString $4 '<your salt>' '<your key>')
apiPass=$(DecryptString $5 '<your salt>' '<your key>')
jpsURL="https://your.jamfpro.com"

## get ID of computer
JSS_ID=$(curl -H "Accept: text/xml" -sfku "${apiUser}:${apiPass}" "${jpsURL}/JSSResource/computers/serialnumber/${serial}/subset/general" | xpath /computer/general/id[1] | awk -F'>|<' '{print $3}')
curl -sku $apiUser:$apiPass $jpsURL/JSSResource/fileuploads/computers/id/$JSS_ID -F name=@${LOGFILE} -X POST

/bin/echo "Evacuating coffee"
/bin/date
kill "$caffeinatepid"

vuho
New Contributor II

Excellent stuff! Thank you so much @stevewood!

dstranathan
Valued Contributor II

Awesome ideas. Thank you all. You have my juices flowing now. I never used self-enrollment before, and we haven't rolled-out Self Service app to production yet (slated for spring 2019), so I hadn't considered them.

My main hang-up was making sure that DEP, Manual/web enrollment, and legacy NetBoot Imaging workflows would not interfere with each other in terms of scopes, triggers etc. Goal is to keep all methods looking as close to DEP as possible for my tech's sanity.

Ill kill legacy imaging soon, and then Ill be left with (2) deployment workflows (DEP and manual invitations for older non DEP Macs), and then in a couple years Ill be 100% DEP...I hope.

Thank you!