Can not control Powerpoint through AppleScript built by Xcode above 10.3

Hi there,

My macOS application is able to control Powerpoint through AppleScript. The first step of controlling is to see if Microsoft Office installed or not. Here is the worked AppleScript:

set appID to "com.microsoft.Powerpoint"
-- check if the application exists
set doesExist to false
try
  tell application "Finder" to get application file id appID
  set doesExist to true
end try

return doesExist

If the Office is installed, the script returns "true" and "false" vice versa. The script is working under Script Editor and Xcode 8.3.3.

However I upgraded my Xcode to 10.3, the script always return "false". This stops me to do notarization. How my object-C call AppleScript API is as below:

// AppleScript macro
+ (NSString *)pptIsInstalledOrNot
{
    return @"\n\
    set appID to \"com.microsoft.Powerpoint\" \n\
    -- check if the application exists \n\
    set doesExist to false \n\
    try \n\
        tell application \"Finder\" to get application file id appID \n\
        set doesExist to true \n\
    end try \n\
    \n\
    return doesExist \n\
    ";
}

// AppleScript execution 
+ (NSAppleEventDescriptor *)runProcess:(NSString *)script
                      errorDescription:(NSString **)errorDescription
{
    NSDictionary *errorInfo = [[NSDictionary alloc] init];
    NSAppleScript *appleScript = [[NSAppleScript alloc] initWithSource:script];
    NSAppleEventDescriptor *eventResult = [appleScript executeAndReturnError:&errorInfo];
    
    // Check errorInfo
    if (!eventResult) {
        // Set error message
        if ([errorInfo valueForKey:NSAppleScriptErrorMessage]) {
            id message = [errorInfo valueForKey:NSAppleScriptErrorMessage];
            if (errorDescription) {
                *errorDescription = (NSString *)message;
            }
        }
    }
    else {
        // Set output to the AppleScript's output
        
    }
    
    return eventResult;
}

// main process 

Boolean isInstalled = false;
    {
        NSString *script = [self pptIsInstalledOrNot];
        NSString *errorMessage = nil;
        NSAppleEventDescriptor *descriptor = [self runProcess:script errorDescription:&errorMessage];
        isInstalled = [descriptor booleanValue];
    }

I ever tried other ways, still failed. How do I modify my program on XCode 10.3? Appreciate for any ideas and

thanks your time.

Accepted Reply

we are developing an application to control PowerPoint/Keynote, we found that the AppleScript is the only way controlling PowerPoint and we developed many AppleScript macros to control it.

Fair enough. It’s perfectly feasible to run AppleScripts to control scriptable apps. There are, however, some hoops you need to jump through.

Note As you mentioned notarisation, I’m assuming that you plan to deploy outside of the Mac App Store. If that’s incorrect, and you’re targeting the Mac App Store, things get trickier. Let me know if that’s the case.

On modern systems (10.14 and later) there are two things that you need to do in order to get a non-sandboxed app to script other apps:

  1. Add an

    NSAppleEventsUsageDescription
    string to your
    Info.plist
    .
  2. If you have the hardened runtime enabled — which you’ll need if you want to notarise your app — you must add the Apple Events entitlement (

    com.apple.security.automation.apple-events
    ).

With both of those, the following code activates Keynote and starts the current presentation:

static NSString * scriptText = @
    "tell application \"Keynote\"\n"
    "    activate\n"
    "    start document 1\n"
    "end tell"
    ;
NSAppleScript * script = [[NSAppleScript alloc] initWithSource:scriptText];
assert(script != nil);
NSDictionary * error = nil;
NSAppleEventDescriptor * result = [script executeAndReturnError:&error];
if (result == nil) {
    // … look at `error` …
    NSLog(@"error: %@", error);
} else {
    // … look at `result` …
    NSLog(@"result: %@", result);
}

I tested this on 10.14.6 but I believe it’ll also work on 10.15 just fine.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

Is there a specific reason you’re doing this via AppleScript? If you’re starting with native code, you could just as easily use

NSWorkspace
. For example, on my machine this code:
NSLog(@"%@", [NSWorkspace.sharedWorkspace URLForApplicationWithBundleIdentifier:@"com.microsoft.Powerpoint"]);
NSLog(@"%@", [NSWorkspace.sharedWorkspace URLForApplicationWithBundleIdentifier:@"com.apple.iWork.Keynote"]);

prints this:

… (null)
… file:///Applications/Keynote.app/

indicating that I have Keynote but no PowerPoint.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thank you Quinn for such efficiently response, to be honest we are developing an application to control PowerPoint/Keynote, we found that the AppleScript is the only way controlling PowerPoint and we developed many AppleScript macros to control it.


I will replace current method by the mothod you are suggesting me, but some other function we've implemented might still not work(I am afraid of it). For instance, Play slide/Stop play slide, draw, blackout screen...,etc.


Anyway I will take your suggestion first then go forward and update my status, thanks!

we are developing an application to control PowerPoint/Keynote, we found that the AppleScript is the only way controlling PowerPoint and we developed many AppleScript macros to control it.

Fair enough. It’s perfectly feasible to run AppleScripts to control scriptable apps. There are, however, some hoops you need to jump through.

Note As you mentioned notarisation, I’m assuming that you plan to deploy outside of the Mac App Store. If that’s incorrect, and you’re targeting the Mac App Store, things get trickier. Let me know if that’s the case.

On modern systems (10.14 and later) there are two things that you need to do in order to get a non-sandboxed app to script other apps:

  1. Add an

    NSAppleEventsUsageDescription
    string to your
    Info.plist
    .
  2. If you have the hardened runtime enabled — which you’ll need if you want to notarise your app — you must add the Apple Events entitlement (

    com.apple.security.automation.apple-events
    ).

With both of those, the following code activates Keynote and starts the current presentation:

static NSString * scriptText = @
    "tell application \"Keynote\"\n"
    "    activate\n"
    "    start document 1\n"
    "end tell"
    ;
NSAppleScript * script = [[NSAppleScript alloc] initWithSource:scriptText];
assert(script != nil);
NSDictionary * error = nil;
NSAppleEventDescriptor * result = [script executeAndReturnError:&error];
if (result == nil) {
    // … look at `error` …
    NSLog(@"error: %@", error);
} else {
    // … look at `result` …
    NSLog(@"result: %@", result);
}

I tested this on 10.14.6 but I believe it’ll also work on 10.15 just fine.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

we found that the AppleScript is the only way controlling PowerPoint and we developed many AppleScript macros to control it


Yep, AppleScript is the only [nominally] supported solution that works right; especially with older Carbon-based apps like PowerPoint. Stick to it.


However, for any non-trivial automation work I strongly recommend you avoid NSAppleScript and instead call into AppleScript via the AppleScript-ObjC bridge:


appscript.sourceforge.net/asoc.html


ASOC makes your AS scripts and handlers appear as native Cocoa objects and methods to your ObjC code, automatically converting argument and result values between standard AS and Foundation types, making it trivially easy to call AS handlers from your ObjC methods and vice-versa.


It’s not perfect—C primitives (bool, int, double, etc) must be manually boxed as NSNumber, you’ll need to trap any AS errors within your AS handlers, and AS object specifiers don’t round-trip through ObjC very well—but it’s still leagues better than writing slow, complicated, error-prone NSAppleScript code, especially if using Late Night’s Script Debugger as your external code editor (SD includes a bunch of features for ASOC development).


If you need any help using ASOC, best place to ask is on the Late Night forums:


forum.latenightsw.com

Appreciate for you guys big support!