Intune Policy Assignment Classification Easy Secrets of using Graph API with PowerShell

Let us discuss Intune Policy Assignment Classification Easy Secrets of using Graph API with PowerShell. This article will be completely different from my other articles, where I mostly explain about behind the scene logic.

But in this article, I will take you through the logic and steps that we go through to create a script for accomplishing an objective.

Problem Statement

From Intune portal, when you check the assignment for a policy (config/compliance/app), it shows you the group name under deployments.

Intune Policy Assignment Classification Easy Secrets
Intune Policy Assignment Classification Easy Secrets

The Assignments blade only shows the names of the Groups (and Intent as well in case of Application) to which the policy is deployed.

Patch My PC

But it does not tell us the Group Type – whether a User group or Device group, which is sometimes necessary to know.

Unless you have a specific naming scheme to distinguish between a Device group and a User group, it isn’t easy to assert whether the deployment is targeted to users or devices.

In such a case, you have to open the portal in another tab/browser window and navigate to Groups blade to check for the group Member and confirm the deployment type.

So is there a way we can achieve this?

Adaptiva

Solution Required Intune Policy Assignment Classification

Get the assignment details with distinguished classification – User or Device-based deployment.

Back to Graph API – the worker behind Microsoft clouds

This is a very legitimate ask and made me search the GitHub repo to check if any function is already available. But I got nothing, so I started working on the script.

Well pretty obvious that I would resort to Graph API to retrieve the policy assignment and then distinguish deployment type – User or Device.

For those who are not quite familiar with the Graph API, I will briefly explain the working details for easy understanding.

PS Script Logic

Meeting the pre-requisites

Unless and untill you are creating all the logic and functions yourself, it’s always good to check if there is already a function/cmdlet available in GitHub or PS repository that you can use in your script and then build upon it to customize the output as you want.

This saves us a lot of time writing code for things that have already been taken care of and can be used by installing and importing the module to the current PS session and calling that function.

The script that we will create will work by implementing the functions from modules already available on GitHub.

The modules in context are MSGraphFunctions and Microsoft.Graph.Intune

Thus you would need to install these above modules in PS

Install-Module -Name MSGraphFunctions
Install-Module -Name Microsoft.Graph.Intune

Post-install, you need to import them to the current PS session to use the functions available in the above modules.

Import-Module -Name MSGraphFunctions
Import-Module -Name Microsoft.Graph.Intune

Getting the auth token and connecting to Graph

With the modules installed and imported to the current PS session, now we need to connect to the cloud service.

Now there are many ways that we can get connected to the Microsoft cloud services. But for, this script requires two-click functions as below.

Connect-Graph   #Retrieves Auth Token with Delegated User Permissions
Connect-MSGraph #Connects with an authenticated account to use Microsoft Graph cmdlet requests
Intune Policy Assignment Classification- Connecting to Graph services from PowerShell
Intune Policy Assignment Classification- Connecting to Graph services from PowerShell. 1

Once connected is where the real work starts…

Graph API calls are based on REST methods, so we have GET, POST, DELETE, PATCH, and PUT operations. I will not explain these as you can get good documentation of REST methods over the internet.

Using the Graph to retrieve policy

The first step is to retrieve the policy we are concerned about.

If we are working on Compliance Policies, then in Graph Explorer, we can get all the compliance policies with the GET call below.

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

Intune Policy Assignment Classification - Getting policy using Graph Explorer
Intune Policy Assignment Classification – Getting policy using Graph Explorer. 2

Graph Explorer is a handy browser-based tool to run your Graph calls, but it does not support commands in batch. It is a single-line command executor. We love to use PS to connect to Graph and use its scripting abilities to execute a series of commands constructed with logic to achieve the goal as required.

The above Graph Explorer call returns all the configured compliance policy details in the response. But there is no way to store it in a variable and use it programmatically for later use.

So let’s come back to our PS session, which is connected to Graph services. The same is achieved here utilizing the below function implemented in MSGraphFunctions module.

$deviceCompliancePolicies = Get-GraphDeviceCompliancePolicy 

Within the function, it is essentially the same Graph GET call to the URI mentioned above, and all the policy details as returned are being stored to the variable.

Checking the assignment for an individual policy

In Graph Explorer, if we need to check the assignment of a policy, we would first require to access the particular policy using the policy GUID and then check its assignment.

This is essentially a GET Graph call to the below URI.

https://graph.microsoft.com/beta/deviceManagement/deviceCompliancePolicies/<policyGUID>/assignments

 Intune Policy Assignment Classification - Getting assignment detail using Graph Explorer
Intune Policy Assignment Classification – Getting assignment detail using Graph Explorer 3

In the PS aspect, we have the variable $deviceCompliancePolicies from the last step, which stores all the Compliance policies.

But to get the assignment details, we would need to get to each policy object individually to fetch the policy GUID with which we can get the assignment.

This is achieved using a for each loop, and then we can get the assignment details of each policy and keep them in a variable, as shown below.

foreach ($policy in $deviceCompliancePolicies) 
{
 $assignments = Get-GraphDeviceCompliancePolicyAssignment -Id $policy.id
} 

Within the function, it is essentially making the same call as we created above in Graph Explorer.

$uri =
"https://graph.microsoft.com/$apiVersion/deviceManagement/deviceCompliancePolicies/$id/assignments"
$query = Invoke-RestMethod -Uri $uri -Headers $authHeader -Method Get -ErrorAction Stop
$query.Value 

Checking the content of the $assignment variable

If you check using Graph Explorer for the response of the GET call, the value as returned is

{
"@odata.context": "https://graph.microsoft.com/beta/$metadata#deviceManagement/deviceCompliancePolicies('<policyGUID>')/assignments",
     "value": [
         {
             "id": "dfc4f58e-8421-40d3-90f1-02e108dc202d_6239fcb0-190e-431f-b618-b1510d71986a",
             "target": {
                 "@odata.type": "#microsoft.graph.groupAssignmentTarget",
                 "groupId": "6239fcb0-190e-431f-b618-b1510d71986a"
             }
         },
         {
             "id": "dfc4f58e-8421-40d3-90f1-02e108dc202d_a837c8b2-2c69-404f-9081-c088730c8abc",
             "target": {
                 "@odata.type": "#microsoft.graph.groupAssignmentTarget",
                 "groupId": "a837c8b2-2c69-404f-9081-c088730c8abc"
             }
         }
     ]
 } 

Notice that it returns the GUID for the Azure AD Group object and not the name. This is because the backend works on GUID and not human-friendly names.

Also, what if a policy has multiple assignments like the above, which is very likely. Our script needs to accommodate that.

Handling multiple assignments

For multiple group assignments, there will be more than 1Value returned. Thus based on the count, we can have a logic implied like below

if($assignments.count -gt 1)
{
  foreach ($assignment in $assignments)
      {
        $groupId=$assignment.target.groupid 
      }
}  

Getting the Group Name using the groupId as retrieved

The variable $assignment will store the value for a particular assignment. Referring to Graph Explorer, that will be like

{
"id": "Assignment GUID",
"source": "direct",
"sourceId": "dfc4f58e-8421-40d3-90f1-02e108dc202d",
"target": {
           "@odata.type": "#microsoft.graph.groupAssignmentTarget",
           "groupId": "6239fcb0-190e-431f-b618-b1510d71986a"
          }
}

What we are interested in here is the value of groupId. So we need to extract this from all the other values as stored in $assignment and store it to a new variable like shown below

$groupId=$assignment.target.groupId   

With the groupId available from above, we can get the Group details as required using a GET Graph call to URI https://graph.microsoft.com/beta/Groups/<groupId> and then retrieve the Group Name using the displayName parameter from the value as returned. This is what is implemented in PS as

$groupName= (Get-AADGroup -groupId $g).displayname

The function is essentially performing the below

$uri = "https://graph.microsoft.com/$graphApiVersion/Groups?`$filter=id
eq '$id'"
(Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).Value 

Till this was easy logic, but here we come to a roadblock…

Does the Group object have the required parameter to support user/device-based classification?

A Group object in Azure has the below properties/parameters

Intune Policy Assignment Classification - Graph returned properties for Group object
Intune Policy Assignment Classification – Graph returned properties for Group object 4

As you can see, a Group has only two classifications based on type – Static or Dynamic. It has no sort based on membership – User or Device

Then how do we classify if a Group is a User/Device group?

To tell if the group is a User group or a Device group, you need to check one step further – the Members of the Group.

From a Graph Explorer perspective, that would be a GET Graph call to URI https://graph.microsoft.com/beta/Groups/<groupId>/members

Device and User member type has unique value identified via the "@odata.type" as returned in the Value

User ->  "@odata.type":"#microsoft.graph.user",
Device ->  "@odata.type":"#microsoft.graph.device",

We can implement a logic to determine the Group Type – whether it is a User group or a Device group.

In the PS aspect, we are doing that as

$groupType=(Get-AADGroupMember -groupId $groupId).'@odata.type'.split('.')[-1]

What if the device contains both users and devices as members?

Such should not be ideal and more the case when we talk about device management solutions. The user group and Device group should be separate and unique.

Still, if you have such a group to which a policy is deployed, then due to obvious reasons, the script/logic fails.

Still, you can be creative and come up with something like this (You will not find this in the sample script I provided below)

$Members =  (Get-AADGroupMember -groupId $groupId)
foreach ($member in $Members) {
  $groupType = $member.'@odata.type'.split('.')[-1]
     if ($memberType -eq "user")
        {
          #Statement for this is User group
        }
     elseif ($memberType -eq "device")
        {
          #Statement for this is a Device group
        }
     else
        {
          #Statement for this is a mixed group
        }
}

Additional considerations when working with client App

For applications, there is some extra parameter to be considered.

One such is the deployment Intent – Required/Available/Uninstall. The script can also accommodate this by retrieving the intent parameter as returned in the response value to the assignment check Graph call and stored in $assignment a variable.

$intent=$assignment.intent 

For Win32 apps, the App_Install_Context is one such parameter that is of more importance now since, from Windows 10 1903, ESP can track Win32 apps.

If you have read till this, you would have already got the concept

  • A Graph Get a call to URI https://graph.microsoft.com/beta/deviceAppManagement/mobileApps
  • Store the value as returned to a variable. This would return all the apps as configured in the tenant.
  • So next step would be to have a logic to cycle through each app from all the apps as returned and stored in the variable from above. This would be a foreach loop.
  • Then it is easy to retrieve the application name using the displayName parameter from the returned value.
  • Also, to get the application type, you would be retrieving the "@odata.type"
  • Based on the application type, you can have a check logic to see if it is a Win32 app. Then you can check for the App_Install_Context, which is the below parameter as present in the value returned for a Win32 app.
"installExperience": 
{
         "runAsAccount": "system/user",
         "deviceRestartBehavior": "basedonreturncode/allow/suppress/force"
}

Visualizing the data tree to help retrieval of data from the returned value of a Graph call

This is what I apply when I work with Graph.

Intune Policy Assignment Classification - Visualizing Graph returned data to retrieve the one we want using PS
Intune Policy Assignment Classification Visualizing Graph returned data to retrieve the one we want using PS 5

If you can get to traverse the data as returned, you can get the data to work using simple logic.

Intune Policy Assignment Classification – The Script

With all this knowledge, let’s get to the complete script.

The script, as shared below, is a sample script to get you the deployment details of Client Apps only. But I believe, with the knowledge acquired by reading this post, you would be able to use it to apply to all other Intune policies as well, as per your requirement.

 $details=@()
  
 $clientApps = Get-GraphClientApp
  
 foreach ($clientApp in $clientApps) #Getting each app from all apps as returned above
 {
 $appName = $clientApp.displayName #Getting the App Name from returned value
  
 $clientAppType = $clientApp.'@odata.type'.split('.')[-1]  #Getting the App Type splitting the @odata.type as returned
  
 <#
 This is the logic to check the app intent of a Win32 LOB app
 #>     
  
 if($clientAppType -eq "Win32LobApp") 
         {
         $installcontext= (Get-GraphClientApp -Id $clientApp.id).installExperience.runAsAccount
         } 
 else 
         { 
         $installcontext="Available for Win32 app type only"
         }
             
 $assignments = Get-GraphClientAppAssignment -Id $clientApp.id #Getting the assignment for the particular app
  
 <#
 The if ($assignments) logic is implemented to skip apps with null assignments
 #>
     if ($assignments)
     
     {
 <#
 This logic is implemented for apps with multiple assignments
 #>
   
     if($assignments.count -gt 1) {
        
        foreach ($assignment in $assignments)  #Getting each assignment for all assignments as returned
        { 
       
             $intent=$assignment.intent            #Getting the intent of the assignment - Required/Available/Uninstall
             $groupId=$assignment.target.groupid   #Getting the groupId to which the assignment is made
             $groupName=(Get-AADGroup -groupId $groupId).displayname   #Getting the groupName using the groupId as obtained above
             $groupType=(Get-AADGroupMember -groupId $groupId).'@odata.type'.split('.')[-1]   #Getting the odata type for associated group member
  
       
       #This part is the output section
  
                 $details+=[PSCustomObject]@{
                 App_Name=$appName
                 AppType=$clientAppType
                 App_Intent =$intent
                 Group_Name=$groupName
                 Group_Type=$groupType
                 App_Install_context=$installcontext
        }
        }
        }     
             
    else
        {
         
         $intent=$assignments.intent   
         $group = $assignments.target.groupId
         $groupname= (Get-AADGroup -groupId $group).displayname
         $groupType=(Get-AADGroupMember -groupId $group).'@odata.type'.split('.')[-1]
  
       #This part is the output section
  
                 $details+=[PSCustomObject]@{
                 App_Name=$appName
                 AppType=$clientAppType
                 APP_Intent =$intent
                 Group_Name=$groupName
                 Group_Type=$groupType
                 App_Install_context=$installcontext
        } 
        }
        } 
 }
  
 $details #Complete output is stored to this variable 

Using the Convert-CSV cmdlet, you can export all the data stored in the $details variable, which stores the entire script output.

The output of the script

Intune Policy Assignment Classification - Script Output showing User or Device based deployment
Intune Policy Assignment Classification Script Output showing User or Device based deployment 6

I hope this article will be helpful for many who are thinking of starting to use Graph API with PowerShell.

That’s all for today. I will be back with some other topic on Intune soon. Until then, read something new every day and learn something new every day…

Resources

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.