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 
   thePageLogContentsList
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, 
--YOU HAVE TO MATCH IT WITH theListedUsers TO GET PAGES/USER!
property pagesByPrinterList : {} --a list with one entry per printer page total. 
   THIS ONLY HAS THE NUMBER, 
--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 
               against
            
            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 
            delimiters
         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"
   else
      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
   try
      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
   
   try
      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.

Conclusion

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 <jwelch@provar.com> is a Technical Strategist for Provar, (http://www.provar.com/) and the Chief Know-It-All for TackyShirt, (http://www.tackyshirt.com/). 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.

 
AAPL
$526.81
Apple Inc.
-4.89
MSFT
$39.57
Microsoft Corpora
-0.42
GOOG
$528.95
Google Inc.
-5.86

MacTech Search:
Community Search:

Software Updates via MacUpdate

TeamViewer 9.0.28116 - Establish remote...
TeamViewer gives you remote control of any computer or Mac over the Internet within seconds, or can be used for online meetings. Find out why more than 200 million users trust TeamViewer! Free for... Read more
Viber 4.1.0 - Send messages and make cal...
Viber lets you send free messages and make free calls to other Viber users, on any device and network, in any country! Viber syncs your contacts, messages and call history with your mobile device,... Read more
Apple iOS 7.1.1 - The latest version of...
The latest version of iOS can be downloaded through iTunes. Apple iOS 7 brings an all-new design and all-new features. Simplicity Simplicity is often equated with minimalism. Yet true simplicity is... Read more
1Password 4.3 - Powerful password manage...
1Password is a password manager that uniquely brings you both security and convenience. It is the only program that provides anti-phishing protection and goes beyond password management by adding Web... Read more
Lens Blur 1.3.0 - True out-of-focus boke...
Let Lens Blur transform your existing photo into true SLR-quality out-of-focus bokeh effect! Everyone needs a gorgeous personalized background for a social profile, blog, Web/UI design, presentation... Read more
VMware Fusion 6.0.3 - Run Windows apps a...
VMware Fusion allows you to create a Virtual Machine on your Mac and run Windows (including Windows 8.1) and Windows software on your Mac. Run your favorite Windows applications alongside Mac... Read more
Tweetbot 1.5.1 - Popular iOS twitter cli...
Tweetbot is a full-featured OS X Twitter client with a lot of personality. Whether it's the meticulously-crafted interface, sounds and animation, or features like multiple timelines and column views... Read more
Mac DVDRipper Pro 4.1.7 - Copy, backup,...
Mac DVDRipper Pro is the DVD backup solution that lets you protect your DVDs from scratches, save your batteries by reading your movies from your hard disk, manage your collection with just a few... Read more
PDFpenPro 6.2 - Advanced PDF toolkit for...
PDFpenPro allows users to edit PDF's easily. Add text, images and signatures. Fill out PDF forms. Merge or split PDF documents. Reorder and delete pages. Even correct text and edit graphics! Create... Read more
PDFpen 6.2 - Edit and annotate PDFs with...
PDFpen allows users to easily edit PDF's. Add text, images and signatures. Fill out PDF forms. Merge or split PDF documents. Reorder and delete pages. Even correct text and edit graphics! Features... Read more

Latest Forum Discussions

See All

Gusto Email App Review
Gusto Email App Review By Jennifer Allen on April 23rd, 2014 Our Rating: :: POWERFUL SEARCHINGiPhone App - Designed for the iPhone, compatible with the iPad Focusing on making it easy to browse files and photos attached to your... | Read more »
New Update Adds Two More Cars to Fishlab...
New Update Adds Two More Cars to Fishlabs’ Sports Car Challenge 2 Posted by Rob Rich on April 23rd, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
What’s Up with Readdle? – The Verge Exam...
What’s Up with Readdle? | Read more »
Knight Saves Queen Review
Knight Saves Queen Review By Jennifer Allen on April 23rd, 2014 Our Rating: :: PUZZLING MASH UPUniversal App - Designed for iPhone and iPad Mixing up puzzle elements with Chess-based sensibilities is a fun move for this simplistic... | Read more »
Scrap Squad Review
Scrap Squad Review By Jennifer Allen on April 23rd, 2014 Our Rating: :: SIMPLE TRASH COLLECTIONUniversal App - Designed for iPhone and iPad It turns out that dividing up trash can be quite fun in a chaotic kind of way.   | Read more »
Micronytes Director’s Cut Review
Micronytes Director’s Cut Review By Cata Modorcea on April 23rd, 2014 Our Rating: :: DECENTUniversal App - Designed for iPhone and iPad Micronytes isn’t for everyone, but for people who’ve seen the screenshots and haven’t lost... | Read more »
148Apps is Looking for News and Review W...
Looks like it’s that time again. We’ve finished draining the nutritious goo from our victi-I mean we’re looking for more writers! So if you love you some iPods, iPads, and iPhones, and have access to up-to-date iOS hardware (and have a knack for... | Read more »
Wonder Golf Review
Wonder Golf Review By Lee Hamlet on April 23rd, 2014 Our Rating: :: MAD AS A HATTERUniversal App - Designed for iPhone and iPad Help Alice explore a mini-golf version of Wonderland in the quirky Wonder Golf.   | Read more »
Zeebox is Now Beamly TV – Still Offers t...
Zeebox is Now Beamly TV – Still Offers the Same Social Networking TV Fun, Plus a New Look Posted by Rob Rich on April 22nd, 2014 [ permalink ] | Read more »
Bandai Namco Unveils Upcoming Slate of M...
At their recent Global Gamers’ Day event, Bandai Namco was largely focused on their console and PC offerings for the upcoming year. However mobile still had a small presence, with some upcoming titles revealed by the company – though few were in a... | Read more »

Price Scanner via MacPrices.net

iPad Sales “Lull” A Reality Correction Of Unm...
I have lots of time for Jean-Louis Gassée, the former Apple Computer executive (1981 to 1990) who succeeded Steve Jobs as head of Macintosh development when the latter was dismissed in 1985. Mr.... Read more
Apple Makes OS X Betas Available To All – Wit...
Apple’s OS X Beta Seed Program, which lets you install the latest pre-release builds, try it out, and submit your feedback, is now open to anyone who wants to sign on rather than to developers and... Read more
Apple Releases iOS 7.1.1 Update
The latest iOS 7.1.1 update contains improvements, bug fixes and security updates, including: • Further improvements to Touch ID fingerprint recognition • Fixes a bug that could impact keyboard... Read more
Logitech Announces Thinner, Lighter, More Fle...
Logitech has announced an update to its Ultrathin for iPad Air, iPad mini and iPad mini with Retina display, improving the flexibility and design of its award-winning predecessor with an even thinner... Read more
Logitech Introduces Hinge, Big Bang and Turna...
Logitech has announced expansion of its tablet product line with three new cases – the Logitech Hinge, the Logitech Big Bang and the Logitech Turnaround – each for the iPad Air, iPad mini and iPad... Read more
WaterField’s Rough Rider Leather Messenger Ba...
WaterField Designs have announced the new 15-inch size of their popular Rough Rider leather messenger bag, a vintage-looking bag that combines Old West charm and ruggedness with distinctly modern... Read more
New Mac Pro on sale, save $100 on the 4-Core...
J&R has the new 4-Core Mac Pro in stock today and on sale for $2899 including free shipping plus NY sales tax only. Their price is $100 off MSRP, and it’s the lowest price available for this... Read more
Apple refurbished iMacs available for up to $...
The Apple Store has Apple Certified Refurbished 2013 iMacs available for up to $300 off the cost of new models. Apple’s one-year warranty is standard, and shipping is free. - 27″ 3.4GHz iMac – $1699... Read more
Updated iPad Price Trackers
We’ve updated our iPad Price Tracker and our iPad mini Price Tracker with the latest information on prices and availability from Apple and other resellers. Using a mobile device? We’ve also updated... Read more
Everything You Wanted To Know And Probably Mo...
Macworld UK’s Lou Hattersley takes a look inside Apple’s A7 System On Chip (SoC) , noting that its processor module is much more powerful than other smartphone chipsets. He notes that the A7 was a... Read more

Jobs Board

*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Position Opening at *Apple* - Apple (United...
…customers purchase our products, you're the one who helps them get more out of their new Apple technology. Your day in the Apple Store is filled with a range of Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Inc. Research Data Specialist - Appl...
…of Worldwide Market Research & Intelligence. The team is responsible for conducting Apple branded consumer market research. It is also responsible for analyzing data Read more
*Apple* Automotive Parts Department position...
Apple Automotive is one of the fastest growing dealer…and it shows. Consider making the switch to the Apple Automotive Group today! At Apple Automotive, we Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.