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

0

This article will be completely different from my other articles where I mostly explain about behind the scene logics. But in this article, I will take you through the logics and steps that we go through to create a script for accomplishment of an objective –

“Getting Intune Policy Assignment Classification to find out the deployement type – User or Device?”

The idea for this came when one of my friend came to me with the below ask.

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.

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

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

In such case, you have to go to open the portal in another tab/browser windows 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?

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 there is any function already available. But I got nothing, so 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.

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

PS Script Logic

Meeting the pre-requisites

Unless and untill you are creating all the logic and functions yourself, its 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 to write code for things which has already being taken care of and can be used by just installing and importing the module to the current PS session and calling that function.

The script that we will create will works by implementing the functions from modules already available in 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 would need to import them to 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 current PS session, now we need to connect to the cloud service.

Now there are a lot ways that we can get connected to the Microsoft cloud services. But for the puspose of this script, it requires to use two connect 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

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 am not going to explain these as you can get a 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 for.

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

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

Graph Explorer is a handy browser based tool to run your Graph calls but it does not supports commands in batch. It is a single line command executor. The reason why we love to use PS to connect to Graph and use its scripting abilites to execute a series of commands constructed with logic to achieve the goal as required.

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

So lets 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 above mentioned URI and all the policy details as returned are being stored to the variable.

Checking the assignment for individual policy

In Graph Explorer, if we need to check 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

In PS aspect, we have the variable $deviceCompliancePolicies from 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 foreach 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 made 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 $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 above, which is very likely. Our script needs to accommodate that.

Handling multiple assignment

For multiple group assignment, 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 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 Group object has 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

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

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

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

From 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",

Based on this, we can implement a logic to determine the Group Type – whether it is a User group or a Device group.

In 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 ideally and more in case when we are talking about device management solution. 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 reason 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 comes some extra parameter to be considered.

One such is the deployment Intent – Required/Available/Uninstall. This can be also accommodated in the script by retrieving the intent parameter as returned in response value of the assignement check Graph call and stored in $assignment variable.

$intent=$assignment.intent 

For Win32 apps, the App_Install_Context is one such parameter which 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 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

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 these knowledge, lets 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.

 [email protected]()
  
 $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 

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

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

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

Thats all for today. I will be back with some other topic on Intune soon. Till then, read something new everyday, learn something new everyday…

Resources

LEAVE A REPLY

Please enter your comment!
Please enter your name here

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