Archive

Posts Tagged ‘Tech’

One Admin to Rule Them All

October 15, 2022 2 comments

During JNUC 2022 the GOATs, Mark Buffington and Sean Rabbitt, presented “One Account to P0wn Them All: How to Move Away from a Shared Admin Account”. One of the workflows that they presented was to utilize the local admin account that is created during a PreStage enrollment as a local admin account for times when you need an admin account. You know, times like when you need to install software on a machine, or do some other admin task but don’t have a user account that is admin. There’s a better way to handle this with Jamf Connect and just in time provisioning of an admin account, but this workflow is for those that maybe are not using Jamf Connect, yet.

The workflow they outlined is to create the PreStage account and the Management Account that is used for User Initiated Enrollment (UIE) with the same password. Then using policies in Jamf Pro, after the Bootstrap Token has been escrowed to Jamf Pro, you can randomize this account password. By randomizing the password you prevent the same password from being on all of your devices. Then when you need to use that account for admin duties, you can use a Jamf Pro policy to change the password to a known password, do the needful, and then re-randomize the password. So how do we turn this into a workflow that is real world?

Note: This workflow is for devices that are enrolled via Automated Device Enrollment only. Can this workflow be adapted for UIE enrolled devices? Probably, but it would require the creation of our admin account along with the escrow of the Bootstrap token. If both of those can be accomodated, then it is possible this workflow could be adapted.

Scenario

We’re going to build out a Self Service method for our field techs and help desk agents to be able change the password for our hidden management/admin account to a known password (something we perhaps store in a password vault and rotate regularly). We’ll also create a script and LaunchDaemon that will run 30 minutes after the password is changed to reset it back to a randomized one. We will also create a Self Service method for them to reset the password back to a randomized one.

Setup

Following along with Mr. Buffington, and using the screenshot from his GitHub for the presentation, the first thing we need to do is create an Extension Attribute that will capture whether the Bootstrap Token has been escrowed to Jamf Pro or not. We need to insure the token is escrowed before we randomize the password, otherwise we could end up with the first SecureToken user being the admin with a randomized password, and that’s not a good idea. In a normal deployment, the Bootstrap token is created and escrowed when the first user signs into the computer interactively (via the login window or via SSH). 

Extension Attribute

The code for the Extension Attribute is the following:

#!/bin/bash

tokenStatus=$(profiles status -type bootstraptoken | awk '{ print $7 }' | sed 1d)
if [ $tokenStatus == "NO" ]
then
	echo "<result>Not Escrowed</result>"
elif [ $tokenStatus == "YES" ]
then
	echo "<result>Escrowed</result>"
else
	echo "<result>Unknown</result>"
fi

Smart Group

Now that we have an EA, let’s create a Smart Group to capture the devices that have escrowed their Bootstrap token. It’s pretty simple, we’re just going to look for “Escrowed” as the results of our EA.

Scripts

Ok, we’re gonna need a couple of policies and a couple of scripts. Let’s start with the scripts first.

The first script we are going to create will be utilized by the policy to set the password to a static, known value. The script will create a script on the target computer, along with a LaunchDaemon that will run the script we create after a 30 minute period. The script we create on the computer will simply trigger a policy to re-randomize the admin account password. This will make more sense when we see the script.

#!/bin/bash

#########################################################################################
#
# Copyright (c) 2022, JAMF Software, LLC.  All rights reserved.
#
# THE SOFTWARE IS PROVIDED "AS-IS," WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
# JAMF SOFTWARE, LLC OR ANY OF ITS AFFILIATES BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT, OR OTHERWISE, ARISING FROM, 
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OF OR OTHER DEALINGS IN
# THE SOFTWARE, INCLUDING BUT NOT LIMITED TO DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# CONSEQUENTIAL OR PUNITIVE DAMAGES AND OTHER DAMAGES SUCH AS LOSS OF USE, PROFITS,
# SAVINGS, TIME OR DATA, BUSINESS INTERRUPTION, OR PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES.
#
#########################################################################################
#
#
# You will want to update the script path and script name to be what you would like it to be.
#
# Update these variables: script_path and script_name
# 
# You will want to update the name of the LaunchDaemon, along with the contents of the daemon
# to match the script path and name that you set.
# Update this variable: launchDaemon
#
#
#########################################################################################
## VARIABLES

script_path="/private/var/acme/scripts/"
script_name="changemgmtpass.sh"
script="$script_path$script_name"

launchDaemon="/Library/LaunchDaemons/com.acme.changeMgmtPass.plist"

#########################################################################################

# create the script on the local machine
# check for our scripts folder first
if [[  ! -d "$script_path" ]]
then
	/bin/mkdir -p "$script_path"
fi

tee "$script" << EOF
#!/bin/bash

# run randomize policy
/usr/local/jamf/bin/jamf policy -event changeMgmtPassword

# bootout launchd
/bin/launchctl bootout system "$launchDaemon" 2> /dev/null

# remove launchdaemon
rm -f "$launchDaemon"

rm -f "$script"

exit 0
EOF

# fix ownership
/usr/sbin/chown root:wheel "$script"

# Set Permissions
/bin/chmod +x "$script"

# now create LaunchDaemon
# Check to see if the file exists
if [[ -f "$launchDaemon" ]]
then
	# Unload the Launch Daemon and surpress the error
	/bin/launchctl bootout system "$launchDaemon" 2> /dev/null
	rm "$launchDaemon"
fi

tee "$launchDaemon" << EOF
<?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>$(basename "$launchDaemon" | sed -e 's/.plist//')</string>
	<key>ProgramArguments</key>
	<array>
		<string>/bin/bash</string>
		<string>/private/var/acme/scripts/changemgmtpass.sh</string>
	</array>
	<key>StartInterval</key>
	<integer>120</integer>
</dict>
</plist>
EOF

# Set Ownership
/usr/sbin/chown root:wheel "$launchDaemon"

# Set Permissions
/bin/chmod 644 "$launchDaemon"

# Load the Launch Daemon
/bin/launchctl bootstrap system "$launchDaemon"

exit 0

Now that we have that script in place, we will create a second script that can be run from a Self Service policy to run the policy to re-randomize the password. This policy can be run prior to the LaunchDaemon running and it will unload the LaunchDaemon and the LaunchDaemon and the script we stored on the system.

#!/bin/bash
#########################################################################################
#
# Copyright (c) 2022, JAMF Software, LLC.  All rights reserved.
#
# THE SOFTWARE IS PROVIDED "AS-IS," WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
# JAMF SOFTWARE, LLC OR ANY OF ITS AFFILIATES BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT, OR OTHERWISE, ARISING FROM, 
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OF OR OTHER DEALINGS IN
# THE SOFTWARE, INCLUDING BUT NOT LIMITED TO DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# CONSEQUENTIAL OR PUNITIVE DAMAGES AND OTHER DAMAGES SUCH AS LOSS OF USE, PROFITS,
# SAVINGS, TIME OR DATA, BUSINESS INTERRUPTION, OR PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES.
#
#########################################################################################
#
#
# You will want to update the script path and script name to be what you would like it to be.
#
# Update these variables: script_path and script_name
# 
# You will want to update the name of the LaunchDaemon, along with the contents of the daemon
# to match the script path and name that you set.
# Update this variable: launchDaemonPath
#
#
#########################################################################################
### Variables
script_path="/private/var/acme/scripts/"
script_name="changemgmtpass.sh"
script="$script_path$script_name"

launchDaemon="/Library/LaunchDaemons/com.acme.changeMgmtPass.plist"

# Run the management randomization policy
/usr/local/jamf/bin/jamf policy -event changeMgmtPassword

# now bootout the launch daemon we loadead and delete
# bootout launchd
/bin/launchctl bootout system "$launchDaemon" 2> /dev/null

# remove launchdaemon
rm -f "$launchDaemon"

# remove the script
rm "$script"

exit 0

Policies

Now that our scripts are in place we can create our policies. We are going to create four (4) policies:

  1. A policy to randomize the management account password on recurring check-in, once.
  2. A policy to randomize the management account password with a custom trigger and set to ongoing.
  3. A policy to change the management account password to a known static value, set to ongoing, and available in Self Service
  4. A policy to randomize the management account password via Self Service, set to ongoing.
Policy 1 – Randomize on check-in

The first policy will simply use the “Management Actions” policy payload set to “Change Account Password” and “Randomly generate new password”.

This policy will be scoped to our “Bootstrap Token Escrowed” Smart Group that we created at the begining. Set this policy to trigger on “Recurring Check-In” and set it to an “Execution Frequency” of “Once Per Computer”. The policy will trigger after the first user has signed into the computer for the first time.

Policy 2 – Randomize on custom event

The second policy can be created by cloning the first policy we created and changing the trigger and the frequency. Uncheck the “Recurring Check-in” trigger and instead check “Custom” and enter a value in the text box. For my policy I set this to “changeMgmtPassword”, but it can be whatever you want. Change the “Execution Frequency” to “Ongoing” and save the policy.

Why did we make those changes to the second policy? Well, we want this policy to be availble to our scripts, so we’re using the custom event, and we want it to run anytime we need it so we set the frequency to Ongoing. Since we will only call this policy via that custom event, we can be fairly certain knowing this policy will only run when we want it to.

Policy 3 – Change to static password via Self Service

We’re on to the third policy. This is the first of our Self Service policies. This policy will have no triggers since it is a Self Service policy, and we want the “Execution Frequency” to be set to “Ongoing”. We will add the first script we created to this policy (it doesn’t matter if it is set to Before or After). Head over to the “Management Actions” portion of the policy and in here you will set the known static password you want this account to use. 

Notice the warning we have above our password box. Best practice is for us to randomize the Management Account password, so that is why we’re letting you know this is a bad idea. But we’ll ignore it for now.

Head over to the Scope tab and we’ll set this one to our “Bootstrap Token Escrowed” Smart Group. While you’re here, we’re going to use a trick to hide this policy from most users. Click on the “Limitations” tab and then the Add button. Click on “LDAP User Groups” and add the group you have all of your techs in (you do have an LDAP group for all of your techs, right?). For me that group is named “Jamf Admins” but it can be whatever you want.

Why did we do that? Well, by adding that group as a limitation, a tech will need to login to Self Service so that the policy will be visible. This will prevent normal users from seeing that policy and running it. If you do not have login enabled for Self Service, you can read about it here. Also, you can set it so that users do not have to login to get into Self Service, just that a login button is available. You can also use the login method for scoping policies to users.

After the Scope is done, you can head over to the Self Service tab and setup the way the item will appear in Self Service. In the “Description” field you may want to put info about where the SuP3r SekReT password is stored. Maybe put in the fact that the password will re-randomize after 30 minutes (or whatever timeframe you want) and a reminder to run the Self Service policy to re-randomize.

Once you’re done there, go ahead and save this policy.

Policy 4 – Randomize password via Self Service

Our last policy to create, this policy will randomize the password via Self Service so that a tech can make sure when they are done the password is changed back. For this policy we will have no triggers, since it is Self Service, and the “Execution Frequency” will be set to “Ongoing”. We’ll be doing our work via the second script we created, so go ahead and attach that second script to this policy. Again, it doesn’t matter if it is set to “Before” or “After”.

On the Scope tab you have the choice of making so everyone sees it, or using our “Limitations” trick from Policy 3 to make it visible only to our techs. Scope to our “Bootstrap Token Escrowed” Smart Group and make your decision on the visibility.

Once you’ve done that, head over to the Self Service tab and setup the look of the policy in Self Service. Once you’re done, go ahead and save our policy.

What’s Next?

Now that we have all of the parts and pieces in place, how do we take advantage of this? Well, any computer that gets enrolled will have the Management Account created, and once the Bootstrap token gets escrowed that computer can take advantage of this workflow. A tech will be able to walk up to the computer, open Self Service, login to Self Service, and then utilize our static password policy to use that Management Account to do the needful.

If you wanted to store what type of password (random or static) was in use, you could use a “reverse Extension Attribute” to do that. Basically, store a value in a plist on the computer indicating if the password is “S”tatic or “R”andom. Then use an Extension Attribute to grab that value. You could put this in the scripts that we created above (make sure to include a recon so the value gets into Jamf Pro).

You can find the screenshots, scripts, and the XML of the Extension Attribute in my GitHub repository here.

Categories: Jamf Pro Tags: , , ,

Using Postman with Jamf Pro Part 5 – More Runners

April 1, 2022 1 comment

Welcome back to my series on using Postman with Jamf Pro. If you haven’t checked out the previous posts, or you’ve never used Postman with the Jamf Pro API, you may want to go read through these:

Using Postman for Jamf Pro API Testing – Part 1: The Basics 

Using Postman for Jamf Pro API Testing – Part 2: Creating And Updating Policies

Using Postman with Jamf Pro Part 3: Computer Groups

Using Postman with Jamf Pro Part 4 – Variables & Runners

In my last post, I showed how you can use the Collection Runner feature in Postman to either run multiple API commands in sequence, or pass a CSV file full of data to an API command. The example we used was disabling a bunch of policies all at once.

In this post, I want to show how I used a Runner to create new installer policies for packages. In the environment, I used to manage we followed Bill Smith’s advice given in his JNUC 2017 talk Moving Beyond “Once Per Computer” Workflows and we created separate installer policies for each application that could then be called by other policies or scripts. When Adobe Creative Cloud would inevitably revision up to the next version (2021 to 2022 for example) we would have several new installer policies we’d have to create (at least 7 or more). Well, clicking around the GUI to do that is just nuts and a waste of time when we can create a CSV file to do it for us.

Setup API Command

First thing we need to do is to setup an API command and save it into a collection in Postman. We will want to set variables for the information that is different in each policy. For our Adobe example, the list of information that is unique is:

  • Policy Name
  • Custom Trigger
  • Package ID
  • Package Name

We will create variables for each of those values so that we can pass a CSV file to a Runner to create each policy. So we need to edit our XML and then save it to a collection in Postman. Our edited XML looks like this:

<policy>
	<general>
		<name>{{name}}</name>
		<enabled>true</enabled>
        <trigger_other>{{trigger}}</trigger_other>
		<frequency>Ongoing</frequency>
        <category>
            <id>14</id>
            <name>zInstallers</name>
        </category>
	</general>
    <scope>
        <all_computers>true</all_computers>
    </scope>
    <package_configuration>
        <packages>
            <size>1</size>
            <package>
                <id>{{pkg-id}}</id>
                <name>{{pkg-name}}</name>
                <action>Install</action>
            </package>
        </packages>
    </package_configuration>
	<reboot><no_user_logged_in>Do not restart</no_user_logged_in><user_logged_in>Do not restart</user_logged_in>
	</reboot>
    <maintenance>
        <recon>true</recon>
    </maintenance>
</policy>

Sidebar: if you wanted to make this a completely generic template for creating policies, you could use variables for Frequency, Category ID (do not have to have the category name as it will pull up based on ID), and more.

Package Info

One thing we’ll need is the name of each package and the corresponding ID value. Obviously, before we can get that information we’ll need to upload each package to our Jamf Pro server. Go ahead and do that. I’ll wait…..

Ok, once you’ve uploaded everything, go into Postman and locate the “Finds All Packages” API command in the Jamf Postman collection (see Part 1 for the Jamf collection). Run that command, which will pull back every package you have uploaded to Jamf Pro, and then use the “Save Response” drop down to save the output to a file. This will save out as an XML file, but you can easily open that in Excel to have it convert to a spreadsheet.

Sidebar: If you do not have Excel, or don’t want to load it on your computer, you can find online services to convert XML to CSV, like Data.page. Also, you do not have to convert to CSV or Excel, it’s just easier to grab the package ID and package name from these formats than it is from XML.

After we have the IDs and names, we’ll want to create our CSV file. When we create the CSV we want to make sure the column headers match the variable names we use. For our example the variables we’re using are:

  • name
  • trigger
  • pkg-id
  • pkg-name

Our CSV file file should look something like this:

name,trigger,pkg-id,pkg-name
Adobe Photoshop 2022,photoshop2022,2,Adobe_Photoshop_2022.pkg
Adobe Illustrator 2022,illustrator2022,3,Adobe_Illustrator_2022.pkg
Adobe InDesign 2022,indesign2022,4,Adobe_InDesign_2022.pkg

Runner

Once we have all of these pieces together, we can open a new Runner tab and run our command. Drag the proper Collection into the Runner tab, select the CSV file you created, and click Run. The Runner will cycle through each line of the CSV file and create the necessary policies in Jamf Pro via the API command.

Wrap Up

Now that we’ve covered a basic Runner example and a little more complicated one using more variables, you can hopefully see the power of Postman and how it can help in the day-to-day administration of Jamf Pro.

Categories: Jamf Pro, Tech Tags: , , ,

Using Postman with Jamf Pro Part 4 – Variables & Runners

March 31, 2022 2 comments

Welcome back to my series on using Postman with Jamf Pro. If you haven’t checked out the previous posts, or you’ve never used Postman with the Jamf Pro API, you may want to go read through these:

Using Postman for Jamf Pro API Testing – Part 1: The Basics 

Using Postman for Jamf Pro API Testing – Part 2: Creating And Updating Policies

Using Postman with Jamf Pro Part 3: Computer Groups

Today we’re going to dive a little deeper into the use of variables and the Runner feature in Postman. We touched briefly on variables in Part 2 when we discussed the use of variables to set the ID of a policy.

Just like in computer programming, we can leverage variables in Postman to store data that we need to re-use. We saw this in Part 1 when we setup our environment variables to store username, password, and URL, and again in Part 2 where we were able to set the ID of a policy using a variable and a Pre-request script.

Runner

To me, the real power of Postman is the Collection Runner function. A Collection Runner allows you to run a sequence of API commands in a specific order. It also allows you to feed values into those commands using the variables that we talked about in Part 2. For example, if you needed to disable a group of policies, you could pass a CSV file with a list of policy IDs to a Collection Runner and allow it to send the necessary PUT commands. Let’s see how we can do this.

The first thing we want to do is create our API command that we want to run and save it to a collection. The reason we do this is so we can gather commands that are similar for use over and over again. To disable a policy you just need to pass the following XML using a PUT command:

<policy>
	<general>
		<enabled>false</enabled>
    </general>
</policy>

That’s all we need to send as a PUT to the policies API endpoint. So once we’ve edited a PUT command and saved it to our collection, we can create a CSV file that contains a list of policy numbers. You can use Excel, Numbers, or a code IDE like Visual Studio Code, to create the CSV file. It is important for the first cell, basically the header, to contain the variable that we are replacing. In this case we want to have it set to “id” since our variable is {{id}} (most Postman API commands from the Jamf collection use that variable for a policy ID). So our CSV file should look something like:

id
1
2
3
4

Obviously the numbers will be different based on which policy IDs you need to update. Save that CSV file somewhere on your system and then head back over to Postman.

In Postman go under the File menu and choose “New Runner Tab” (or press Command-Shift-R).

This will open a new tab named “Runner” in your Postman window. Locate the collection you saved your API command in and drag that into the Runner window.

This will place all of the commands you have in that collection into the Runner tab with a checkmark, indicating they are “active”. If you have multiple commands but only want to run one, uncheck (or use the Deselect All link at the top of the Runner window) all of the commands you do not want to run.

Now that we have our command in the Runner tab, use the “Select File” button to the right and select the CSV file you created.

All we have to do now is click on the blue “Run” button and watch Postman do its thing. Once the Runner is done, you can go back to Jamf Pro and check the policies you put in the CSV file to see that they are now disabled. Pretty sweet, huh?

What about enabling a bunch of policies? You guessed it, just create a command that sets the <enabled></enabled> key to “TRUE” instead of “FALSE” and run it through a Runner.

You can use the Console in Postman to debug your commands and get feedback for what went right, or wrong.

Wrap Up

There’s plenty more you can do with Runners, like chaining API commands together and passing values between the calls, or creating more complicated policies or groups. In the next post I’m going to cover one use case for using Runners to create a series of policies.

Categories: Jamf Pro, Tech Tags: , , ,

Back At It

January 4, 2022 Leave a comment

It’s been a minute, hasn’t it? 2020 started off so well, nice and slow (thanks pandemic), and I thought for sure I’d have time to post something new each month. Then in the blink of an eye, I was overwhelmed with projects at work and the next thing you know it’s 2022. Yikes!

2021 saw even more workload, along with the security team driving the project list, and there was just no time to write. Then towards the end of 2021 I decided to make a change of careers, and I joined Jamf as a Sales Engineer. Still in the Mac tech industry, but now on the vendor side which is somewhere I haven’t been for well over 20 years.

So here we are, January 2022 and I’m going to make another run at reviving this blog. I have some ideas for posts, including completing my Postman series. So keep an eye out and we’ll see if I cannot keep this going.

Happy New Year!

Categories: Ramblings, Tech Tags: ,

Using lpoptions To Identify Printer Options

October 20, 2018 1 comment

In my previous post on adding printers via script I mentioned using lpoptions to identify the different option settings for a printer. Let’s open up Terminal and get started with identifying the options. You’ll need to have the printer already installed on the system, so if it isn’t installed go follow my previous post and get it installed.

First, let’s find the name of the printer. For that we will use lpstat -a:

Screen Shot 2018-10-20 at 3.46.45 PM

Now that we know the name, Wayne_HOLD, let’s figure out what the options are that we can set. For that we’ll use lpoptions -p Wayne_HOLD -l. Now this list is way too much information to post here, so I’ll just cut it off at a few lines:

Screen Shot 2018-10-20 at 3.48.56 PM

Wow, and there’s plenty more information beyond this. This part of setting the options can be a bit trial and error. We probably aren’t going to want to set everything,  but we will want to add any options like the “Fiery Graphic Arts Package” that is installed, or the “Output option”, or perhaps the “Xerox high capacity feeder”.

One way we can figure out what option we need to set is by using grep along with the lpoptions command. For example, to know which option sets the “Fiery Graphic Arts Package” we might try this:

lpoptions -p Wayne_HOLD -l | grep -i "graphic arts"

This gives us the following:

Screen Shot 2018-10-20 at 3.53.52 PM

That’s great, but what does “GA2” and “GA1” mean? Open up the Options & Supplies window for the printer by going to System Preferences -> Printers & Scanners -> click on the printer and then click on the Options & Supplies button. For this printer, we can see that “GA1” is the “Fiery Graphics Arts Package”.

Screen Shot 2018-10-20 at 3.55.54 PM

What happens when we change that in the GUI to:

Screen Shot 2018-10-20 at 3.57.26 PM

This is what we see from Terminal:

Screen Shot 2018-10-20 at 3.57.41 PM

So now we know that the “GA2” option is the “Fiery Graphics Arts Package, Premium Edition”.

For other settings we need to do some investigation in the Print pane when printing a document. For most printers we’ll want to see what the default view is in the Print dialog window and then make the change in the Terminal using lpoptions and finally go back to the Print dialog window to see what that change did.

In our case we want to set the Output option and the High Capacity Feeder option from their default settings:

Screen Shot 2018-10-20 at 4.09.10 PM

To use a high capacity paper source, and to put the output in a different tray:

Screen Shot 2018-10-20 at 4.09.41 PM

Notice that in the second screen shot we now have Tray 6 available to us. We can acheive this using the lpadmin command to set the settings (note: the lpoptions command works most of the time, but I have far more success using lpadmin).

Screen Shot 2018-10-20 at 4.12.46 PM

By figuring out which settings we want to use, we can now configure all of the options for a printer from a script. One more example would be setting a color printer to default to B&W and duplex print. This is often done as a cost savings measure.

lpa='/usr/sbin/lpadmin'
${lpa} -p CopyThat -E -o printer-is-shared=false -v lpd://10.89.170.5 \
-P "/Library/Printers/PPDs/Contents/Resources/Xerox WorkCentre 7855.gz"
${lpa} -p CopyThat -o Duplex=DuplexNoTumble -o XROutputColor=PrintAsGrayscale
view raw gistfile1.txt hosted with ❤ by GitHub

I hope this has inspired you to dive further into setting up printers via script.

Categories: Casper, Jamf Pro, Tech Tags: , ,

Identify EFI Fiery Driver

October 20, 2018 1 comment

In this post I talked about how we can use the lpadmin command to add a printer via script. In this post we will cover how we can identify the driver for an EFI Fiery RIP. Note: all Fiery RIPs do not use the same driver, so you will want to follow this process for any Fiery you may have in your environment.

Open your favorite web browser and enter the IP address (or DNS name) for the printer. This should take you to the Fiery RIP webpage.

Screen Shot 2018-10-20 at 3.22.16 PM

Now, click on the Configure tab, then on the “Check for product updates” link.

Screen Shot 2018-10-20 at 3.22.30 PM

This will open up a new tab in your browser that will take you to the EFI Fiery live update page. From here you’ll want to click on the Printer Drivers tab and then scroll to locate the latest printer driver.

Screen Shot 2018-10-20 at 3.23.07 PM

We’ve scrolled down to locate the latest driver that handles macOS 10.14 Mojave.

Screen Shot 2018-10-20 at 3.23.16 PM

We can then click the Download link to download the latest version of the Fiery driver for our specific model of Fiery.

One Last Thing

Now that you have your driver downloaded, I would strongly suggest heading over to Foigus’ post, Trial By Fiery, to find out how to use AutoPKG to create a driver package that will install via a management tool, and not have update dialogs popping up.

Categories: Casper, Jamf Pro, Tech Tags: , , ,

Deploying Printers via Script

October 20, 2018 2 comments

Deploying printers on the Mac in an enterprise environment, or heck, just in a small office environment, can be done in multiple ways. If you don’t have a management tool, or ARD, you’re going to be running around doing it by hand. If you have a management tool, like Munki or Jamf, then you can deploy printers in a more automated fashion. My preferred method is to use a Bash script to deploy printers because it provides me a little more flexibility

Identify The Driver

The first thing to do is to identify the printer and the driver that is required. For most printers this is pretty simple, just navigate to the IP address of the printer to verify the make and model, then head over to the vendor’s website to download the latest driver. Once you have the driver, install it on your machine and then go find the driver file on your system. On the Mac, most printer drivers are stored in:

/Library/Printers/PPDs/Contents/Resources

If your printer has a “RIP”, or “Raster Image Processor“, identifying those drivers can be a little trickier. Head over to this post on how to identify the driver, and download it, on an EFI Fiery RIP. Drivers for an EFI Fiery or other RIP are usually stored in:

/Library/Printers/PPDs/Contents/Resources/<localization folder>

For us here in North America, that folder path would be:

/Library/Printers/PPDs/Contents/Resources/en.lproj

Once you have identified the driver, copy the full path of the driver file (Option-Command-C, or hold down Command then Edit->Copy as Pathname, or right click while holding Command) to your clipboard.

Build The Command

The next thing we need to do is figure out the command to run to add the printer. Fire up Terminal and let’s figure out the commands to use. We will utilize the lpadmin command to get the printer on the system. For this post this is what our command will look like:

sudo lpadmin -p <name> -E -o printer-is-shared=false -v ipp://1.1.1.1 -D "<name>" -P "/Library/Printers/PPDs/Contents/Resources/Xerox WorkCentre 5955.gz"

That looks a little daunting, so let’s break it down a little bit.

-p <name>  This flag sets the name of the printer as seen by the cups process. Use a name with no spaces, or substitute underscores for the space.

-E  This flag enables the printer to accept jobs

-o printer-is-shared=false  The -o flag allows us to pass options to the printer. In this case we are making sure the printer is not shared on the network.

-v ipp://1.1.1.1  The -v flag sets the URI of the printer.

-D <name>  Where -p set the name the cups process saw, the -D flag sets what I call the “friendly” name, the name that is visible in the GUI.

-P <driver path>  Pretty self explanatory, the -P flag sets the path to the driver.

Now that we have everything, run the command in Terminal to add the printer. You can verify the printer added by using the lpstat -a command. With the printer added, open up a program and send a test print to the printer. It is important for us to do this step so that we know our handy work is working properly.

Put It In A Script

Let’s get to the script to add the printer. Open your favorite code editor (TextMate or Sublime Text for me) and start a new script. I utilize Bash, but you could just as easily do this in Python if you prefer. First we want to make sure the proper driver is on the system, and if it isn’t we want to install it.

if [[ ! -f "/Library/Printers/PPDs/Contents/Resources/Xerox WorkCentre 5955.gz" ]]; then
/usr/local/bin/jamf policy -trigger xeroxGenericDriver
fi
view raw gistfile1.txt hosted with ❤ by GitHub

If the driver file is not on the system, we call the jamf binary to trigger our install policy. Adjust this to fit your management toolset.

Now with the driver check complete, we use a case statement to choose the printer (or printers) to install. We pass the choice to the script using Script Parameters in our Jamf Pro server policy. Here’s an example showing how we can install one printer, or multiple printers in an office.

lpa='/usr/sbin/lpadmin'
case $printer in
Printer1)
${lpa} -p Printer1 -E -o printer-is-shared=false -v ipp://10.1.1.1/ipp/print \
-D "Printer1" -P "/Library/Printers/PPDs/Contents/Resources/Xerox WorkCentre 5955.gz"
;;
Printer2)
${lpa} -p Printer2 -E -o printer-is-shared=false -v ipp://10.1.1.2/ipp/print \
-D "Printer2" -P "/Library/Printers/PPDs/Contents/Resources/Xerox WorkCentre 5955.gz"
;;
OfficePrinters)
${lpa} -p Printer1 -E -o printer-is-shared=false -v ipp://10.1.1.1/ipp/print \
-D "Printer1" -P "/Library/Printers/PPDs/Contents/Resources/Xerox WorkCentre 5955.gz"
${lpa} -p Printer2 -E -o printer-is-shared=false -v ipp://10.1.1.2/ipp/print \
-D "Printer2" -P "/Library/Printers/PPDs/Contents/Resources/Xerox WorkCentre 5955.gz"
;;
esac
view raw gistfile1.txt hosted with ❤ by GitHub

You can hopefully see the flexibility this provides us for using one script to install multiple printers. Sure, we still have multiple policies in the JPS, but rather than have multiple printers or multiple scripts as well, we can do this with just the one. And, when a printer needs to change, we just edit the script.

Bonus Round

What about adding printer options, like paper trays or output trays or setting a printer to B&W instead of color? We can use lpoptions to figure out what those options are and to set them. Since that can be a daunting task, head over to this post about using lpoptions to identify the settings.

Hopefully this post has helped you evaluate the use of a script to add printers and has given you a new tool for your toolbox.

Categories: Jamf Pro, Tech Tags: , , ,

Scripting Remote Desktop Bookmarks

February 29, 2016 Leave a comment

A few years ago I was searching for a way to easily create bookmarks in Microsoft Remote Desktop 8 on the Mac. Prior to version 8 you could drop an .RDP file on a machine and that was really all you needed to do to give your users the ability to connect to servers. Granted, you can still use this method, it’s just a bit sloppier, in my opinion.

So I went searching for a way to script the bookmarks, and that led me to my good friend Ben Toms’ (@macmuleblog) blog. I found his post, “HOW TO: CREATE A MICROSOFT REMOTE DESKTOP 8 CONNECTION” and started experimenting. After some trial and error, I discovered that using PlistBuddy to create the bookmarks just wasn’t being consistent. So I looked into using the defaults command instead. I finally was able to settle on the following script:

#!/bin/sh
# date: 18 Jun 2014
# Name: RDC-Connection.sh
# Author: Steve Wood (swood@integer.com)
# updated: 29 Feb 2016 - included line to add remote program to start on connection for @gmarnin
# grab the logged in user's name
loggedInUser=`/bin/ls -l /dev/console | /usr/bin/awk '{ print $3 }'`
# global
RDCPLIST=/Users/$loggedInUser/Library/Containers/com.microsoft.rdc.mac/Data/Library/Preferences/com.microsoft.rdc.mac.plist
myUUID=`uuidgen`
LOGPATH='/private/var/inte/logs'
# set variables
connectionName="NAME YOUR CONNECTION"
hostAddress="SERVERIPADDRESS"
# if you need to put an AD domain name, put it in the userName variable, otherwise leave blank
userName='DOMAINNAME\'
userName+=$loggedInUser
resolution="1280 1024"
colorDepth="32"
fullScreen="FALSE"
scaleWindow="FALSE"
useAllMonitors="TRUE"
set -xv; exec 1> $LOGPATH/rdcPlist.txt 2>&1
defaults write $RDCPLIST bookmarkorder.ids -array-add "'{$myUUID}'"
defaults write $RDCPLIST bookmarks.bookmark.{$myUUID}.label -string "$connectionName"
defaults write $RDCPLIST bookmarks.bookmark.{$myUUID}.hostname -string $hostAddress
defaults write $RDCPLIST bookmarks.bookmark.{$myUUID}.username -string $userName
defaults write $RDCPLIST bookmarks.bookmark.{$myUUID}.resolution -string "@Size($resolution)"
defaults write $RDCPLIST bookmarks.bookmark.{$myUUID}.depth -integer $colorDepth
defaults write $RDCPLIST bookmarks.bookmark.{$myUUID}.fullscreen -bool $fullScreen
defaults write $RDCPLIST bookmarks.bookmark.{$myUUID}.scaling -bool $scaleWindow
defaults write $RDCPLIST bookmarks.bookmark.{$myUUID}.useallmonitors -bool $useAllMonitors
#comment out the following if you do not need to execute a program on start of connection
# You can adjust the string to be any app that is installed.
defaults write $RDCPLIST bookmarks.bookmark.{$myUUID}.remoteProgram -string "C:\\\\Program Files\\\\\\\\Windows NT\\\\Accessories\\\\wordpad.exe"
chown -R "$loggedInUser:staff" /Users/$loggedInUser/Library/Containers/com.microsoft.rdc.mac
view raw gistfile1.txt hosted with ❤ by GitHub

You can find that code in my GitHub repo here.

RDC URI Attribute Support

I had posted that script up on JAMF Nation back in June 2014 when someone had asked about deploying connections. Recently user @gmarnin posted to that thread asking if anyone knew how to add an alternate shell key to the script. After no response, he reached out to me on the Twitter (I’m @stevewood_tx in case you care). So, I dusted off my script, fired up my Mac VM, and started experimenting.

The RDC GUI does not allow for a place to add these URI Attributes. I read through that web page and Marnin forwarded me this one as well. Marnin explained that he was able to get it to work when he exported the bookmark as an .RDP file and then used a text editor to add the necessary “alternate shell:s:” information. Armed with this knowledge, I went to the VM and started testing.

First I created a bookmark in a fresh installation of RDC. I had no bookmarks at all. After creating a bookmark I jumped into Terminal and did a read of the plist file and came up with this:

YosemiteVM:Preferences integer$ defaults read /Users/integer/Library/Containers/com.microsoft.rdc.mac/Data/Library/Preferences/com.microsoft.rdc.mac.plist
{
QmoteUUIDKey = "ff870b10-7e8e-47c2-98bd-f14f3f0cd1b0";
"bld_number" = 26665;
"bookmarklist.expansionStates" = {
GENEREAL = 1;
};
"bookmarkorder.ids" = (
"{2a3925d6-659e-456e-ab03-86919b30b54b}"
);
"bookmarks.bookmark.{2a3925d6-659e-456e-ab03-86919b30b54b}.fullscreenMode" = "@Variant(\177\017FullscreenMode\001)";
"bookmarks.bookmark.{2a3925d6-659e-456e-ab03-86919b30b54b}.hostname" = "termserv.company.com";
"bookmarks.bookmark.{2a3925d6-659e-456e-ab03-86919b30b54b}.label" = Test;
"bookmarks.bookmark.{2a3925d6-659e-456e-ab03-86919b30b54b}.username" = "";
"connectWindow.geometry" = <01d9d0cb 00010000 000001b4 000000a0 000003b9 0000033f 000001b4 000000fc 000003b9 0000033f 00000000 0000>;
"connectWindow.windowState" = <000000ff 00000000 fd000000 00000002 06000002 44000000 04000000 04000000 08000000 08fc0000 00010000 00020000 00010000 000e0074 006f006f 006c0042 00610072 01000000 00ffffff ff000000 00000000 00>;
lastdevinfoupd = 1456781093;
lastdevresourceupd = 1456781153;
"preferences.ignoredhosts" = (
"10.93.209.210:3389"
);
"preferences.resolutions" = (
"@Size(640 480)",
"@Size(800 600)",
"@Size(1024 768)",
"@Size(1280 720)",
"@Size(1280 1024)",
"@Size(1600 900)",
"@Size(1920 1080)",
"@Size(1920 1200)"
);
"show_whats_new_dialog" = 0;
"stored_version_number" = "8.0.26665";
tlmtryOn = 1;
}
view raw gistfile1.txt hosted with ❤ by GitHub

Now that we had a baseline, I exported the bookmark to the desktop of the VM, edited it to add the “alternate shell” bits, and then re-imported it into RDC as a new bookmark. I then tested to make sure it would work as advertised. After some trial and error, I was able to get the exact syntax for the “alternate shell” entry to work. Now I just needed to see what changes were made in the plist file. A quick read showed me the following:

YosemiteVM:Preferences integer$ defaults read /Users/integer/Library/Containers/com.microsoft.rdc.mac/Data/Library/Preferences/com.microsoft.rdc.mac.plist
{
QmoteUUIDKey = "ff870b10-7e8e-47c2-98bd-f14f3f0cd1b0";
"bld_number" = 26665;
"bookmarklist.expansionStates" = {
GENEREAL = 1;
};
"bookmarkorder.ids" = (
"{2a3925d6-659e-456e-ab03-86919b30b54b}"
);
"bookmarks.bookmark.{2a3925d6-659e-456e-ab03-86919b30b54b}.fullscreenMode" = "@Variant(\177\017FullscreenMode\001)";
"bookmarks.bookmark.{2a3925d6-659e-456e-ab03-86919b30b54b}.hostname" = "termserv.company.com";
"bookmarks.bookmark.{2a3925d6-659e-456e-ab03-86919b30b54b}.label" = Test;
"bookmarks.bookmark.{2a3925d6-659e-456e-ab03-86919b30b54b}.username" = "";
"bookmarks.bookmark.{2a3925d6-659e-456e-ab03-86919b30b54b}.remoteProgram" = "C:\\\\Program Files\\\\\\\\Windows NT\\\\Accessories\\\\wordpad.exe";
"connectWindow.geometry" = <01d9d0cb 00010000 000001b4 000000a0 000003b9 0000033f 000001b4 000000fc 000003b9 0000033f 00000000 0000>;
"connectWindow.windowState" = <000000ff 00000000 fd000000 00000002 06000002 44000000 04000000 04000000 08000000 08fc0000 00010000 00020000 00010000 000e0074 006f006f 006c0042 00610072 01000000 00ffffff ff000000 00000000 00>;
lastdevinfoupd = 1456781093;
lastdevresourceupd = 1456781153;
"preferences.ignoredhosts" = (
"10.93.209.210:3389"
);
"preferences.resolutions" = (
"@Size(640 480)",
"@Size(800 600)",
"@Size(1024 768)",
"@Size(1280 720)",
"@Size(1280 1024)",
"@Size(1600 900)",
"@Size(1920 1080)",
"@Size(1920 1200)"
);
"show_whats_new_dialog" = 0;
"stored_version_number" = "8.0.26665";
tlmtryOn = 1;
}
view raw gistfile1.txt hosted with ❤ by GitHub

The key is the line that has “remoteProgram” as part of the entry. You have to get the full path on the Windows machine to the application you want to run on connection to the server. Once you know that path, you can adjust your bookmark script however you need.

The script I posted above, and is linked in my GitHub repo, contains the line to add that Remote Program (alternate shell). If you do not need it, just comment it out of the script.

 

AutoPkg – Your New Best Friend

October 30, 2013 2 comments

There’s been a lot of talk in the Mac admin community recently about AutoPkg (http://autopkg.github.io/autopkg/).  This tool, currently being developed by Greg Neagle and Tim Sutton, allows for the use of “recipes” to download and package different software found on the Internet.  This ability takes a lot of the work out of locating software, downloading it, and then packaging it, something a Mac admin may do multiple times a week.  AutoPkg automates all of that.  And, if you are a user of Munki, there are even recipes in AutoPkg that will import directly into Munki and update the catalogs.

As a prerequisite for AutoPKG to work, you need to have the command line version of git installed.  If you’ve installed XCode, then you have git.  If not, you can get git by downloading either a GUI GitHub tool like this one here, or download the command line version of git here.

Once you have git installed, go ahead and download the AutoPkg installer file from this location.  Now that it is downloaded, go ahead and run the AutoPkg installer.  This will install the AutoPkg software in the proper  locations on the machine.  Once completed, fire up Terminal and you can verify the software is properly installed by running the following command:

autopkg version

That will give you the current version installed on the system.  Now that AutoPkg is installed, you may want to create a directory in a common location, like the root of the drive or in the /Users/Shared folder, to hold the output from AutoPKG.  By default AutoPkg stores it’s output inside the user’s Library folder at ~/Library/AutoPkg/Cache.  This location can be changed by adjusting the CACHE_DIR in the preferences.  I like to store it at the root of the drive in a folder named Autopkg_Done.

mkdir /Autopkg_Done
chmod 777 /Autopkg_Done

Now that you have the CACHE_DIR created, you’ll need to tell AutoPkg about it.  This is done with the following command:

defaults write com.github.autopkg CACHE_DIR /path/to/cache/dir

So, in our example, the command we’d use is this:

defaults write com.github.autopkg CACHE_DIR /Autopkg_Done
NOTE:  AutoPKG stores its preferences in the user’s Preferences folder (~/Library/Preferences/com.github.autopkg.plist).  Because of this, any new user on the machine that will need to use AutoPKG will need to have the CACHE_DIR set and will need to download the recipe repositories (more on this in a minute).

With AutoPkg installed, and the CACHE_DIR set, all we have left is to tell it about some recipe repositories.  AutoPkg utilizes git repositories that hold recipes for building different packages.  Like using a recipe to bake a cake, AutoPkg uses these recipes to build the different files.

As of this writing, there are four repositories that I am aware of.  They are:

https://github.com/Jaharmi/autopkg_recipes
http://github.com/autopkg/recipes.git
https://github.com/hjuutilainen/autopkg-recipes
http://github.com/keeleysam/recipes
We need to tell AutoPkg about these repositories.  This is accomplished with the following command line:

autopkg repo-add <repoURL>

So for the four repositories above, we would use the following:

autopkg repo-add https://github.com/Jaharmi/autopkg_recipes
autopkg repo-add http://github.com/autopkg/recipes.git
autopkg repo-add https://github.com/hjuutilainen/autopkg-recipes
autopkg repo-add http://github.com/keeleysam/recipes

Now that we have our repositories in AutoPkg, we are free to run one of the recipes.  We can see what recipes we have access to with the following:

autopkg list-recipes

If all went right, you should see a list of the recipes that your AutoPkg installation knows about.  To run one, simply use the following:

autopkg run <recipe>

For example, to run Adobe Flash Player:

autopkg run AdobeFlashPlayer.pkg

That will download the Flash Player and create a PKG file for deployment inside of our CACHE_DIR (/Autopkg_Done in our case).  We can navigate through the folders there to find a pristine Flash installer package that we can now deploy to our machines.
There’s plenty more to know about AutoPkg, including how to import directly into Munki during a build.  Check out the wiki for more information on Munki integration, or if you want help with installation and config, go here.

Next up, we’ll setup Jenkins, a continuous integration server that will eventually allow us to build packages while we sleep

Categories: Tech Tags: , , ,