Apple Automated Device Enrollment profile duplicator

First off, it’s been a while since I posted and for good reason, I’ve been heads down with work, family, and much more. While I love writing and posted every month for years, I decided back in February to scale back a bit. I do love writing and generating ideas to share with the community and going forward I intend to continue posting new content.

This post continues the theme of automation with Microsoft Endpoint Manager and is about duplicating Apple Automated Device Enrollment profiles across different Apple Business Manager enrollment tokens in Intune.

Scenario

Organizations typically have one or more enrollment tokens within Intune for various reasons, for example Apple devices may be distributed globally through subsidiaries and thus the devices may be managed by different individuals or teams and the choice was made to separate Apple Business Manager environments for tracking or may through a merger or acquisition.

Within a single Microsoft Endpoint Manager (i.e. Intune) tenant, organizations can add one or more enrollment tokens that are bound to different Apple Business Manager (ABM) instances. Having multiple enrollment tokens within Intune organizations may have created multiple enrollment profiles, sometimes many. Imagine having just added a new enrollment token to Intune only to realize there are no enrollment profiles yet and you want to just duplicate existing profiles from an existing profile and go from there.

Solution

I’m happy to say that there is now a solution to duplication of enrollment profiles across enrollment tokens, and it involves Power Apps and Power Automate.

Video (no audio)

Let’s get started

Requirements

  • Microsoft Endpoint Manager – Intune
  • Microsoft Graph
  • Power Automate
  • Power Apps

Graph API reference

https://docs.microsoft.com/en-us/graph/api/resources/intune-enrollment-depiosenrollmentprofile?view=graph-rest-beta

Power Automate

To accomplish everything, we need to do we’ll need to create three different Power Automate Flows:

  • Get Apple enrollment profile tokens – queries all the enrollment profile tokens within Intune
  • Get Apple enrollment token profiles – queries enrollment profiles associated with enrollment tokens
  • Copy Apple enrollment token profile – creates a duplicate of selected enrollment profiles within a different enrollment token

Get Apple enrollment profile tokens

To populate token selectors in Power Apps we need to build a Flow to query for all the Apple enrollment profile tokens.

This is a simple Flow, at the top we have a PowerApps trigger, followed by HTTP action to pull all the enrollment tokens from Intune using Graph. We then parse the JSON from the HTTP action, and finally add a Response action that Power Apps will create a collection from. We’ll cover Power Apps design later in this post.

HTTP action (Get enrollment profiles) graph query:

https://graph.microsoft.com/beta/deviceManagement/depOnboardingSettings

Parse JSON action

We parse the JSON by selecting “Body” of the HTTP action from the dynamic content that way we can select individual items to take action on. If you’re new to parse JSON actions and are wondering how to create the JSON, simply run the HTTP action, copy the output, and in the Parse from JSON action, select “Generate from sample” and paste the output and select done.

{
    "type": "object",
    "properties": {
        "@@odata.context": {
            "type": "string"
        },
        "@@odata.count": {
            "type": "integer"
        },
        "value": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "appleIdentifier": {
                        "type": "string"
                    },
                    "tokenExpirationDateTime": {
                        "type": "string"
                    },
                    "lastModifiedDateTime": {
                        "type": "string"
                    },
                    "lastSuccessfulSyncDateTime": {
                        "type": "string"
                    },
                    "lastSyncTriggeredDateTime": {
                        "type": "string"
                    },
                    "shareTokenWithSchoolDataSyncService": {
                        "type": "boolean"
                    },
                    "lastSyncErrorCode": {
                        "type": "integer"
                    },
                    "tokenType": {
                        "type": "string"
                    },
                    "tokenName": {
                        "type": "string"
                    },
                    "syncedDeviceCount": {
                        "type": "integer"
                    },
                    "dataSharingConsentGranted": {
                        "type": "boolean"
                    },
                    "roleScopeTagIds": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        }
                    }
                },
                "required": [
                    "id",
                    "appleIdentifier",
                    "tokenExpirationDateTime",
                    "lastModifiedDateTime",
                    "lastSuccessfulSyncDateTime",
                    "lastSyncTriggeredDateTime",
                    "shareTokenWithSchoolDataSyncService",
                    "lastSyncErrorCode",
                    "tokenType",
                    "tokenName",
                    "syncedDeviceCount",
                    "dataSharingConsentGranted",
                    "roleScopeTagIds"
                ]
            }
        }
    }
}

Response action

The response action is utilized to send data to Power Apps to populate a collection. Make sure the JSON is in the form as an array or all you’ll get in the collection is a “true” value. Basically all we’re doing is removing all the JSON above the “type”: “array” line from the HTTP response above.

{
    "type": "array",
    "items": {
        "type": "object",
        "properties": {
            "id": {
                "type": "string"
            },
            "appleIdentifier": {
                "type": "string"
            },
            "tokenExpirationDateTime": {
                "type": "string"
            },
            "lastModifiedDateTime": {
                "type": "string"
            },
            "lastSuccessfulSyncDateTime": {
                "type": "string"
            },
            "lastSyncTriggeredDateTime": {
                "type": "string"
            },
            "shareTokenWithSchoolDataSyncService": {
                "type": "boolean"
            },
            "lastSyncErrorCode": {
                "type": "integer"
            },
            "tokenType": {
                "type": "string"
            },
            "tokenName": {
                "type": "string"
            },
            "syncedDeviceCount": {
                "type": "integer"
            }
        },
        "required": [
            "id",
            "appleIdentifier",
            "tokenExpirationDateTime",
            "lastModifiedDateTime",
            "lastSuccessfulSyncDateTime",
            "lastSyncTriggeredDateTime",
            "shareTokenWithSchoolDataSyncService",
            "lastSyncErrorCode",
            "tokenType",
            "tokenName",
            "syncedDeviceCount"
        ]
    }
}

Get Apple enrollment token profiles

Once a token is selected in Power Apps we need to populate a table of profiles associated with the token. The following Flow does exactly that.

Initialize variable AppleEnrollmentTokenID

We’ll initialize a variable so when a token is selected from Power Apps we’ll utilize that token ID to query all the profiles associated with it. Do this by selecting the Value box and selecting “Ask in PowerApps” this will associate the Power Automate Flow with Power Apps and Power Apps will ask for the input for the variable in code. Again, we’ll cover Power Apps design later in this post.

HTTP action (Get enrollment profile config)

This query utilizes the variable above:

https://graph.microsoft.com/beta/deviceManagement/depOnboardingSettings/@{variables('AppleEnrollmentTokenID')}/EnrollmentProfiles

Parse JSON

{
    "type": "object",
    "properties": {
        "@@odata.context": {
            "type": "string"
        },
        "@@odata.count": {
            "type": "integer"
        },
        "value": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "@@odata.type": {
                        "type": "string"
                    },
                    "id": {
                        "type": "string"
                    },
                    "displayName": {
                        "type": "string"
                    },
                    "description": {
                        "type": "string"
                    }
                },
                "required": [
                    "@@odata.type",
                    "id",
                    "displayName",
                    "description"
                ]
            }
        }
    }
}

Response

As with the Response action in the previous Flow, make sure the JSON is formatted as an array.

{
    "type": "array",
    "items": {
        "type": "object",
        "properties": {
            "@@odata.type": {
                "type": "string"
            },
            "id": {
                "type": "string"
            },
            "displayName": {
                "type": "string"
            },
            "description": {
                "type": "string"
            }
        },
        "required": [
            "@@odata.type",
            "id",
            "displayName",
            "description"
        ]
    }
}

Copy Apple enrollment token profile

This is a fairly complex Flow, however if there is any confusion when reading through, feel free to reference a previous post of mine about device config profile duplicator which has a lot of similarities with this Flow: https://uem4all.com/2021/08/09/duplicate-intune-policies/

Add three Initialize variable actions:

Initialize variable varPrefix

Do this by selecting the Value box and selecting “Ask in PowerApps” this will associate the Power Automate Flow with Power Apps and Power Apps will ask for the input for the variable in code.

Initialize variable varProfileID

This variable is used to store the copy from enrollment profile ID in the Power App. Do this by selecting the Value box and selecting “Ask in PowerApps” this will associate the Power Automate Flow with Power Apps and Power Apps will ask for the input for the variable in code.

Initialize variable varEnrollmentProfileID

This variable utilized to store the selected enrollment profiles in the Power App. Do this by selecting the Value box and selecting “Ask in PowerApps” this will associate the Power Automate Flow with Power Apps and Power Apps will ask for the input for the variable in code.

Compose varProfileID

We want to convert the profile ID to a string for use later.

replace(string(variables('varProfileID')),'"', '')

Compose varEnrollmentProfileID

We want to convert the enrollment profile ID to a string for use later.

replace(string(variables('varEnrollmentProfileID')),'"', '')

Parse JSON

{
    "type": "array",
    "items": {
        "type": "object",
        "properties": {
            "id": {
                "type": "string"
            }
        },
        "required": [
            "id"
        ]
    }
}

Apply to each

You can create this manually or have it auto created after adding the HTTP action.

HTTP action (Get enrollment profile settings)

We want use HTTP get action to query the profiles under the selected enrollment token. We do this because we want to take each profile and remove some properties from the JSON to format them to be duplicated within new enrollment profile. More details on this in the next steps.

This graph query is looks at the compose varProfileID and the content within the body of the Parse JSON outside of the “Apply to each” where we’ll select the ID and add it to the graph query as shown below.

https://graph.microsoft.com/beta/deviceManagement/depOnboardingSettings/@{outputs('Compose_varProfileID')}/enrollmentProfiles/@{items('Apply_to_each')['id']}

Parse JSON 2

As with most HTTP gets, we want to parse the body of the graph output so we can select individual attributes such as id and so on.

{
    "type": "object",
    "properties": {
        "@@odata.context": {
            "type": "string"
        },
        "@@odata.type": {
            "type": "string"
        },
        "id": {
            "type": "string"
        },
        "displayName": {
            "type": "string"
        },
        "description": {
            "type": "string"
        },
        "requiresUserAuthentication": {
            "type": "boolean"
        },
        "configurationEndpointUrl": {
            "type": "string"
        },
        "enableAuthenticationViaCompanyPortal": {
            "type": "boolean"
        },
        "requireCompanyPortalOnSetupAssistantEnrolledDevices": {
            "type": "boolean"
        },
        "isDefault": {
            "type": "boolean"
        },
        "supervisedModeEnabled": {
            "type": "boolean"
        },
        "supportDepartment": {
            "type": "string"
        },
        "isMandatory": {
            "type": "boolean"
        },
        "locationDisabled": {
            "type": "boolean"
        },
        "supportPhoneNumber": {
            "type": "string"
        },
        "profileRemovalDisabled": {
            "type": "boolean"
        },
        "restoreBlocked": {
            "type": "boolean"
        },
        "appleIdDisabled": {
            "type": "boolean"
        },
        "termsAndConditionsDisabled": {
            "type": "boolean"
        },
        "touchIdDisabled": {
            "type": "boolean"
        },
        "applePayDisabled": {
            "type": "boolean"
        },
        "siriDisabled": {
            "type": "boolean"
        },
        "diagnosticsDisabled": {
            "type": "boolean"
        },
        "displayToneSetupDisabled": {
            "type": "boolean"
        },
        "privacyPaneDisabled": {
            "type": "boolean"
        },
        "screenTimeScreenDisabled": {
            "type": "boolean"
        },
        "deviceNameTemplate": {
            "type": "string"
        },
        "configurationWebUrl": {
            "type": "boolean"
        },
        "iTunesPairingMode": {
            "type": "string"
        },
        "restoreFromAndroidDisabled": {
            "type": "boolean"
        },
        "awaitDeviceConfiguredConfirmation": {
            "type": "boolean"
        },
        "sharedIPadMaximumUserCount": {
            "type": "integer"
        },
        "enableSharedIPad": {
            "type": "boolean"
        },
        "companyPortalVppTokenId": {
            "type": "string"
        },
        "enableSingleAppEnrollmentMode": {
            "type": "boolean"
        },
        "homeButtonScreenDisabled": {
            "type": "boolean"
        },
        "iMessageAndFaceTimeScreenDisabled": {
            "type": "boolean"
        },
        "onBoardingScreenDisabled": {
            "type": "boolean"
        },
        "simSetupScreenDisabled": {
            "type": "boolean"
        },
        "softwareUpdateScreenDisabled": {
            "type": "boolean"
        },
        "watchMigrationScreenDisabled": {
            "type": "boolean"
        },
        "appearanceScreenDisabled": {
            "type": "boolean"
        },
        "expressLanguageScreenDisabled": {
            "type": "boolean"
        },
        "preferredLanguageScreenDisabled": {
            "type": "boolean"
        },
        "deviceToDeviceMigrationDisabled": {
            "type": "boolean"
        },
        "welcomeScreenDisabled": {
            "type": "boolean"
        },
        "passCodeDisabled": {
            "type": "boolean"
        },
        "zoomDisabled": {
            "type": "boolean"
        },
        "restoreCompletedScreenDisabled": {
            "type": "boolean"
        },
        "updateCompleteScreenDisabled": {
            "type": "boolean"
        },
        "forceTemporarySession": {
            "type": "boolean"
        },
        "temporarySessionTimeoutInSeconds": {
            "type": "integer"
        },
        "userSessionTimeoutInSeconds": {
            "type": "integer"
        },
        "passcodeLockGracePeriodInSeconds": {},
        "carrierActivationUrl": {
            "type": "string"
        },
        "userlessSharedAadModeEnabled": {
            "type": "boolean"
        },
        "managementCertificates": {
            "type": "array"
        }
    }
}

Add 6 Compose actions – we’ll utilize the compose actions to manipulate the JSON for each profile returned.

Let’s split the compose action into groups so it’s easier to understand:

Convert odata.context to a new value of odatacontext

Because I need to use the “removeProperty” function to remove properties from the JSON output I need to convert from JSON to a string. The removeProperty function does not like “.” so we have to transform it to a string by removing the “.”.

Compose JSON to String

string(body('Parse_JSON_2'))

Compose replace odatacontext

replace(outputs('Compose_JSON_to_String'),'@odata.context','odatacontext')

Compose convert to JSON, all we’re doing here is converting the policy string back to JSON

json(replace(outputs('Compose_replace_odatacontext'),'@odata.context','odatacontext'))


Remove JSON properties

To duplicate the profiles, we need to remove two properties from the JSON (odataconext and id):

Compose remove odatacontext

removeProperty(outputs('Compose_convert_to_JSON'),'odatacontext')

 Compose remove id

removeProperty(outputs('Compose_remove_odatacontext'),'id')

Combine prefix with display name and create duplicate enrollment profiles

Here we need to combine the prefix (if one exists) with the existing display name of the policy. If there is no prefix provided, we’ll utilize the existing display name, however this can get confusing when duplicating profiles, so I recommend adding a prefix when duplicating profiles in Power Apps:

setProperty(outputs('Compose_remove_id'),'displayName',concat(variables('varPrefix'),body('Parse_JSON_2')?['displayName']))

Create copy of enrollment profile (HTTP action)

Change the Method to “Post” and add the query below and in the body of the HTTP action add the “Outputs” dynamic content from “Combine prefix with display name” action so it knows what settings to create in the profile.

https://graph.microsoft.com/beta/deviceManagement/depOnboardingSettings/@{outputs('Compose_varProfileID')}/enrollmentProfiles/

Power Apps

I’m going to be brief on explaining each element of the Power App some with images, some with code, however if you want more details, please refer to a previous post: https://uem4all.com/2021/08/09/duplicate-intune-policies/

Select profile combobox – TokenSelector

Once a token is selected, we’ll set the ID as a variable and call the Flow to pull all the profiles over into a collection.

Screen onvisible

For onvisible for the “screen” e.g. “screen 1” the code is as follows:

What the code is doing calling Power Automate to get the enrollment tokens, then setting checkboxes in the gallery we’ll add below.

ClearCollect(
    enrollmentprofileCollection,
    GetAppleenrollmenttokens.Run()
);
Set(
    JSONVariable,
    JSON(
        enrollmentprofileCollection,
        JSONFormat.IncludeBinaryData
    )
);
Clear(enrollmentTokenPofiles);
UpdateContext({ClearCheckbox: true});
UpdateContext({ClearCheckbox: false});
UpdateContext({CheckCheckbox: true});
UpdateContext({CheckCheckbox: false})

Search profile textbox

No code required here, just add the text box and change the name if needed.

Prefix value text box

No code required here, just add the text box and change the name if needed.

Blank Gallery

Associate the gallery with the profile collection and add three labels. The labels will auto associate themselves with columns in the collection. Select the label to change which column it’s associated with. What we’re doing in the code below is sorting and searching, sort is associated with the up/down icons and search is associated with search profile text box added above as shown in the code below.

SortByColumns(Search(enrollmentTokenPofiles,Searchbox.Text,"displayName","description","id"),SortColumn, If(SortDecending,Ascending,Descending))

Gallery properties

Gallery checkbox

Add a checkbox to the Gallery which will duplicate itself automatically when there is more than one record. When items a are checked, they’re added to a collection as shown below.

Each up/down icon has code similar to the following:

UpdateContext({SortColumn: "displayName",SortDecending: !SortDecending})

Copy to profile combobox

Here we’re setting a variable to store which enrollment token was selected so Power Automate knows where to duplicate the profiles.

Duplicate button

In the code below, we’re sending over the prefix, the selected token to duplicate from, and the selected token to duplicate to. Note in the copy Flow, we needed to know which profiles to duplicate and is why we send these values over.

CopyAppleenrollmentprofile.Run(
    'TextInput prefix'.Text,
    JSON(
        DuplicateTokenSelector.Selected.id,
        JSONFormat.IndentFour
    ),
    JSON(
        selectedprofilecollection.id,
        JSONFormat.IndentFour
    )
)

Note: once the profiles are duplicated, I highly recommend going into each profile to make sure they duplicated properly and/or if any items need to be updated. For example, if you’re deploying the Intune Company Portal as part of the enrollment profile settings you will need to point it to new VPP token (if it’s different).

Conclusion

That’s it! We created three Flows and linked those with a Power App to duplicate Apple Automated Device Enrollment profiles. I hope this was informative and provided some ideas around duplication of Apple Automated Device Enrollment profiles across multiple enrollment tokens.