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

Latest Forum Discussions

See All

Tokkun Studio unveils alpha trailer for...
We are back on the MMORPG news train, and this time it comes from the sort of international developers Tokkun Studio. They are based in France and Japan, so it counts. Anyway, semantics aside, they have released an alpha trailer for the upcoming... | Read more »
Win a host of exclusive in-game Honor of...
To celebrate its latest Jujutsu Kaisen crossover event, Honor of Kings is offering a bounty of login and achievement rewards kicking off the holiday season early. [Read more] | Read more »
Miraibo GO comes out swinging hard as it...
Having just launched what feels like yesterday, Dreamcube Studio is wasting no time adding events to their open-world survival Miraibo GO. Abyssal Souls arrives relatively in time for the spooky season and brings with it horrifying new partners to... | Read more »
Ditch the heavy binders and high price t...
As fun as the real-world equivalent and the very old Game Boy version are, the Pokemon Trading Card games have historically been received poorly on mobile. It is a very strange and confusing trend, but one that The Pokemon Company is determined to... | Read more »
Peace amongst mobile gamers is now shatt...
Some of the crazy folk tales from gaming have undoubtedly come from the EVE universe. Stories of spying, betrayal, and epic battles have entered history, and now the franchise expands as CCP Games launches EVE Galaxy Conquest, a free-to-play 4x... | Read more »
Lord of Nazarick, the turn-based RPG bas...
Crunchyroll and A PLUS JAPAN have just confirmed that Lord of Nazarick, their turn-based RPG based on the popular OVERLORD anime, is now available for iOS and Android. Starting today at 2PM CET, fans can download the game from Google Play and the... | Read more »
Digital Extremes' recent Devstream...
If you are anything like me you are impatiently waiting for Warframe: 1999 whilst simultaneously cursing the fact Excalibur Prime is permanently Vault locked. To keep us fed during our wait, Digital Extremes hosted a Double Devstream to dish out a... | Read more »
The Frozen Canvas adds a splash of colou...
It is time to grab your gloves and layer up, as Torchlight: Infinite is diving into the frozen tundra in its sixth season. The Frozen Canvas is a colourful new update that brings a stylish flair to the Netherrealm and puts creativity in the... | Read more »
Back When AOL WAS the Internet – The Tou...
In Episode 606 of The TouchArcade Show we kick things off talking about my plans for this weekend, which has resulted in this week’s show being a bit shorter than normal. We also go over some more updates on our Patreon situation, which has been... | Read more »
Creative Assembly's latest mobile p...
The Total War series has been slowly trickling onto mobile, which is a fantastic thing because most, if not all, of them are incredibly great fun. Creative Assembly's latest to get the Feral Interactive treatment into portable form is Total War:... | Read more »

Price Scanner via MacPrices.net

Early Black Friday Deal: Apple’s newly upgrad...
Amazon has Apple 13″ MacBook Airs with M2 CPUs and 16GB of RAM on early Black Friday sale for $200 off MSRP, only $799. Their prices are the lowest currently available for these newly upgraded 13″ M2... Read more
13-inch 8GB M2 MacBook Airs for $749, $250 of...
Best Buy has Apple 13″ MacBook Airs with M2 CPUs and 8GB of RAM in stock and on sale on their online store for $250 off MSRP. Prices start at $749. Their prices are the lowest currently available for... Read more
Amazon is offering an early Black Friday $100...
Amazon is offering early Black Friday discounts on Apple’s new 2024 WiFi iPad minis ranging up to $100 off MSRP, each with free shipping. These are the lowest prices available for new minis anywhere... Read more
Price Drop! Clearance 14-inch M3 MacBook Pros...
Best Buy is offering a $500 discount on clearance 14″ M3 MacBook Pros on their online store this week with prices available starting at only $1099. Prices valid for online orders only, in-store... Read more
Apple AirPods Pro with USB-C on early Black F...
A couple of Apple retailers are offering $70 (28%) discounts on Apple’s AirPods Pro with USB-C (and hearing aid capabilities) this weekend. These are early AirPods Black Friday discounts if you’re... Read more
Price drop! 13-inch M3 MacBook Airs now avail...
With yesterday’s across-the-board MacBook Air upgrade to 16GB of RAM standard, Apple has dropped prices on clearance 13″ 8GB M3 MacBook Airs, Certified Refurbished, to a new low starting at only $829... Read more
Price drop! Apple 15-inch M3 MacBook Airs now...
With yesterday’s release of 15-inch M3 MacBook Airs with 16GB of RAM standard, Apple has dropped prices on clearance Certified Refurbished 15″ 8GB M3 MacBook Airs to a new low starting at only $999.... Read more
Apple has clearance 15-inch M2 MacBook Airs a...
Apple has clearance, Certified Refurbished, 15″ M2 MacBook Airs now available starting at $929 and ranging up to $410 off original MSRP. These are the cheapest 15″ MacBook Airs for sale today at... Read more
Apple drops prices on 13-inch M2 MacBook Airs...
Apple has dropped prices on 13″ M2 MacBook Airs to a new low of only $749 in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, now available for $679 for 8-Core CPU/7-Core GPU/256GB models. Apple’s one-year warranty is included, shipping is free, and each... Read more

Jobs Board

Seasonal Cashier - *Apple* Blossom Mall - J...
Seasonal Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Seasonal Fine Jewelry Commission Associate -...
…Fine Jewelry Commission Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) Read more
Seasonal Operations Associate - *Apple* Blo...
Seasonal Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Read more
Hair Stylist - *Apple* Blossom Mall - JCPen...
Hair Stylist - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.