Updating your app package installer to use the new Service Management API
Learn about the Service Management API with a GUI-less agent app.
Overview
This sample code project demonstrates how to structure your app package installer and use the new Service Management APIs. The sample project places the helper executable’s launchd property lists in the helper executable’s bundle, rather than relying on shared locations like /Library/LaunchDaemons. The advantages of this approach include containing your LaunchDaemon and LaunchAgent property lists in a fully codesigned app bundle that neither the system nor a third party can modify without breaking the code signature. Users can also see which app is providing the launch daemons or launch agents by choosing System Settings > General > Login Items.
This sample project contains three targets:
SampleLaunchAgent, which builds a sampleLaunchAgenthelper executable.SMAppServiceSampleCode, which builds a GUI-less app that contains the sample launch agent binary property list and an executable to register the agent at install time.SMAppServiceSamplePackage, which builds theSMAppServiceSample.pkginstaller you use to install theSampleLaunchAgenthelper executable.
In addition to demonstrating the new package structure, this sample demonstrates the APIs for registering helper executables. To register a launch agent, the sample creates an agent object, SMAppService.agent(plistName:) and then calls register().
let service = SMAppService.agent(plistName: "com.xpc.example.agent.plist")
do {
try service.register()
print("Successfully registered \(service)")
} catch {
print("Unable to register \(error)")
exit(1)
}To unregister a launch agent, the sample creates an agent object, SMAppService.agent(plistName:) and then calls unregister().
let service = SMAppService.agent(plistName: "com.xpc.example.agent.plist")
do {
try service.unregister()
print("Successfully unregistered \(service)")
} catch {
print("Unable to unregister \(error)")
exit(1)
}To determine the authorization state of a launch agent, the sample creates an agent object, SMAppService.agent(plistName:) and then calls status() to determine the helper executable’s authorization state.
let service = SMAppService.agent(plistName: "com.xpc.example.agent.plist")
print("\(service) has status \(service.status)")Configure the sample code project
To set the Team ID for the sample app and package targets, follow these steps:
Open
SMAppServiceSampleCode.xcodeprojin Xcode.Select the
SMAppServiceSampleCodetarget from the project editor.Click Signing & Capabilities.
Choose your team from the Team pop-up menu.
Select the
SMAppServiceSamplePackagefrom the project editor and repeat step 4.
Build the sample app service
Open Terminal and change to the directory into which you downloaded the
UpdatingYourAppPackageInstallerToUseTheNewServiceManagementAPIsource code.Run the command
xcodebuild -target SMAppServiceSamplePackage.
Xcode builds the SMAppServiceSamplePackage installer package, and the process concludes with Xcode printing ** BUILD SUCCEEDED ** to the terminal.
Install the login item and launch agent
In Terminal, navigate to the
build/Releasedirectory using the commandcd build/Release.Open the build directory in Finder with the command
open ..The Finder window contains all the products that the Xcode build process creates, including a package installer named
SMAppServiceSample. Double-click the installer icon to run it, and follow the instructions to approve the installation of theSMAppServiceSampleCodelogin item and its supportingSampleLaunchAgenthelper executable.
Run the sample launch agent
The installer adds a new login item in System Settings and installs the sample launch agent SampleLaunchAgent in the directory /Library/Application Support/YOURDEVELOPERNAME. Run the SampleLaunchAgent executable from the Terminal by invoking the command line app inside the app bundle with an additional command argument:
/Library/Application Support/YOURDEVELOPERNAME/SMAppServiceSampleCode.app/Contents/MacOS/SMAppServiceSampleCode COMMAND
The SMAppServiceSampleCode app supports the following commands:
register— Registers theSampleLaunchAgent.unregister— Unregisters theSampleLaunchAgent.status— Checks the authorization status of theSampleLaunchAgent.test— Sends an XPC message to theSampleLaunchAgentand displays the reply.
The output from the app resembles the following, for each of the app’s commands:
% /Library/Application Support/YOURDEVELOPERNAME/SMAppServiceSampleCode.app/Contents/MacOS/SMAppServiceSampleCode register
Successfully registered LaunchAgent(com.xpc.example.agent.plist)
% /Library/Application Support/YOURDEVELOPERNAME/SMAppServiceSampleCode.app/Contents/MacOS/SMAppServiceSampleCode status
LaunchAgent(com.xpc.example.agent.plist) has status SMAppServiceStatus(rawValue: 1)
% /Library/Application Support/YOURDEVELOPERNAME/SMAppServiceSampleCode.app/Contents/MacOS/SMAppServiceSampleCode test
Received "Hello World"
% /Library/Application Support/YOURDEVELOPERNAME/SMAppServiceSampleCode.app/Contents/MacOS/SMAppServiceSampleCode unregister
Successfully unregistered LaunchAgent(com.xpc.example.agent.plist)Running the app before registering the service, or after unregistering the service, results in an error.
% /Library/Application Support/YOURDEVELOPERNAME/SMAppServiceSampleCode.app/Contents/MacOS/SMAppServiceSampleCode test
Unable to create xpc_session <OS_xpc_rich_error: Can Retry: 0. Description:"Underlying connection was invalidated. Reason: failed at lookup with error 3 - No such process">Uninstall the login item and launch agent
To uninstall the sample app’s login item from System Settings, as well as the SampleLaunchAgent, use the following command:
sudo rm -rf /Library/Application Support/YOURDEVELOPERNAME
The login item may remain visible in the System Settings > General > Login Items for some time. This is expected because the system removes deleted items as part of its maintenance processes overnight. To remove all third-party login items and reset the System Settings > General > Login Items to its installation defaults, use the sfltool: sudo sfltool resetbtm.