So, we have this new fancy Windows Autopilot v2 aka Device Preparation that I discussed recently. Personally, I’m a fan for my use cases at my company and I’ve fully adopted it across the organization. Today, we’re going to discuss my main issue (the importing of corporate identifiers into Intune). In this article we’ll discuss:
Why did I Move to Device Preparation?
Simply, I was tired and a little bit intrigued by the move because we’re a cloud native company that primarily uses Autopilot User-Driven Enrollment and I hate paying $20 for hardware hash.
Additionally, their user experience is really good, which is something I love. Device preparation isn’t for everyone, but if it’s a good fit I am a big proponent of it.
As you can see it’s really simple, we’re a small business, we like saving money, and it’s a net win for us. A company that refreshes 100 laptops a year can save a few thousand dollars a year just by moving to device preparation. Sure, it’s not the only reason, but it helps.
As a reminder, some of the aspects to device prep are:
- It’s a user-driven flow (no pre-provisioning aka white glove support).
- only supports Entra joined devices.
- sets up users as admin or basic users based on the policy.
- more transparent experience with progress bars for the user.
- LOB and Win32 apps can be deployed in the same deployment.
- Shift from confusing hardware hashes to corporate identifiers (which are still confusing, but slightly less so).
The Power Automate Solution for Corporate Identifiers
We’re going to start by covering a basic overview of the solution. Once that is done, we will dig into the different components that make up the solution.
You can see the overall solution below. Simply we do the following:
- It runs every 12 hours.
- Grabs CDW Emails with by the subject and the sender.
- Loops through each email, grab the body, converts it to text, and grabs the serial number and model number.
- Checks if that model number is for a Microsoft Surface (we’re only buying surfaces now).
- If it’s a Surface, it cleans up the model and serial and runs an API command to import the corporate device identifiers to power device preparation.
The overall solution wasn’t too incredibly painful, but I did have some struggles getting the data out of the email because of how CDW formats their emails. The good news is if you have Power Automate premium licenses, it’s not too bad.
My friend and fellow MVP Lindsay Shelton said I could use Encodian, but the solution works pretty well either way. I’ve come to learn over the years that it’s not about the journey, but it’s more about the destination (especially with the Power Platform).

Pulling Emails into the Corporate Device Identifier Flow
First, we want to figure out what information we need to get the ACTUAL emails we need. I created a GetEmailsv3 action with the “From” address of CDW’s shipping DL and a subject filter that matches those emails like below:

My JSON is below as well:
"type": "OpenApiConnection",
"inputs": {
"parameters": {
"importance": "Any",
"fetchOnlyWithAttachment": false,
"subjectFilter": "CDW Shipping Confirmation",
"folderPath": "REDACTED",
"from": "[email protected]",
"fetchOnlyUnread": true,
"includeAttachments": false,
"top": 25
},
"host": {
"apiId": "/providers/Microsoft.PowerApps/apis/shared_office365",
"connection": "shared_office365",
"operationId": "GetEmailsV3"
}
},
Next, I will add a conditional statement and pass the body to it:

Our JSON for that is simple:
{
"type": "Foreach",
"foreach": "@outputs('Get_CDW_Shipping_Confirmation_Emails')?['body/value']",
Converting CDW Email Bodies to Text for the Power Automate Flow
Now, we convert those email bodies to text to extract the data we will need for our API calls.
That is relatively easy with the “HtmlToText” action as seen below:

I’m sure there are a number of ways you could tackle this. The reason it was so complicated is CDW’s email is HTML and is also partially encoded. When it’s encoded in that way, it can get crazy. Later on, we will deal with the encoding and leftover HTML tags.
Extracting Serial Numbers and Manufacturer Model Numbers from the Email Body
First thing we’re going to do is use a neat little expression to pull out the serial number. It is not as easy as you would think I assure you.
We’re going to use the “Compose’ action with this expression:
substring(last(split(split(body('Convert_Email_from_HTML_to_Text'), 'Shipping Details')[0], ' ')), sub(length(last(split(split(body('Convert_Email_from_HTML_to_Text'), 'Shipping Details')[0], ' '))), 16), 16)
Essentially, what we’re doing here is splitting the content and returning the string RIGHT before Shipping Details as you can see here below. After it gets that string, it will take the 16 characters starting from the right, which meets the requirements to return our serial number. You will notice the serial number is in the delightfully (lots of sarcasm there) constructed table:

Next, we will use a similar concept to grab the manufacturer model number with this code:
substring(split(split(body('Convert_Email_from_HTML_to_Text'), 'Mfg. Part#:')[1], ' ')[1], 0, 9)
This code is easier, and will return the string RIGHT after “Mfg. Part #:”

The last piece is where we get “dirty” with a basically terrible IF statement in another compose. The reason I did it like this is we only order two model numbers right now (Surface Laptop 7 and 5). This simply sets the model field for our API call based on the model number pulled out of the order email:
if(equals(outputs('Get_Manufacturer_Model_Number'), 'ZHB-00001'), 'SurfaceLaptop7', if(equals(outputs('Get_Manufacturer_Model_Number'), 'RB1-00001'), 'SurfaceLaptop5', ''))
In larger enterprises, I would probably replace this with an API call to ServiceNow’s CMDB to resolve the model number in CDW to the actual model number that I need for the spreadsheet. As my environment has fewer devices, I can keep it simple.
The Conditional Statement to Filter Out Non-Device Purchases
The concern with everything we’ve done so far is we don’t want to go buck wild on running API calls. So, we add in a nice conditional statement to see if that order actually contains a valid Surface laptop.
As you can see below, we feed the output from our “Set_Model_Number” command, which in the previous step we used to set it to SurfaceLaptop5 or SurfaceLaptop7. If it STARTS with Surface, then we will tell it to continue to import the corporate device identifier:

Building the App Registration for the Corporate Device Identifier Import API Request
The app registration is pretty easy to build. First, you create a standard app registration and add a few API permissions (DeviceManagementConfiguration.ReadWrite.All and DeviceManagementServiceConfig.ReadWrite.All):

Once you create those, you will generate a client secret: (document that value)

The final two pieces you will need are the directory ID and the client ID:

Now, we have everything we need for the final part of our flow.
Building the HTTP action to Import Corporate Device Identifiers
Now, we add the HTTP action, which is easier than I thought it would be.
We start with the URI from my Device Preparation article: https://graph.microsoft.com/beta/deviceManagement/importedDeviceIdentities/importDeviceIdentityList.
We’ll also set the method to POST, add the Content-type header.
The tricky part will be the body. Here’s an amazing trick to HTTP Body’s in the Power Platform:
You need to add a second @ symbol as below (this is my full code below, which will decode my entries from earlier and set them exactly as is needed for the API command):
{
"importedDeviceIdentities": [
{
"@@odata.type": "#microsoft.graph.importedDeviceIdentity",
"importedDeviceIdentifier": "MICROSOFTCORPORATION,@{concat(
replace(outputs('Set_Model_Number'), decodeUriComponent('%0A'), ''),
',',
replace(outputs('Get_Serial_Number'), decodeUriComponent('%0A'), '')
)}",
"importedDeviceIdentityType": "manufacturerModelSerial"
}
],
"overwriteImportedDeviceIdentities": "true"
}

The only other part to this is the authentication, which I highlight below. You set “Active Directory OAuth” which will let you use your app registration you created earlier. Note my authority and audience, which luckily work just fine:

Evolving the Workflow
After it was rolled out for a bit, I realized I needed to account for other devices, like iOS devices. I don’t care about these corporate identifiers currently, but we should update the logic to account for it.
First, we flip the order of scraping info out of the emails like this:

We wanted to get the model number first, because that will help facilitate the rest of the flow. Once we have that, we do a basic conditional statement. You could do a “contains” with /, but since I am only buying one specific corporate iPad model at the moment, this works fine:

With the new condition, if it’s true I use a different function to pull the Apple serial number, since it doesn’t follow the same format as a Microsoft serial number:
substring(last(split(split(body('Convert_Email_from_HTML_to_Text'), 'Shipping Details')[0], ' ')), sub(length(last(split(split(body('Convert_Email_from_HTML_to_Text'), 'Shipping Details')[0], ' '))), 12), 12)

Simply, this code will ensure that it understands the serial number appropriately and extracts it in the right format to get ingested by the API. I probably could have skipped this code above, but the point is that we will likely add to it in the future to ingest Apple corporate identifiers instead. This is just a nice example of how you will continue to evolve your workflow with the growing needs of your business.
Closing Thoughts
Well, there you have it! Honestly, I would have preferred other methods, but the Power Platform does a great job of crawling emails and orchestrating what we needed here. I’m not a Power expert by any means, but sometimes it’s good to have some dirty code. As sometimes we need to remind people, us Intune people are not software engineers we are systems engineers.
I’m sure I will refine and improve upon this as I move forward, but overall leveraging this code and using a 12-hour trigger is an excellent way to enhance device preparation and shift right on managing the lifecycle of Autopilot v2.
