Skip to content

Apps, files & profiles

App-container file access, configuration profiles, and provisioning profiles. (InstallationProxyService and AfcService live under Lockdown services.)

pymobiledevice3.services.house_arrest.HouseArrestService

Bases: AfcService

AFC access to an installed application's container.

house_arrest vends an application sandbox over the AFC protocol, allowing the container's files to be browsed and transferred. The container is selected by calling send_command (or via the create factory) with the target app's bundle id; documents_only controls whether the whole container or only its Documents subtree is exposed.

Being an AfcService, instances may be used as an async context manager::

async with await HouseArrestService.create(lockdown, bundle_id) as house_arrest:
    ...
Source code in pymobiledevice3/services/house_arrest.py
class HouseArrestService(AfcService):
    """
    AFC access to an installed application's container.

    house_arrest vends an application sandbox over the AFC protocol, allowing the
    container's files to be browsed and transferred. The container is selected by
    calling `send_command` (or via the `create` factory) with the target app's
    bundle id; ``documents_only`` controls whether the whole container or only its
    ``Documents`` subtree is exposed.

    Being an `AfcService`, instances may be used as an async context manager::

        async with await HouseArrestService.create(lockdown, bundle_id) as house_arrest:
            ...
    """

    SERVICE_NAME = "com.apple.mobile.house_arrest"
    RSD_SERVICE_NAME = "com.apple.mobile.house_arrest.shim.remote"

    def __init__(self, lockdown: LockdownServiceProvider, documents_only: bool = False):
        if isinstance(lockdown, LockdownClient):
            super().__init__(lockdown, self.SERVICE_NAME)
        else:
            super().__init__(lockdown, self.RSD_SERVICE_NAME)
        self.documents_only = documents_only

    @classmethod
    async def create(
        cls, lockdown: LockdownServiceProvider, bundle_id: str, documents_only: bool = False
    ) -> "HouseArrestService":
        """
        Create a service already vending the container of the given application.

        Instantiates the service and immediately issues the vend command, leaving the AFC
        session rooted at the application's container. On failure the service is closed
        before the error propagates.

        :param lockdown: service provider used to start the service and reach the device.
        :param bundle_id: bundle identifier of the application whose container to vend.
        :param documents_only: when True, vend only the container's ``Documents`` subtree
            (``VendDocuments``); otherwise vend the whole container (``VendContainer``).
        :returns: a connected `HouseArrestService` rooted at the application's container.
        :raises AppNotInstalledError: if no application with ``bundle_id`` is installed.
        :raises PyMobileDevice3Exception: if the device reports any other vend error.
        """
        service = cls(lockdown, documents_only=documents_only)
        cmd = VEND_DOCUMENTS if documents_only else VEND_CONTAINER
        try:
            await service.send_command(bundle_id, cmd)
        except PyMobileDevice3Exception:
            await service.close()
            raise
        return service

    async def send_command(self, bundle_id: str, cmd: str = "VendContainer") -> None:
        """
        Vend an application's container so subsequent AFC operations act on it.

        :param bundle_id: bundle identifier of the application to vend.
        :param cmd: vend command, either ``VendContainer`` (whole container) or
            ``VendDocuments`` (only the ``Documents`` subtree).
        :raises AppNotInstalledError: if no application with ``bundle_id`` is installed.
        :raises PyMobileDevice3Exception: if the device reports any other error.
        """
        response = await self.service.send_recv_plist({"Command": cmd, "Identifier": bundle_id})
        error = response.get("Error")
        if error:
            if error == "ApplicationLookupFailed":
                raise AppNotInstalledError(f"No app with bundle id {bundle_id} found")
            else:
                raise PyMobileDevice3Exception(error)

    def shell(self) -> None:
        """
        Launch an interactive AFC shell over the vended container.

        The shell starts in the container's ``Documents`` directory when the service was
        created with ``documents_only``, otherwise at the container root.
        """
        AfcShell.create(self.lockdown, service=self, auto_cd=DOCUMENTS_ROOT if self.documents_only else "/")

create async classmethod

create(lockdown: LockdownServiceProvider, bundle_id: str, documents_only: bool = False) -> HouseArrestService

Create a service already vending the container of the given application.

Instantiates the service and immediately issues the vend command, leaving the AFC session rooted at the application's container. On failure the service is closed before the error propagates.

Parameters:

Name Type Description Default
lockdown LockdownServiceProvider

service provider used to start the service and reach the device.

required
bundle_id str

bundle identifier of the application whose container to vend.

required
documents_only bool

when True, vend only the container's Documents subtree (VendDocuments); otherwise vend the whole container (VendContainer).

False

Returns:

Type Description
HouseArrestService

a connected HouseArrestService rooted at the application's container.

Raises:

Type Description
AppNotInstalledError

if no application with bundle_id is installed.

PyMobileDevice3Exception

if the device reports any other vend error.

Source code in pymobiledevice3/services/house_arrest.py
@classmethod
async def create(
    cls, lockdown: LockdownServiceProvider, bundle_id: str, documents_only: bool = False
) -> "HouseArrestService":
    """
    Create a service already vending the container of the given application.

    Instantiates the service and immediately issues the vend command, leaving the AFC
    session rooted at the application's container. On failure the service is closed
    before the error propagates.

    :param lockdown: service provider used to start the service and reach the device.
    :param bundle_id: bundle identifier of the application whose container to vend.
    :param documents_only: when True, vend only the container's ``Documents`` subtree
        (``VendDocuments``); otherwise vend the whole container (``VendContainer``).
    :returns: a connected `HouseArrestService` rooted at the application's container.
    :raises AppNotInstalledError: if no application with ``bundle_id`` is installed.
    :raises PyMobileDevice3Exception: if the device reports any other vend error.
    """
    service = cls(lockdown, documents_only=documents_only)
    cmd = VEND_DOCUMENTS if documents_only else VEND_CONTAINER
    try:
        await service.send_command(bundle_id, cmd)
    except PyMobileDevice3Exception:
        await service.close()
        raise
    return service

send_command async

send_command(bundle_id: str, cmd: str = 'VendContainer') -> None

Vend an application's container so subsequent AFC operations act on it.

Parameters:

Name Type Description Default
bundle_id str

bundle identifier of the application to vend.

required
cmd str

vend command, either VendContainer (whole container) or VendDocuments (only the Documents subtree).

'VendContainer'

Raises:

Type Description
AppNotInstalledError

if no application with bundle_id is installed.

PyMobileDevice3Exception

if the device reports any other error.

Source code in pymobiledevice3/services/house_arrest.py
async def send_command(self, bundle_id: str, cmd: str = "VendContainer") -> None:
    """
    Vend an application's container so subsequent AFC operations act on it.

    :param bundle_id: bundle identifier of the application to vend.
    :param cmd: vend command, either ``VendContainer`` (whole container) or
        ``VendDocuments`` (only the ``Documents`` subtree).
    :raises AppNotInstalledError: if no application with ``bundle_id`` is installed.
    :raises PyMobileDevice3Exception: if the device reports any other error.
    """
    response = await self.service.send_recv_plist({"Command": cmd, "Identifier": bundle_id})
    error = response.get("Error")
    if error:
        if error == "ApplicationLookupFailed":
            raise AppNotInstalledError(f"No app with bundle id {bundle_id} found")
        else:
            raise PyMobileDevice3Exception(error)

shell

shell() -> None

Launch an interactive AFC shell over the vended container.

The shell starts in the container's Documents directory when the service was created with documents_only, otherwise at the container root.

Source code in pymobiledevice3/services/house_arrest.py
def shell(self) -> None:
    """
    Launch an interactive AFC shell over the vended container.

    The shell starts in the container's ``Documents`` directory when the service was
    created with ``documents_only``, otherwise at the container root.
    """
    AfcShell.create(self.lockdown, service=self, auto_cd=DOCUMENTS_ROOT if self.documents_only else "/")

pymobiledevice3.services.mobile_config.MobileConfigService

Bases: LockdownService

Manage configuration profiles and cloud (supervision) configuration.

Wraps the com.apple.mobile.MCInstall lockdown service used by MDM/Apple Configurator. It can list, install and remove configuration profiles, drive supervision and cloud-configuration state, and erase the device. Some operations require supervised/silent installation, which is unlocked by escalate using a supervision identity (a PEM keybag holding both certificate and private key).

Being a LockdownService, instances may be used as an async context manager::

async with MobileConfigService(lockdown) as mc:
    profiles = await mc.get_profile_list()
Source code in pymobiledevice3/services/mobile_config.py
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
class MobileConfigService(LockdownService):
    """
    Manage configuration profiles and cloud (supervision) configuration.

    Wraps the ``com.apple.mobile.MCInstall`` lockdown service used by MDM/Apple
    Configurator. It can list, install and remove configuration profiles, drive
    supervision and cloud-configuration state, and erase the device. Some operations
    require supervised/silent installation, which is unlocked by `escalate` using a
    supervision identity (a PEM keybag holding both certificate and private key).

    Being a `LockdownService`, instances may be used as an async context manager::

        async with MobileConfigService(lockdown) as mc:
            profiles = await mc.get_profile_list()
    """

    SERVICE_NAME = "com.apple.mobile.MCInstall"
    RSD_SERVICE_NAME = "com.apple.mobile.MCInstall.shim.remote"

    def __init__(self, lockdown: LockdownServiceProvider) -> None:
        if isinstance(lockdown, LockdownClient):
            super().__init__(lockdown, self.SERVICE_NAME)
        else:
            super().__init__(lockdown, self.RSD_SERVICE_NAME)

    async def hello(self) -> None:
        """Perform the ``HelloHostIdentifier`` handshake with the service."""
        await self._send_recv({"RequestType": "HelloHostIdentifier"})

    async def flush(self) -> None:
        """Issue a ``Flush`` request to the service."""
        await self._send_recv({"RequestType": "Flush"})

    async def escalate(self, keybag_file: Path) -> None:
        """
        Authenticate as a supervisor to unlock silent/supervised operations.

        Runs the ``Escalate`` challenge-response handshake using the supervision identity,
        then requests keybag migration. Must be called before silent profile installation.

        :param keybag_file: path to a PEM file containing both the supervisor certificate
            and its (unencrypted) private key.
        """
        with open(keybag_file, "rb") as keybag_file:
            keybag_file = keybag_file.read()
        private_key = serialization.load_pem_private_key(keybag_file, password=None)
        cer = x509.load_pem_x509_certificate(keybag_file)
        public_key = cer.public_bytes(Encoding.DER)
        escalate_response = await self._send_recv({"RequestType": "Escalate", "SupervisorCertificate": public_key})
        signed_challenge = (
            PKCS7SignatureBuilder()
            .set_data(escalate_response["Challenge"])
            .add_signer(cer, private_key, hashes.SHA256())
            .sign(Encoding.DER, [])
        )
        await self._send_recv({"RequestType": "EscalateResponse", "SignedRequest": signed_challenge})
        await self._send_recv({"RequestType": "ProceedWithKeybagMigration"})

    async def get_stored_profile(self, purpose: Purpose = Purpose.PostSetupInstallation) -> dict:
        """
        Retrieve a profile stored on the device for a given purpose.

        :param purpose: purpose the profile was stored under.
        :returns: the device's response plist.
        """
        return await self._send_recv({"RequestType": "GetStoredProfile", "Purpose": purpose.value})

    async def store_profile(self, profile_data: bytes, purpose: Purpose = Purpose.PostSetupInstallation) -> None:
        """
        Store a profile on the device for later use under a given purpose.

        :param profile_data: raw profile bytes to store.
        :param purpose: purpose to store the profile under.
        """
        await self._send_recv({"RequestType": "StoreProfile", "ProfileData": profile_data, "Purpose": purpose.value})

    async def get_cloud_configuration(self) -> dict:
        """
        Retrieve the device's cloud (supervision) configuration.

        :returns: the ``CloudConfiguration`` dictionary, or None if not set.
        """
        return (await self._send_recv({"RequestType": "GetCloudConfiguration"})).get("CloudConfiguration")

    async def set_cloud_configuration(self, cloud_configuration: dict) -> None:
        """
        Set the device's cloud (supervision) configuration.

        :param cloud_configuration: cloud configuration dictionary to apply.
        """
        await self._send_recv({"RequestType": "SetCloudConfiguration", "CloudConfiguration": cloud_configuration})

    async def establish_provisional_enrollment(self, nonce: bytes) -> None:
        """
        Establish a provisional MDM enrollment.

        :param nonce: enrollment nonce.
        """
        await self._send_recv({"RequestType": "EstablishProvisionalEnrollment", "Nonce": nonce})

    async def set_wifi_power_state(self, state: bool) -> None:
        """
        Turn the device's Wi-Fi radio on or off.

        :param state: True to power Wi-Fi on, False to power it off.
        """
        await self._send_recv({"RequestType": "SetWiFiPowerState", "PowerState": state})

    async def erase_device(self, preserve_data_plan: bool, disallow_proximity_setup: bool) -> None:
        """
        Erase all content and settings from the device.

        The connection is normally terminated by the device as it begins erasing; that
        termination is suppressed so the call returns cleanly.

        :param preserve_data_plan: whether to preserve the cellular data plan across the erase.
        :param disallow_proximity_setup: whether to disallow proximity setup after the erase.
        """
        with contextlib.suppress(ConnectionTerminatedError):
            await self._send_recv({
                "RequestType": "EraseDevice",
                "PreserveDataPlan": preserve_data_plan,
                "DisallowProximitySetup": disallow_proximity_setup,
            })

    async def get_profile_list(self) -> dict:
        """
        List the configuration profiles installed on the device.

        :returns: the device's response plist (including ``ProfileMetadata`` keyed by
            profile identifier).
        """
        return await self._send_recv({"RequestType": "GetProfileList"})

    async def install_profile(self, payload: bytes) -> None:
        """
        Install a configuration profile, prompting the user for confirmation.

        :param payload: raw configuration profile bytes to install.
        """
        await self._send_recv({"RequestType": "InstallProfile", "Payload": payload})

    async def install_profile_silent(self, keybag_file: Path, profile: bytes) -> None:
        """
        Install a configuration profile silently, without user interaction.

        Escalates to supervisor privileges with the supervision identity, then installs
        the profile.

        :param keybag_file: path to a PEM file containing the supervisor certificate and
            its private key, used to escalate.
        :param profile: raw configuration profile bytes to install.
        """
        await self.escalate(keybag_file)
        await self._send_recv({"RequestType": "InstallProfileSilent", "Payload": profile})

    async def remove_profile(self, identifier: str) -> None:
        """
        Remove an installed configuration profile by its identifier.

        Looks up the profile's metadata in the installed profile list and, if present,
        sends a signed-free removal request. Does nothing if no profiles are installed or
        the identifier is not currently installed.

        :param identifier: payload identifier of the profile to remove.
        """
        profiles = await self.get_profile_list()
        if not profiles:
            return
        if identifier not in profiles["ProfileMetadata"]:
            self.logger.info(f"Trying to remove not installed profile: {identifier}")
            return
        meta = profiles["ProfileMetadata"][identifier]
        data = plistlib.dumps({
            "PayloadType": "Configuration",
            "PayloadIdentifier": identifier,
            "PayloadUUID": meta["PayloadUUID"],
            "PayloadVersion": meta["PayloadVersion"],
        })
        await self._send_recv({"RequestType": "RemoveProfile", "ProfileIdentifier": data})

    async def _send_recv(self, request: dict) -> dict:
        response = await self.service.send_recv_plist(request)
        if response.get("Status", None) != "Acknowledged":
            error_chain = response.get("ErrorChain")
            if error_chain is not None:
                error_code = error_chain[0]["ErrorCode"]
                if error_code == ERROR_CLOUD_CONFIGURATION_ALREADY_PRESENT:
                    raise CloudConfigurationAlreadyPresentError()
            raise ProfileError(f"invalid response {response}")
        return response

    async def install_wifi_profile(
        self,
        encryption_type: str,
        ssid: str,
        password: str,
        auto_join: bool = True,
        captive_bypass: bool = False,
        disable_association_mac_randomization: bool = False,
        hidden_network: bool = False,
        is_hotspot: bool = False,
        keybag_file: Optional[Path] = None,
    ) -> None:
        """
        Build and install a ``com.apple.wifi.managed`` profile for a Wi-Fi network.

        :param encryption_type: Wi-Fi encryption type (e.g. ``WPA``, ``WEP``, ``None``).
        :param ssid: network SSID.
        :param password: network password.
        :param auto_join: whether the device should automatically join the network.
        :param captive_bypass: whether to bypass the captive portal check.
        :param disable_association_mac_randomization: whether to disable MAC-address
            randomization when associating.
        :param hidden_network: whether the network is hidden (does not broadcast its SSID).
        :param is_hotspot: whether the network is treated as a personal hotspot.
        :param keybag_file: when provided, install silently using this supervision identity;
            otherwise install with a user prompt.
        """
        payload_uuid = str(uuid4())
        await self.install_managed_profile(
            f"WiFi Profile For {ssid}",
            {
                "AutoJoin": auto_join,
                "CaptiveBypass": captive_bypass,
                "DisableAssociationMACRandomization": disable_association_mac_randomization,
                "EncryptionType": encryption_type,
                "HIDDEN_NETWORK": hidden_network,
                "IsHotspot": is_hotspot,
                "Password": password,
                "PayloadDescription": "Configures Wi-Fi settings",
                "PayloadDisplayName": "Wi-Fi",
                "PayloadIdentifier": f"com.apple.wifi.managed.{payload_uuid}",
                "PayloadType": "com.apple.wifi.managed",
                "PayloadUUID": payload_uuid,
                "PayloadVersion": 1,
                "ProxyType": "None",
                "SSID_STR": ssid,
            },
            keybag_file=keybag_file,
        )

    async def install_http_proxy(self, server: str, server_port: int, keybag_file: Optional[Path] = None) -> None:
        """
        Build and install a global manual HTTP-proxy profile.

        The profile uses the fixed `GLOBAL_HTTP_PROXY_UUID` so it can later be removed by
        `remove_http_proxy`.

        :param server: proxy server host.
        :param server_port: proxy server port.
        :param keybag_file: when provided, install silently using this supervision identity;
            otherwise install with a user prompt.
        """
        payload_uuid = str(uuid4())
        await self.install_managed_profile(
            f"HTTP Proxy for {server}:{server_port}",
            {
                "PayloadDescription": "Global HTTP Proxy",
                "PayloadDisplayName": "Global HTTP Proxy",
                "PayloadIdentifier": f"com.apple.proxy.http.global.{payload_uuid}",
                "PayloadType": "com.apple.proxy.http.global",
                "PayloadUUID": payload_uuid,
                "PayloadVersion": 1,
                "ProxyCaptiveLoginAllowed": False,
                "ProxyServer": server,
                "ProxyServerPort": server_port,
                "ProxyType": "Manual",
            },
            payload_uuid=GLOBAL_HTTP_PROXY_UUID,
            keybag_file=keybag_file,
        )

    async def remove_http_proxy(self) -> None:
        """Remove the global HTTP-proxy profile previously installed by `install_http_proxy`."""
        await self.remove_profile(GLOBAL_HTTP_PROXY_UUID)

    async def supervise(self, organization: str, keybag_file: Path) -> None:
        """
        Place the device under supervision by writing a cloud configuration.

        Sets a supervised cloud configuration that names the organization, registers the
        supervisor certificate, and skips all setup-assistant panes.

        :param organization: organization name recorded in the cloud configuration.
        :param keybag_file: path to a PEM file containing the supervisor certificate whose
            DER public bytes are registered as a supervisor host certificate.
        """
        cer = x509.load_pem_x509_certificate(keybag_file.read_bytes())
        public_key = cer.public_bytes(Encoding.DER)
        await self.set_cloud_configuration({
            "AllowPairing": True,
            "CloudConfigurationUIComplete": True,
            "ConfigurationSource": 2,
            "ConfigurationWasApplied": True,
            "IsMDMUnremovable": False,
            "IsMandatory": True,
            "IsMultiUser": False,
            "IsSupervised": True,
            "OrganizationMagic": str(uuid4()),
            "OrganizationName": organization,
            "PostSetupProfileWasInstalled": True,
            "SkipSetup": [
                "Location",
                "Restore",
                "SIMSetup",
                "Android",
                "AppleID",
                "IntendedUser",
                "TOS",
                "Siri",
                "ScreenTime",
                "Diagnostics",
                "SoftwareUpdate",
                "Passcode",
                "Biometric",
                "Payment",
                "Zoom",
                "DisplayTone",
                "MessagingActivationUsingPhoneNumber",
                "HomeButtonSensitivity",
                "CloudStorage",
                "ScreenSaver",
                "TapToSetup",
                "Keyboard",
                "PreferredLanguage",
                "SpokenLanguage",
                "WatchMigration",
                "OnBoarding",
                "TVProviderSignIn",
                "TVHomeScreenSync",
                "Privacy",
                "TVRoom",
                "iMessageAndFaceTime",
                "AppStore",
                "Safety",
                "Multitasking",
                "ActionButton",
                "TermsOfAddress",
                "AccessibilityAppearance",
                "Welcome",
                "Appearance",
                "RestoreCompleted",
                "UpdateCompleted",
                "WiFi",
                "Display",
                "Tone",
                "LanguageAndLocale",
                "TouchID",
                "TrueToneDisplay",
                "FileVault",
                "iCloudStorage",
                "iCloudDiagnostics",
                "Registration",
                "DeviceToDeviceMigration",
                "UnlockWithWatch",
                "Accessibility",
                "All",
                "ExpressLanguage",
                "Language",
                "N/A",
                "Region",
                "Avatar",
                "DeviceProtection",
                "Key",
                "LockdownMode",
                "Wallpaper",
                "PrivacySubtitle",
                "SecuritySubtitle",
                "DataSubtitle",
                "AppleIDSubtitle",
                "AppearanceSubtitle",
                "PreferredLang",
                "OnboardingSubtitle",
                "AppleTVSubtitle",
                "Intelligence",
                "WebContentFiltering",
                "CameraButton",
                "AdditionalPrivacySettings",
                "EnableLockdownMode",
                "OSShowcase",
                "SafetyAndHandling",
                "Tips",
                "AgeBasedSafetySettings",
            ],
            "SupervisorHostCertificates": [public_key],
        })

    async def install_managed_profile(
        self,
        display_name: str,
        payload_content: dict[str, Any],
        payload_uuid: str = str(uuid4()),
        keybag_file: Optional[Path] = None,
    ) -> None:
        """
        Wrap a single payload in a ``Configuration`` profile and install it.

        :param display_name: profile display name.
        :param payload_content: the inner payload dictionary to wrap.
        :param payload_uuid: UUID used as both the profile identifier and UUID.
        :param keybag_file: when provided, install silently using this supervision identity;
            otherwise install with a user prompt.
        """
        profile_data = plistlib.dumps({
            "PayloadContent": [payload_content],
            "PayloadDisplayName": display_name,
            "PayloadIdentifier": payload_uuid,
            "PayloadRemovalDisallowed": False,
            "PayloadType": "Configuration",
            "PayloadUUID": payload_uuid,
            "PayloadVersion": 1,
        })
        if keybag_file is not None:
            await self.install_profile_silent(keybag_file, profile_data)
        else:
            await self.install_profile(profile_data)

    async def install_restrictions_profile(
        self,
        enforced_software_update_delay: int = 0,
        payload_uuid: str = GLOBAL_RESTRICTIONS_UUID,
        keybag_file: Optional[Path] = None,
    ) -> None:
        """
        Build and install a ``com.apple.applicationaccess`` restrictions profile.

        The profile permits essentially every restricted capability; only the software
        update delay is parameterized.

        :param enforced_software_update_delay: number of days to delay visibility of
            software updates.
        :param payload_uuid: UUID used as the profile identifier and UUID.
        :param keybag_file: when provided, install silently using this supervision identity;
            otherwise install with a user prompt.
        """
        await self.install_managed_profile(
            "Restrictions",
            {
                "PayloadDescription": "Configures restrictions",
                "PayloadDisplayName": "Restrictions",
                "PayloadIdentifier": f"com.apple.applicationaccess.{payload_uuid}",
                "PayloadType": "com.apple.applicationaccess",
                "PayloadUUID": payload_uuid,
                "PayloadVersion": 1,
                "allowActivityContinuation": True,
                "allowAddingGameCenterFriends": True,
                "allowAirPlayIncomingRequests": True,
                "allowAirPrint": True,
                "allowAirPrintCredentialsStorage": True,
                "allowAirPrintiBeaconDiscovery": True,
                "allowAppCellularDataModification": True,
                "allowAppClips": True,
                "allowAppInstallation": True,
                "allowAppRemoval": True,
                "allowApplePersonalizedAdvertising": True,
                "allowAssistant": True,
                "allowAssistantWhileLocked": True,
                "allowAutoCorrection": True,
                "allowAutoUnlock": True,
                "allowAutomaticAppDownloads": True,
                "allowBluetoothModification": True,
                "allowBookstore": True,
                "allowBookstoreErotica": True,
                "allowCamera": True,
                "allowCellularPlanModification": True,
                "allowChat": True,
                "allowCloudBackup": True,
                "allowCloudDocumentSync": True,
                "allowCloudPhotoLibrary": True,
                "allowContinuousPathKeyboard": True,
                "allowDefinitionLookup": True,
                "allowDeviceNameModification": True,
                "allowDeviceSleep": True,
                "allowDictation": True,
                "allowESIMModification": True,
                "allowEnablingRestrictions": True,
                "allowEnterpriseAppTrust": True,
                "allowEnterpriseBookBackup": True,
                "allowEnterpriseBookMetadataSync": True,
                "allowEraseContentAndSettings": True,
                "allowExplicitContent": True,
                "allowFilesNetworkDriveAccess": True,
                "allowFilesUSBDriveAccess": True,
                "allowFindMyDevice": True,
                "allowFindMyFriends": True,
                "allowFingerprintForUnlock": True,
                "allowFingerprintModification": True,
                "allowGameCenter": True,
                "allowGlobalBackgroundFetchWhenRoaming": True,
                "allowInAppPurchases": True,
                "allowKeyboardShortcuts": True,
                "allowManagedAppsCloudSync": True,
                "allowMultiplayerGaming": True,
                "allowMusicService": True,
                "allowNews": True,
                "allowNotificationsModification": True,
                "allowOpenFromManagedToUnmanaged": True,
                "allowOpenFromUnmanagedToManaged": True,
                "allowPairedWatch": True,
                "allowPassbookWhileLocked": True,
                "allowPasscodeModification": True,
                "allowPasswordAutoFill": True,
                "allowPasswordProximityRequests": True,
                "allowPasswordSharing": True,
                "allowPersonalHotspotModification": True,
                "allowPhotoStream": True,
                "allowPredictiveKeyboard": True,
                "allowProximitySetupToNewDevice": True,
                "allowRadioService": True,
                "allowRemoteAppPairing": True,
                "allowRemoteScreenObservation": True,
                "allowSafari": True,
                "allowScreenShot": True,
                "allowSharedStream": True,
                "allowSpellCheck": True,
                "allowSpotlightInternetResults": True,
                "allowSystemAppRemoval": True,
                "allowUIAppInstallation": True,
                "allowUIConfigurationProfileInstallation": True,
                "allowUSBRestrictedMode": True,
                "allowUnpairedExternalBootToRecovery": False,
                "allowUntrustedTLSPrompt": True,
                "allowVPNCreation": True,
                "allowVideoConferencing": True,
                "allowVoiceDialing": True,
                "allowWallpaperModification": True,
                "allowiTunes": True,
                "enforcedSoftwareUpdateDelay": enforced_software_update_delay,
                "forceAirDropUnmanaged": False,
                "forceAirPrintTrustedTLSRequirement": False,
                "forceAssistantProfanityFilter": False,
                "forceAuthenticationBeforeAutoFill": False,
                "forceAutomaticDateAndTime": False,
                "forceClassroomAutomaticallyJoinClasses": False,
                "forceClassroomRequestPermissionToLeaveClasses": False,
                "forceClassroomUnpromptedAppAndDeviceLock": False,
                "forceClassroomUnpromptedScreenObservation": False,
                "forceDelayedSoftwareUpdates": True,
                "forceEncryptedBackup": False,
                "forceITunesStorePasswordEntry": False,
                "forceLimitAdTracking": False,
                "forceWatchWristDetection": False,
                "forceWiFiPowerOn": False,
                "forceWiFiWhitelisting": False,
                "ratingApps": 1000,
                "ratingMovies": 1000,
                "ratingRegion": "us",
                "ratingTVShows": 1000,
                "safariAcceptCookies": 2.0,
                "safariAllowAutoFill": True,
                "safariAllowJavaScript": True,
                "safariAllowPopups": True,
                "safariForceFraudWarning": False,
            },
            payload_uuid=payload_uuid,
            keybag_file=keybag_file,
        )

hello async

hello() -> None

Perform the HelloHostIdentifier handshake with the service.

Source code in pymobiledevice3/services/mobile_config.py
async def hello(self) -> None:
    """Perform the ``HelloHostIdentifier`` handshake with the service."""
    await self._send_recv({"RequestType": "HelloHostIdentifier"})

flush async

flush() -> None

Issue a Flush request to the service.

Source code in pymobiledevice3/services/mobile_config.py
async def flush(self) -> None:
    """Issue a ``Flush`` request to the service."""
    await self._send_recv({"RequestType": "Flush"})

escalate async

escalate(keybag_file: Path) -> None

Authenticate as a supervisor to unlock silent/supervised operations.

Runs the Escalate challenge-response handshake using the supervision identity, then requests keybag migration. Must be called before silent profile installation.

Parameters:

Name Type Description Default
keybag_file Path

path to a PEM file containing both the supervisor certificate and its (unencrypted) private key.

required
Source code in pymobiledevice3/services/mobile_config.py
async def escalate(self, keybag_file: Path) -> None:
    """
    Authenticate as a supervisor to unlock silent/supervised operations.

    Runs the ``Escalate`` challenge-response handshake using the supervision identity,
    then requests keybag migration. Must be called before silent profile installation.

    :param keybag_file: path to a PEM file containing both the supervisor certificate
        and its (unencrypted) private key.
    """
    with open(keybag_file, "rb") as keybag_file:
        keybag_file = keybag_file.read()
    private_key = serialization.load_pem_private_key(keybag_file, password=None)
    cer = x509.load_pem_x509_certificate(keybag_file)
    public_key = cer.public_bytes(Encoding.DER)
    escalate_response = await self._send_recv({"RequestType": "Escalate", "SupervisorCertificate": public_key})
    signed_challenge = (
        PKCS7SignatureBuilder()
        .set_data(escalate_response["Challenge"])
        .add_signer(cer, private_key, hashes.SHA256())
        .sign(Encoding.DER, [])
    )
    await self._send_recv({"RequestType": "EscalateResponse", "SignedRequest": signed_challenge})
    await self._send_recv({"RequestType": "ProceedWithKeybagMigration"})

get_stored_profile async

get_stored_profile(purpose: Purpose = Purpose.PostSetupInstallation) -> dict

Retrieve a profile stored on the device for a given purpose.

Parameters:

Name Type Description Default
purpose Purpose

purpose the profile was stored under.

PostSetupInstallation

Returns:

Type Description
dict

the device's response plist.

Source code in pymobiledevice3/services/mobile_config.py
async def get_stored_profile(self, purpose: Purpose = Purpose.PostSetupInstallation) -> dict:
    """
    Retrieve a profile stored on the device for a given purpose.

    :param purpose: purpose the profile was stored under.
    :returns: the device's response plist.
    """
    return await self._send_recv({"RequestType": "GetStoredProfile", "Purpose": purpose.value})

store_profile async

store_profile(profile_data: bytes, purpose: Purpose = Purpose.PostSetupInstallation) -> None

Store a profile on the device for later use under a given purpose.

Parameters:

Name Type Description Default
profile_data bytes

raw profile bytes to store.

required
purpose Purpose

purpose to store the profile under.

PostSetupInstallation
Source code in pymobiledevice3/services/mobile_config.py
async def store_profile(self, profile_data: bytes, purpose: Purpose = Purpose.PostSetupInstallation) -> None:
    """
    Store a profile on the device for later use under a given purpose.

    :param profile_data: raw profile bytes to store.
    :param purpose: purpose to store the profile under.
    """
    await self._send_recv({"RequestType": "StoreProfile", "ProfileData": profile_data, "Purpose": purpose.value})

get_cloud_configuration async

get_cloud_configuration() -> dict

Retrieve the device's cloud (supervision) configuration.

Returns:

Type Description
dict

the CloudConfiguration dictionary, or None if not set.

Source code in pymobiledevice3/services/mobile_config.py
async def get_cloud_configuration(self) -> dict:
    """
    Retrieve the device's cloud (supervision) configuration.

    :returns: the ``CloudConfiguration`` dictionary, or None if not set.
    """
    return (await self._send_recv({"RequestType": "GetCloudConfiguration"})).get("CloudConfiguration")

set_cloud_configuration async

set_cloud_configuration(cloud_configuration: dict) -> None

Set the device's cloud (supervision) configuration.

Parameters:

Name Type Description Default
cloud_configuration dict

cloud configuration dictionary to apply.

required
Source code in pymobiledevice3/services/mobile_config.py
async def set_cloud_configuration(self, cloud_configuration: dict) -> None:
    """
    Set the device's cloud (supervision) configuration.

    :param cloud_configuration: cloud configuration dictionary to apply.
    """
    await self._send_recv({"RequestType": "SetCloudConfiguration", "CloudConfiguration": cloud_configuration})

establish_provisional_enrollment async

establish_provisional_enrollment(nonce: bytes) -> None

Establish a provisional MDM enrollment.

Parameters:

Name Type Description Default
nonce bytes

enrollment nonce.

required
Source code in pymobiledevice3/services/mobile_config.py
async def establish_provisional_enrollment(self, nonce: bytes) -> None:
    """
    Establish a provisional MDM enrollment.

    :param nonce: enrollment nonce.
    """
    await self._send_recv({"RequestType": "EstablishProvisionalEnrollment", "Nonce": nonce})

set_wifi_power_state async

set_wifi_power_state(state: bool) -> None

Turn the device's Wi-Fi radio on or off.

Parameters:

Name Type Description Default
state bool

True to power Wi-Fi on, False to power it off.

required
Source code in pymobiledevice3/services/mobile_config.py
async def set_wifi_power_state(self, state: bool) -> None:
    """
    Turn the device's Wi-Fi radio on or off.

    :param state: True to power Wi-Fi on, False to power it off.
    """
    await self._send_recv({"RequestType": "SetWiFiPowerState", "PowerState": state})

erase_device async

erase_device(preserve_data_plan: bool, disallow_proximity_setup: bool) -> None

Erase all content and settings from the device.

The connection is normally terminated by the device as it begins erasing; that termination is suppressed so the call returns cleanly.

Parameters:

Name Type Description Default
preserve_data_plan bool

whether to preserve the cellular data plan across the erase.

required
disallow_proximity_setup bool

whether to disallow proximity setup after the erase.

required
Source code in pymobiledevice3/services/mobile_config.py
async def erase_device(self, preserve_data_plan: bool, disallow_proximity_setup: bool) -> None:
    """
    Erase all content and settings from the device.

    The connection is normally terminated by the device as it begins erasing; that
    termination is suppressed so the call returns cleanly.

    :param preserve_data_plan: whether to preserve the cellular data plan across the erase.
    :param disallow_proximity_setup: whether to disallow proximity setup after the erase.
    """
    with contextlib.suppress(ConnectionTerminatedError):
        await self._send_recv({
            "RequestType": "EraseDevice",
            "PreserveDataPlan": preserve_data_plan,
            "DisallowProximitySetup": disallow_proximity_setup,
        })

get_profile_list async

get_profile_list() -> dict

List the configuration profiles installed on the device.

Returns:

Type Description
dict

the device's response plist (including ProfileMetadata keyed by profile identifier).

Source code in pymobiledevice3/services/mobile_config.py
async def get_profile_list(self) -> dict:
    """
    List the configuration profiles installed on the device.

    :returns: the device's response plist (including ``ProfileMetadata`` keyed by
        profile identifier).
    """
    return await self._send_recv({"RequestType": "GetProfileList"})

install_profile async

install_profile(payload: bytes) -> None

Install a configuration profile, prompting the user for confirmation.

Parameters:

Name Type Description Default
payload bytes

raw configuration profile bytes to install.

required
Source code in pymobiledevice3/services/mobile_config.py
async def install_profile(self, payload: bytes) -> None:
    """
    Install a configuration profile, prompting the user for confirmation.

    :param payload: raw configuration profile bytes to install.
    """
    await self._send_recv({"RequestType": "InstallProfile", "Payload": payload})

install_profile_silent async

install_profile_silent(keybag_file: Path, profile: bytes) -> None

Install a configuration profile silently, without user interaction.

Escalates to supervisor privileges with the supervision identity, then installs the profile.

Parameters:

Name Type Description Default
keybag_file Path

path to a PEM file containing the supervisor certificate and its private key, used to escalate.

required
profile bytes

raw configuration profile bytes to install.

required
Source code in pymobiledevice3/services/mobile_config.py
async def install_profile_silent(self, keybag_file: Path, profile: bytes) -> None:
    """
    Install a configuration profile silently, without user interaction.

    Escalates to supervisor privileges with the supervision identity, then installs
    the profile.

    :param keybag_file: path to a PEM file containing the supervisor certificate and
        its private key, used to escalate.
    :param profile: raw configuration profile bytes to install.
    """
    await self.escalate(keybag_file)
    await self._send_recv({"RequestType": "InstallProfileSilent", "Payload": profile})

remove_profile async

remove_profile(identifier: str) -> None

Remove an installed configuration profile by its identifier.

Looks up the profile's metadata in the installed profile list and, if present, sends a signed-free removal request. Does nothing if no profiles are installed or the identifier is not currently installed.

Parameters:

Name Type Description Default
identifier str

payload identifier of the profile to remove.

required
Source code in pymobiledevice3/services/mobile_config.py
async def remove_profile(self, identifier: str) -> None:
    """
    Remove an installed configuration profile by its identifier.

    Looks up the profile's metadata in the installed profile list and, if present,
    sends a signed-free removal request. Does nothing if no profiles are installed or
    the identifier is not currently installed.

    :param identifier: payload identifier of the profile to remove.
    """
    profiles = await self.get_profile_list()
    if not profiles:
        return
    if identifier not in profiles["ProfileMetadata"]:
        self.logger.info(f"Trying to remove not installed profile: {identifier}")
        return
    meta = profiles["ProfileMetadata"][identifier]
    data = plistlib.dumps({
        "PayloadType": "Configuration",
        "PayloadIdentifier": identifier,
        "PayloadUUID": meta["PayloadUUID"],
        "PayloadVersion": meta["PayloadVersion"],
    })
    await self._send_recv({"RequestType": "RemoveProfile", "ProfileIdentifier": data})

install_wifi_profile async

install_wifi_profile(encryption_type: str, ssid: str, password: str, auto_join: bool = True, captive_bypass: bool = False, disable_association_mac_randomization: bool = False, hidden_network: bool = False, is_hotspot: bool = False, keybag_file: Optional[Path] = None) -> None

Build and install a com.apple.wifi.managed profile for a Wi-Fi network.

Parameters:

Name Type Description Default
encryption_type str

Wi-Fi encryption type (e.g. WPA, WEP, None).

required
ssid str

network SSID.

required
password str

network password.

required
auto_join bool

whether the device should automatically join the network.

True
captive_bypass bool

whether to bypass the captive portal check.

False
disable_association_mac_randomization bool

whether to disable MAC-address randomization when associating.

False
hidden_network bool

whether the network is hidden (does not broadcast its SSID).

False
is_hotspot bool

whether the network is treated as a personal hotspot.

False
keybag_file Optional[Path]

when provided, install silently using this supervision identity; otherwise install with a user prompt.

None
Source code in pymobiledevice3/services/mobile_config.py
async def install_wifi_profile(
    self,
    encryption_type: str,
    ssid: str,
    password: str,
    auto_join: bool = True,
    captive_bypass: bool = False,
    disable_association_mac_randomization: bool = False,
    hidden_network: bool = False,
    is_hotspot: bool = False,
    keybag_file: Optional[Path] = None,
) -> None:
    """
    Build and install a ``com.apple.wifi.managed`` profile for a Wi-Fi network.

    :param encryption_type: Wi-Fi encryption type (e.g. ``WPA``, ``WEP``, ``None``).
    :param ssid: network SSID.
    :param password: network password.
    :param auto_join: whether the device should automatically join the network.
    :param captive_bypass: whether to bypass the captive portal check.
    :param disable_association_mac_randomization: whether to disable MAC-address
        randomization when associating.
    :param hidden_network: whether the network is hidden (does not broadcast its SSID).
    :param is_hotspot: whether the network is treated as a personal hotspot.
    :param keybag_file: when provided, install silently using this supervision identity;
        otherwise install with a user prompt.
    """
    payload_uuid = str(uuid4())
    await self.install_managed_profile(
        f"WiFi Profile For {ssid}",
        {
            "AutoJoin": auto_join,
            "CaptiveBypass": captive_bypass,
            "DisableAssociationMACRandomization": disable_association_mac_randomization,
            "EncryptionType": encryption_type,
            "HIDDEN_NETWORK": hidden_network,
            "IsHotspot": is_hotspot,
            "Password": password,
            "PayloadDescription": "Configures Wi-Fi settings",
            "PayloadDisplayName": "Wi-Fi",
            "PayloadIdentifier": f"com.apple.wifi.managed.{payload_uuid}",
            "PayloadType": "com.apple.wifi.managed",
            "PayloadUUID": payload_uuid,
            "PayloadVersion": 1,
            "ProxyType": "None",
            "SSID_STR": ssid,
        },
        keybag_file=keybag_file,
    )

install_http_proxy async

install_http_proxy(server: str, server_port: int, keybag_file: Optional[Path] = None) -> None

Build and install a global manual HTTP-proxy profile.

The profile uses the fixed GLOBAL_HTTP_PROXY_UUID so it can later be removed by remove_http_proxy.

Parameters:

Name Type Description Default
server str

proxy server host.

required
server_port int

proxy server port.

required
keybag_file Optional[Path]

when provided, install silently using this supervision identity; otherwise install with a user prompt.

None
Source code in pymobiledevice3/services/mobile_config.py
async def install_http_proxy(self, server: str, server_port: int, keybag_file: Optional[Path] = None) -> None:
    """
    Build and install a global manual HTTP-proxy profile.

    The profile uses the fixed `GLOBAL_HTTP_PROXY_UUID` so it can later be removed by
    `remove_http_proxy`.

    :param server: proxy server host.
    :param server_port: proxy server port.
    :param keybag_file: when provided, install silently using this supervision identity;
        otherwise install with a user prompt.
    """
    payload_uuid = str(uuid4())
    await self.install_managed_profile(
        f"HTTP Proxy for {server}:{server_port}",
        {
            "PayloadDescription": "Global HTTP Proxy",
            "PayloadDisplayName": "Global HTTP Proxy",
            "PayloadIdentifier": f"com.apple.proxy.http.global.{payload_uuid}",
            "PayloadType": "com.apple.proxy.http.global",
            "PayloadUUID": payload_uuid,
            "PayloadVersion": 1,
            "ProxyCaptiveLoginAllowed": False,
            "ProxyServer": server,
            "ProxyServerPort": server_port,
            "ProxyType": "Manual",
        },
        payload_uuid=GLOBAL_HTTP_PROXY_UUID,
        keybag_file=keybag_file,
    )

remove_http_proxy async

remove_http_proxy() -> None

Remove the global HTTP-proxy profile previously installed by install_http_proxy.

Source code in pymobiledevice3/services/mobile_config.py
async def remove_http_proxy(self) -> None:
    """Remove the global HTTP-proxy profile previously installed by `install_http_proxy`."""
    await self.remove_profile(GLOBAL_HTTP_PROXY_UUID)

supervise async

supervise(organization: str, keybag_file: Path) -> None

Place the device under supervision by writing a cloud configuration.

Sets a supervised cloud configuration that names the organization, registers the supervisor certificate, and skips all setup-assistant panes.

Parameters:

Name Type Description Default
organization str

organization name recorded in the cloud configuration.

required
keybag_file Path

path to a PEM file containing the supervisor certificate whose DER public bytes are registered as a supervisor host certificate.

required
Source code in pymobiledevice3/services/mobile_config.py
async def supervise(self, organization: str, keybag_file: Path) -> None:
    """
    Place the device under supervision by writing a cloud configuration.

    Sets a supervised cloud configuration that names the organization, registers the
    supervisor certificate, and skips all setup-assistant panes.

    :param organization: organization name recorded in the cloud configuration.
    :param keybag_file: path to a PEM file containing the supervisor certificate whose
        DER public bytes are registered as a supervisor host certificate.
    """
    cer = x509.load_pem_x509_certificate(keybag_file.read_bytes())
    public_key = cer.public_bytes(Encoding.DER)
    await self.set_cloud_configuration({
        "AllowPairing": True,
        "CloudConfigurationUIComplete": True,
        "ConfigurationSource": 2,
        "ConfigurationWasApplied": True,
        "IsMDMUnremovable": False,
        "IsMandatory": True,
        "IsMultiUser": False,
        "IsSupervised": True,
        "OrganizationMagic": str(uuid4()),
        "OrganizationName": organization,
        "PostSetupProfileWasInstalled": True,
        "SkipSetup": [
            "Location",
            "Restore",
            "SIMSetup",
            "Android",
            "AppleID",
            "IntendedUser",
            "TOS",
            "Siri",
            "ScreenTime",
            "Diagnostics",
            "SoftwareUpdate",
            "Passcode",
            "Biometric",
            "Payment",
            "Zoom",
            "DisplayTone",
            "MessagingActivationUsingPhoneNumber",
            "HomeButtonSensitivity",
            "CloudStorage",
            "ScreenSaver",
            "TapToSetup",
            "Keyboard",
            "PreferredLanguage",
            "SpokenLanguage",
            "WatchMigration",
            "OnBoarding",
            "TVProviderSignIn",
            "TVHomeScreenSync",
            "Privacy",
            "TVRoom",
            "iMessageAndFaceTime",
            "AppStore",
            "Safety",
            "Multitasking",
            "ActionButton",
            "TermsOfAddress",
            "AccessibilityAppearance",
            "Welcome",
            "Appearance",
            "RestoreCompleted",
            "UpdateCompleted",
            "WiFi",
            "Display",
            "Tone",
            "LanguageAndLocale",
            "TouchID",
            "TrueToneDisplay",
            "FileVault",
            "iCloudStorage",
            "iCloudDiagnostics",
            "Registration",
            "DeviceToDeviceMigration",
            "UnlockWithWatch",
            "Accessibility",
            "All",
            "ExpressLanguage",
            "Language",
            "N/A",
            "Region",
            "Avatar",
            "DeviceProtection",
            "Key",
            "LockdownMode",
            "Wallpaper",
            "PrivacySubtitle",
            "SecuritySubtitle",
            "DataSubtitle",
            "AppleIDSubtitle",
            "AppearanceSubtitle",
            "PreferredLang",
            "OnboardingSubtitle",
            "AppleTVSubtitle",
            "Intelligence",
            "WebContentFiltering",
            "CameraButton",
            "AdditionalPrivacySettings",
            "EnableLockdownMode",
            "OSShowcase",
            "SafetyAndHandling",
            "Tips",
            "AgeBasedSafetySettings",
        ],
        "SupervisorHostCertificates": [public_key],
    })

install_managed_profile async

install_managed_profile(display_name: str, payload_content: dict[str, Any], payload_uuid: str = str(uuid4()), keybag_file: Optional[Path] = None) -> None

Wrap a single payload in a Configuration profile and install it.

Parameters:

Name Type Description Default
display_name str

profile display name.

required
payload_content dict[str, Any]

the inner payload dictionary to wrap.

required
payload_uuid str

UUID used as both the profile identifier and UUID.

str(uuid4())
keybag_file Optional[Path]

when provided, install silently using this supervision identity; otherwise install with a user prompt.

None
Source code in pymobiledevice3/services/mobile_config.py
async def install_managed_profile(
    self,
    display_name: str,
    payload_content: dict[str, Any],
    payload_uuid: str = str(uuid4()),
    keybag_file: Optional[Path] = None,
) -> None:
    """
    Wrap a single payload in a ``Configuration`` profile and install it.

    :param display_name: profile display name.
    :param payload_content: the inner payload dictionary to wrap.
    :param payload_uuid: UUID used as both the profile identifier and UUID.
    :param keybag_file: when provided, install silently using this supervision identity;
        otherwise install with a user prompt.
    """
    profile_data = plistlib.dumps({
        "PayloadContent": [payload_content],
        "PayloadDisplayName": display_name,
        "PayloadIdentifier": payload_uuid,
        "PayloadRemovalDisallowed": False,
        "PayloadType": "Configuration",
        "PayloadUUID": payload_uuid,
        "PayloadVersion": 1,
    })
    if keybag_file is not None:
        await self.install_profile_silent(keybag_file, profile_data)
    else:
        await self.install_profile(profile_data)

install_restrictions_profile async

install_restrictions_profile(enforced_software_update_delay: int = 0, payload_uuid: str = GLOBAL_RESTRICTIONS_UUID, keybag_file: Optional[Path] = None) -> None

Build and install a com.apple.applicationaccess restrictions profile.

The profile permits essentially every restricted capability; only the software update delay is parameterized.

Parameters:

Name Type Description Default
enforced_software_update_delay int

number of days to delay visibility of software updates.

0
payload_uuid str

UUID used as the profile identifier and UUID.

GLOBAL_RESTRICTIONS_UUID
keybag_file Optional[Path]

when provided, install silently using this supervision identity; otherwise install with a user prompt.

None
Source code in pymobiledevice3/services/mobile_config.py
async def install_restrictions_profile(
    self,
    enforced_software_update_delay: int = 0,
    payload_uuid: str = GLOBAL_RESTRICTIONS_UUID,
    keybag_file: Optional[Path] = None,
) -> None:
    """
    Build and install a ``com.apple.applicationaccess`` restrictions profile.

    The profile permits essentially every restricted capability; only the software
    update delay is parameterized.

    :param enforced_software_update_delay: number of days to delay visibility of
        software updates.
    :param payload_uuid: UUID used as the profile identifier and UUID.
    :param keybag_file: when provided, install silently using this supervision identity;
        otherwise install with a user prompt.
    """
    await self.install_managed_profile(
        "Restrictions",
        {
            "PayloadDescription": "Configures restrictions",
            "PayloadDisplayName": "Restrictions",
            "PayloadIdentifier": f"com.apple.applicationaccess.{payload_uuid}",
            "PayloadType": "com.apple.applicationaccess",
            "PayloadUUID": payload_uuid,
            "PayloadVersion": 1,
            "allowActivityContinuation": True,
            "allowAddingGameCenterFriends": True,
            "allowAirPlayIncomingRequests": True,
            "allowAirPrint": True,
            "allowAirPrintCredentialsStorage": True,
            "allowAirPrintiBeaconDiscovery": True,
            "allowAppCellularDataModification": True,
            "allowAppClips": True,
            "allowAppInstallation": True,
            "allowAppRemoval": True,
            "allowApplePersonalizedAdvertising": True,
            "allowAssistant": True,
            "allowAssistantWhileLocked": True,
            "allowAutoCorrection": True,
            "allowAutoUnlock": True,
            "allowAutomaticAppDownloads": True,
            "allowBluetoothModification": True,
            "allowBookstore": True,
            "allowBookstoreErotica": True,
            "allowCamera": True,
            "allowCellularPlanModification": True,
            "allowChat": True,
            "allowCloudBackup": True,
            "allowCloudDocumentSync": True,
            "allowCloudPhotoLibrary": True,
            "allowContinuousPathKeyboard": True,
            "allowDefinitionLookup": True,
            "allowDeviceNameModification": True,
            "allowDeviceSleep": True,
            "allowDictation": True,
            "allowESIMModification": True,
            "allowEnablingRestrictions": True,
            "allowEnterpriseAppTrust": True,
            "allowEnterpriseBookBackup": True,
            "allowEnterpriseBookMetadataSync": True,
            "allowEraseContentAndSettings": True,
            "allowExplicitContent": True,
            "allowFilesNetworkDriveAccess": True,
            "allowFilesUSBDriveAccess": True,
            "allowFindMyDevice": True,
            "allowFindMyFriends": True,
            "allowFingerprintForUnlock": True,
            "allowFingerprintModification": True,
            "allowGameCenter": True,
            "allowGlobalBackgroundFetchWhenRoaming": True,
            "allowInAppPurchases": True,
            "allowKeyboardShortcuts": True,
            "allowManagedAppsCloudSync": True,
            "allowMultiplayerGaming": True,
            "allowMusicService": True,
            "allowNews": True,
            "allowNotificationsModification": True,
            "allowOpenFromManagedToUnmanaged": True,
            "allowOpenFromUnmanagedToManaged": True,
            "allowPairedWatch": True,
            "allowPassbookWhileLocked": True,
            "allowPasscodeModification": True,
            "allowPasswordAutoFill": True,
            "allowPasswordProximityRequests": True,
            "allowPasswordSharing": True,
            "allowPersonalHotspotModification": True,
            "allowPhotoStream": True,
            "allowPredictiveKeyboard": True,
            "allowProximitySetupToNewDevice": True,
            "allowRadioService": True,
            "allowRemoteAppPairing": True,
            "allowRemoteScreenObservation": True,
            "allowSafari": True,
            "allowScreenShot": True,
            "allowSharedStream": True,
            "allowSpellCheck": True,
            "allowSpotlightInternetResults": True,
            "allowSystemAppRemoval": True,
            "allowUIAppInstallation": True,
            "allowUIConfigurationProfileInstallation": True,
            "allowUSBRestrictedMode": True,
            "allowUnpairedExternalBootToRecovery": False,
            "allowUntrustedTLSPrompt": True,
            "allowVPNCreation": True,
            "allowVideoConferencing": True,
            "allowVoiceDialing": True,
            "allowWallpaperModification": True,
            "allowiTunes": True,
            "enforcedSoftwareUpdateDelay": enforced_software_update_delay,
            "forceAirDropUnmanaged": False,
            "forceAirPrintTrustedTLSRequirement": False,
            "forceAssistantProfanityFilter": False,
            "forceAuthenticationBeforeAutoFill": False,
            "forceAutomaticDateAndTime": False,
            "forceClassroomAutomaticallyJoinClasses": False,
            "forceClassroomRequestPermissionToLeaveClasses": False,
            "forceClassroomUnpromptedAppAndDeviceLock": False,
            "forceClassroomUnpromptedScreenObservation": False,
            "forceDelayedSoftwareUpdates": True,
            "forceEncryptedBackup": False,
            "forceITunesStorePasswordEntry": False,
            "forceLimitAdTracking": False,
            "forceWatchWristDetection": False,
            "forceWiFiPowerOn": False,
            "forceWiFiWhitelisting": False,
            "ratingApps": 1000,
            "ratingMovies": 1000,
            "ratingRegion": "us",
            "ratingTVShows": 1000,
            "safariAcceptCookies": 2.0,
            "safariAllowAutoFill": True,
            "safariAllowJavaScript": True,
            "safariAllowPopups": True,
            "safariForceFraudWarning": False,
        },
        payload_uuid=payload_uuid,
        keybag_file=keybag_file,
    )

pymobiledevice3.services.misagent.MisagentService

Bases: LockdownService

Manage provisioning profiles installed on the device.

Wraps the com.apple.misagent lockdown service to install, remove and enumerate provisioning profiles. Being a LockdownService, instances may be used as an async context manager::

async with MisagentService(lockdown) as misagent:
    profiles = await misagent.copy_all()
Source code in pymobiledevice3/services/misagent.py
class MisagentService(LockdownService):
    """
    Manage provisioning profiles installed on the device.

    Wraps the ``com.apple.misagent`` lockdown service to install, remove and enumerate
    provisioning profiles. Being a `LockdownService`, instances may be used as an async
    context manager::

        async with MisagentService(lockdown) as misagent:
            profiles = await misagent.copy_all()
    """

    SERVICE_NAME = "com.apple.misagent"
    RSD_SERVICE_NAME = "com.apple.misagent.shim.remote"

    def __init__(self, lockdown: LockdownClient):
        if isinstance(lockdown, LockdownClient):
            super().__init__(lockdown, self.SERVICE_NAME)
        else:
            super().__init__(lockdown, self.RSD_SERVICE_NAME)

    async def install(self, plist: BytesIO) -> dict:
        """
        Install a provisioning profile on the device.

        :param plist: stream whose contents are the raw provisioning profile to install;
            read in full and sent to the device.
        :returns: the device's response plist.
        :raises PyMobileDevice3Exception: if the device reports a non-zero status.
        """
        response = await self.service.send_recv_plist({
            "MessageType": "Install",
            "Profile": plist.read(),
            "ProfileType": "Provisioning",
        })
        if response["Status"]:
            raise PyMobileDevice3Exception(f"invalid status: {response}")

        return response

    async def remove(self, profile_id: str) -> dict:
        """
        Remove an installed provisioning profile.

        :param profile_id: identifier of the profile to remove.
        :returns: the device's response plist.
        :raises PyMobileDevice3Exception: if the device reports a non-zero status.
        """
        response = await self.service.send_recv_plist({
            "MessageType": "Remove",
            "ProfileID": profile_id,
            "ProfileType": "Provisioning",
        })
        if response["Status"]:
            raise PyMobileDevice3Exception(f"invalid status: {response}")

        return response

    async def copy_all(self) -> list[ProvisioningProfile]:
        """
        Retrieve all provisioning profiles installed on the device.

        :returns: list of `ProvisioningProfile` objects, one per installed profile.
        :raises PyMobileDevice3Exception: if the device reports a non-zero status.
        """
        response = await self.service.send_recv_plist({"MessageType": "CopyAll", "ProfileType": "Provisioning"})
        if response["Status"]:
            raise PyMobileDevice3Exception(f"invalid status: {response}")

        return [ProvisioningProfile(p) for p in response["Payload"]]

install async

install(plist: BytesIO) -> dict

Install a provisioning profile on the device.

Parameters:

Name Type Description Default
plist BytesIO

stream whose contents are the raw provisioning profile to install; read in full and sent to the device.

required

Returns:

Type Description
dict

the device's response plist.

Raises:

Type Description
PyMobileDevice3Exception

if the device reports a non-zero status.

Source code in pymobiledevice3/services/misagent.py
async def install(self, plist: BytesIO) -> dict:
    """
    Install a provisioning profile on the device.

    :param plist: stream whose contents are the raw provisioning profile to install;
        read in full and sent to the device.
    :returns: the device's response plist.
    :raises PyMobileDevice3Exception: if the device reports a non-zero status.
    """
    response = await self.service.send_recv_plist({
        "MessageType": "Install",
        "Profile": plist.read(),
        "ProfileType": "Provisioning",
    })
    if response["Status"]:
        raise PyMobileDevice3Exception(f"invalid status: {response}")

    return response

remove async

remove(profile_id: str) -> dict

Remove an installed provisioning profile.

Parameters:

Name Type Description Default
profile_id str

identifier of the profile to remove.

required

Returns:

Type Description
dict

the device's response plist.

Raises:

Type Description
PyMobileDevice3Exception

if the device reports a non-zero status.

Source code in pymobiledevice3/services/misagent.py
async def remove(self, profile_id: str) -> dict:
    """
    Remove an installed provisioning profile.

    :param profile_id: identifier of the profile to remove.
    :returns: the device's response plist.
    :raises PyMobileDevice3Exception: if the device reports a non-zero status.
    """
    response = await self.service.send_recv_plist({
        "MessageType": "Remove",
        "ProfileID": profile_id,
        "ProfileType": "Provisioning",
    })
    if response["Status"]:
        raise PyMobileDevice3Exception(f"invalid status: {response}")

    return response

copy_all async

copy_all() -> list[ProvisioningProfile]

Retrieve all provisioning profiles installed on the device.

Returns:

Type Description
list[ProvisioningProfile]

list of ProvisioningProfile objects, one per installed profile.

Raises:

Type Description
PyMobileDevice3Exception

if the device reports a non-zero status.

Source code in pymobiledevice3/services/misagent.py
async def copy_all(self) -> list[ProvisioningProfile]:
    """
    Retrieve all provisioning profiles installed on the device.

    :returns: list of `ProvisioningProfile` objects, one per installed profile.
    :raises PyMobileDevice3Exception: if the device reports a non-zero status.
    """
    response = await self.service.send_recv_plist({"MessageType": "CopyAll", "ProfileType": "Provisioning"})
    if response["Status"]:
        raise PyMobileDevice3Exception(f"invalid status: {response}")

    return [ProvisioningProfile(p) for p in response["Payload"]]

pymobiledevice3.services.file_relay.FileRelayService

Bases: LockdownService

Retrieve diagnostic data archives from the device.

Wraps the legacy com.apple.mobile.file_relay service, which bundles one or more named data sources (see SRCFILES for known source names) into a single gzip-compressed CPIO archive returned by request_sources.

Being a LockdownService, instances may be used as an async context manager::

async with FileRelayService(lockdown) as file_relay:
    archive = await file_relay.request_sources(["UserDatabases"])
Source code in pymobiledevice3/services/file_relay.py
class FileRelayService(LockdownService):
    """
    Retrieve diagnostic data archives from the device.

    Wraps the legacy ``com.apple.mobile.file_relay`` service, which bundles one or more
    named data sources (see `SRCFILES` for known source names) into a single gzip-compressed
    CPIO archive returned by `request_sources`.

    Being a `LockdownService`, instances may be used as an async context manager::

        async with FileRelayService(lockdown) as file_relay:
            archive = await file_relay.request_sources(["UserDatabases"])
    """

    SERVICE_NAME = "com.apple.mobile.file_relay"

    def __init__(self, lockdown: LockdownClient):
        super().__init__(lockdown, self.SERVICE_NAME)
        self.packet_num = 0

    async def stop_session(self):
        """Close the underlying service connection."""
        self.logger.info("Disconecting...")
        await self.service.close()

    async def request_sources(self, sources=None):
        """
        Request one or more data sources and return the combined archive.

        Sends the requested source names and, once the device acknowledges, reads the
        full response stream into memory.

        :param sources: list of source names to request (see `SRCFILES` for known names);
            defaults to ``["UserDatabases"]`` when not given.
        :returns: the gzip-compressed CPIO archive bytes on success, or None if the device
            did not acknowledge the request.
        """
        if sources is None:
            sources = ["UserDatabases"]
        await self.service.send_plist({"Sources": sources})
        while 1:
            res = await self.service.recv_plist()
            if res:
                s = res.get("Status")
                if s == "Acknowledged":
                    z = b""
                    while True:
                        x = await self.service.recv_any()
                        if not x:
                            break
                        z += x
                    return z
                else:
                    print(res.get("Error"))
                    break
        return None

stop_session async

stop_session()

Close the underlying service connection.

Source code in pymobiledevice3/services/file_relay.py
async def stop_session(self):
    """Close the underlying service connection."""
    self.logger.info("Disconecting...")
    await self.service.close()

request_sources async

request_sources(sources=None)

Request one or more data sources and return the combined archive.

Sends the requested source names and, once the device acknowledges, reads the full response stream into memory.

Parameters:

Name Type Description Default
sources

list of source names to request (see SRCFILES for known names); defaults to ["UserDatabases"] when not given.

None

Returns:

Type Description

the gzip-compressed CPIO archive bytes on success, or None if the device did not acknowledge the request.

Source code in pymobiledevice3/services/file_relay.py
async def request_sources(self, sources=None):
    """
    Request one or more data sources and return the combined archive.

    Sends the requested source names and, once the device acknowledges, reads the
    full response stream into memory.

    :param sources: list of source names to request (see `SRCFILES` for known names);
        defaults to ``["UserDatabases"]`` when not given.
    :returns: the gzip-compressed CPIO archive bytes on success, or None if the device
        did not acknowledge the request.
    """
    if sources is None:
        sources = ["UserDatabases"]
    await self.service.send_plist({"Sources": sources})
    while 1:
        res = await self.service.recv_plist()
        if res:
            s = res.get("Status")
            if s == "Acknowledged":
                z = b""
                while True:
                    x = await self.service.recv_any()
                    if not x:
                        break
                    z += x
                return z
            else:
                print(res.get("Error"))
                break
    return None