Images not loading in Gmail canned responses

Do you use images in canned responses in Gmail? Did you notice your images suddenly showing up as blank boxes (both in the composition and in the actual sent message)?


Well, there's a fix for that. Use the new Gmail instead of the old Gmail:


Your images should now load (you do not need to re-create your old canned responses).

Integrating DetectX Swift with Munki

If you like DetectX Swift and want to integrate it with Munki, this is how I did it. Hat tip to Zack McCauley for doing the heavy lifting, which I'm now building on. I'd recommend you read his blog post first.

So instead of having an Outset script or separate Launch Agent, I decided to put the DetectX Swift scan as part of the Munki run (specifically a script in the preflight.d directory that MunkiReport creates):

#!/bin/bash

# Run a DetectX Swift scan
/Applications/Utilities/DetectX\ Swift.app/Contents/MacOS/DetectX\ Swift search -aj /usr/local/munki/preflight.d/cache/detectx.json

Outside of MunkiReport (but connecting to the MunkiReport MySQL database), I have a script that generates a Python list of files that DetectX Swift has flagged as "issues":

$query="SELECT issues FROM detectx WHERE numberofissues > 0";
$result=mysqli_query($YOURDATABASECONNECTION, $query);
if(mysqli_num_rows($result)>0){
   // Create an array to store the results
   $larger_issues=array();
   while($row=mysqli_fetch_assoc($result)){
      
      // Create an array based on a semi-colon delimiter
      $smaller_issues=explode(";", $row['issues']);
      foreach($smaller_issues AS $smaller_issue){
          if((trim($smaller_issue)!='') AND (!in_array($smaller_issue, $larger_issues))){
            array_push($larger_issues, $smaller_issue);
         }
      }

   // End fetching results
   }

   if(!empty($larger_issues)){
      echo '<p>okay_to_delete = [ ';
      $counter=0;
      while($counter+1<count($larger_issues)){
         echo '\'' . $larger_issues[$counter] . '\',<br />';   
         $counter+=1;
      }
      echo '\'' . $larger_issues[$counter] . '\' ]</p>';
      //print_r($larger_issues);
   
   // End checking there are elements in larger issues (there should be)
   }

// End checking there are any issues
}

And finally I have a nopkg to do the actual cleaning of the issues DetectX flagged.

So why even have an array of okay-to-delete things?

Well, DetectX Swift has command-line options to scan, but it (at least as of this writing) does not have the option to command-line remove things, presumably so someone has a chance to review the things removed before actually removing them. Also, since it's just forcefully removing things (yes, I know about using shutil to remove, but I've run into weird situations in which that doesn't work consistently, so I'm using a subprocess to invoke rm instead), it's probably a good idea for at least one human to review things before they get removed.

The nopkg also copies the .json to /var/log (with a datetime stamp in the name) before removing anything.

Use docklib to manage macOS docks

docklib instead of dockutil

I have a few posts about using dockutil to manage the macOS dock. dockutil is still a valid and working project, but I'm starting to migrate my scripts to docklib instead.

Installing docklib

The installation instructions for docklib say you can put the docklib.py file in the same directory as the scripts that invoke it or you can put it "in your Python path." I'd recommend just grabbing the docklib .pkg from the releases page or using the AutoPkg docklib recipes to download it. The .pkg puts docklib.py in /Library/Python/2.7/site-packages/docklib.py.

Using docklib with Outset

docklib can be used in an Outset login-once or login-every script. There is no need to explicitly put in a delay to wait for the initial dock to appear before running your script. There is also no need, if you're specifying a dock (rather than modifying an existing one) to remove the default applications Apple puts on the dock. If you're specifying a dock, just say what you want to add. Use this suggested template:

import os
from docklib import Dock
tech_dock = [
   '/Applications/Google Chrome.app',
   '/Applications/App Store.app',
   '/Applications/Managed Software Center.app',
   '/Applications/System Preferences.app',
   '/Applications/Utilities/Activity Monitor.app',
   '/Applications/Utilities/Console.app',
   '/Applications/Utilities/Disk Utility.app',
   '/Applications/Utilities/Migration Assistant.app',
   '/Applications/Utilities/Terminal.app',
]
dock = Dock()
dock.items['persistent-apps'] = []
for item in tech_dock:
   if os.path.exists(item):
      item = dock.makeDockAppEntry(item)
      dock.items['persistent-apps'].append(item)
dock.save()

Checking if an item exists before removing/adding via docklib?

Here's an example of checking for something's existence on the right side of the dock before adding it. To check on the left side, it's a very similar process, except you just replace

section='persistent-others'
with
section='persistent-apps'

For example, this will add Microsoft Word only if it's not in the dock already:

from docklib import Dock
dock = Dock()
if dock.findExistingLabel('Microsoft Word', section='persistent-apps') == -1:
   item = dock.makeDockAppEntry('/Applications/Microsoft Word.app')
   dock.items['persistent-apps'].append(item)
   dock.save()

If you add an item using docklib that already exists in the dock, a second instance of it will be added to the dock, so you definitely should check for the existence of the item first.

However, if you want to remove an item, just use the standard removal procedure:

from docklib import Dock
dock = Dock()
dock.removeDockEntry('Microsoft Word')
dock.save()
If the item isn't in the dock when you try to remove it, docklib won't give any error or warning.

What I learned upgrading from MunkiReport 2 to MunkiReport 3

The setup for the old (version 2) MunkiReport was fairly simple. You essentially just downloaded the folder to your web server.

The setup for the new (version 3) MunkiReport has a bit more nuance to it. I had a lot of trouble setting it up, but thanks to some help from other Mac admins (special thanks to Rick Heil for getting me over the finish line), I was able to finally get it up and running.

Here are a few issues I ran into. Maybe if you're running into the same or similar issues, this list may help:

  • Ubuntu 16.04 doesn't have PHP 7.0.27 or higher, which the new version of MunkiReport requires, so I had to add it in with the appropriate PPA.
  • On a related note, once you add that PPA to your sources.list in Ubuntu, you get a whole ton of PHP versions available via apt: 7.0, 7.1, 7.2. It's best to make sure you have only version of PHP installed and remove all the rest. I'd recommend 7.2 at this point.
  • It's a good idea to start with the sqlite database just to eliminate MySQL connection issues as a possibility. That said, since the main MunkiReport 3 files don't live in the public-facing part of the web server, be especially mindful of this part of the wiki instructions: when using SQLite as backend (which is the default), check if the directory /app/db/ is writeable by the webserver.
  • If you're using AD or SAML authentication, move it up in the composer.json file from suggest to require and get rid of the description after the version number. For example, "adldap2/adldap2": "^8.0 Required for AD authentication" should change to "adldap2/adldap2": "^8.0" after being moved.
  • If you're running the composer command and get a [RuntimeException] The "--no-suggest" option does not exist. message, just ditch the --no-suggest part of the command.

Backing up database data from a Crypt server

This is really just a step-by-step version of what's available in the July 2015 update doc, which links to Django dumpdata and loaddata.

If you want back up your Crypt server database, this is how you can dump the data out:

docker ps
to find the name of your docker container, in case you forgot it? Let's just assume, for the next command, that the container's name is Crypt.
docker exec -it Crypt bash
This gets you to a bash prompt inside the Crypt docker container.
cd /home/docker/crypt/
Change to the docker crypt directory.
python manage.py dumpdata > db.json
Dump the data out into a .json file.
exit
Exit out of the docker container bash shell.
docker cp Crypt:/home/docker/crypt/db.json .
Copy the .json file to where you are outside the docker container.

If you want to get back into the docker container to delete the original .json (since there is no docker mv, only docker cp), you can do

docker exec -it Crypt bash
rm /home/docker/crypt/db.json
exit

P.S. Thanks to Graham Gilbert for the tip about not needing to chmod the manage.py file.

P.P.S. This data dump method will work regardless of whether you're using the built-in sqlite3 database or an external postgresql database.

Upgrading to High Sierra: “You may not install this volume because the computer is missing a firmware partition”

If you try to upgrade to High Sierra (macOS 10.13) and get You may not install this volume because the computer is missing a firmware partition when trying to select your drive to upgrade, it may be because you're upgrading on an OWC drive.

If you're using Munki, the error may appear in your /Library/Managed Installs/Logs/Install.log as Starting macOS install: FAILED: startosinstall failed with return code 243.

Previously, you'd have to physically swap back the OEM drive, and then put the OWC drive back again, but now OWC has its own firmware updater tool that fixes the problem:
Aura SSDs: Firmware Update (beta).

Parental Controls keeps blocking allowed apps

If macOS Parental Controls keep blocking allowed apps (both allowed through the checklist in System Preferences and through manually approving via password), deleting the account and recreating it may not be enough to fix the glitch. Instead, try recreating an account with a new name.

I've found Santa to block things more reliably (or not to block things you've allowed). You can block by certificate or (for Apple applications you'd need to do this), block by binary.

Handling cfqueryparam errors in ColdFusion queries

<cfqueryparam> is ColdFusion's way of fighting against SQL injection attacks. If, however, you just put <cfqueryparam> in your SQL or MySQL query, people entering the wrong type of input will get a server-side error message, which will make them think your website is messed up instead of realizing they put in the wrong type of input.

If you use <cfcatch>, be sure to specify that the type is database if you want to be able to display your own error message.

Here's an example:

<cftry>
<cfquery name="somequeryname" datasource="somedatasource">
SELECT somefield
FROM somedatabase
WHERE someotherfield = <cfqueryparam
value="#formname.someotherfieldname#"
cfsqltype="CF_SQL_INTEGER"
maxlength="12">
</cfquery>
<cfcatch type="database">
yoursupercoolandinformativeerrormessage
</cfcatch>
</cftry>

Preventing alarms from going off on MDM’ed iPads

If you have alarms set on iPads (either an actual alarm or an alarm from the "bedtime" portion of the Alarm app), you can't disable the alarm by blocking the app. All blocking the app does is prevent the user from launching up the app.

To prevent the alarm itself from going off, you have to block notifications from the Clock app.

Bulk-extracting and combining audio from .mov to .mp3

If you aren't able to use an audio recording app on your iPad for whatever reason and need to resort to using the Camera app to record audio (long story), you will get a bunch of .MOV files.

So I wrote up a script that employs ffmpeg to extract just the audio from each file in a subdirectory, and then combine the separate audio files from that subdirectory into one audio file.

Script here: ExtractCombineMOVtoMP3.py