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.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Macs Fan Control 1.5.14 - Monitor and co...
Macs Fan Control allows you to monitor and control almost any aspect of your computer's fans, with support for controlling fan speed, temperature sensors pane, menu-bar icon, and autostart with... Read more
VueScan 9.7.96 - Scanner software with a...
VueScan is a scanning program that works with most high-quality flatbed and film scanners to produce scans that have excellent color fidelity and color balance. VueScan is easy to use, and has... Read more
FileMaker Pro 19.6.1 - Quickly build cus...
FileMaker Pro is the tool you use to create a custom app. You also use FileMaker Pro to access your app on a computer. Start by importing data from a spreadsheet or using a built-in Starter app to... Read more
Duet 3.1.0.0 - Use your iPad as an exter...
Duet is the first app that allows you to use your iDevice as an extra display for your Mac using the Lightning or 30-pin cable. Note: This app requires a iOS companion app. Release notes were... Read more
Firefox 107.0.1 - Fast, safe Web browser...
Firefox offers a fast, safe Web browsing experience. Browse quickly, securely, and effortlessly. With its industry-leading features, Firefox is the choice of Web development professionals and casual... Read more
War Thunder 2.21.1.91 - Multiplayer war...
In War Thunder, aircraft, attack helicopters, ground forces and naval ships collaborate in realistic competitive battles. You can choose from over 1,500 vehicles and an extensive variety of combat... Read more
Numbers 12.2.1 - Apple's spreadshee...
With Apple Numbers, sophisticated spreadsheets are just the start. The whole sheet is your canvas. Just add dramatic interactive charts, tables, and images that paint a revealing picture of your data... Read more
DEVONthink Pro 3.8.7 - Knowledge base, i...
DEVONthink is DEVONtechnologies' document and information management solution. It supports a large variety of file formats and stores them in a database enhanced by artificial intelligence (AI). Many... Read more
Drive Genius 6.2.3 - $79.00
Drive Genius features a comprehensive Malware Scan. Automate your malware protection. Protect your investment from any threat. The Malware Scan is part of the automated DrivePulse utility. DrivePulse... Read more
VLC Media Player 3.0.18 - Popular multim...
VLC Media Player is a highly portable multimedia player for various audio and video formats (MPEG-1, MPEG-2, MPEG-4, DivX, MP3, OGG, ...) as well as DVDs, VCDs, and various streaming protocols. It... Read more

Latest Forum Discussions

See All

‘Genshin Impact’ Version 3.3 Pre-Install...
Following the reveal of the release date and more for Genshin Impact (Free) version 3.3 ‘All Senses Clear, All Existence Void’, HoYoverse showcased the Genius Invokation TCG that arrives this week in the update. | Read more »
TouchArcade Game of the Week: ‘Sling Min...
The world of PC games has always blown my mind because there’s just SO MUCH stuff out there that it’s not uncommon at all for there to be a game that’s well-liked and well-reviewed, and seemingly quite popular with a solid fanbase, and have it be... | Read more »
SwitchArcade Round-Up: Reviews Featuring...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for December 2nd, 2022. So, today turned out a little quieter than the usual Friday. It was so quiet, in fact, that I decided to pen a few reviews. The Knight Witch, Railbound, and Donut... | Read more »
Blue Archive reveals its latest event st...
Nexon has announced the new update for Blue Archive, under the name of An Unconcealed Heart. Featuring a battle between two academies, the story will follow a group struggling to gain recognition, and will bring three new students to recruit. [... | Read more »
Dead Cells+ Is Out Now on Apple Arcade a...
Following the major update for Dead Cells on iOS and Android a few days ago, Playdigious has brought Dead Cells+ () to Apple Arcade. As an App Store Great, Dead Cells+ includes all prior paid DLC and content updates. It also has exclusive mobile... | Read more »
SwitchArcade Round-Up: ‘Romancing SaGa’,...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for December 1st, 2022. Wow, December. We’re already at the last month of the year? Phew. I have a lot of work to finish in the next few weeks. As for today, we’ve got a little news, a... | Read more »
‘Railbound’ Update Now Available Adding...
One of our favorite puzzlers released this year is Railbound from Afterburn Games, which hit in early September and earned our Game of the Week recognition for being an absolutely ace logic puzzler. The goal is to place rail pieces down in order to... | Read more »
The Seven Deadly Sins: Grand Cross celeb...
Netmarble Corporation has pulled out all the stops to celebrate the 3 and a half year anniversary of The Seven Deadly Sins: Grand Cross. The Grand Cross 3.5th Year Anniversary the Ultimate One, a rather wordy title, brings with it a brand new... | Read more »
‘Skullgirls Mobile’ Major Update 5.2 Out...
Developer Hidden Variable pushed out a major update for Skullgirls Mobile (Free) a few hours ago. The version 5.2 update brings in Black Dahlia (before the console and PC game), Retakes, XP Treats, free gifts, and more. Since launch, Skullgirls... | Read more »
Out Now: ‘Disgaea 4’, ‘Romancing SaGa: M...
Each and every day new mobile games are hitting the App Store, and so each week we put together a big old list of all the best new releases of the past seven days. Back in the day the App Store would showcase the same games for a week, and then... | Read more »

Price Scanner via MacPrices.net

Holiday Sale: Apple AirPods Pro for only $199...
Amazon has new 2022 AirPods Pro in stock and on sale for $199.99 shipped as part of their Holiday sale. Their price is $50 off Apple’s MSRP, equaling their Black Friday price, and it’s the lowest... Read more
New Holiday Sale: Apple retailers are offerin...
Several Apple retailers lowered prices on 10.9″ iPad Airs overnight to lows of $100 off MSRP starting at $499. Their prices are the lowest available for iPad Airs anywhere this Holiday season right... Read more
New Holiday sale at Amazon: Take $50 off Appl...
Amazon has Apple’s new 10th-generation iPads in stock and on sale, for the first time, for $50 off MSRP starting at only $399. Their discount applies to all models and all colors. With the discount,... Read more
Holiday Sale: Get an 8.3″ Apple iPad mini for...
Sams Club has 10.9″ 64GB iPad minis on Holiday sale for $80-$100 off MSRP through December 7, 2022. With their discount, prices start at $399 — the cheapest price for a new iPad mini from any of the... Read more
Sams Club Holiday December Event sale: Apple...
Apple AirPods Max headphones are on sale at Sams Club for $110 off MSRP ($439) as part of their December Event sale, ending on December 7, 2022, valid for all colors. Sale price for online orders... Read more
Apple’s 10.2″ 64GB 9th-generation iPads are o...
Sams Club has 9th-generation 64GB iPads on Holiday sale for $60 off MSRP through December 7, 2022. With their discount, prices start at $259 — the cheapest price for a new iPad from any of the Apple... Read more
11″ 128GB WiFi M2 iPad Pro on sale for $749,...
B&H Photo has the new 11″ 128GB WiFi M2-powered iPad Pro (in Space Gray or Silver) on Holiday sale for $749 including free 1-2 day shipping to most US addresses. Their price is $50 off MSRP and... Read more
Find the best Holiday sale price on an iPad u...
We’ve updated our iPad Price Trackers with the latest information on the new 10th-generation iPads, M2-powered iPad Pros, M1 iPad Airs, iPad minis, and 9th generation iPads from Apple’s authorized... Read more
Apple retailers are offering $100-$150 Holida...
Apple retailers have posted their most-recent Holiday sale prices on 13″ MacBook Airs. Take up to $150 off MSRP on M2-powered Airs with these sales with prices starting at only $1099. Free shipping... Read more
Holiday Sale: Apple’s 14″ MacBook Pros with M...
B&H Photo is offering $200-$300 discounts on Apple’s 14″ MacBook Pros with M1 Pro CPUs as part of their Holiday 2022 sale, with prices starting at $1799. Free 1-2 day shipping is available to... Read more

Jobs Board

Support Technician II - *Apple* Support - O...
…problems and acting as a liaison between customers and resolving groups. As an Apple Technical Specialist, you will be supporting many of our popular Apple Read more
*Apple* Electronic Repair Technician - PlanI...
…a highly motivated individual to join our Production Department as an Apple Electronic Repair Technician. The computer repair technician will diagnose, assemble, Read more
Lead Developer - *Apple* tvOS - Rumble (Uni...
…earnings, and positive sentiment About the role: We are looking for a Lead Apple tvOS Developer to join our application engineering team to expand our video centric Read more
Tier 1 Endpoint Engineer - *Apple* - Red Ri...
…Desk on site, at our Client's location, with a focus on support to Apple products. This position will handle technical support requests directly from customers and Read more
Product Manager II - *Apple* - DISH (United...
…you will be doing We seek an ambitious, data-driven thinker to assist the Apple Product Development team as our new Retail Wireless division continues to grow and Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.