Let us discuss Intune Policy Assignment Classification Easy Secrets of using Graph API with PowerShell. This article will completely differ from my other articles, where I mostly explain behind-the-scenes logic.
But in this article, I will explain the logic and steps involved in creating a script to accomplish an objective.
Problem Statement
When you check the assignment for a policy (config/compliance/app) from the Intune portal, the group name is shown under deployments.
The Assignments blade only shows the names of the Groups (and Intents, in the case of Applications) to which the policy is deployed. But it does not tell us the Group Type—whether a User group or Device group—which is sometimes necessary.
Unless you have a specific naming scheme to distinguish between a Device group and a User group, asserting whether the deployment targets users or devices isn’t easy.
In such a case, you must open the portal in another tab or browser window, navigate to the Groups blade, 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 any function is already available. But I got nothing, so I started working on the script.
Well, it’s pretty obvious that I would resort to Graph API to retrieve the policy assignment and then distinguish the 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 create 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 the 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, we now need to connect to the cloud service.
There are many ways to connect to Microsoft cloud services. However, this script requires two-click functions, as shown below.
Connect-Graph #Retrieves Auth Token with Delegated User Permissions
Connect-MSGraph #Connects with an authenticated account to use Microsoft Graph cmdlet requests
Once connected, 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 online.
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, we can get all the compliance policies in Graph Explorer with the GET call below.
https://graph.microsoft.com/beta/deviceManagement/deviceCompliancePolicies
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. However, this information cannot be stored in a variable and used programmatically later.
So, let’s return to our PS session connected to Graph services. We achieve this by utilizing the function below implemented in the 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 stored in the variable.
Checking the assignment for an individual policy
In Graph Explorer, if we need to check a policy’s assignment, we first need to access the particular policy using its 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
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. 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 Azure AD Group object’s GUID rather than its name. This is because the backend works on GUIDs rather than human-friendly names.
Also, 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
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 or 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 a situation should not be ideal, and this is especially true 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 are some extra parameters 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 storing 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 this, you would have already understood 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 the 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 returnedvalue
.
- Also, to get the application type, you would be retrieving the
"@odata.type"
- You can check the logic based on the application type 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.
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 shared below is a sample script that will get you only the deployment details of Client Apps. However, with the knowledge acquired by reading this post, you could use it to apply to all other Intune policies as well, as per your requirements.
$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
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 another topic on Intune soon. Until then, read something new every day and learn something new every day.
Resources
We are on WhatsApp. To get the latest step-by-step guides and news updates, Join our Channel. Click here –HTMD WhatsApp.
Author
Joymalya Basu Roy is an experienced professional in the IT services field with almost 5 years of experience working with Microsft Intune. He is currently working as a Senior Consultant – Architect with Atos India. He is an ex-MSFT where he worked as a Premiere Support Engineer for Microsoft Intune. He was also associated with Wipro and TCS in the early stages of his career. He is awarded the Microsoft MVP award for Enterprise Mobility in 2021. You can find all his latest posts on his own blog site MDM Tech Space at https://joymalya.com