Scripting Bridge
Say you want to talk to another app through Applescript. With 10.5, you can much more easily get there from Cocoa without complex forays into CoreServices, Carbon and AppleEvents. The docs on how to do it are a little thin at times (as all Applescript docs are), so let’s walk through it. The relevant docs you’ll want to read are these:
Scripting Bridge Framework Reference
And most importantly (and most hidden):
And now for a step-by-step example. We’re going to send some mail with an attachment through Mail.app. I’m going to assume you know enough Applescript to have written this:
tell application "Mail" set theMessage to make new outgoing message with properties ¬ {subject:"Test outgoing", content:"Test body"} tell theMessage make new to recipient at end of to recipients with properties ¬ {name:"Rob Napier", address:"sb@robnapier.net"} tell content make new attachment with properties {filename:"/etc/hosts"} ¬ at after last paragraph set visible of theMessage to true end tell end tell activate end tell |
Let’s convert it to Scripting Bridge.
Setting up your project
- Add ScriptingBridge.Framework to your project:
- Open Targets
- Double-click the Target
- Select the General Tab
- Click “+” for Linked Libraries
- Add ScriptingBridge.framework
- Add a rule for creating .h files for scriptable Applications (this should be built into XCode, but isn’t). You could also do this by hand one time, and just add the resulting .h to your project.
- Select the Rules tab
- Click “+”
- Process: Source files with name matching: *.app
- Using: Custom Script: (the following needs to be one long line)
sdef "$INPUT_FILE_PATH" | sdp -fh -o "$DERIVED_FILES_DIR" --basename "$INPUT_FILE_BASE" --bundleid `defaults read "$INPUT_FILE_PATH/Contents/Info" CFBundleIdentifier`
- Click the “+” under “with output files:”
$(DERIVED_FILES_DIR)/$(INPUT_FILE_BASE).h
- Close the Target window. We’re done with the really crazy part.
- Add the application as one of your sources.
- Drag the desired application (Mail.app) into your Groups & Files tree. You can put it in a group if you like
- Unselect “Copy items into destination group’s folder” (if it is selected)
- Drag the application into your “Compile Sources” step in your Target (it should be first, so the .h gets created before it’s needed). Yes, we are “compiling” an application into a header.
- Include the new header file in your program
- In your .m file:
#import <ScriptingBridge/SBApplication.h> #import "Mail.h"
- In your .m file:
- Build
- Now is a good time to build. That will get your .h created, making everything easier later. It’s created in your
DerivedSourcesdirectory. The easiest way to open it is with Cmd-Shift-D (Open Quickly). Just hit Cmd-Shift-D, and then type “mail.h”. Once you’ve found it, you can drag it into your Groups & Files list if you like. It will be deleted when you Build Clean, so don’t be surprised by that. Aren’t you glad you learned about Open Quickly? It’s my favorite way to move between files.
- Now is a good time to build. That will get your .h created, making everything easier later. It’s created in your
Writing the code
OK, we now have everything in place to write some code. The process of converting from Applescript to SB is fairly mechanical, but like all Applescript things there are some things you just need to know. We’re going to take this one line at a time.
tell application "Mail" |
We need an SBApplication object to tell things to. So we make one:
MailApplication *mail = [SBApplication applicationWithBundleIdentifier:@"com.apple.mail"]; |
Notice that you can’t call [[MailApplication alloc] init]. This is more a limitation of the sdp tool we used to create the .h than of ScriptingBridge. There is no Mail.m file to actually implement the MailApplication class, so you can’t directly allocate the class. You’ll see more of this limitation later.
set theMessage to make new outgoing message with properties ¬ {subject:"Test outgoing", content:"Test body"} |
We’re creating a new outgoing message. This includes a special step that you can partially guess, and somewhat just have to know. Every scripting object you talk to has to chain back to the SBApplication. You can’t deal with stand-alone SBObjects. In this case, the Applescript has an implicit step that we need to make explicit. The Applescript above is implicitly adding theMessage to outgoing messages. When you get used to it, it’s kind of obvious, and if you look in Mail.app, you’ll see that MailApplication has an -outgoingMessages property. But it can be a little surprising when you’re getting stared. So let’s rewrite the Applescript to be more explicit:
set theMessage to make new outgoing message at end of outgoing messages ¬ with properties {subject:"Test outgoing", content:"Test body"} |
And so here’s the code:
MailOutgoingMessage *mailMessage = [[[[mail classForScriptingClass:@"outgoing message"] alloc] initWithProperties:[NSDictionary dictionaryWithObjectsAndKeys: @"Test outgoing", @"subject", @"Test body\n\n", @"content", nil]] autorelease]; [[mail outgoingMessages] addObject:mailMessage]; |
This is a very common pattern, so it’s worth studying. First, note that we can’t directly +alloc the MailOutgoingMessage. We have to ask for it through the SBApplication object. This is more of the limitation discussed above. And we need to pass the Applescript class “outgoing message.” This is obvious from the MailOutgoingMessage name once you see how sdp creates the .h file. The properties we pass SB are identical to the ones we pass Applescript. And once we create it, we add it into the object tree with -addObject:, which adds “at end of” the list just like we need (just like an NSArray). OK, now go back and read this paragraph again and make sure you’ve got it. We’re going to use this several times.
tell theMessage make new to recipient at end of to recipients with properties ¬ {name:"Rob Napier", address:"sb@robnapier.net"} |
You should be able to guess the code for this one:
MailToRecipient *recipient = [[[[mail classForScriptingClass:@"to recipient"] alloc] initWithProperties:[NSDictionary dictionaryWithObjectsAndKeys: @"Rob Napier", @"name", @"sb@robnapier.net", @"address", nil]] autorelease]; [[mailMessage toRecipients] addObject:recipient]; |
And once more for fun:
tell content make new attachment with properties {filename:"/etc/hosts"} ¬ at after last paragraph |
==>
MailAttachment *attachment = [[[[mail classForScriptingClass:@"attachment"] alloc] initWithProperties:[NSDictionary dictionaryWithObjectsAndKeys: @"/etc/hosts", @"filename", nil]] autorelease]; [[[mailMessage content] paragraphs] addObject:attachment]; |
Those really are the complicated ones (and they aren’t bad at all once you see how to read them). After that, everything should be obvious:
set visible of theMessage to true end tell end tell activate |
Becomes:
[mailMessage setVisible:YES]; [mail activate]; |
The Finished Code
So let’s look at the full code now, including a @try/@catch, since Applescript can generate exceptions (more about this below):
@try { MailApplication *mail = [SBApplication applicationWithBundleIdentifier:@"com.apple.mail"]; MailOutgoingMessage *mailMessage = [[[[mail classForScriptingClass:@"outgoing message"] alloc] initWithProperties:[NSDictionary dictionaryWithObjectsAndKeys: @"Test outgoing", @"subject", @"Test body\n\n", @"content", nil]] autorelease]; [[mail outgoingMessages] addObject:mailMessage]; MailToRecipient *recipient = [[[[mail classForScriptingClass:@"to recipient"] alloc] initWithProperties:[NSDictionary dictionaryWithObjectsAndKeys: @"Rob Napier", @"name", @"sb@robnapier.net", @"address", nil]] autorelease]; [[mailMessage toRecipients] addObject:recipient]; MailAttachment *attachment = [[[[mail classForScriptingClass:@"attachment"] alloc] initWithProperties:[NSDictionary dictionaryWithObjectsAndKeys: @"/etc/hosts", @"filename", nil]] autorelease]; [[[mailMessage content] paragraphs] addObject:attachment]; [mailMessage setVisible:YES]; [mail activate]; } @catch (NSException *e) { NSLog(@"Exception:%@"); } |
Error Handling
I like @try/@catch better than SBApplicationDelegate because the delegate can’t easily interrupt the script if there’s an error. If you let it raise an exception and then @catch it, the entire block aborts, which is what I generally want. This also exactly matches the normal AppleScript error handling pattern.
Summary
Apple has created an incredible new framework with Scripting Bridge, making it easier than ever to tie your application into the system and interact with other programs. Unfortunately, they buried much of the documentation, and left much to the imagination of the reader (like most Applescript documentation). Hopefully this article will help improve that situation and make Applescript a bigger part of your programs.

I need to find someone out there who is interested in the same and who has the understanding to redirect me toward a solution. Am I trying to do the impossible? I believe AppKit.h is not normal for iPhone. I get errors relating to it as you will see below.
Following instructions, 1. I added ScriptingBridge.framework to the General Tab of the Target Info Page
defaults read "$INPUT_FILE_PATH/Contents/Info" CFBundleIdentifierfollowed by : the info for ‘with output files’
I created a Group(folder) in my Project ,name AssociatedApp and dragged Mail.app there after unselecting the “Copy Items into destination group’s folder”
Next we are instructed to :
Drag the application into your “Compile Sources” step in your
Target (it should be first, so the .h gets created before it’s needed). Yes, we are “compiling” an application into a header.
end of instructions;
When I drag Mail.app to the Rules Tab of the Target Info Page I see only one place that I get a cut and paste ‘green Plus sign paste accepted” icon. This is in the box with the text beginning with “sdef “$Input_FILE”, inserting a blank first, I get
sdef “$INPUT_FILE_PATH” | sdp -fh -o “$DERIVED_FILES_DIR” –basename “$INPUT_FILE_BASE” –bundleid
defaults read "$INPUT_FILE_PATH/Contents/Info" CFBundleIdentifier/Users/appleuser/Cocoa/iHungry7/Mail.appI have an source file from which I will attempt to email with an attachment. at the top of this *.m file I paste, as directed: #import #import “Mail.h”
I find the newly created Mail.h file as predicted in DerivedSources below the build folder. I copy it to my project and include it in the project, using ‘add existing file’.
I then Build as directed and get multiple errors. I am stuck . Many Many thanks for reading. Mark
cd /Users/appleuser/Cocoa/iHungry7 setenv PATH “/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin:/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin” /Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin/gcc-4.0 -x objective-c -arch i386 -fmessage-length=0 -pipe -Wno-trigraphs -fpascal-strings -fasm-blocks -O0 -Wreturn-type -Wunused-variable -D__IPHONE_OS_VERSION_MIN_REQUIRED=20000 -isysroot /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator2.2.1.sdk -fvisibility=hidden -mmacosx-version-min=10.5 -gdwarf-2 -I/Users/appleuser/Cocoa/iHungry7/build/iHungry7.build/Debug-iphonesimulator/iHungry.build/iHungry.hmap -F/Users/appleuser/Cocoa/iHungry7/build/Debug-iphonesimulator -I/Users/appleuser/Cocoa/iHungry7/build/Debug-iphonesimulator/include -I/Users/appleuser/Cocoa/iHungry7/build/iHungry7.build/Debug-iphonesimulator/iHungry.build/DerivedSources -include /var/folders/lJ/lJDuzOmaEX4Q1OyHZP8pTE+++TI/-Caches-/com.apple.Xcode.501/SharedPrecompiledHeaders/iHungry_Prefix-besecngakovcoubtihetjjsibyjz/iHungry_Prefix.pch -c /Users/appleuser/Cocoa/iHungry7/RecipeNoteViewController.m -o /Users/appleuser/Cocoa/iHungry7/build/iHungry7.build/Debug-iphonesimulator/iHungry.build/Objects-normal/i386/RecipeNoteViewController.o /Users/appleuser/Cocoa/iHungry7/RecipeNoteViewController.m:16:42: error: ScriptingBridge/SBApplication.h: No such file or directory In file included from /Users/appleuser/Cocoa/iHungry7/RecipeNoteViewController.m:17: /Users/appleuser/Cocoa/iHungry7/Mail.h:5:26: error: AppKit/AppKit.h: No such file or directory /Users/appleuser/Cocoa/iHungry7/Mail.h:6:44: error: ScriptingBridge/ScriptingBridge.h: No such file or directory In file included from /Users/appleuser/Cocoa/iHungry7/RecipeNoteViewController.m:17: /Users/appleuser/Cocoa/iHungry7/Mail.h:134: error: cannot find interface declaration for ‘SBObject’, superclass of ‘MailItem’ /Users/appleuser/Cocoa/iHungry7/Mail.h:140: error: syntax error before ‘SBObject’ /Users/appleuser/Cocoa/iHungry7/Mail.h:141: fatal error: method definition not in @implementation context compilation terminated. {standard input}:32:FATAL:.abort detected. Assembly stopping.
typo above: I actually correctly added two imports
import
import “Mail.h”
I actually included the files named below ScriptingBridge/SBApplication.h main.h
The pointy brackets were throwing the script.
Scripting Bridge does not exist on iPhone. The Mail.app you are importing here is a completely different application from the iPhone application, so wouldn’t be helpful even if Scripting Bridge were available. You may be able to eventually get it compiling on Simulator because Simulator uses the Mac’s version of Foundation, but it can’t work on iPhone because iPhone doesn’t provide an Applescript interface to other applications.
The only interface 3rd party apps have to the Mail app on iPhone in 2.2 is the mailto: URL, which is limited, but all we really have.
Thanks for this Rob. I tried to get Scripting Bridge working a few months ago and eventually gave up. I mention this because I was trying to convert an Applescript that ran perfectly, but I couldn’t get certain properties with Scripting Bridge.
It’s a long, long time ago and I can’t remember the specifics except that I was working with System Events, but it’s worth noting that in some edge cases, Scripting Bridge won’t allow you to access some areas that AppleScript does.
So I eventually resigned myself to using a third party tool called AppScript – http://appscript.sourceforge.net/
Hope this is helpful to someone else for who Scripting Bridge isn’t suitable.
Hi. Excellent article. I’ve written a ScriptingBridge Cocoa app in Xcode and it works fine for text emails and attachments. However, I want the attachment to show in the body of the email on Outlook email clients. To do this, I need to convert the email to HTML by adding a header of “Content-Type: text/html”. Do you know how to do this or know where I can find other classForScriptingClass objects — specifically for headers? Best Regard Dalton Hamilton Chapel Hill, NC
I’ve seen no easy way to create an HTML mail through Applescript. You can write the HTML to a file and read it in, but that’s a lot of work. And I’ve seen folks who pass keyboard events, but that’s very fragile.
There is a private property called “htmlContent,” but I’ve never been able to read or write it. There’s also a “source” property, but it’s readonly. The mail headers are not modifiable on outgoing messages.
I don’t know if anyone has solved this problem in a good way.
Excellent article, thanks! I’m using what I learned from it. I have a working app, but I’m having trouble with error handling. Essentially, my code looks like this:
This works fine. To test that the exception handling works, I change the name of the app to something nonexistent. When I run it, an error like this is NSLog’d:
The “catch” doesn’t execute. I can check that “safari” isn’t nil, but I apparently con’t control that an error appears in the Console and on standard out.
Should the try/catch work for this error? Is there any way I can get control of the error?
Thanks! I used this article to write a ScriptingBridge app. The app works, but I have one question: shouldn’t the first line be outside the @try block? If this line fails:
…then mailApp will be set to nil. To test it, I changed “com.apple.mail” to, like, “com.apple.xxx”. As documented, mailApp was set to nil. Also, a line like this appeared on standard out and in the Console:
2010-07-07 11:11:59.026 myApp[1584:903] Can’t find app with identifier com.apple.xxx
Apparently, applicationWithBundleIdentifier doesn’t throw exceptions, and it NSLog’s on errors whether you like it or not.
@Pete Siemsen
Correct on both counts. This isn’t particularly unusual in Cocoa. SBApplication is unusual in that it will raise exceptions (once created), and this turns out to be handy in my opinion. Because there are several pieces of Cocoa that will NSLog, I always freopen() STDOUT and STDERR to a log file in Release mode so that I can capture the failure when users send in problem reports.
Rob,
What would be the Scripting Bridge equivalent of the following AppleScript?
set previousApp to the name of the current application — some app gets activated here, raising is window(s) to the front tell application previousApp to activate
My attempt, which probably just shows that a little knowledge is dangerous, partially works:
Although this identifies the app that has focus, it seems like this is the wrong approach – the loop through the processes is slow – it stops for a second or two on one of the processes. Is there somewhere I can look for guidance? A mailing list?
Thanks,
– Pete
Thanks a lot for this tutorial. There’s very little information about Scripting Bridge on the web even though it is much more user friendly in cocoa applications. Anyway, very useful information! Thanks.
I could not get the attachment to attach with this code: [[[mailMessage content] paragraphs] addObject:attachment]. “content” does not appear in the current Mail.h header for an outgoing mail message. Any suggestions?
@John MacMullin What version of Mail are you testing against? I see a
contentproperty inMailOutgoingMessageon 10.7. One approach I often use is to build things up in AppleScript and then translate into Scripting Bridge.I am using Mail 5.2
@John MacMullin Open AppleScript Editor. Hit Cmd-Shift-O and select “Mail.” Search for “content.” You should see it in the Message Suite. Then search your generated Mail.h file and make sure it matches what you’re seeing in AppleScript Editor.
Ok, I know its in the editor as I have a ton of Applescript that uses it.
To test/fix the problem, I added the following to the MailOutgoingMessage interface in the Mail.h file: ”@property (copy) MailRichText *content; // Contents of an email message”. This I copied from the MailMessage interface, but removed the read only.
Now both of the following work and I have no warning messages:
[[[emailMessage content] paragraphs] addObject:theAttachment]; [[emailMessage.content attachments] addObject: theAttachment];
It would seem that the generation of the Mail.h file in implementing Scripting Bridge may have an issue as the resulting Mail.h file did not have a ‘content’ property.
Oops, [[[emailMessage content] attachments] addObject: theAttachment]; is the variation tested although the ‘paragraphs’ alternative should also.
@John MacMullin Interesting. I just tested with this:
Are you getting a header without
content?My header for the MailOutgoingMessage interface had no content property. I thought it was very strange.
Hmmm, to your point, when you ran the sdef stuff, did the MailOutgoingMessage interface have a content property?
Yep. I see it from the line I gave.
Ok, I went back and looked and my sdef was coming out of an older 3.x version of Xcode. When I used the current Xcode, it worked just fine and created a header with the content property.
Thanks for helping!
I tried the System Events example from above and noted the following: first, the sdef/sdp terminal command failed with the error that the system events application had no sdef file (yet the application is scriptable), second, I guessed at giving the systemEventsApp object a type of “SystemEventsApplication” and the code compiled and executed with no errors, third, when executing, the loop logged about 10 or so processes and then two minutes later, logged the rest of the open processes, which I thought was odd.
The question that remains is: where do I find, obtain, create a system events.h file?
As an add-on to the last comment, I did include the system events app in my application per the Apple instructions as a compile source, etc.
and checking the derived sources again, the system event.h appeared. ok….
Hi Rob thanks for this great article. I tried to convert the header file for Microsoft Word. I got
sdp: enumerator of enumeration “e183″: missing required “name” attribute.
to fix this, i went through http://stackoverflow.com/questions/15338454/scripting-bridge-and-generate-microsoft-word-header-file
After that i got sdp: warning: property “style” of class “revision” refers to undefined type ‘null’; assuming type ‘id’. Could you please tell me how i fix this issue and move to next step.
I haven’t built any bridges based on Microsoft Word. Many of their AppleScript interfaces are unusual at best. I don’t know any general way to make them work well.