Friday, July 30, 2010

#1 2006-08-24 12:16:53 pm

waltr
Member
Registered: 2005-09-13
Posts: 316

working with 'PlistBuddy'

hello,

i've been working with .plist files lately and needed to both add and delete a 'dict' entry nested in an array.  needless to say, i've found little documentation on doing this and while i've found that adding with '/usr/bin/defaults' works pretty well, i cannot seem to find an array entry and delete it.  this may be just my inability to find the proper documentation.

in my search i discovered 'PlistBuddy', a mysterious program that is installed when 'Automatic Updates' are perfomed on a Macintosh.  i've written a test script to find 'PlistBuddy' and use it to find an array entry and delete it.  you can often find PlistBuddy installed in weird places like:

Code:

/Library/Receipts/AdditionalEssentials.pkg/Contents/Resources/PlistBuddy

i realize that the use could just 'drag' an icon off the Dock, and that this program is fairly useless.  it's real intention is as an example for anyone who may need the same kind of functionality i do.  i have not found anything like it out on the web.  i've tried to do my best to comment the code, but i'm certainly willing to answer any questions you may have.

here's the code:

Applescript:


(* removeDockItem -- pulls your Dock items into a list and allows you to delete a single item.
   This is more a study on how to find and use PlistBuddy to remove items from a .plist file *)


(* Our properties. We need to set myPlistBuddy to something so that we can test
   the variable after our 'Try' block. The 'myPersistent' variables refer to
   'arrays' in our .plist file. You can check these with the Property List Editor,
   which is available when you install the Developer Tools (Xcode). The global
   is so that we can always refer to the App or Other that the user picks to delete,
   and give good feedback *)

property myPlistBuddy : "no"
property myPersistentApps : "persistent-apps"
property myPersistentOthers : "persistent-others"
global myRm

(* This try block tests for PlistBuddy. If PlistBuddy does not exist, we do _not_
   want our program to run. Note that the 'sed' command effectively
   limits us to the _first_ instance of PlistBuddy that is found by
   'locate'. In my experience most users have several of these on
   their machines. *)

try
   set myPlistBuddy to (do shell script "/usr/bin/locate PlistBuddy | sed 2,10000d")
end try
if myPlistBuddy is "no" then
   display dialog "Cannot find PlistBuddy on this computer."
else
   doMain()
end if

(* If our 'Try' block worked, we can get into the main subroutine. We'll let
   the user pick a category of alias to delete from the Dock, and use getArray()
   with the proper parameters. We could probably subroutine the deletion as well,
   but it's just one line, so no big savings. *)

on doMain()
   set myButton to button returned of (display dialog "What would you like to remove from the Dock?" buttons {"Application", "Other", "Cancel"} default button "Application")
   if myButton is "Application" then
       set myDockApp to (getArray(myPersistentApps))
       if myDockApp ≥ 0 then
           do shell script myPlistBuddy & " -c \"delete " & myPersistentApps & ":" & myDockApp & " dict\" ~/Library/Preferences/com.apple.dock.plist"
           respawnDock()
           display dialog myRm & " has been deleted from the Dock"
       end if
   else if myButton is "Other" then
       set myDockOther to (getArray(myPersistentOthers))
       if myDockOther ≥ 0 then
           do shell script myPlistBuddy & " -c \"delete " & myPersistentOthers & ":" & myDockOther & " dict\" ~/Library/Preferences/com.apple.dock.plist"
           respawnDock()
           display dialog myRm & " has been deleted from the Dock"
       end if
   end if
end doMain

(* getArray() is where most of the work is done. Regardless of the array we are
   working with, it does the same thing. Our 'Repeat' block allows for an 'unlimited'
   (heh!) number of Dock items, since we know that the text, "Does Not Exist" will be
   returned for non-existant elements. We have an extra test for the "Dashboard",
   because for some reason this item is not named like the others. We make up for this
   to avoid confusing the user. *)

on getArray(a)
   set myArray to {missing value}
   set x to 0
   repeat while (do shell script myPlistBuddy & " -c \"print " & a & ":" & x & "\" ~/Library/Preferences/com.apple.dock.plist") does not contain "Does Not Exist"
       if x is 0 then
           if (do shell script myPlistBuddy & " -c \"print " & a & ":" & x & ":tile-data:file-label" & "\" ~/Library/Preferences/com.apple.dock.plist") is "" and (do shell script myPlistBuddy & " -c \"print " & a & ":" & x & ":tile-type" & "\" ~/Library/Preferences/com.apple.dock.plist") is "dashboard-tile" then
               set myArray to "Dashboard" as list
           else
               set myArray to (do shell script myPlistBuddy & " -c \"print " & a & ":" & x & ":tile-data:file-label" & "\" ~/Library/Preferences/com.apple.dock.plist") as list
           end if
       else
           
           if ((do shell script myPlistBuddy & " -c \"print " & a & ":" & x & ":tile-data:file-label" & "\" ~/Library/Preferences/com.apple.dock.plist") is "") and ((do shell script myPlistBuddy & " -c \"print " & a & ":" & x & ":tile-type" & "\" ~/Library/Preferences/com.apple.dock.plist") is "dashboard-tile") then
               set myArray to (myArray & "Dashboard")
           else
               set myArray to (myArray & (do shell script myPlistBuddy & " -c \"print " & a & ":" & x & ":tile-data:file-label" & "\" ~/Library/Preferences/com.apple.dock.plist"))
           end if
       end if
       set x to (x + 1)
   end repeat
   set myRm to (choose from list myArray) as string
   set y to 1
   set z to -1
   -- This little 'Repeat' block searches the list we created above for the users selection.
   repeat while y ≤ length of myArray
       --display dialog item y of myArray
       if quoted form of item y of myArray is quoted form of myRm then
           set z to y
       end if
       set y to (y + 1)
   end repeat
   -- we return 'z - 1' because our array elements start at '0' but our list starts at '1'.
   if z ≥ 0 then
       return (z - 1)
   else
       return z
   end if
end getArray

-- respawnDock() is code i took from a Qwerty Denzel post. Thanks Qwerty!
on respawnDock()
   tell application "Dock"
       quit
       repeat
           try
               activate
               exit repeat
           end try
       end repeat
   end tell
end respawnDock

NOTE:  i've only tested this with 10.4.7.  results with other versions of OS X would be informative.

Last edited by waltr (2006-08-24 02:23:53 pm)


I use Google or any search engine to find out things I don't know!

Filed under: dock

Offline

 

#2 2006-08-24 02:47:13 pm

waltr
Member
Registered: 2005-09-13
Posts: 316

Re: working with 'PlistBuddy'

here is another example of the same script.  this one is a bit simpler.  we are looking for 'Global Login Items', which are an Apple unapproved way of starting a service at login by any user.  you'll find this technique used by Symantec in particular.

since these are not named, as Dock items were above, we just pull a value pair that will make sense to us.  the .plist we are looking at with this example is '/Library/Preferences/loginwindow.plist'.  might i suggest you make a copy of this file before using the AppleScript below?  it may save you some headaches to do so.

here is the code:

Applescript:


(* removeGlobalLoginItem -- removes startup items located in
   /Library/Preferences/loginwindow.plist *)


property myPlistBuddy : "no"
property myAutoLaunched : "AutoLaunchedApplicationDictionary"
global myRm

(* This try block tests for PlistBuddy. If PlistBuddy does not exist, we do _not_
   want our program to run. Note that the 'sed' command effectively
   limits us to the _first_ instance of PlistBuddy that is found by
   'locate'. In my experience most users have several of these on
   their machines. *)

try
   set myPlistBuddy to (do shell script "/usr/bin/locate PlistBuddy | sed 2,10000d")
end try
if myPlistBuddy is "no" then
   display dialog "Cannot find PlistBuddy on this computer."
else
   doMain()
end if

(* If our 'Try' block worked, we can get into the main subroutine. We'll let
   the user pick a category of alias to delete from the Dock, and use getArray()
   with the proper parameters. We could probably subroutine the deletion as well,
   but it's just one line, so no big savings. *)

on doMain()
   set myGlobalLoginItem to (getArray(myAutoLaunched))
   if myGlobalLoginItem ≥ 0 then
       do shell script myPlistBuddy & " -c \"delete " & myAutoLaunched & ":" & myGlobalLoginItem & " dict\" /Library/Preferences/loginwindow.plist"
       display dialog myRm & " has been deleted the Global Login Items list"
   end if
end doMain

(* getArray() is where most of the work is done. Regardless of the array we are
   working with, it does the same thing. Our 'Repeat' block allows for an 'unlimited'
   (heh!) number of Global Login items, since we know that the text, "Does Not Exist" will be
   returned for non-existant elements. *)

on getArray(a)
   set myArray to {missing value}
   set x to 0
   repeat while (do shell script myPlistBuddy & " -c \"print " & a & ":" & x & "\" /Library/Preferences/loginwindow.plist") does not contain "Does Not Exist"
       if x is 0 then
           set myArray to (do shell script myPlistBuddy & " -c \"print " & a & ":" & x & ":Path" & "\" /Library/Preferences/loginwindow.plist") as list
       else
           set myArray to (myArray & (do shell script myPlistBuddy & " -c \"print " & a & ":" & x & ":Path" & "\" /Library/Preferences/loginwindow.plist"))
       end if
       set x to (x + 1)
   end repeat
   set myRm to (choose from list myArray) as string
   set y to 1
   set z to -1
   -- This little 'Repeat' block searches the list we created above for the users selection.
   repeat while y ≤ length of myArray
       if quoted form of item y of myArray is quoted form of myRm then
           set z to y
       end if
       set y to (y + 1)
   end repeat
   -- we return 'z - 1' because our array elements start at '0' but our list starts at '1'.
   if z ≥ 0 then
       return (z - 1)
   else
       return z
   end if
end getArray


I use Google or any search engine to find out things I don't know!

Offline

 

#3 2006-08-31 05:43:18 pm

waltr
Member
Registered: 2005-09-13
Posts: 316

Re: working with 'PlistBuddy'

hello,

recently, i needed to add a <dict> entry to a .plist file (/Library/Preferences/loginwindow.plist) and i specifically needed to add a '&' at the end of my <Path> string entry.  i needed this because when the program is started, i wanted it to be in the background.  in other words, i did not want a Terminal window opening at every startup, just to annoy and be exited by my users.

to my chagrin, i have not been able to add a '&' with the defaults command.  here is one of my attempts:

Code:

/usr/bin/defaults write /Library/Preferences/loginwindow AutoLaunchedApplicationDictionary -array-add '<dict><key>Hide</key><true/><key>Path</key><string>/Library/Application Support/Path/To/Executable &</string></dict>'

i tried escaping it with a '\', quoting it, etc.  and was never able to get this working.  however, with PlistBuddy i can easily do this.

NOTE:  i've since found that you can do this by using '&amp;', like this:

Code:

/usr/bin/defaults write /Library/Preferences/loginwindow AutoLaunchedApplicationDictionary -array-add '<dict><key>Hide</key><true/><key>Path</key><string>/Library/Application Support/Path/To/Executable &amp;</string></dict>'

first--i've created an AppleScript that is a bundle, and placed PlistBuddy in '/Contents/Resources' (i could also use the technique outlined above).  then, the script below:

Applescript:


property myAutoLaunched : "AutoLaunchedApplicationDictionary"
global myPlistBuddy

set myPlistBuddy to quoted form of POSIX path of (path to me) & "Contents/Resources/PlistBuddy"

on doMain()
   do shell script myPlistBuddy & " -c \"add " & myAutoLaunched & ":0 dict\" /Library/Preferences/loginwindow.plist"
   do shell script myPlistBuddy & " -c \"add " & myAutoLaunched & ":0:Hide bool true\" /Library/Preferences/loginwindow.plist"
   do shell script myPlistBuddy & " -c \"add " & myAutoLaunched & ":0:Path string '/Library/Application Support/Path/To/Executable &'\" /Library/Preferences/loginwindow.plist"
end doMain

doMain()

for a script so short, it's a bit convoluted, but it's just adapted from the above scripts.  i'm trying to keep things modular so that i can cut & paste & reuse.  it adds a new <dict> entry at the beginning of the array (moving all other entries up one) and then adds my key values.

you can view the changes by opening Plist Editor (note that every time you make a change you must close and reopen the .plist), or by giving the defaults command below:

Code:

/usr/bin/defaults read /Library/Preferences/loginwindow

i hope this stuff is interesting and/or helps someone.

cheers.

EDITED:  to add my note about &amp;

Last edited by waltr (2006-09-18 05:54:50 pm)


I use Google or any search engine to find out things I don't know!

Offline

 

#4 2006-09-02 03:55:03 pm

Adam Bell
Administrator
From: Nova Scotia, Canada
Registered: 2005-10-04
Posts: 4250

Re: working with 'PlistBuddy'

waltr wrote:

i hope this stuff is interesting and/or helps someone.

Extremely interesting WaltR. I'm working on (but haven't finished) a "watcher" script (basically a hidden on-idle stay-open folder-action kind of thing) and I'd like to make the preparation of the plist required absolutely transparent.

IF I can figure out how to, I'd like to write an 'on wakeup' handler based on launchd as well since I'd like certain scripts to run when I wake up my machine for the first time in a day, and a subset of those when I wake again it later. I'm sure I'll be using variations on your scripts, so thanks.

I'm also interested (Craig Smith's tutorial got me started) on a watcher for an external fw HD that I use for backup so that when I turned it on (I have a PowerKey Pro to do that) and it mounted, (watching /volumes/ for that), my backup routine would run automagically.


Scripts are tested on a PowerMac dual-core G5/2.3 running OS X 10.5.8 or MacBook Pro Intel Core 2 Duo running OS X 10.6.4

Offline

 

Board footer

Powered by FluxBB

[ Generated in 0.335 seconds, 10 queries executed ]

RSS (new topics) RSS (active topics)