Workaround to call "airport -s" from Swift OSX app? - xcode

I'm working on an alternative to the wireless network organization and diagnostics utilities provided by Apple. So far it's going fairly well. I'm able to pull and store the current network, some network details, password, and location data and store it in an encrypted realm.
The area I'm running into issues with is being able to access a list of the networks that are currently in range of the computer. I know this can be accessed by using the "airport -s" command from the terminal. My initial plan was to call this command from an NSTask and parse and store the results. However, after hours of tinkering and googling and coming up empty, I finally came upon Objective-C: NSCommand "airport -s" returning empty which seems to reflect what I'm seeing.
I've to get around this by writing a quick bash script to store in my bundle resources and call from an NSTask and return the output, or even to call a script that will write the output from "airport -s" to a .txt file in the bundle resources... but these all seem to fail when initiated from an NSTask even though they all run fine from the terminal.
func runCommand(cmd : String, args : String...) -> (output: [String], error: [String], exitCode: Int32) {
var output : [String] = []
var error : [String] = []
let task = NSTask()
task.launchPath = cmd
task.arguments = args
let outpipe = NSPipe()
task.standardOutput = outpipe
let errpipe = NSPipe()
task.standardError = errpipe
task.launch()
let outdata = outpipe.fileHandleForReading.readDataToEndOfFile()
if var string = String.fromCString(UnsafePointer(outdata.bytes)) {
string = string.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet())
output = string.componentsSeparatedByString("\n")
}
let errdata = errpipe.fileHandleForReading.readDataToEndOfFile()
if var string = String.fromCString(UnsafePointer(errdata.bytes)) {
string = string.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet())
error = string.componentsSeparatedByString("\n")
}
task.waitUntilExit()
let status = task.terminationStatus
return (output, error, status)
}
Is the method I'm using elsewhere to utilize NSTask. I'm attempting get a list of nearby networks with the following code:
func findNearby() -> String {
var checkNearby = sb.runCommand("/bin/sh", args: "-c", "sh '/Users/<username>/Library/Containers/com.user.app/Data/Library/Application Support/getnearby.sh'")
if checkNearby.output != [] {
return checkNearby.output[0]
}
return "could not access airport"
}
Is there an issue with my implementation of NSTask that is causing these scripts to time out, or is Apple really going through all the trouble to stop me from calling "airport -s" from NSTask?
Are there any alternatives to "airport -s" I could try?
Note: I know I'm not really storing the .sh file in my Resources, this location was just temporary while I tried to figure out a solution.

Related

MacOS xcrun error preventing from invoking a shell script

I want to write a simple app that puts the result of the command
/usr/bin/strings myfile
into a label in the normal MacOS interface. I am receiving this error
xcrun: error: cannot be used within an App Sandbox.
I tried to use the info here:
https://forums.developer.apple.com/thread/73554
and here:
ANY possible way to run the clang compiler from a Sandboxed app?
The actual piece of code invoking the instruction is:
let path = "/usr/bin/strings"
let arguments = ["/path/to/my/file"]
let task = Process()
task.arguments = arguments
task.executableURL = URL(fileURLWithPath: path)
let outputPipe = Pipe()
let errorPipe = Pipe()
task.standardOutput = outputPipe
task.standardError = errorPipe
do {
try task.run()
let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
let output = String(decoding: outputData, as: UTF8.self)
let error = String(decoding: errorData, as: UTF8.self)
testo.stringValue="OUTPUT: \(output)\nERROR: \(error)"
} catch {
lbl.stringValue="Error somewhere"
}
Anyone does know how to solve this?
The problem is not the command (/usr/bin/strings) but the access to the filesystem. In fact, replacing the first two lines with:
let path = "/sbin/ping"
let arguments = ["-c", "1", "www.google.com"]
for instance, and allowing for outbound connections, would work fine. Hence, it is important that the application is allowed to access the path of the file to be processed, by means of the Sandbox.

NSTask : Couldn't posix_spawn: error 13 when launching app

I have a sub-app in my main Swift app. I made it so it's copied automatically in the Resources folder of the main app when building it. That way, I want to be able to launch an instance of the sub-app from the main app.
The thing is, I'm having an error that is hard to debug/find answers about.
Here is my code :
let args = ["--args", "-admin_url", site.url, "-login", site.login, "-pass", site.password]
let helperPath = (NSBundle.mainBundle().pathForResource("App Helper", ofType: "app"))!
let task = NSTask.init()
task.launchPath = helperPath
task.arguments = args
task.launch()
And the error :
[56490:7218926] Couldn't posix_spawn: error 13
I have no idea where to look, what to search for. I don't know what I'm doing wrong.
I'm wondering if the issue is related to the sub-app itself. That sub-app is empty for now. I set Application is Agent to YES. And in MainMenu.xib, I set the Visible at launch option to no.
That sub-app needs to do some work in the background and doesn't need any UI at all.
Thanks !
Don't use NSTask for this, use NSWorkspace:
let helperAppURL = NSBundle.mainBundle().URLForResource("App Helper",
withExtension:"app")!
_ = try? NSWorkspace.sharedWorkspace().openURL(helperAppURL,
options:[.Default],
configuration:[NSWorkspaceLaunchConfigurationArguments :
["--args", "-admin_url", site.url, "-login",
site.login, "-pass", site.password]])
In the above code, for brevity, I ignored the result of the openURL() command, but in reality it can return an instance of NSRunningApplication which represents the task.
To keep track of the instances of your helper app you launch, you could keep references to this NSRunningApplication in an appropriate kind of collection class, and when the time comes, call its terminate() method.
the launch() function is deprecated, using run()
func shell(_ command: String) -> String {
let task = Process()
task.launchPath = "/usr/bin/"
task.arguments = ["-c", command]
let pipe = Pipe()
task.standardOutput = pipe
if #available(macOS 10.13, *) {
try? task.run()
} else {
task.launch()
}
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String
return output
}
or using swift-commands
import Commands
Commands.Bash.run("say hello")

Swift 2.1 OSx shell commands using NSTask work when run from xcode, but not when exported

I wrote a simple OSx (10.11) application to execute shell commands when a button is pressed. It works when I run it from xcode, but when I export the application via "archive", one of the buttons no longer works. I don't get an error and I don't get any output either. I am using absolute paths so I don't understand why it works in xcode but not as an exported application, Nor do I understand why one button works and the other doesn't.
Here is the main function that I am using the make the shell commands
func runCommand(path : String, args : [String]) -> (output: [String], error: [String], exitCode: Int32) {
var output : [String] = []
var error : [String] = []
let task = NSTask()
task.launchPath = path
task.arguments = args
let outpipe = NSPipe()
task.standardOutput = outpipe
let errpipe = NSPipe()
task.standardError = errpipe
task.launch()
let outdata = outpipe.fileHandleForReading.readDataToEndOfFile()
if var string = String.fromCString(UnsafePointer(outdata.bytes)) {
string = string.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet())
output = string.componentsSeparatedByString("\n")
}
let errdata = errpipe.fileHandleForReading.readDataToEndOfFile()
if var string = String.fromCString(UnsafePointer(errdata.bytes)) {
string = string.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet())
error = string.componentsSeparatedByString("\n")
}
//task.waitUntilExit()
let status = task.terminationStatus
return (output, error, status)
}
and here is the button that works:
// Check for configurator 2 app installation
let (output, error, status) = self.runCommand("/bin/bash", args: ["-c", "/bin/ls", "/Applications/Apple Configurator 2.app"])
and here is the button that doesn't:
// Check if the phone is plugged in and paired
let (output, error, status) = self.runCommand("/bin/bash", args: ["-c", "/usr/local/bin/cfgutil", "get", "isPaired"])
What is even more strange, I discovered (through sheer frustration) that if I repeatedly click the button that doesn't work, it will sometimes eventually work.
Your issue is the result of two things happening together:
you return default values
you don't specify alternative branches for the control flow
What happens is that it hides potential failures, and leads to code that is very hard to debug, as you experienced.
A possible solution with your existing code is to cover all possible ways, meaning providing else branches to your if var string = String.fromCString(UnsafePointer(errdata.bytes)) conditions, where you will handle errors.
Thanks to Eric D. I simplified my code and now everything is working.
func runCommand(path : String, args : [String]) -> (output: NSString, error: NSString, exitCode: Int32) {
let task = NSTask()
task.launchPath = path
task.arguments = args
let outpipe = NSPipe()
task.standardOutput = outpipe
let errpipe = NSPipe()
task.standardError = errpipe
task.launch()
let outdata = outpipe.fileHandleForReading.readDataToEndOfFile()
let output = NSString(data: outdata, encoding: NSUTF8StringEncoding)
let errdata = errpipe.fileHandleForReading.readDataToEndOfFile()
let error_output = NSString(data: errdata, encoding: NSUTF8StringEncoding)
task.waitUntilExit()
let status = task.terminationStatus
return (output!, error_output!, status)
}

Calling xcodebuild from Swift command line tool fails

I'm writing simple swift command line tool application that automates some of my iOS Dev work. Everything is working except one thing - calling xcodebuild script.
My method looks like this:
func runTest(deviceName: String, os: String) {
killAllSimulators()
let sdk = "iphonesimulator"
let destination = "platform=iOS Simulator,name=" + deviceName + ",OS=" + os
let arguments = ["-workspace", workspace, "-scheme", scheme, "-sdk", sdk, "-destination", destination, "test"]
executor.executeCommand("xcodebuild", arguments: arguments)
}
deviceName is equal to "iPhone 4s", os is equal to "9.0", workspace and scheme variables are properly set.
My execute command has simple implementation:
func executeCommand(command: String, arguments: [String]) -> NSString? {
_ = NSProcessInfo.processInfo().processIdentifier
let pipe = NSPipe()
let fileHandle = pipe.fileHandleForReading
let task = NSTask()
task.launchPath = "/usr/bin/" + command
task.arguments = arguments
task.standardOutput = pipe
task.launch()
let data = fileHandle.readDataToEndOfFile()
fileHandle.closeFile()
return NSString(data: data, encoding: NSUTF8StringEncoding)
}
i got error that test failed because cdtool cannot compile. Thats very weird because exactly the same script called from terminal works..
Any ideas?

OS X return to Xcode swift from shell

I have code which invokes shell. Shell does some testing of the arguments and if they pass, it runs.
Right now, I post any errors to a log file but would like to return them to my swift program ...
let bundle = NSBundle.mainBundle()
let cmd = bundle.pathForResource("model", ofType: "sh")
let task = NSTask()
task.launchPath = cmd
task.arguments = [ "\(arg1.stringValue)", "\(arg2.stringValue)" ]
task.launch()
This works but how do I get the output of the shell short of reading the log file created in the shell.
Ran into this. Hope it helps.
http://practicalswift.com/2014/06/25/how-to-execute-shell-commands-from-swift/
#!/usr/bin/env xcrun swift -i
import Foundation
let task = NSTask()
task.launchPath = "/bin/echo"
task.arguments = ["first-argument", "second-argument"]
let pipe = NSPipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output: String = NSString(data: data, encoding: NSUTF8StringEncoding)
print(output)
assert(output == "first-argument second-argument\n")
A project posted at github may be useful also:
https://github.com/kareman/SwiftShell

Resources