I want to do with an NSTask what I am able to do in the terminal via
$ myprogram myfile.ext
I know that myprogram (I don't have any control on this program) launches another program myauxprogram. Furthermore, the path to myprogram is path1 and the path to myprogram is path2.
If I do
NSTask * myTask = [[NSTask alloc] init];
NSArray * arguments = #[#"myfile.ext"] ;
[myTask setCurrentDirectoryPath:[URLOfTheFolder path]];
[myTask setLaunchPath:#"/path1/myprogram"];
[myTask setArguments:arguments];
[myTask launch] ;
I get the following error sh: myauxprogam: command not found
If I create a symbol link in path1 to myauxprogram, the problem is the same.
It looks like you mostly have the idea, you're just missing a few key elements. NSTask should have a defined LaunchPath, such as /usr/bin/sh, /usr/bin, etc. from there depending on the type of path you're requiring you set up your arguments and options.
NSTask *auxTask;
NSTask *auxPath = #"./path1/myprogram"; // period might be needed
NSTask *auxArgs = #"myfile.ext";
auxTask = [[NSTask alloc] init];
[auxTask setLaunchPath:#"/usr/bin/sh"]; // use shell
[auxTask setArguments:[NSArray arrayWithObjects:
#"-c", // -c will exec as shell cmd
auxPath,
auxArgs,
nil]];
// using `try` is optional
#try
{
[auxTask launch];
}
#catch(id exc)
{
auxTaskSuccess = NO;
}
[auxTask release];
return;
No idea if this works, but this is the way you would likely set things up. If you run into issues with errors or the NSTask failing you can set up an NSLog with the auxTask NSArray to see exactly what it's doing.
The problem was:
You need to [myTask setEnvironment:...] so that the task knows which are the environment variables (PATH, etc.)
Related
in Apple Document Technical Note 2065, it mention "do shell script command with administrator privileges",when using this way,"Once a script is correctly authenticated, it will not ask for authentication again for five minutes."
but, it still need ask for authentication again and again.
I find,when use ScriptEditor.app, the Apple Document is right.
eg:
do shell script "/bin/cp -r /Users/Simon/Desktop/Test/test.zip /Users/Simon/Desktop/ " with administrator privileges
but, when use NSAppleScript running shell script, the Apple Document is wrong.
eg:
NSDictionary *error = nil;
NSString *copyScript = #"do shell script \"/bin/cp -r /Users/Simon/Desktop/Test/test.zip /Users/Simon/Desktop \" with administrator privileges";
NSAppleScript *copyAppleScript = [[NSAppleScript alloc] initWithSource:copyScript];
if ([copyAppleScript executeAndReturnError:&error])
{
NSLog(#"copyAppleScript Success!");
}
else
{
NSLog(#"copyAppleScript Failuer!");
}
I hope, when do shell script with NSAppleScript, it will not ask for authentication again for five minutes also.
First, I don't think this is the best way to approach this problem. But let me answer the question first, and then suggest another avenue.
Note that tech note 2065 says this:
The authentication only applies to that specific script: a different
script, or a modified version of the same one, will ask for its own
authentication.
Every time you run this line:
NSAppleScript *copyAppleScript = [[NSAppleScript alloc] initWithSource:copyScript];
...you create a new script, and that new script will need authorization of its own. If you want to reuse a given script without constantly reauthorizing, you'll need to create an NSAppleScript property, store your script there once, and then call it repeatedly. In other words:
#interface MyObject ()
#property (strong) NSAppleScript *myAppleScript;
#end
#implementation MyObject
- (void)setUpScript {
// call this once
NSDictionary *error = nil;
NSString *copyScript = #"do shell script \"/bin/cp -r /Users/Simon/Desktop/Test/test.zip /Users/Simon/Desktop \" with administrator privileges";
self.myAppleScript = [[NSAppleScript alloc] initWithSource:copyScript];
}
- (void)runScript {
// call this as needed; shouldn't need reauthorization
if ([self.myAppleScript executeAndReturnError:&error]) {
NSLog(#"myAppleScript Success!");
} else {
NSLog(#"myAppleScript Failure!");
}
}
#end
However, since your goal here is to run a shell script, I'd suggest you forget about NSAppleScript entirely, and use NSTask instead. NSTask is how shell scripts are meant to be run from objC, and should avoid authorization problems entirely. The easiest way to do this is to write an explicit shell script and store it in the bundle:
#!/bin/bash
cp -r /Users/Simon/Desktop/Test/test.zip /Users/Simon/Desktop
Then call and run that shell script:
NSError *err;
NSTask *task = [NSTask launchedTaskWithExecutableURL:[[NSBundle mainBundle] URLForResource:#"ShellScript"
withExtension:#"sh"]
arguments:[NSArray array]
error:&err
terminationHandler:nil];
...or maybe this, if you don't want to bundle a script...
NSError *err;
NSTask *task = [[NSTask alloc] init]
task.arguments = [NSArray arrayWithObjects:#"#!/bin/bash",
#"-c",
#"cp -r /Users/Simon/Desktop/Test/test.zip /Users/Simon/Desktop",
nil];
[task launchAndReturnError:&err];
P.S.
I've been assuming that this cp command is just proof-of-concept, but if your actual goal is to copy a file, forget about NSAppleScript and NSTask. Use NSFileManager's copyItemAtURL:toURL:error: instead.
My goal is to create an extension that executes clang-format. My code looks something like this:
- (void)performCommandWithInvocation:(XCSourceEditorCommandInvocation *)invocation completionHandler:(void (^)(NSError * _Nullable nilOrError))completionHandler
{
NSError *error = nil;
NSURL *executableURL = [[self class] executableURL];
if (!executableURL)
{
NSString *errorDescription = [NSString stringWithFormat:#"Failed to find clang-format. Ensure it is installed at any of these locations\n%#", [[self class] clangFormatUrls]];
completionHandler([NSError errorWithDomain:SourceEditorCommandErrorDomain
code:1
userInfo:#{NSLocalizedDescriptionKey: errorDescription}]);
return;
}
NSMutableArray *args = [NSMutableArray array];
[args addObject:#"-style=LLVM"];
[args addObject:#"someFile.m"];
NSPipe *outputPipe = [NSPipe pipe];
NSPipe *errorPipe = [NSPipe pipe];
NSTask *task = [[NSTask alloc] init];
task.launchPath = executableURL.path;
task.arguments = args;
task.standardOutput = outputPipe;
task.standardError = errorPipe;
#try
{
[task launch];
}
#catch (NSException *exception)
{
completionHandler([NSError errorWithDomain:SourceEditorCommandErrorDomain
code:2
userInfo:#{NSLocalizedDescriptionKey: [NSString stringWithFormat:#"Failed to run clang-format: %#", exception.reason]}]);
return;
}
[task waitUntilExit];
NSString *output = [[NSString alloc] initWithData:[[outputPipe fileHandleForReading] readDataToEndOfFile]
encoding:NSUTF8StringEncoding];
NSString *errorOutput = [[NSString alloc] initWithData:[[errorPipe fileHandleForReading] readDataToEndOfFile]
encoding:NSUTF8StringEncoding];
[[outputPipe fileHandleForReading] closeFile];
[[errorPipe fileHandleForReading] closeFile];
int status = [task terminationStatus];
if (status == 0)
{
NSLog(#"Success: %#", output);
}
else
{
error = [NSError errorWithDomain:SourceEditorCommandErrorDomain
code:3
userInfo:#{NSLocalizedDescriptionKey: errorOutput}];
}
completionHandler(error);
}
The reason I need that try-catch block is because an exception is thrown when I try to run this code. The exception reason is:
Error: launch path not accessible
The path for my clang-format is /usr/local/bin/clang-format. What I discovered is that it doesn't like me trying to access an application in /usr/local/bin, but /bin is ok (e.g. If I try to execute /bin/ls there is no problem).
Another solution I tried was to run /bin/bash by setting the launch path and arguments like this:
task.launchPath = [[[NSProcessInfo processInfo] environment] objectForKey:#"SHELL"];
task.arguments = #[#"-l", #"-c", #"/usr/local/bin/clang-format -style=LLVM someFile.m"];
This successfully launches the task, but it fails with the following error output:
/bin/bash: /etc/profile: Operation not permitted
/bin/bash: /usr/local/bin/clang-format: Operation not permitted
The first error message is due to trying to call the -l parameter in bash, which tries to log in as the user.
Any idea how I can enable access to those other folders? Is there some kind of sandbox environment setting I need to enable?
I guess that because of the sandboxing this is not possible.
You could bundle the clang-format executable and use it from there.
Personally, I think you are going at it all wrong. Extensions are supposed to be quick (if you watch the video on Xcode extensions he repeats multiple times to get in and get out). And they are severely limited.
However, there is another - the container app may be able to do this processing for your extension without all the hacks. The downside is that you have to pass the buffer to and from the extension.
It’s not easy, but it can be done. Easy peasy way to get your container to run. First, modify the container app’s Info.plist (not the extension Info.plist) so that it has a URL type.
In your extension you can “wake up” the container app by running the following:
let customurl = NSURL.init(string: “yoururlschemehere://")
NSWorkspace.shared().open(customurl as! URL)
As for communication between the two, Apple has a plethora of methods. Me, I’m old-school, so I’m using DistributedNotificationCenter - for the moment.
Although I haven’t tried it, I do not see why the container app should have an issue chatting with clang (I’m using the container app for settings).
Im building an cocoa app that monitors something™ and I am planning to have some hooks for users. So I want to enable the user to put a script (Bash, Ruby, Python you name it) with a specified name (let's say after_event) into the Application Support directory and that script gets executed after a certain event in my code. Ideally I could pass some variables to the script so the script knows what happened.
Any ideas on this?
So problem one is: How do I get the path of the Application Support "the SDK way"? problem two is: How do I execute script with variables like THAT_APPEND="foo"?
Thanks,
Philip
Because sharing is caring here is the method that executes the scripts:
-(void) runScript:(NSString*)scriptName withVariables:(NSDictionary *)variables
{
NSString *appSupportPath = [NSFileManager defaultManager] applicationSupportDirectory];
NSArray *arguments;
NSString* newpath = [NSString stringWithFormat:#"%#/%#",appSupportPath, scriptName];
if ([[NSFileManager defaultManager]fileExistsAtPath:newpath]){
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath: newpath];
NSLog(#"Executing hook: %#",newpath);
arguments = [NSArray arrayWithObjects:newpath, nil];
[task setArguments: arguments];
[task setEnvironment:variables];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
NSData *data;
data = [file readDataToEndOfFile];
NSString *string;
string = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog (#"script returned:\n%#", string);
}
}
}
UPDATE: I updated the code to be more generic. Now NSTask will tell the kernel to execute the script directly so your user can not online use Bash scripts but also python, perl, php whatever she likes. The only thing she needs to use is a Shebang in that file.
The NSFileManager Category can be found here.
Look for NSTask documentation. There's an environment member you can manipulate. Also adding command line parameters in a form -name = value should be trivial.
I have an application that can import an XML file through this terminal command :
open /path/to/main\ app.app --args myXML.xml
This works great with no issues. And i have used Applescript to launch this command through shell and it works just as well. Yet when try using Cocoa's NSTask Launcher using this code :
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:#"/usr/bin/open"];
[task setCurrentDirectoryPath:#"/Applications/MainApp/InstallData/App/"];
[task setArguments:[NSArray arrayWithObjects:[(NSURL *)foundApplicationURL path], #"--args", #"ImportP.xml", nil]];
[task launch];
the applications will start up to the initial screen and then crash when either the next button is clicked or when trying to close the window. Ive tried using NSAppleScript with this :
NSAppleScript *script = [[NSAppleScript alloc] initWithSource:#"tell application \"Terminal\" do script \"open /Applications/MainApp/InstallData/App/Main\\\\ App.app\" end tell"];
NSDictionary *errorInfo;
[script executeAndReturnError:&errorInfo];
This will launch the program and it will crash as well and i get this error in my Xcode debug window :
12011-01-04 17:41:28.296 LaunchAppFile[4453:a0f]
Error loading /Library/ScriptingAdditions/Adobe Unit Types.osax/Contents/MacOS/Adobe Unit Types: dlopen(/Library/ScriptingAdditions/Adobe Unit Types.osax/Contents/MacOS/Adobe Unit Types, 262): no suitable image found.
Did find: /Library/ScriptingAdditions/Adobe Unit Types.osax/Contents/MacOS/Adobe Unit Types: no matching architecture in universal wrapper
LaunchAppFile: OpenScripting.framework - scripting addition "/Library/ScriptingAdditions/Adobe Unit Types.osax" declares no loadable handlers.
So with research i came up with this :
NSAppleScript *script = [[NSAppleScript alloc] initWithSource:#"do shell script \"arch -i386 osascript /Applications/MainApp/InstallData/App/test.scpt\""];
NSDictionary *errorInfo;
[script executeAndReturnError:&errorInfo];
But this causes the same results as the last command.
Any ideas on what causes this crash?
Check here to fix the adobe errors. I'm not sure that's the problem though. Actually I couldn't get the open command to pass arguments to anything so I couldn't look into your problem.
Give NSTask another try with full paths only:
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:#"/usr/bin/open"];
[task setArguments:[NSArray arrayWithObjects:[ #"'/path/to/main app.app'", #"--args", #"/path/to/ImportP.xml", nil]];
[task launch];
Another approach would be to give NSTask the following command line:
sh -c '/usr/bin/open "/path/to/main app.app" --args /path/to/myXML.xml'
...
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:#"/bin/sh"];
NSString *cmd = [NSString stringWithFormat:
#"%# %# %# %#",
#"/usr/bin/open",
#"'/path/to/main app.app'",
#"--args",
#"/path/to/myXML.xml"
];
[task setArguments:[NSArray arrayWithObjects:[ #"-c", cmd, nil]];
[task launch];
In Cocoa, I am trying to implement a button, which when the user clicks on will capture the System profiler report and paste it on the Desktop.
Code
NSTask *taskDebug;
NSPipe *pipeDebug;
taskDebug = [[NSTask alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:selfselector:#selector(taskFinished:) name:NSTaskDidTerminateNotification object:taskDebug];
[profilerButton setTitle:#"Please Wait"];
[profilerButton setEnabled:NO];
[taskDebug setLaunchPath: #"/usr/sbin/system_profiler"];
NSArray *args = [NSArray arrayWithObjects:#"-xml",#"-detailLevel",#"full",#">", #"
~/Desktop/Profiler.spx",nil];
[taskDebug setArguments:args];
[taskDebug launch];
But this does not save the file to the Desktop. Having
NSArray *args = [NSArray arrayWithObjects:#"-xml",#"-detailLevel",#"full",nil]
works and it drops the whole sys profiler output in the Console Window.
Any tips on why this does not work or how to better implement this ? I am trying to refrain from using a shell script or APpleScript to get the system profiler. If nothing work's that would be my final option.
Thanks in advance.
NSArray *args = [NSArray arrayWithObjects:#"-xml",#"-detailLevel",#"full",#">", #"~/Desktop/Profiler.spx",nil];
That won't work because you aren't going through the shell, and > is a shell operator. (Also, ~ isn't special except when you expand it using stringByExpandingTildeInPath.)
Create an NSFileHandle for writing to that Profiler.spx file, making sure to use the full absolute path, not the tilde-abbreviated path. Then, set that NSFileHandle as the task's standard output. This is essentially what the shell does when you use a > operator in it.
This got it done ( thanks to Peter and Costique)
[taskDebug setLaunchPath: #"/usr/sbin/system_profiler"];
NSArray *args = [NSArray arrayWithObjects:#"-xml",#"- detailLevel",#"full",nil];
[taskDebug setArguments:args];
[[NSFileManager defaultManager] createFileAtPath: [pathToFile stringByExpandingTildeInPath] contents: nil attributes: nil];
outFile = [ NSFileHandle fileHandleForWritingAtPath:[pathToFile stringByExpandingTildeInPath]];
[taskDebug setStandardOutput:outFile];
[taskDebug launch];
Create an NSPipe, send [taskDebug setStandardOutput: myPipe] and read from the pipe's file handle.