TweetFollow Us on Twitter

Printer Usage Reports with AppleScript and CUPS

Volume Number: 19 (2003)
Issue Number: 11
Column Tag: Programming

Patch Panel

Printer Usage Reports with AppleScript and CUPS

by John C. Welch

Finding out who's printing what in Mac OS X

Print Logs

One of the biggest issues for any network administrator is dealing with print accounting. Now, with Mac OS X server, you have the tools to deal with printer accounting, and Mac OS X Server 10.3 should give you even more tools for managing printers, and printing. But what about folks who are just sharing a printer from Mac OS X? One could argue that in this case, you don't need to manage your printer access at the same level that you do in a server - based network. However, for many small companies, or small networks that share printers, it's good to be able to know who is printing what and how much paper they are using. It's also a neat exercise for AppleScripters, and sometimes, just doing something in a way that most people wouldn't think to, and having it work, is pretty cool in and of itself.

Again, if you are looking for brilliance in AppleScript, talk to people like Sal Soghoian, or Bill Briggs, or Paul Berkowitz. I, like any other programmer or scripter, do things the way they make sense to me. If you can, as has happened in the past, rework my code and get huge efficiency gains, etc. out of it, by all means do so, and send it back to me. I can use the help! I also realize that there are a half dozen shell utilities that I could use to make this a LOT faster, and simpler. But then, I wanted to do this entirely in AppleScript, and so, have to live with its rather anemic text processing abilities. (Yes, yes, Perl folks can feel rightly smug. But I can script Photoshop, so nyah!)

So the object of this script is to process the printer logs that are created by CUPS when you print in Mac OS X, and output a number of things as a tab-delimited text file, giving you the option to then open it in Microsoft Excel or Filemaker Pro, or just leave it where it is. The text file lists the total number of printers, total number of users printing to those printers, total number of pages printed through that machine, total number of pages per printer, and total number of pages per user. Since this script uses CUPS, it obviously requires at least Mac OS X 10.2 or later.

The CUPS log

Since we're talking about the CUPS log, we should take a quick look at it. Every time you print in Mac OS X, the CUPS printing system records the printer name, the user name, the page count for each page in a job, the date/time stamp for each page, the page number, the total number of pages, optional billing information, and the name of the host that sent the job to the host acting as a server to the printer. The printer in this case doesn't have to be a physical printer. If you have Adobe Distiller 6 installed, it also logs jobs sent to the "Adobe PDF" printer, even though it only creates PDF files.

In a Mac OS X system, the log file we are using is the page_log file, stored in /var/log/cups/. While you can't easily modify that file without using sudo, or su, anyone can read the file, so this script can be run by any valid user on an OS X system. A sample of the log file from my machine is shown below.

Printer name                 user   job id   Date/Time stamp          page/copy #'s    acct.   host
HP_LaserJet_8150_Series2    jwelch    16    [08/Oct/2003:10:34:58 -0500]    1 1    -    localhost
HP_LaserJet_8150_Series2    jwelch    16    [08/Oct/2003:10:35:00 -0500]    2 1    -    localhost
HP_LaserJet_8150_Series2    jwelch    16    [08/Oct/2003:10:35:01 -0500]    3 1    -    localhost
HP_LaserJet_8150_Series2    jwelch    16    [08/Oct/2003:10:35:02 -0500]    4 1    -    localhost
HP_LaserJet_8150_Series2    jwelch    16    [08/Oct/2003:10:35:04 -0500]    5 1    -    localhost
HP_LaserJet_8150_Series2    jwelch    16    [08/Oct/2003:10:35:06 -0500]    6 1    -    localhost

This is from a 6 page job, printed to an HP LaserJet 8150, by me. The printer is on an AppleTalk connection to my machine. So going field by field, we see the printer name is "HP_LaserJet_8150_Series2, the user sending the print job is "jwelch", the job id is "16", the Date/Time stamp tells us this job was printed on October 8, 2003, around 10 am, there were 6 pages for one copy, (note that if the printer doesn't have a "mopy" function, or multiple copies per job, this is always 1), the accounting field is a "-" since by default, Mac OS X doesn't include this information, and since this job was sent to my computer by my computer, the host name is "localhost". In the page_log file, the fields are space delimited, but I used tabs here to break the fields up better. As well, there is one entry per page printed. Both of the last two bits of information will be important to us as we process this file in the script.

Script Properties

property thePrinterNameList : {} --a list of printer names, unsorted, one per line in 
   the thePageLogContentsList
property thePrinterTypeList : {} --a list of unique printer names, 1 line per printer name, 
   derived from the getThingTypes handler
property wasThereAMatch : 0 --used in the getThingTypes handler as a match flag
property theUserNameList : {} -- a list of user names, unsorted, one per line in the 
property theListedUsers : {} --a list of unique user names, 1 line per user name, 
   derived from the getThingTypes handler
property totalPagesForMachine : 0 --holds the total pages printed by the machine
property pagesByUserList : {} --a list with one entry per user page total. THIS ONLY HAS THE NUMBER, 
property pagesByPrinterList : {} --a list with one entry per printer page total. 
--YOU HAVE TO MATCH IT WITH thePrinterTypeList TO GET PAGES/printer!
property theTotalCountedThings : 0 --used as a counter in the getTheList handler
property theTotalCountedThingsList : {} -- generic handler processed list variable for 
   passing lists back and forth
property theListedThing : {} --generic list variable for passing data to handlers
property theCurrentThing : "" --generic text variable for various handlers, used mostly for 
   matching ops
property theCurrentThingPages : "" --used as a variable in getDataByPrinterOrUser for holding 
   page counts by user or printer
property theCurrentLine : "" --when processing thePageLogContentsList, holds one line of that 
   list as a string
property theCurrentLineContentsList : {} --when processing thePageLogContentsList, holds one 
   line of that list as a list
property usernameOrPrinterName : 0 --handler variable, tells if you are dealing with a printer 
   name or a user name, is a 
--list placeholder, 2 = user, 1 = printer
property userOrPrinter : "" --text flag used in getDataByPrinterOrUser for determining user or 
   printer in report line
property theLastThing : {} -- generic handler unprocessed list variable for passing lists back 
   and forth
property theListedThingType : "" -- used as an inner repeat loop matching variable holder in 
   the getThingTypes handler
property theListedThingPages : {} -- generic handler list variable for processing 
   pagesByPrinterList and the pagesByUserList
property thePrinterReportFilePath : "" -- alias to where the tab delimited file will be, 
   set by user
property thePageLogAlias : "" --alias to the cups page log file
property thePageLogPath : "" --POSIX path to the cups page log file
property thePageLogFile : 0 --file handle when reading the cups page log file
property thePageLogContents : "" --holds the contents of the cups page log file
property thePageLogContentsList : {} --holds the list version of thePageLogContents
property theWrite : "" --placeholder used to generate the report file

The comments following each - are the ones I used while developing the script. I have learned over the years that a certain reflexive obsession with regard to commenting comes in handy when revisiting code for things like updates, or writing articles on it. Because AppleScript's native text handling capabilities are anemic at best, we use a lot of lists to compensate. While there are a number of AppleScript extensions, or OSAX that make up for this, I try to avoid them where possible, so my scripts will run on the largest number of machines possible without requiring the user to download and install extensions. If the property names and comments don't make sense at the moment, don't worry. As we go through the rest of the code, you'll see what they're all used for.

The Main Script Body

The first thing we see is a call to System Events, a part of Mac OS X used for certain system functions that were handled by the Finder in Mac OS X. While we could use the Finder for this, System Events is the more correct way to handle our needs here. In this case, we ask System events for the name of every disk labeled a startup disk, and get the name of the first one. Since there should only be one of these, assuming the first one is the one we want poses little risk of error. Since a list is returned, we have to grab the first item from the list, even if it is the only item:

tell application "System Events" --get the name of the startup disk code
   set theStartupDiskList to (the name of every disk whose startup is true) --gets a list 
      of all startup disks
   set theStartupDiskName to item 1 of theStartupDiskList --on any running Mac, there is 
      only one startup disk, so it's always item 1
end tell

The next set of lines get an alias to the page_log file, and the POSIX, or Unix - style path version of that alias. The first line also concatenates the name of the startup disk that we got earlier so that we have a full alias to the page_log file. The conversion to the POSIX path handles renaming the disk name in the alias to a more proper "/":

set thePageLogAlias to (theStartupDiskName & ":private:var:log:cups:page_log") 
   as alias --get the path to the page log file. Hardcoded unless it needs to move about
set thePageLogPath to POSIX path of thePageLogAlias --posix path of thePageLogAlias, just in case

The next lines open the page_log file without write permission. It would be hard to open it with write permission unless root was running the script, and this way, we avoid accidents. We then dump the contents of the file to the variable "thePageLogContents". We then close the file access handler so that we don't have open file handlers laying about:

set thePageLogFile to open for access thePageLogAlias without 
   write permission --open the page log file read-only, it's all we need, and it's safer
set thePageLogContents to read thePageLogFile --dump the contents of the file into a variable
close access thePageLogFile --close the file handler, leaving those open is bad

Now, so that we can more easily use the contents we've just read, we turn it into a list, with each line in the log being one item in the list. To do this, we use the end of line characters as text item delimiters, by telling AppleScript to use the "\n" character as its new text item delimiter, saving the original value to the variable "oldDelims" so we can restore it later. Then we make a list from every text item in thePageLogContents, one item per line. Once that's done, we clean up after ourselves by resetting AppleScript's text item delimiters to their original value:

set oldDelims to AppleScript's text item delimiters --temp store Applescript 
   current delimiters
set AppleScript's text item delimiters to "" --use \n as the current delimiter, since it's a 
   Unix text file
set thePageLogContentsList to (every text item of thePageLogContents) --turn the page log 
   into a list
set AppleScript's text item delimiters to oldDelims --reset the delimiters

We then take advantage of CUPS creating one line per page printed, and use the length of the list to get us that information, and store it in totalPagesForMachine. Since the last line of the log is blank, we subtract one from the length to get the correct number of pages:

set totalPagesForMachine to ((length of thePageLogContentsList) - 1) 
   --the last line is always blank

The next bit of code takes advantage of the consistency of the page_log format. Since it always has the same number of fields and each field always has the same contents, we can make some assumptions that let us get the printer name and user name. In this case, the first field is always the name of the printer, and the second field is always the user name. So, what we do is make two lists, one of user names and one of printer names by manipulating the AppleScript text item delimiters for each entry in thePageLogContentsList, which is, as we should still recall, one line from the original page_log file. By using the space character for the delimiter, we temporarily turn each entry in thePageLogContentsList to a list of its own, then grab item one and shove it into a list of printer names, then shove item two into a list of user names. Yes, we'll have duplicates, but we'll handle those in a bit. Once we've done this for the entire thePageLogContentsList, we're pretty much done with the main body of code for the script for the moment, as the next few sections are mostly calls to handlers:

repeat with x from 1 to (length of thePageLogContentsList) --this is to get us 
   two lists...user names and printers
   set theCurrentLine to item x of thePageLogContentsList --grab a line out of the list
   set oldDelims to AppleScript's text item delimiters --temp store Applescript current delimiters
   set AppleScript's text item delimiters to " " --use space as the current delimiter
   set theCurrentLineContentsList to (every text item of theCurrentLine) --turn the page log  
      line into a list
   if item 1 of theCurrentLineContentsList is not "" then --check for that blank line
      set the end of thePrinterNameList to item 1 of theCurrentLineContentsList --item 1 is 
         always the printer
      set the end of theUserNameList to item 2 of theCurrentLineContentsList --item 2 is always 
         the the username
   end if
   set AppleScript's text item delimiters to oldDelims --reset the delimiters
end repeat

So, now we have a list of users and a list of printers, but at the moment, they aren't that useful. Since each job has one entry per page, we are going to have a lot of repeats, so we need to make sure we only have one of each user or printer. First, lets take care of the printers. While we could use unique handlers for each of these, we don't have to. The code to handle the printers and the names is almost identical, so with a little work, we can use one handler to process both. One thing I did here, which you may or may not agree with procedurally, is to use generic names for the handlers. If nothing else, it makes it harder for me to forget that I'm out of the main body of code, and in a handler.

Calling our handler, and getting a return back takes three lines. The first sets our handler variable to the contents of thePrinterNameList. The second sets the variable thePrinterTypeList to the results of getThingTyeps(theListedThing). Once the handler is finished, then we clear theTotalCountedThingsList, (used in the handler):

set theListedThing to thePrinterNameList --I prefer using generic vars for 
   multipurpose handlers, it's easer for me
set thePrinterTypeList to getThingTypes(theListedThing) of me --get a list of unique printers
set theTotalCountedThingsList to {} --clear this list

The getThingTypes(theListedThing) Handler

Now, let's take a look at the getThingTypes(theListedThing) handler. The handler uses a repeat loop to iterate through the list, theListedThing. We check to see if theLastThing is blank, which indicates that this is the first time through the list. If it is, we set theLastThing to the first item in theListedThing. This is important, since this handler's purpose is to create a list of unique user or printer names. We also set the current item of theListedThing to the end of theTotalCountedThingsList, since this is the first time through the list:

repeat with y from 1 to (length of theListedThing) --run the list of names
      if theLastThing is "" then --if this is the first iteration   
         set theLastThing to item y of theListedThing --set theLastThing as a comparator
         set the end of theTotalCountedThingsList to item y of theListedThing -- first name 
            in the list is the first name on the new list
      end if

Next, we set theCurrentThing to the current item of theListedThing. This is going to be the prime variable used to check for duplicates:

set theCurrentThing to item y of theListedThing --test against the current item

We now compare theCurrentThing to theLastThing. If they aren't equal, good. But since we have to check for more than one duplicate, we can't stop there. This is where theTotalCountedThingsList comes into play. We run another repeat loop through that list, assigning theListedThing type to the current item of the list as we run through this loop, and compare it to theCurrentThing. If there is a match, we set wasThereAMatch to 1, and exit the loop. If we make it through theTotalCountedThingsList without a match, we set wasThereAMatch to 0. Once we are out of the loop, if wasThereAMatch is 0, then there was no match, and theCurrentThing is a unique name, so we tack it onto the end of theTotalCountedThingsList. We then set wasThereAMatch to 0, to clear the loop. This is done for every entry in theListed thing. Once we have checked every entry in that list, we clear wasThereAMatch and theLastThing. We pass theTotalCountedThingsList, which is now a list of unique printers or usernames back to the calling code line, and that's the end of the handler.

if theCurrentThing <> theLastThing then --no, not the same thing
         repeat with z from 1 to (length of the theTotalCountedThingsList) --run the list of 
            printer types we already have
            set theListedThingType to item z of theTotalCountedThingsList --grab a name to test 
            if theCurrentThing = theListedThingType then --is this printer already in here?
               set wasThereAMatch to 1 --yes
            end if
         end repeat
         if wasThereAMatch = 0 then --new printer type
            set the end of theTotalCountedThingsList to theCurrentThing --stick this in the list 
               of printer types
         end if
         set wasThereAMatch to 0 --clear the var in the loop
      end if
   end repeat
   set wasThereAMatch to 0 --clear the var
   set theLastThing to "" --clear the var
   return theTotalCountedThingsList --here's a list of unique names

Our next three lines back in the main body of code call getThingTypes(theListedThing) to get a list of unique user names in theListedUsers, and works the same as what we just saw for getting printer names. We, therefore, have the same handler used to get two different lists of data, without duplicating the code. Code reuse is real, and it rocks.

So, we now have a list of users and a list of printers, and the total pages printed. Not bad, but we need to know how many pages each user printed, and how many pages each printer printed. Again, we're going to use the same handler to get this information, but we also need to track what we're trying to get, printer or user info, so we use the variable usernameOrPrinterName to do this. Remember that in the page_log file, the printer name is always the first field for each entry and the user name is always the second field. If we are looking for user data, we set usernameOrPrinterName to 2, and if we want printer data, we set usernameOrPrinterName to 1.

We again use theListedThing as the list variable we pass, and since we are looking for user data, we set it to the contents of theListedUsers. We set usernameOrPrinterName to 2, indicating we want user data, and then pass both of them, along with thePageLogContentsList to getTheList, and return the results to pagesByUserList. Once we are done with the handler, we again clear theTotalCountedThingsList:

set theListedThing to theListedUsers
set usernameOrPrinterName to 2 --we're going to get user pages
set pagesByUserList to getTheList(theListedThing, thePageLogContentsList, usernameOrPrinterName) 
   of me
set theTotalCountedThingsList to {}

The getTheList(theListedThing,thePageLogContentsList, usernameOrPrinterName) Handler

Now, lets look at the getTheList(theListedThing,thePageLogContentsList, usernameOrPrinterName) handler. We use a repeat loop to iterate through theListedThing, which, in this case is the list of unique user names. We set theCurrentThing to the current item in that list. We then set another repeat loop for thePageLogContentsList. We set theCurrentLine to the current item of that list. Next, we call on our old friend, AppleScript's text item delimiters to turn theCurrentLine's contents into a list, theCurrentLineContentsList. Since CUPS loves to insert blanks, we make sure that item 1 isnt' blank. Now, the next part is a little tricky to follow. We have set theCurrentThing to the current item of theListedThing, which is for now, a list of user names. We had also set usernameOrPrinterName to 2, indicating we are looking at pages per user. So, we need to only count the pages for a particular user. So, we check to see if item usernameOrPrinterName, or 2, of theCurrentLineContentsList matches theCurrentThing. If it does, then we want to count this line as a page printed by that user, (page_log uses one line per page printed), and increment our page count, theTotalCountedThings. Once we have gone through thePageLogContentsList, we reset AppleScript's text item delimiters to their default. Next we set the end of theTotalCountedThingsList to theTotalCountedThings, and set theTotalCountedThings back to zero, so we can get the page count for the next user name in theListedThing list. Once we have completely run through that list, we then return theTotalCountedThingsList to the calling line, and exit the handler:

on getTheList(theListedThing, thePageLogContentsList, usernameOrPrinterName)
   repeat with x from 1 to (length of theListedThing) --let's get the total pages by user first
      set theCurrentThing to item x of theListedThing --set the current user we are looking at
      repeat with y from 1 to (length of thePageLogContentsList) --now we want to get total 
         user pages by user
         set theCurrentLine to item y of thePageLogContentsList --grab a line
         set oldDelims to AppleScript's text item delimiters --temp store Applescript current 
         set AppleScript's text item delimiters to " " --use space as the current delimiter
         set theCurrentLineContentsList to (every text item of theCurrentLine) --make the line a list
         if item 1 of theCurrentLineContentsList is not "" then --there's always a blank line to 
            blow things up
            if item usernameOrPrinterName of theCurrentLineContentsList = theCurrentThing then 
               --if the user we're looking at printed this
               set theTotalCountedThings to theTotalCountedThings + 1 --increment the page count 
                  for the user
            end if
         end if
         set AppleScript's text item delimiters to oldDelims --reset the delimiters
      end repeat
      set the end of theTotalCountedThingsList to theTotalCountedThings --build the list
      set theTotalCountedThings to 0 --clear the var
   end repeat
   return theTotalCountedThingsList
end getTheList

Back in the main body of the code, we call this handler again, only to get the printer pages. The code works exactly the same, as in fact, it's the same handler. The only difference is which field in the page_log file we're dealing with.

Now that we've collected a bunch of data, we need to create the ouput file for it. First, we ask the person running the script where they want to save the file, and what they want to call it. This is done with choose file name, which gives us an alias to a file that doesn't exist yet.

set thePrinterReportFilePath to choose file name with prompt -
   "Pick the location for the printer report file" default name "Printer Report 
      File.txt" --user interaction, pick where you want the output file to go, and 
      give it a name

Then we set the variable theWrite to a nice long text string with the data we already have, such as total number of printers, total number of users, and total pages printed from this machine. Each line is tab-delimited with the "\t" character, which doesn't show up on screen once the script is saved, and each line is created with the "\r" character, which is invisible once the script is saved, and interpreted as a return:

set theWrite to ("Total number of Printers   " & (length of thePrinterTypeList) & "
Total number of Users   " & (length of theListedUsers) & "
Total number of pages for all printers   " & totalPagesForMachine) as text --get the non-user, 
   non-specific things, dump it into a text string var

But we still want a few more things, like pages per printer, etc. in the report. So, we set theListedThing to thePrinterTypeList, and theListedThingPages to pagesByPrinterList. We use the usernameOrPrinterName variable again, setting it to 0, to show the next handler that we are getting pages per printer. We then set theReport to the result of the handler getDataByPrinterOrUser(theListedThing, theListedThingPages, theWrite, usernameOrPrinterName):

set theListedThing to thePrinterTypeList
set theListedThingPages to pagesByPrinterList
set usernameOrPrinterName to 0 --we're going to get how many pages each printer 
   printed, and dump it into a text string
set theReport to getDataByPrinterOrUser(theListedThing, theListedThingPages, theWrite, 
   usernameOrPrinterName) of me --get pages by printer, and glom it on the end of theReport

The getDataByPrinterOrUser(theListedThing, theListedThingPages, theWrite, usernameOrPrinterName) Handler

The handler we use here is fairly simple. First, we check to see if we are doing printers or reports:

if usernameOrPrinterName = 0 then --which one are we looking at
      set userOrPrinter to "Printer"
      set userOrPrinter to "User"
   end if

Next, we run a repeat loop to iterate through theListedThing. We set theCurrentThing to the current user or printer name in theListedThing. We then set theCurrentThingPages to the same item in a different list, theListedThingPages. (The only reason this works is that thanks to the way CUPS sets up its page_log file, and the way we have gotten the data from that file, the lists synchronize nicely. I know this is a rather fragile way of doing things, but for now it works, and saved me a lot of work when I wrote this script. Obviously, anytime CUPS or Mac OS X get updated, the script should be tested to see when this finally breaks.) Then we concatenate a new line onto the end of theWrite, which gives us a tab-delimited line that shows "Printer" <tab> theCurrentThing <tab>"total pages"<tab> theCurrentThingPages. By placing the return character in front of the word "Printer" we ensure that each time we run through this list, we are creating a new line. Once we've run through theListedThing, the repeat loop exits. We clear out theListedThing and theListedThingPages, return theWrite, and that's the end of the handler:

repeat with x from 1 to (length of theListedThing) --run through the list of 
   printers or users
      set theCurrentThing to item x of theListedThing --grab the first printer or username
      set theCurrentThingPages to item x of theListedThingPages --grab the first page count by 
         user or printer
      (*This is to work around some annoyances with records that make it easier to use two lists. 
         As it turns out, positionally, everything lines up. I imagine this will bite me one day)
      set theWrite to theWrite & "
" & userOrPrinter & " " & theCurrentThing & " total pages   " & theCurrentThingPages --append pages 
   for a printer or a user in 
      --tab - delimited format 
   end repeat
   set theListedThing to {} --clear the var
   set theListedThingPages to {} --clear the var
   return theWrite --return the new text data
end getDataByPrinterOrUser

Now, we reuse the handler to get pages per user into our report in the correct format:

set theListedThing to theListedUsers
set theListedThingPages to pagesByUserList
set theWrite to theReport --theWrite doesn't update completely when you jack it between 
   handlers, so this fixes that
set usernameOrPrinterName to 1 --we're going to get how many pages each user printed, and 
   dump it into a text string
set theReport to getDataByPrinterOrUser(theListedThing, theListedThingPages, theWrite, 
   usernameOrPrinterName) of me --get pages by user, and glom it on the end of theReport

Okay, we've got our report. Let's write it out to a text file! First, we set thePrinterReportFile to the file handle returned by the open for access function. Since we are writing to the file, we obviously want to open the file, (using thePrinterReportFilePath we got from the user earlier) with write access. We then write the data in theReport to thePrinterReportFile, and then close the file handle. Almost done!

We could just end the script there, but face it, what do you do with a tab-delimited text file? Well, you mostly use it in other applications, such as FileMaker Pro, or Microsoft Excel. So let's save the user some time, and deal with this now.

First we set up a nice dialog box that tells the user the report has been created and where. We also allow them the option of inserting this report into FileMaker Pro or Microsoft Excel. We also, of course, allow them to do nothing, end the script and get on with other things. We set theProcessingRecord to the record returned from display dialog:

set theProcessingRecord to display dialog "The Printer Report has been created at:
" & (thePrinterReportFilePath as text) & "
Please click the button that corresponds to the application
you would like this file opened in" buttons {"Microsoft Excel", "Filemaker Pro", "None"} 
   default button "None" with icon note --nice option if you want to process this file right away

Inserting into Microsoft Excel or FileMaker Pro

When you are dealing with scripting any of the Microsoft Office applications, other than Entourage, you have to deal with a rather odd dictionary setup, primarily because things like Excel can be scripted in either AppleScript or Visual Basic for Applications (VBA). To make things easier, Microsoft had to make some unique decisions in the dictionaries for Excel and Word, so they do look a little odd. Excel's dictionary is quite useful, you just have to wrap your head around it.

The biggest oddity that affects the script is that Excel really can't handle aliases, so we have to convert the alias to a text string, then tell Excel to open it:

if button returned of theProcessingRecord = "Microsoft Excel" then --dump it into Excel
      tell application "Microsoft Excel"
         open (thePrinterReportFilePath as text) --Open can't handle aliases, so we have to 
            use a text version
      end tell
   end try

FileMaker Pro is a bit simpler, since it can handle aliases:

else if button returned of theProcessingRecord = "Filemaker Pro" then --dump the 
   file into FMPro
      tell application "FileMaker Pro"
         open thePrinterReportFilePath
      end tell
   end try
end if

If they don't pick either Excel or FileMaker Pro, then we leave the file alone, and the script is done.


Well, that's a lot of list work, but you get a neat, and possibly useful result. Printers can be a huge expense if their use isn't monitored closely. Thanks to CUPS, Mac OS X and AppleScript give you a way to do this. Now, there are still some things we could do with the script, such as trying to get pages per printer per user, etc., but this is a good start. As well, some judicious shell commands could make this script much shorter, and probably faster. iIt's not that long now, though, and my tests didn't show that it took all that long to run on an 800MHz PowerBook G4, so it's probably okay as it is. If nothing else, consider this an example of how AppleScript is still quite useful to the network administrator in Mac OS X.

John Welch <> is a Technical Strategist for Provar, ( and the Chief Know-It-All for TackyShirt, ( He has over fifteen years of experience at making Macs, and other computers work. John specializes in figuring out ways to make the Mac do what nobody thinks it can, showing that the Mac is a superior administrative platform, and teaching others how to use it in interesting, if sometimes frightening ways. He also does things that don't involve computertry on occasion, or at least that's the rumor.


Community Search:
MacTech Search:

Software Updates via MacUpdate

Path Finder 7.5 - Powerful, award-winnin...
Path Finder makes you a master of file management. Take full control over your file system. Save your time: compare and synchronize folders, view hidden files, use Dual Pane and full keyboard... Read more
Merlin Project 4.2.3 - $349.00
Merlin Project is the leading professional project management software for OS X. If you plan complex projects on your Mac, you won’t get far with a simple list of tasks. Good planning raises... Read more
TextSoap 8.4 - Automate tedious text doc...
TextSoap can automatically remove unwanted characters, fix up messed up carriage returns, and do pretty much anything else that we can think of to text. Save time and effort. Be more productive. Stop... Read more
Smultron 9.4 - Easy-to-use, powerful tex...
Smultron 9 is an elegant and powerful text editor that is easy to use. Use it to create or edit any text document. Everything from a web page, a note or a script to any single piece of text or code.... Read more
QuarkXPress - Desktop publishin...
QuarkXPress 2017 is the new version that raises the bar for design and productivity. With non-destructive graphics and image editing directly within your layout, you no longer have to choose between... Read more
Brackets 1.9.0 - Open Source Web design...
Brackets is an Open-Source editor for Web design and development built on top of Web technologies such as HTML, CSS, and JavaScript. The project was created and is maintained by Adobe, and is... Read more
Audio Hijack 3.3.4 - Record and enhance...
Audio Hijack (was Audio Hijack Pro) drastically changes the way you use audio on your computer, giving you the freedom to listen to audio when you want and how you want. Record and enhance any audio... Read more
Tunnelblick 3.7.1a - GUI for OpenVPN.
Tunnelblick is a free, open source graphic user interface for OpenVPN on OS X. It provides easy control of OpenVPN client and/or server connections. It comes as a ready-to-use application with all... Read more
Amazon Chime 4.3.5721 - Amazon-based com...
Amazon Chime is a communications service that transforms online meetings with a secure, easy-to-use application that you can trust. Amazon Chime works seamlessly across your devices so that you can... Read more
BBEdit 11.6.6 - Powerful text and HTML e...
BBEdit is the leading professional HTML and text editor for the Mac. Specifically crafted in response to the needs of Web authors and software developers, this award-winning product provides a... Read more

Latest Forum Discussions

See All

Magikarp Jump splashes onto Android worl...
If you're tired ofPokémon GObut still want something to satisfy your mobilePokémon fix,Magikarp Jumpmay just do the trick. It's out now on Android devices the world over. While it looks like a simple arcade jumper, there's quite a bit more to it... | Read more »
Purrfectly charming open-world RPG Cat Q...
Cat Quest, an expansive open-world RPG from former Koei-Tecmo developers, got a new gameplay trailer today. The video showcases the combat and exploration features of this feline-themed RPG. Cat puns abound as you travel across a large map in a... | Read more »
Jaipur: A Card Game of Duels (Games)
Jaipur: A Card Game of Duels 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: ** WARNING: iPad 2, iPad Mini 1 & iPhone 4S are NOT compatible. ** *** Special Launch Price for a limited... | Read more »
Subdivision Infinity (Games)
Subdivision Infinity 1.03 Device: iOS Universal Category: Games Price: $2.99, Version: 1.03 (iTunes) Description: Launch sale! 40% Off! Subdivision Infinity is an immersive and pulse pounding sci-fi 3D space shooter. https://www.... | Read more »
Clash of Clans' gets a huge new upd...
Clash of Clans just got a massive new update, and that's not hyperbole. The update easily tacks on a whole new game's worth of content to the hit base building game. In the update, that mysterious boat on the edge of the map has been repaired and... | Read more »
Thimbleweed Park officially headed to iO...
Welp, it's official. Thimbleweed Park will be getting a mobile version. After lots of wondering and speculation, the developers confirmed it today. Thimbleweed Park will be available on both iOS and Android sometime in the near future. There's no... | Read more »
Pokémon GO might be getting legendaries...
The long-awaited legendary Pokémon may soon be coming to Pokémon GO at long last. Data miners have already discovered that the legendary birds, Articuno, Moltres, and Zapdos are already in the game, it’s just a matter of time. [Read more] | Read more »
The best deals on the App Store this wee...
If you’ve got the Monday blues we have just the thing to cheer you up. The week is shaping up to be a spectacular one for sales. We’ve got a bunch of well-loved indie games at discounted prices this week along with a few that are a little more... | Read more »
Honor 8 Pro, a great choice for gamers
Honor is making strides to bring its brand to the forefront of mobile gaming with its latest phone, the Honor 8 Pro. The Pro sets itself apart from its predecessor, the Honor 8, with a host of premium updates that boost the device’s graphical and... | Read more »
The 4 best outdoor adventure apps
Now that we're well into the pleasant, warmer months, it's time to start making the most of the great outdoors. Spring and summer are ideal times for a bit of trekking or exploration. You don't have to go it alone, though. There are plenty of... | Read more »

Price Scanner via

Sale! 15-inch 2.6GHz Silver Touch Bar MacBook...
DataVision has the 15″ 2.6GHz Silver Touch Bar MacBook Pro (MLW72LL/A) on sale for $2199 including free shipping. Their price is $200 off MSRP, and it’s the lowest price available for this model (... Read more
A Kaby Lake Processor Upgrade For The MacBook...
Now they tell me! Well, actually Apple hasn’t said anything official on the subject, but last week Bloomberg News’s Mark Gurman and Alex Webb cited unnamed “people familiar with the matter”... Read more
Kodak’s Camera-First Smartphone EKTRA Launche...
The Eastman Kodak Company and Bullitt Group have announced the availability of a U.S. GSM version of the KODAK EKTRA Smartphone. The U.S. launch coincides with a software update addressing requests... Read more
Apple Launches App Development Curriculum for...
Apple today launched a new app development curriculum designed for students who want to pursue careers in the fast-growing app economy. The curriculum is available as a free download today from Apple... Read more
Check Apple prices on any device with the iTr...
MacPrices is proud to offer readers a free iOS app (iPhones, iPads, & iPod touch) and Android app (Google Play and Amazon App Store) called iTracx, which allows you to glance at today’s lowest... Read more
9.7-inch 2017 iPad available for $298, save $...
Sams Club has 32GB 9.7″ Apple iPads available for $298 for Sams Club members. That’s $21 off MSRP. Order online and choose free local store pickup (if available) or free shipping. Read more
touchbyte Releases PhotoSync 3.2 for iOS With...
Hamburg, Germany based touchbyte has announced the release of PhotoSync 3.2 for iOS, a major upgrade to the versatile and powerful app to transfer, backup and share photos and videos over the air.... Read more
Emerson Adds Touchscreen Display and Apple Ho...
Emerson has announced the next evolution of its nationally recognized smart thermostat. The new Sensi Touch Wi-Fi Thermostat combines proven smarthome technology with a color touchscreen display and... Read more
SurfPro VPN for Mac Protects Data While Offer...
XwaveSoft has announced announce the release and immediate availability of SurfPro VPN 1.0, their secure VPN client for macOS. SurfPro VPN allows Mac users to protect their internet traffic from... Read more
13-inch Touch Bar MacBook Pros on sale for $1...
B&H Photo has 13″ MacBook Pros in stock today for up to $150 off MSRP. Shipping is free, and B&H charges NY & NJ sales tax only: - 13″ 2.9GHz/512GB Touch Bar MacBook Pro Space Gray (... Read more

Jobs Board

*Apple* Store Leader Program - Apple, Inc (U...
…Summary Learn and grow as you explore the art of leadership at the Apple Store. You'll master our retail business inside and out through training, hands-on Read more
*Apple* Integration Specialist - A3 Solution...
Apple Integration Specialist Contract-To-HireWe are searching for dedicated, well-experienced and energetic individuals for an information technology corporation Read more
Sr. Software Engineer, *Apple* Online Store...
Changing the world is all in a day's work at Apple . If you love innovation, here's your chance to make a career of it. You'll work hard. But the job comes with more Read more
Senior Engineering Project Manager, *Apple*...
Changing the world is all in a day's work at Apple . If you love innovation, here's your chance to make a career of it. You'll work hard. But the job comes with more Read more
*Apple* Mobile Master - Best Buy (United Sta...
**508456BR** **Job Title:** Apple Mobile Master **Location Number:** 000040-Eau Claire-Store **Job Description:** **What does a Best Buy Apple Mobile Master Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.