On Internet Shortcut Files

If you are managing Macs and PCs, then you will frequently connect to other computers or virtual machines through screen sharing, secure shell or other means. Here is a simple trick to make those connections easier:

Open your favored UI text editor (Text Edit will do) and type

vnc://<username>@<hostname>

Replace <username> and <hostname> with your username and hostname for a Mac you frequently share the screen with.

Then select the entire text and drag it to the desktop. It will create a file with “@” sign on the icon. When you double click this file, Screen Sharing opens and asks for the password (unless it is stored in the Keychain) and connects the remote session.

Now rename this file to VNC <hostname>. Then activate Spotlight (cmd-space) and start typing “VNC” and the hostname. After a few characters the VNC link file should be the top result. Hit return and the session starts.

As an extra bonus you can put these files in a folder in your cloud file system of choice (iCloud Drive, Dropbox, Google Drive, Box, etc.) and they will sync to all your Macs.

Other Protocols

You can do the same with any URI scheme:

ssh://<username>@<hostname>:<port>

afp://<username>@<hostname>/<share>

smb://<username>@<hostname>/<share>

http://<hostname/<path>

Microsoft Remote Desktop/RDP

The new Microsoft Remote Desktop application available in the Mac App Store also supports rdp URI schemes, however the syntax is a bit odd. To connect to a remote host you have to use:

rdp://full%20address=s:<hostname>&username=s:<username>

You can find more options for this URI scheme in this technote. One I have found useful is screen mode id where a value of 1 means to open the remote screen in a window a value of 2 means full screen mode.

Looking into the files

If inspect the file generated by dragging, you can see that it is a property list file with a single key URL and a string value containing the link.

Files for a vnc URI get the .vncloc extension, http[s] URIs get the .webloc extension, ftp URIs get the .ftploc extension and everything else seems to get .inetloc extensions. (There may be more, let me know if you find any.)

This makes it easy to write a short script that simplifies the creation of these internet location files:

Sample use:

./create_netloc.py vnc://user@host.example.com

will create VNC host.vncloc

./create_netloc.py http://munki.example.com/munkireport-php/ Munkireport

will create Munkireport.webloc

./create_netloc.py "rdp://full%20address=s:host.example.com&username=s:name&screen%20mode%20id=i:1"

will create RDP host.inetloc

Note on rdp links: since the ‘&’ is special in shell commands you need to quote the uri if any are present.

Fun with find and git

find ~ -type d -name .git -exec dirname {} \;

will show you all folders in your home directory which have a .git subfolder — i.e. they are git repositories.

This is useful, but can be extended to:

find ~ -type d -name .git -exec dirname {} \; | tr '\n' '\000' | xargs -0 -n1 -I % git -C "%" status

to show the git status output of all your git repositories.

Time to clean up!

Control ssh access with munki nopkg scripts

Often you want to control settings on client machine with scripts rather than packages. One such example is controlling ssh acces, aka ‘Remote Login’ in the OS X UI.

Note: please consider supporting the author of this site by purchasing one of my books! Thank you!

Having ssh access to the clients allows for remote access for trouble shooting and analysis. You can also remotely start or stop processes, like initiating managedsoftwareupdate right now instead of waiting.

However, since there is potential to abuse ssh, we should restrict ssh access to a subset of users. The OS X UI allows you restrict ssh access to a list of users or groups. In our example we will enable Remote Login and give access to any user with administrative privileges, i.e. the admin group.

You could write the scripts to enable these settings and put them in a so called “payload free package.” However, Munki has a simpler and more flexible way of handling this. Ironically, you create a pkginfo file with the nopkg setting.

Building the Install_Check script

Usually when you create the pkginfo for a package or dmg installation, Munki will analyze the files you give it and create conditions on which to install. If the dmg contains an application Firefox, it will look in /Applications for an application named Firefox and compare versions. If the application on the client is not present or of an older version, Munki will perform the installation.

For configurations other than files or applications, we can provide a script that runs and can tell Munki wether to perform the installation first. It might seem a little odd, but it makes a lot of sense to develop this install_check script first.

We need to check wether

  • Remote Login/ssh is enabled
  • access is not set to ‘All Users’
  • the admin user group is allowed access

Open your favorite text editor and create a new script called ssh_install_check.sh and start typing:

#!/bin/bash

PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/munki export PATH

# this will run as a munki install_check script
# exit status of 0 means install needs to run
# exit status not 0 means no installation necessary

So if the script runs and returns 0 as the exit code, Munki will perform the installation. If the script returns any non-zero code, then Munki will assume everything is alright and not perform the installation.

First thing we want to test for is wether Remote Login/ssh is enabled. The CLI tool systemsetup has a command for that:

systemsetup -getremotelogin

Will return Remote Login: On/Off depending on the status. Testing for this is easy enough. Add these lines to the script:

# Is SSH enabled
if [[ $(systemsetup -getremotelogin) = 'Remote Login: Off' ]]; then
    echo 'Remote login is off!'
    exit 0
fi

You can save and test the script on your Mac right now. Use the UI in System Preferences > Sharing to toggle Remote Login and see what the script returns.

Next we want to test wether the access is set to ‘All Users’. There is a group called com.apple.access_ssh on your Mac that contains the users which are allowed ssh access. However, if the access is set to ‘All Users’ the OS renames this group to com.apple.access_ssh-disabled. Add these lines to the script:

ssh_group="com.apple.access_ssh"

# Does a group named "com.apple.access_ssh" exist?
if [[ $(dscl /Local/Default list /Groups | grep "${ssh_group}-disabled" | wc -l) -eq 1 ]]; then
    echo "access set to 'All Users'"
    exit 0
elif [[ $(dscl /Local/Default list /Groups | grep "$ssh_group" | wc -l) -eq 0 ]]; then
    echo "no group '$ssh_group'"
    exit 0
fi

First we defined a variable with the name ofthe group test for, this saves a lot of typing, reduces errors and makes the code more re-usable incase you want to use this for other control groups as well.

Then we run dscl /Local/Default list Groups and grep for the names to see if these groups exist. Then we do the same again in case the group does not exist at all (it really should, but it cannot hurt to test).

You can save and run the script to test again. Switch from ‘All Users’ to ‘Only these users’ in the UI. and see the results of the script.

Finally we want to test wether the admin group is allowed for ssh. We need to see if the group ‘admin’ is contained in the access_ssh group. This is a bit harder than it sounds. We can list nested groups using dscl /Local/Default read Groups/com.apple.access_ssh NestedGroups, but this will list long UUID strings, not the names of the groups.

To get the UUID of the admin we can use dsmemberutil getuuid -G admin. and then rest is easy. Add these lines to your script:

# does the group contain the admin group?
admin_uuid=$(dsmemberutil getuuid -G admin)
if [[ $(dscl /Local/Default read Groups/com.apple.access_ssh NestedGroups | grep "$admin_uuid" | wc -l) -eq 0 ]]; then
    echo 'admin group not nested in $ssh_group!'
    exit 0
fi

Sidenote 1: this will only test wether the admin group is directly nested in the access_ssh group. You could have another group in the access_ssh group, which itself contains the admin group. So the admin group would have the privilege to use ssh, but this script would still fail. You could also have all members of the admin group listed directly in the access_ssh group, and the script would not check for that. Either way the script will fail and cause Munki to add the admin group (we will learn how later) and it wouldn’t change access, so we are ok with this somewhat superficial test.
Sidenote 2: this script will also ignore other groups or users in the access_ssh group. If you wanted to enforce stricter access policies, you might want to test wether admin is the only group nested in access_ssh.

Finally the script should return a non-zero value when all the tests are passed. Our final script will look like this (cleaning it up a bit):

#!/bin/bash

PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/munki export PATH

# this will run as a munki install_check script
# exit status of 0 means install needs to run
# exit status not 0 means no installation necessary

ssh_group="com.apple.access_ssh"

# Is SSH enabled
if [[ $(systemsetup -getremotelogin) = 'Remote Login: Off' ]]; then
    echo 'Remote login is off!'
    exit 0
fi

# Does a group named "com.apple.access_ssh" exist?
if [[ $(dscl /Local/Default list /Groups | grep "${ssh_group}-disabled" | wc -l) -eq 1 ]]; then
    echo "access set to 'All Users'"
    exit 0
elif [[ $(dscl /Local/Default list /Groups | grep "$ssh_group" | wc -l) -eq 0 ]]; then
    echo "no group '$ssh_group'"
    exit 0
fi

# does the group contain the admin group?
admin_uuid=$(dsmemberutil getuuid -G admin)
if [[ $(dscl /Local/Default read Groups/com.apple.access_ssh NestedGroups | grep "$admin_uuid" | wc -l) -eq 0 ]]; then
    echo 'admin group not nested in $ssh_group!'
    exit 0
fi

echo "everything seems as it should be, no install needed"
exit 1

Building the postinstall_script

Now that we have a script that tests wether we need to change settings, we can start building the actual script to do the changes. The good news is that while writing the install_check script we already built the outline for the actual install script. We use the same same tests, but instead of merely reporting and exiting, we now perform the necessary change:

#!/bin/bash

PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/munki export PATH

ssh_group="com.apple.access_ssh"

# enable ssh
if [[ $(systemsetup -getremotelogin) = 'Remote Login: Off' ]]; then
    echo "turning on Remote Login/SSH"
    systemsetup -setremotelogin On
fi

# Does a group named "com.apple.access_ssh" exist?
if [[ $(dscl /Local/Default list /Groups | grep "${ssh_group}-disabled" | wc -l) -eq 1 ]]; then
    #rename this group
    echo "renaming group '${ssh_group}-disabled'"
    dscl localhost change /Local/Default/Groups/${ssh_group}-disabled RecordName ${ssh_group}-disabled $ssh_group
elif [[ $(dscl /Local/Default list /Groups | grep "$ssh_group" | wc -l) -eq 0 ]]; then
    # create group
    echo "creating group $ssh_group"
    dseditgroup -o create -n "/Local/Default" -r "Remote Login Group" -T group $ssh_group
fi

# does the group contain the admin group?
admin_uuid=$(dsmemberutil getuuid -G admin)
if [[ $(dscl /Local/Default read Groups/$ssh_group NestedGroups | grep "$admin_uuid" | wc -l) -eq 0 ]]; then
    echo "adding admin group to $ssh_group"
    dseditgroup -o edit -n "/Local/Default" -a admin -t group $ssh_group
fi

exit 0

Note that we use dseditgroup instead of manipulating the NestedGroup property with dscl. This is the official and safe way to manipulate groups in OS X.

Save this as ssh_postinstall.sh. Open the Remote Login Sharing UI in System Preferences > Sharing, change the settings and then run this script. To see the changes from the script reflected in the Preference you have to quit and restart System Preferences.

Building the pkginfo file

Now we that we have a script to test and another to change the settings we have to build a pkginfo file that will explain all of this to Munki. Let’s use makepkginfo to get us started:

makepkginfo --name EnableSSH --nopkg --pkgvers=1.0 --installcheck_script=ssh_install_check.sh --postinstall_script=ssh_postinstall.sh --unattended_install > enableSSH.pkginfo

--nopkg sets the install_type and tells Munki that this item has no pkg or dmg file associated with it. The two scripts we built are included in the plist. Munki will read this pkginfo and execute the code when necessary.

You can change or add some more keys (such as description or displayname) and then copy the file to your Munki repository and run makecatalogs. Then add EnableSSH to a manifest on your test machine(s) and run Managed Software Update on it. EnableSSH should appear and run the postinstall script. Check wether SSH works now.

Further, you can go in the UI and change some setting for Remote login, then run Managed Software Update again (or logout to make it run). Notice how our scripts detect that the settings changed from what they are supposed to be and re-ran our postinstall script to re-set them.

more options

You could add a third script to the pkginfo to ‘uninstall’ the settings. That way you could revert your settings to the default. There is even an option for a uninstallcheck_script that will check wether Munki should uninstall your settings.

Summary

The nopkg option allows us to run scripts with Munki, without need to bundle a pkg or dmg payload. Using an installcheck_script allows us to give Munki very precise instructions on when to run the script and repeatedly enforce configurations.

Create a NetInstall image to Bootstrap Munki

Munki’s wiki describes a process called Bootstrapping, where you install a Base OS, add the Munki tools and some settings and at first boot of the freshly installed Mac, Munki takes over and does the rest of the installations and configuration.

Here I will describe how to build a NetInstall Image (nbi) with System Image Utility to install a base OS with Munki. I will describe how to dip this for Mavericks 10.9.5 and Yosemite 10.10.1. There are some differences.

Either way, there is a restriction in System Image Utility that you can only build an NetInstall (or NetBoot or NetRestore) set of the same version of the OS you are running. So you if want both a Mavericks and Yosemite NetInstall set you will have to do this twice, once on a Mavericks machine and once on a Yosemite machine.

Download Mavericks or Yosemite

Open the Mac App Store and download the installer for Mavericks or Yosemite. The installed OS has to match the version of the installer and the NetInstall image you want to build.

Mac App Store will complain that the OS is already installed, just click ‘continue’ to start the download anyway. Once the download is complete the installer will actually open, just quit it, we do not need that now. You can also use a copy of the “Install OS X Mavericks|Yosemite.app” you may have archived somewhere.

Open System Image Utility

In Mavericks you can find it in /System/Library/CoreServices. On Yosemite they grouped several useful applications, including System Image Utility, into a subfolder /System/Library/CoreServices/Applications/. Easiest Way to get there is Finder’s “Go To Folder…” option (command-shift G).

In Yosemite, I find it useful to link this folder to the /Applications folder for easier access: ln -s /System/Library/CoreServices/Applications /Applications/System

If the Install application is in the default location (/Applications folder) SIU should pick it up automatically and show the version and build number below. Make sure they match your current OS.

NetInstall Image is the default selection. Choose “Customize” to proceed.

Customize the image workflow

The window will change to the customizable workflow view. You will have two steps: “Define Image Source” and “Create Image.”

“Define Image Source” only has one option and that should be already set. The default values in “Create Image” will need some adjusting though.

“Type” should be “NetInstall” which also grays out the “Installed Volume” field.

“Save to” defines the location where the nbi folder will be built. Ultimately you will have to upload this folder to your netboot server. Choose a local folder to build it. If you have a fast SSD drive, building it there will speed up the process significantly.

“Image Name” is the name of the folder set. Change the default to “Munki Yosemite” or “Munki Mavericks”.

“Network Disk” is the display that will be used in the Server application, Startup Disk and the EFI boot picker. Change the default to “Munki Yosemite” or “Munki Mavericks”.

“Image Index” should be an integer that is unique to each different NetBoot/NetInstall image in your network. If you are serving the image from a single NetBoot server, the number should be less than 4095, otherwise greater than. SIU will choose a random for you, but you can change it now or later in the Server app.

Finally the Description field. SIU puts a decent summary here, I would add the version of Munki you are installing.

Adding Custom Packages

There should be a second “Automator Library” window with additional workflow steps. Find the “Add Packages and Install Scripts” step and drag it between the two existing steps. You can also drag to change the order of the steps later.

Download the latest Munki release.

Drag the munkitools-VERSION.pkg file into the “Add Packages and Install Scripts” step. (As of this writing be sure to get the very latest package [2.0.1 or newer] as the older versions might not install completely during NetInstall. https://github.com/munki/munki/commit/53edd6a050b9e6612b2b827145b40a6cc9a50792)

This will install Munki tools along side OS X. Then at the first boot, Munki will take over the mac and install additional software and configurations according to Munki’s catalog and manifest.

However the munkitools installer does not know to point the client to your Munki repository. This is one step we need to configure.

Open your preferred text editor and paste

#!/bin/bash

MUNKI_REPO_URL="http://munki-server/munki_repo/"

/bin/echo "setting Munki Repo URL at $3/Library/Preferences/ManagedInstalls defaults to $MUNKI_REPO_URL"

/usr/bin/defaults write "$3/Library/Preferences/ManagedInstalls" SoftwareRepoURL $MUNKI_REPO_URL
/usr/bin/touch "$3/Users/Shared/.com.googlecode.munki.checkandinstallatstartup"

Save the script as ‘setupMunki.sh’ and drag the script into “Add Packages and Installation Scripts” step in your SIU workflow.

Modify the MUNKI_REPO_URL variable to point to your server.

The defaults command sets the SoftwareRepoURL in the proper preference file. The touch command creates a flag file so that Munki starts installing package right away on first boot as described here: https://github.com/munki/munki/wiki/Bootstrapping-With-Munki#details

The $3 parameter is passed into the script by the installer process and contains the target volume for the installation. In this case it will be something like /Volumes/Macintosh HD/. Note that this path likely contains spaces (like any OS X path), so you need to remember to quote this variable properly when you modify this script.

Build and Upload Netboot set

Save your Workflow and give it a meaningful name. Then run it. This may take several minutes. Once the workflow is done you can upload the nbi folder to your NetBoot Server. (OS X Server stores these folders by default in /Library/NetBoot/NetBootSP0/) Enable and try it on a client.

Further customization

(Note: every time you update or add a package or script, you need to rebuild and re-upload the netinstall folder)

The point of “bootstrapping” is to do as much configuration as possible with Munki later, so the modifications to the NetInstall process are minor. You can even update the Munki software on the client through Munki, so you will not have to update the NetInstall image on every Munki update.

There are a few things, however, that do make sense to do in the NetInstall process. Two common settings are to add a admin user and/or suppress the OS X setup process.

The setup dialogs will be suppressed if the file /var/db/.AppleSetupDone exist. So we can just add a line to our setupMunki.sh script:

/usr/bin/touch "$3/var/db/.AppleSetupDone"

Alternatively you can create a second script that does just this, so you separate OS X configuration from Munki configuration.

Create Default Users

You can use the application CreateUserPKG to create a pkg file that will create the desired user for you. If you need multiple default users you can create multiple different packages with CreateUserPKG.

Yosemite requires packages added to the NetInstall process to be of a specific format, called ‘distribution type packages.’ CreateUserPKG packages are not in this particular format, so we have to convert them.

productbuild --package createuser-1.0.pkg createuser-1.0dist.pkg

Applying Apple Software Updates

Munki allows you to include Software Updates from Apple during the Munki install/update process. If you want to do that at first boot, then add this line to your setupMunki.sh.

/usr/bin/defaults write "$3/Library/Preferences/ManagedInstalls" InstallAppleSoftwareUpdates -bool true

If you want to a local Software Update Server, then you can provide that, too:

/usr/bin/defaults write "$3/Library/Preferences/ManagedInstalls"  SoftwareUpdateServerURL http://sus.example.com/content/catalogs/index_production.sucatalog

Automate the installation

You may want to make the installation process completely without any user interaction. In this case a use will choose the NetInstall volume in System Preferences or at the EFI Boot Picker and the process will erase a partition with a pre-defined name and install a fresh OS with the Munki Tools and your settings.

This can be very useful, but also very dangerous.

To achieve this add the “Enabled Automated Installation” workflow step before the “Create Image” step. It only has three options:

  • the partition or volume name to install into
  • wether to erase the target volume
  • the language to use for the installation UI

If you enable automated installation, then there will be no UI provided, even if there is no volume with the expected name. (Then the installer process will just give up and boot back to the previous boot volume.) So you will have no access to other tools provided on the NetInstall netboot image.

Also if you choose a generic name for the automated volume (such as “Macintosh HD”) the potential that someone might accidentally delete a volume might be very high. In this case you really want to restrict access to the NetInstall by subnet or some other means.

Things Command Line Tool

So, between AutoPkg and some other tool I am working I have been spending a lot of time in Terminal recently. I have also (once again) tried to organize myself with the GTD app Things from CulturedCode. While I really like the simplicity and design of Things, I also wanted a fast way to add ideas I might have while on the command line and maybe also check off one or the other to do item from the Terminal. In short I wanted a command line tool for things.

Luckily, Things provides an AppleScript interface. So, I just wrote this tool for myself:

Things Command Line Tool

I have found it quite useful and fun. Also this is a nice example of writing a command line tool in AppleScript.

 

Build Link List from Safari Window

The previous post reminded of one of the oldest scripts I have and still use regularly. I regularly have to gather information found on different webpages and email them to somebody. I try to gather all the relevant webpages in tabs in one Safari and then run this script. It will look at all the tabs in the frontmost Safari Window and build a plain text list out of the titles and links and place that in clipboard, ready to paste into an email or elsewhere.The result will look like this:

Apple
<http://www.apple.com/>

Google
<http://www.google.com/>

Scripting OS X | #! is not a curse word
<https://scriptingosx.com/>

The script is fairly straightforward:

global linkText

on run
	set linkText to ""

	tell application "Safari"
		activate
		set w to window 1

		set n to 0
		try -- this will fail for the downloads window
			set n to count tabs of w
		end try

		if n > 1 then
			repeat with t in every tab of w
				my appendLineWithDoc(t)
			end repeat
		else if n = 1 then
			my appendLineWithDoc(document of w)
		end if
	end tell

	set the clipboard to linkText

	return linkText
end run

on appendLineWithDoc(theDoc)
	tell application "Safari"
		tell theDoc
			try
				set linkText to linkText & name
				set linkText to linkText & return & "<" & URL & ">" & return & return
			end try
		end tell
	end tell
end appendLineWithDoc

You can save this script in your ~/Library/Scripts/Applications/Safari  folder and enable the Script menu and it will be shown only in Safari.