Wrapping iOS apps with the Intune App Wrapping Tool: a practical guide
How to wrap an iOS app for Intune without touching its code: prerequisites, signing, MSAL settings, which app protection policies a wrapped app supports, and the three failures that cost me the most time.
At Nimway we needed a version of the app that enterprise customers could manage with Microsoft
Intune: their own PIN rules, their own limits on copy and paste, their own wipe. The app had no
Intune code in it, and the fastest way to get there was the Intune App Wrapping Tool for iOS.
You give it a signed app, a provisioning profile and a signing certificate. It gives back a new
.ipa with Intune’s app protection layer built in, signed and ready to upload.
Microsoft’s documentation covers the happy path well. This post is the guide I wanted at the time: what wrapping can and can’t do, what you need before you start, how to run the tool, and the three things that broke for me and aren’t obvious from the docs.
Wrapping tool or Intune App SDK?
Intune has two ways to make an iOS app respond to app protection policies:
- The App Wrapping Tool works on a finished app, with no source code. Microsoft positions it for internal line-of-business apps.
- The Intune App SDK is a framework you integrate into the app’s code. It’s meant for apps in the App Store, but any app can use it.
The wrapping tool doesn’t work on App Store apps at all: it needs an unencrypted app, signed by your company or by the vendor who built it for you. An app downloaded from the App Store is encrypted, so wrapping is only an option for apps you distribute yourself.
Most policy settings work the same way in a wrapped app as with the SDK: PIN or Face ID to open the app, limits on sending and receiving data, cut, copy and paste restrictions, encrypted app data, blocking jailbroken devices, offline grace period, full wipe of app data, minimum app and OS versions. A few need code in the app and are SDK only:
| Feature | SDK | Wrapping tool |
|---|---|---|
| Prevent “Save as” | yes | no |
| App configuration through the MAM channel | yes | no |
| Multi-identity (work and personal accounts in one app) | yes | no |
| Selective wipe of work data in multi-identity apps | yes | no |
| Conditional Access “Require app protection policy” | yes | no |
| Mobile Threat Defense device risk level | yes | no |
| Customizable style | yes | no |
If your customers need any of these, wrapping is the wrong choice and you should budget for the SDK. If the app is internal, gets released a few times a year and needs PIN, data transfer and copy-paste rules, wrapping gets you there without changing the code.
What you need before you start
From the prerequisites in Microsoft’s documentation:
- A Mac with Xcode 16 or later. The tool is a macOS command-line app.
- The right version of the tool. Releases are tied to the Xcode version the app was built with. At the time of writing (October 2026), the 22.x releases are for apps compiled with Xcode 27 and the 21.x releases are for Xcode 26. Check the release notes on GitHub before every wrap.
- The input app: an
.ipaor.app, signed, built for iOS 16 or later, not encrypted, with no extended file attributes. Its entitlements must already be set. - Signing for distribution: an Apple Developer Enterprise Program account, an In-House distribution certificate and an In-House provisioning profile. For internal testing, a development certificate works too, as long as the profile includes the test devices.
- An app registration in Microsoft Entra ID. Wrapped apps sign in with MSAL, so the tool needs your app’s client ID and redirect URI.
Register the app in Entra ID
- Register the app in Microsoft Entra ID and note the Application (client) ID.
- Add a redirect URI for iOS in the MSAL format:
msauth.<bundle id>://auth. - Give the app access to the Intune MAM service: in the app registration, add a permission from
APIs my organization uses, find Microsoft Mobile Application Management and select the
delegated permission
DeviceManagementManagedApps.ReadWrite.
The client ID (-ac) and redirect URI (-ar) are required, and each app needs its own pair. The
authority (-aa, for example https://login.microsoftonline.com/<tenant ID>/) is only required
for single-tenant apps.
If you’re updating old scripts: older guides and scripts use ADAL settings. ADAL reached end of support in 2023, and the current tool only takes the MSAL ones:
MSAL Client ID,MSAL Reply URIandMSAL Authorityin the plist, or-ac,-arand-aaon the command line.
Running the tool
The tool ships as a disk image from Microsoft’s GitHub repository. Mount it and you get
IntuneMAMPackager. You can pass everything as flags, but a plist file is easier to keep in
version control and to run from CI. The template is Parameters.plist next to the binary:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Input Application Package Path</key>
<string>/path/to/MyApp.ipa</string>
<key>Output Application Package Path</key>
<string>/path/to/MyApp_Wrapped.ipa</string>
<key>Provisioning Profile Path</key>
<string>/path/to/MyApp_InHouse.mobileprovision</string>
<key>SHA-1 Certificate Hash</key>
<string>12A3BC45D67EF8901A2B3CDEF4ABC5D6E7890FAB</string>
<key>MSAL Client ID</key>
<string>xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx</string>
<key>MSAL Reply URI</key>
<string>msauth.com.example.myapp://auth</string>
<key>Extension Provisioning Profile Paths</key>
<array>
<string>/path/to/MyAppWidget_InHouse.mobileprovision</string>
</array>
<key>Verbose Enabled</key>
<true/>
</dict>
</plist>./IntuneMAMPackager/Contents/MacOS/IntuneMAMPackager -f Parameters.plistThe same run with flags:
./IntuneMAMPackager/Contents/MacOS/IntuneMAMPackager \
-i ~/Desktop/MyApp.ipa -o ~/Desktop/MyApp_Wrapped.ipa \
-p ~/Desktop/MyApp_InHouse.mobileprovision \
-c "12A3BC45D67EF8901A2B3CDEF4ABC5D6E7890FAB" \
-ac "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -ar "msauth.com.example.myapp://auth" \
-x ~/Desktop/MyAppWidget_InHouse.mobileprovision \
-vAlways turn on verbose output (-v, or Verbose Enabled in the plist). Without it, the errors
are often too short to tell what went wrong. On success the tool prints “The application was
successfully wrapped”.
Two more flags worth knowing:
-bsets the bundle version (CFBundleVersion) of the wrapped app. By default the tool changes it;-bwithout a value keeps the original, which Microsoft doesn’t recommend.-eremoves entitlements that the provisioning profile doesn’t have. It makes the wrap succeed, but the features behind those entitlements stop working. Use it for a vendor app that asks for more than it needs, not to silence an error in your own app.
Three things that broke
Settings.bundle with a malformed Root.plist
The first run failed with:
Failed to extract Root.plist from the app's settings bundle.
Found non-key inside <dict> at line 7
Malformed data byte group at line 1; invalid hexA wrapped app gets a Microsoft Intune section in its page in the Settings app (that’s where the
diagnostics console lives), so the tool reads the app’s Settings.bundle. Our Root.plist had a
PreferenceSpecifiers key with no value after it:
<!-- broken: the key has no value -->
<dict>
<key>PreferenceSpecifiers</key>
<key>StringsTable</key>
<string>Root</string>
</dict>iOS shrugged that off; the wrapping tool didn’t. An empty array is enough:
<!-- fixed -->
<dict>
<key>PreferenceSpecifiers</key>
<array/>
<key>StringsTable</key>
<string>Root</string>
</dict>Check the plist in the exported app before you wrap it:
unzip -p MyApp.ipa "Payload/MyApp.app/Settings.bundle/Root.plist" | plutil -lint -While you’re there, make sure Settings.bundle belongs to the main app target only, not to any
extension. It’s easy to tick the wrong box when you add a target.
Every extension needs its own provisioning profile
The tool re-signs every extension in the app (widgets, share extensions, notification service extensions), so each one needs a provisioning profile for its own bundle ID. If one is missing, the wrap stops:
There are more extensions than specified provisioning profiles.
Each extension must have a corresponding provisioning profile.To see which extensions the app has and which entitlements each of them needs:
./IntuneMAMPackager/Contents/MacOS/IntuneMAMPackager -xe -i MyApp.ipaThen add one profile per extension to Extension Provisioning Profile Paths (or -x). The
entitlements in each profile have to match what the extension uses: app groups, keychain sharing
and push notifications are the usual suspects. To compare them, look at what the app is signed
with and what the profile allows:
codesign -d --entitlements :- "Payload/MyApp.app"
security cms -D -i "Payload/MyApp.app/embedded.mobileprovision"Watch the expiry dates. The tool doesn’t check whether a profile or a certificate has expired. It wraps and signs the app anyway, and you find out only when the app refuses to install on a device.
The signing certificate
The tool identifies the signing certificate by its SHA-1 hash (-c, or SHA-1 Certificate Hash
in the plist). Old scripts and guides that pass a certificate name don’t work. To list the hashes
of the signing identities in your keychain:
security find-identity -v -p codesigningEach line starts with the 40-character hash, followed by the certificate name. You can also copy it in Keychain Access: open the certificate, Get Info, and look under Fingerprints.
What cost me time was having the same certificate in the keychain more than once, which happens easily after renewals and re-imports. The tool then fails with “An invalid signing certificate was specified”, even though the certificate is fine. Delete the duplicates, keep one copy with its private key, and run again. If the certificate and profile can sign the app in Xcode, they’re good enough for the tool.
Deploying the wrapped app and its policies
- Upload the app. In the Intune admin center, add it under Apps as an iOS/iPadOS
line-of-business app and upload the wrapped
.ipa. When you update an app that’s already there and the upload fails, Microsoft’s advice is to add it as a new app and delete the old one. - Create an app protection policy. Create an iOS/iPadOS app protection policy and, in the app selection, add your app under custom apps by its bundle ID. A custom app can’t be combined with “all public apps” in the same policy. Assign the policy to the user groups that should get it.
- Start with relaxed settings. For the first test, use a long PIN timeout, allow Face ID and leave copy and paste open. Tighten the settings once the basic flow works: debugging policy behaviour and integration problems at the same time is no fun.
- Test on a device. Install the app and open it. It should ask for a work account sign-in and then apply the policy, for example by asking you to set a PIN. If it opens like the unwrapped app, the wrap didn’t take effect.
A few behaviours surprise testers:
- Encryption of app data only works when the device has a passcode.
- By default, a wrapped app asks for the work account again when it’s relaunched.
- File upload dialogs can be used to get around copy and paste restrictions, for example by uploading a screenshot. Microsoft lists this as a known limitation.
- When something goes wrong on a device, open the app’s page in the Settings app, then Microsoft Intune, and turn on Display Diagnostics Console. The next launch lets you collect and share the Intune logs.
Rewrapping
You don’t wrap once. You wrap again:
- For every release of your app. Always wrap the original build: the tool refuses an app it has already wrapped.
- When Microsoft releases a new version of the tool. That happens every 6–8 weeks, with bug fixes and support for new policy settings and new iOS versions. A new Xcode version usually means a new major version of the tool.
You can wrap with a different certificate or profile than the one the app was originally signed with, but the new profile must have all the entitlements the app uses.
What I’d do differently
I’d put the wrap in the release pipeline from the first day instead of running it by hand. The inputs never change between runs: the app, the profiles, the certificate hash, the client ID and the redirect URI. Scripting them removes the whole class of mistakes that come from picking the wrong profile or the certificate you forgot to renew. Pin the tool version in the same script and update it on purpose, after reading the release notes.
And I’d keep a calendar reminder for certificate and profile expiry. The tool won’t warn you, and the first sign of trouble is a release that installs on nobody’s device.
Sources
- Microsoft Learn: Prepare iOS apps for app protection policies with the Intune App Wrapping Tool, Prepare apps for mobile application management (wrapping tool and SDK feature comparison).
- GitHub: Microsoft Intune App Wrapping Tool for iOS and its release notes.
- MSAL for iOS and macOS: redirect URI format.