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.
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
|
The output will look exactly like the output here.
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
|
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
.

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