I am trying to use PowerShell Graph cmdlets instead of the Azure AD module cmdlets. With the Azure AD module, I can do this:
# This is what I want:
get-azureadgroupmember -objectid $GroupID | select-object -property displayname, `
mail, userprincipalname, objectid
DisplayName Mail UserPrincipalName ObjectId
----------- ---- ----------------- --------
John Smith John.Smith@example.org jsmith@example.org 4bae8291-6ec3-192b-32ce-dd21869ef784
(...)
# All of these properties are directly accessible in the returned objects:
$res = get-azureadgroupmember -objectid $GroupID
$res[0] | fl -prop *
# Shows long list of directly accessible properties
I'm trying to figure out the equivalent with PowerShell Graph:
$res = get-mggroupmember -groupid $GroupID
$res[0] | fl -prop *
# Only properties are DeletedDateTime, Id, and AdditionalProperties
# Want to do something like this, but it doesn't work:
get-mggroupmember -groupid $GroupID | select-object -property id, `
additionalproperties['displayName'], additionalproperties['mail'], `
additionalproperties['userPrincipalName']
# This works, but is there a better option???
get-mggroupmember -groupid $GroupID | foreach-object { `
"{0},{1},{2},{3}" -f $_.id, $_.additionalproperties['displayName'], `
$_.additionalproperties['mail'], $_.additionalproperties['userPrincipalName']
}
AdditionalProperties is a dictionary (IDictionary) which contains displayname, mail, and userprincipalname. My thought is there is probably a better way to do this or to get at the information.
There are a few interesting parameters in Get-MgGroupmember
that I'm not clear on including -ExpandProperty
and -Property
. I've tried playing around with these but haven't had any luck. I'm wondering if there's a way to use these to do what I want.
Suggestions?
Given the following $object
, 3 properties and one of them AdditionalProperties
is a Dictionary<TKey,TValue>
:
$dict = [Collections.Generic.Dictionary[object, object]]::new()
$dict.Add('displayName', 'placeholder')
$dict.Add('mail', 'placeholder')
$dict.Add('userPrincipalName', 'placeholder')
$object = [pscustomobject]@{
DeletedDateTime = 'placeholder'
Id = 'placeholder'
AdditionalProperties = $dict
}
Supposing from this object you're interested in Id
, displayName
and mail
, you could use Select-Object
with calculated properties:
$object | Select-Object @(
'Id'
@{
Name = 'displayName'
Expression = { $_.additionalProperties['displayName'] }
}
@{
Name = 'mail'
Expression = { $_.additionalProperties['mail'] }
}
)
However this gets messy as soon as you need to pick more property values from the objects, PSCustomObject
with a loop comes in handy in this case:
$object | ForEach-Object {
[pscustomobject]@{
Id = $_.Id
displayName = $_.additionalProperties['displayName']
mail = $_.additionalProperties['mail']
}
}
Both alternatives would output the same "flattened" object that can be converted to Csv without any issue:
Id displayName mail
-- ----------- ----
placeholder placeholder placeholder
"Id","displayName","mail"
"placeholder","placeholder","placeholder"
In that sense, you could construct an array of objects using one of the above techniques, for example:
Get-MgGroupMember -GroupId $GroupID | ForEach-Object {
[pscustomobject]@{
Id = $_.id
displayName = $_.additionalproperties['displayName']
mail = $_.additionalproperties['mail']
userPrincipalName = $_.additionalproperties['userPrincipalName']
}
}
If you're looking for a programmatical way to flatten the object, you can start by using this example, however it's important to note that this can only handle an object which's property is nested only once, in other words, it can't handle recursion:
$newObject = [ordered]@{}
foreach($property in $object.PSObject.Properties) {
if($property.Value -is [Collections.IDictionary]) {
foreach($addproperty in $property.Value.GetEnumerator()) {
$newObject[$addproperty.Key] = $addproperty.Value
}
continue
}
$newObject[$property.Name] = $property.Value
}
[pscustomobject] $newObject
The output from this would become a flattened object like this, which also, can be converted to Csv without any issue:
DeletedDateTime : placeholder
Id : placeholder
displayName : placeholder
mail : placeholder
userPrincipalName : placeholder
It's also worth noting that above example is not handling possible key collision, if there are 2 or more properties with the same name, one would override the others.
Bonus function that should work with the objects returned by the cmdlets from Graph, AzureAD and Az Modules. This function can be useful to flatten their Dictionary`2
property. It only looks one level deep if the property value implements IDictionary
so don't expect it to flatten any object. For the given example should work well.
function Select-GraphObject {
[CmdletBinding(PositionalBinding = $false)]
param(
[Parameter(Mandatory, ValueFromPipeline, DontShow)]
[object] $InputObject,
[Parameter(Position = 0)]
[SupportsWildcards()]
[string[]] $Properties = '*'
)
begin {
$firstObject = $true
$toSelect = [Collections.Generic.List[object]]::new()
}
process {
if($firstObject) {
foreach($property in $InputObject.PSObject.Properties) {
foreach($item in $Properties) {
if($property.Value -is [Collections.IDictionary]) {
foreach($key in $property.Value.PSBase.Keys) {
if($key -like $item -and $key -notin $toSelect.Name) {
$toSelect.Add(@{
$key = { $_.($property.Name)[$key] }
})
}
}
continue
}
if($property.Name -like $item -and $property.Name -notin $toSelect) {
$toSelect.Add($property.Name)
}
}
}
$firstObject = $false
}
$out = [ordered]@{}
foreach($item in $toSelect) {
if($item -isnot [hashtable]) {
$out[$item] = $InputObject.$item
continue
}
$enum = $item.GetEnumerator()
if($enum.MoveNext()) {
$out[$enum.Current.Key] = $InputObject | & $enum.Current.Value
}
}
[pscustomobject] $out
}
}
Using copies of the $object
from above examples, if using the default value of -Properties
, the example objects would be flattened:
PS /> $object, $object, $object | Select-GraphObject
DeletedDateTime Id displayName mail userPrincipalName
--------------- -- ----------- ---- -----------------
placeholder placeholder placeholder placeholder placeholder
placeholder placeholder placeholder placeholder placeholder
placeholder placeholder placeholder placeholder placeholder
Or we can filter for specific properties, even Keys from the AdditionalProperties
Property:
PS /> $object, $object, $object | Select-GraphObject Id, disp*, user*
Id displayName userPrincipalName
-- ----------- -----------------
placeholder placeholder placeholder
placeholder placeholder placeholder
placeholder placeholder placeholder