Automating Corporate Device Identifier Imports with Power Automate and the Graph API

letterkenny

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:

  1. It runs every 12 hours.
  2. Grabs CDW Emails with by the subject and the sender.
  3. Loops through each email, grab the body, converts it to text, and grabs the serial number and model number.
  4. Checks if that model number is for a Microsoft Surface (we’re only buying surfaces now).
  5. 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).

The diagram of the Power Automate flow for Corporate Device Identifier imports via API

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:

graphic showing the from address and subject filter for pulling CDW emails

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:

graphic showing the conditional statement using the body from the previous steps

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:

graphic showing converting the body from HTML to text

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:

Graphic of the CDW email portion where the CDW part number and serial numbers are

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 #:”

graphic showing the Surface laptop part number and CDW part number

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:

graphic of the conditional statement to filter out non-device purchases

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):

graphic showing the Graph permissions for my app registration needed to import the corporate device identifiers

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

graphic of my app registration's client secret

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

graphic showing where I can get my client ID and tenant ID for my app registration

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"
}
graphic of the API command in Power Automate

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:

graphic showing the use of the Active Directory OAuth method in Power Automate

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:

Graphic of moving the model number logic above the serial number logic to handle Apple iPads

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:

Graphic showing the condition we are using to handle the logic for iPads

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)
Graphic of the new condition that will get a serial number in the proper format based on the device platform.

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.

Facebook
Twitter
LinkedIn
The article discusses the author's positive experience with Windows Autopilot v2, focusing on the integration of corporate identifiers into Intune through a Power Automate solution. It details the process of extracting data from CDW emails, filtering valid device purchases, and constructing API requests for device identity import, ultimately enhancing device management efficiency.

Let me know what you think

Scroll to Top

Discover more from Mobile Jon's Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading