Make Invoke-MgGraphRequest Return PowerShell Objects

In this post I'll show you how you can use Invoke-MgGraphRequest and make it return a proper PowerShell object instead of just a hash table.

I’ve blogged about the Invoke-MgGraphRequest Cmdlet before. The funny thing is that I already used the -OutputType parameter there. However, I used the parameter value HttpResponseMessage and I did not know or even think of other possible values then.

Most Microsoft Graph Functions included in Microsoft.Graph.* PowerShell modules will return a PSObject by default. That’s not the case with Invoke-MgGraphRequest though. That Cmdlet usually returns a hash table by default.

What’s the Difference between a Cmdlet and a Function in PowerShell?

Before I get to it, I want to do a quick detour. You might have noticed that I referred to one command as a function and to the other one as a Cmdlet. That was totally intentional. I knew both terms already and I’m pretty sure you did as well. However, I never really cared enough to learn the difference until now. You might find some old blog posts of me where I incorrectly referenced functions as Cmdlets or vice versa. The difference is quite simple. Cmdlets consist of compiled .NET code while functions are written entirely in PowerShell.

It’s pretty easy to check yourself if a PowerShell command included in a module is a Cmdlet or a function as well. Simply run Get-Command followed by its name to check.

1
2
3
4
5
Get-Command "Get-MgUser"

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Function        Get-MgUser                                         2.28.0     Microsoft.Graph.Users
1
2
3
4
5
Get-Command "Invoke-MgGraphRequest"

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Cmdlet          Invoke-MgGraphRequest                              2.28.0     Microsoft.Graph.Authentication

Now let’s compare the default output of Get-MgUser and Invoke-MgGraphRequest to the /users Graph endpoint.

Get-MgUser vs. Invoke-MgGraphRequest Output

Get-MgUser

Note: For a better comparison, I’m using Select-Object to only include the same properties which are returned by the Invoke-MgGraphRequest Cmdlet below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$mgUser = Get-MgUser -UserId a60938e1-994e-4d19-bb48-711b89938245 | Select-Object mail, officeLocation, jobTitle, preferredLanguage, surname, givenName, businessPhones, userPrincipalName, mobilePhone, "@odata.context", id, displayName
$mgUser

# Output
Mail              : admin@nocaptech.ch
OfficeLocation    : 
JobTitle          : ADMIN
PreferredLanguage : 
Surname           : NCT
GivenName         : ADMIN
BusinessPhones    : {}
UserPrincipalName : admin@nocaptech.ch
MobilePhone       : 
@odata.context    : 
Id                : a60938e1-994e-4d19-bb48-711b89938245
DisplayName       : ADMIN NCT

This returns a PSObject. I can confirm this running the following code.

1
2
3
4
5
6
$mgUser.GetType()

# Output
IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    PSCustomObject                           System.Object

This also means that I can access any property of $mgUser by simply adding `. to it.

1
2
3
4
$mgUser.DisplayName

# Output
ADMIN NCT

Bonus Tip: Get the names of all Properties available on an Object

If you want to store all the property names of an object in a variable you can use this code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
$propertyNames = $mgUser.PSObject.Properties | ? { $_.MemberType -eq "NoteProperty" } | Select-Object -ExpandProperty Name
$propertyNames

# Output
Mail
OfficeLocation
JobTitle
PreferredLanguage
Surname
GivenName
BusinessPhones
UserPrincipalName
MobilePhone
Id
DisplayName

If you only want to store the properties who have a value in a variable, you can use this code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$propertyNames = $mgUser.PSObject.Properties | ? { $_.MemberType -eq "NoteProperty" -and $_.Value } | Select-Object -ExpandProperty Name

# Output
Mail
JobTitle
Surname
GivenName
UserPrincipalName
Id
DisplayName

If you’re looking for a use case for this, think about a scenario where you want to to a Format-Table, Format-List or Select-Object with lots of properties but are too lazy to type everything by hand. Now that we have all property names in a variable, we can easily reference $propertyNames in other commands.

Of course this is not limited to the commands mentioned above. You could also use this to as a list of properties you want to include when you do something like Get-MgUser.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Get-MgUser -Top 1 -Property $propertyNames | fl $propertyNames                                                                     

# Output
Mail              : admin@nocaptech.ch
JobTitle          : ADMIN
Surname           : NCT
GivenName         : ADMIN
UserPrincipalName : admin@nocaptech.ch
Id                : a60938e1-994e-4d19-bb48-711b89938245
DisplayName       : ADMIN NCT

Now only the properties which have a value are shown in the output.

A more specific example is when you get a SharePoint list item through the Graph API. In my case, my list has a lot of columns which are field properties of list items and I want to compare the values stored in the list to a PowerShell object which holds the values of actual Entra ID objects. Since my PowerShell object doesn’t have any SharePoint specific properties which are present on the list item, I needed to create a new object for each list item which only includes the properties present on the PowerShell object of Entra ID values as well. Instead of manually copying and pasting each property name I want included, I used the code below to get all the property names and join all the relevant ones so I could then copy it and append to Select-Object.

1
($sharePointListItems[0].PSObject.Properties | ? { $_.MemberType -eq "NoteProperty" -and $_.Name -notmatch "_" } | Select-Object -ExpandProperty Name) -join ", "

I still had to manually remove a couple of properties but at least I didn’t have to copy and paste each property name manually. Yes, I’m lazy like that.

1
2
$currentSharePointListItem = $sharePointListItems.IndexOf[$currentIndex] | Select-Object `
    AppOwnerTenantId, AppNotifiedSecretCount, AppStatusReason, AppExpiredCertificateCount, AppStatus, AppObjectId, AppDelegatedPermissionsCount, SpCreatedOn, AssignmentRequired, AppExpiringSoonCertificateCount, AppTotalCertificateCount, AppConfigApplicationPermissions, AppValidSecretCount, SpObjectId, LinkTitle, SpApplicationPermissionsCount, AppConfigDelegatedPermissions, SpPortalLink, AppEditorLookupId, EditorLookupId, Objects, AppValidCertificateCount, PublisherDomain, AppApplicationPermissionsCount, AppType, AppAuthorLookupId, AppAndSpOwnersMismatch, ApplicationPermissionsMismatch, AppExpiredSecretCount, AppExpiringSoonSecretCount, AppTotalSecretCount, SpAppId, Title, AppOwners, SpEnabled, AppCreatedOn, SpGrantedApplicationPermissions, AppNotifiedCertificateCount, SpDelegatedPermissionsCount, SpAdminConsentPermissions, DelegatedPermissionsMismatch, SpType, AppAppId, AppPortalLink, SignInAudience, SpGrantedDelegatedPermissions, AppOwnerTenantName

As you can see, this would have been quite a lot of copying and pasting, if I had done it manually. Now I have a new object, only containing the properties I want so I can easily compare the object of SharePoint list values against the already existing object containing the values from Entra ID.

Invoke-MgGraphRequest

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
$mgUserREST = Invoke-MgGraphRequest -Method Get -Uri "https://graph.microsoft.com/v1.0/users/a60938e1-994e-4d19-bb48-711b89938245"
$mgUserREST

# Output
Name                           Value
----                           -----
mail                           admin@nocaptech.ch
officeLocation
jobTitle                       ADMIN
preferredLanguage
surname                        NCT
givenName                      ADMIN
businessPhones                 {}
userPrincipalName              admin@nocaptech.ch
mobilePhone
@odata.context                 https://graph.microsoft.com/v1.0/$metadata#users/$entity
id                             a60938e1-994e-4d19-bb48-711b89938245
displayName                    ADMIN NCT

When I run Invoke-MgGraphRequest for the same user Id, I get a hash table instead. Hash tables consist of name/value pairs.

1
2
3
4
5
6
$mgUserREST.GetType()

# Output
IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Hashtable                                System.Object

While it’s still possible to access $mgUserREST.displayName like that, I can also access it like this $mgUserREST["displayName"]. However, I much prefer PSObjects to hash tables.

The main reason why I prefer PSObjects is because hash tables don’t really support Format-Table and Format-List doesn’t look nearly as nice as with PSObjects.

Format-Table Example of PSObject

1
2
3
4
5
6
$mguser | ft $propertyNames                                                                                                        

# Output
Mail               JobTitle Surname GivenName UserPrincipalName  Id                                   DisplayName
----               -------- ------- --------- -----------------  --                                   -----------
admin@nocaptech.ch ADMIN    NCT     ADMIN     admin@nocaptech.ch a60938e1-994e-4d19-bb48-711b89938245 ADMIN NCT

Format-Table Example of Hash Table

The output will look exactly like the output here.

Format-List Example of PSObject

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$mgUser | fl $propertyNames

# Output
Mail              : admin@nocaptech.ch
JobTitle          : ADMIN
Surname           : NCT
GivenName         : ADMIN
UserPrincipalName : admin@nocaptech.ch
Id                : a60938e1-994e-4d19-bb48-711b89938245
DisplayName       : ADMIN NCT

Format-List Example of Hash Table

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$mgUserREST.GetEnumerator() | ? { $_.Value } | fl

Name  : mail
Value : admin@nocaptech.ch

Name  : jobTitle
Value : ADMIN

Name  : surname
Value : NCT

Name  : givenName
Value : ADMIN

Name  : userPrincipalName
Value : admin@nocaptech.ch

Name  : @odata.context
Value : https://graph.microsoft.com/v1.0/$metadata#users/$entity

Name  : id
Value : a60938e1-994e-4d19-bb48-711b89938245

Name  : displayName
Value : ADMIN NCT

As you can see, PSObjects are much easier to work with in these types of scenarios. Especially if you’re working with arrays and multiple objects.

All the responses of the examples so far only included one user object. It only gets worse with multiple user objects.

1
2
3
4
5
6
7
8
$mgUserRESTMultipleUsers = Invoke-MgGraphRequest -Method Get -Uri "https://graph.microsoft.com/v1.0/users?`$orderby=displayName&`$top=2"

# Output
Name                           Value
----                           -----
value                          {a60938e1-994e-4d19-bb48-711b89938245, defd4bfe-987c-4c5b-a9e3-c59bfb15b57b}
@odata.nextLink                https://graph.microsoft.com/v1.0/users?$orderby=displayName&$top=2&$skiptoken=RFNwdAIAAQAAADE6Q2FybG9zIERpYXogZGVmZDRiZmUtOTg3Y… 
@odata.context                 https://graph.microsoft.com/v1.0/$metadata#users

First of all, this is still a hash table but the actual values are now nested in the Value property.

1
2
3
4
5
$mgUserRESTMultipleUsers.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Hashtable                                System.Object

PowerShell says that $mgUserRESTMultipleUsers.Value is an Object[] which means it’s an array of objects.

1
2
3
4
5
6
$mgUserRESTMultipleUsers.Value.GetType()

# Output
IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

However, when I access the Value property, it’s still a hash table and we can’t even see clearly where one object ends and the next one begins.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$mgUserRESTMultipleUsers.Value

Name                           Value
----                           -----
mail                           admin@nocaptech.ch
officeLocation
jobTitle                       ADMIN
preferredLanguage
surname                        NCT
givenName                      ADMIN
businessPhones                 {}
userPrincipalName              admin@nocaptech.ch
mobilePhone
id                             a60938e1-994e-4d19-bb48-711b89938245
displayName                    ADMIN NCT
mail                           carlos@nocaptech.ch
officeLocation
jobTitle                       Customer Support Representative
preferredLanguage
surname                        Diaz
givenName                      Carlos
businessPhones                 {+41 43 123 45 67}
userPrincipalName              carlos@nocaptech.ch
mobilePhone                    +41 79 456 78 90
id                             defd4bfe-987c-4c5b-a9e3-c59bfb15b57b
displayName                    Carlos Diaz

Up until now, I’ve always used the simple yet effective trick to convert to and from json to convert a hash table to a PSObject.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$mgUserRESTMultipleUsersObject =  $mgUserRESTMultipleUsers.Value | ConvertTo-Json -Depth 99 | ConvertFrom-Json -Depth 99
$mgUserRESTMultipleUsersObject

# Output
mail              : admin@nocaptech.ch
officeLocation    : 
jobTitle          : ADMIN
preferredLanguage : 
surname           : NCT
givenName         : ADMIN
businessPhones    : {}
userPrincipalName : admin@nocaptech.ch
mobilePhone       : 
id                : a60938e1-994e-4d19-bb48-711b89938245
displayName       : ADMIN NCT

mail              : carlos@nocaptech.ch
officeLocation    : 
jobTitle          : Customer Support Representative
preferredLanguage : 
surname           : Diaz
givenName         : Carlos
businessPhones    : {+41 43 123 45 67}
userPrincipalName : carlos@nocaptech.ch
mobilePhone       : +41 79 456 78 90
id                : defd4bfe-987c-4c5b-a9e3-c59bfb15b57b
displayName       : Carlos Diaz

$mgUserRESTMultipleUsersObject.GetType()

# Output
IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

But thanks to a recent inline suggestion by GitHub Copilot, I discovered that you can actually change the OutputType to PSObject when using Invoke-MgGraphRequest. When multiple values/users are returned, we’ll still need to select the Value property explicitly.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$mgUserRESTMultipleUsers = Invoke-MgGraphRequest -Method Get -Uri "https://graph.microsoft.com/v1.0/users?`$orderby=displayName&`$top=2" -OutputType PSObject | Select-Object -ExpandProperty Value
$mgUserRESTMultipleUsers

# Output
businessPhones    : {}
displayName       : ADMIN NCT
givenName         : ADMIN
jobTitle          : ADMIN
mail              : admin@nocaptech.ch
mobilePhone       : 
officeLocation    : 
preferredLanguage : 
surname           : NCT
userPrincipalName : admin@nocaptech.ch
id                : a60938e1-994e-4d19-bb48-711b89938245

businessPhones    : {+41 43 123 45 67}
displayName       : Carlos Diaz
givenName         : Carlos
jobTitle          : Customer Support Representative
mail              : carlos@nocaptech.ch
mobilePhone       : +41 79 456 78 90
officeLocation    : 
preferredLanguage : 
surname           : Diaz
userPrincipalName : carlos@nocaptech.ch
id                : defd4bfe-987c-4c5b-a9e3-c59bfb15b57b

$mgUserRESTMultipleUsers.GetType()

# Output
IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

When only a single object is returned, the type will be PSCustomObject since the base type isn’t an array.

1
2
3
4
5
6
7
$mgUserREST = Invoke-MgGraphRequest -Method Get -Uri "https://graph.microsoft.com/v1.0/users/a60938e1-994e-4d19-bb48-711b89938245" -OutputType PSObject
$mgUserREST.GetType()                                                                                                              

# Output
IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    PSCustomObject                           System.Object

This is very helpful as it allows you to use Invoke-MgGraphRequest much more like the Graph PowerShell functions and Cmdlets behave, whenever you need to use it.

Other Output Types

Other output types are:

  • HashTable (default one for most endpoints)
  • Json
  • HttpResponseMessage
Json Response

If you use Json you will need to use ConvertFrom-Json if you want to further process the response in PowerShell.

1
$mgUserREST = Invoke-MgGraphRequest -Method Get -Uri "https://graph.microsoft.com/v1.0/users/a60938e1-994e-4d19-bb48-711b89938245" -OutputType Json | ConvertFrom-Json
HttpResonseMessage

This is the value I used in the blog post I linked at the beginning of this article. While this doesn’t include any of the queried object’s data, it’s certainly handy if you need to get a hold of the access token used in that session.

Summary

I’m really glad that GitHub Copilot helped me (re)discover this helpful parameter. It certainly makes my scripts more efficient and clean looking. Although it’s minimal, I can even save a few lines of code and processing power because I don’t need to serialize and deserialize (Json conversion) Graph responses in my code anymore.

All you need to do to go from the result on the left to the one on the right is to append -OutputType PSObject to Invoke-MgGraphRequest.

Without OutputType parameter with OutputType parameter

I hope that you enjoyed this blog post with lots of code examples and that you learned something as well.

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
Hosted on GitHub Pages
Built with Hugo
Theme Stack designed by Jimmy