Merill’s blog
I just launched 🦋 Bluesky.ms!
🚀 I just launched my weekend hack project 🦋. With Twitter becoming unusable a whole heap of the Microsoft community is now on Bluesky and having a blast.
I made Bluesky.ms to make it easy to find each other. If you are already on Bluesky, add your profile so others can find you.
If you haven’t started on Bluesky here’s a primer.
🦋 What is Bluesky?
Bluesky is a social app that is designed to not be controlled by a single company. It’s an open network and a version of social media where it’s built by many people, and it still comes together as a cohesive, easy-to-use experience.
✅ Where do I sign up
- Web → Bluesky Web app
- iPhone → Bluesky iOS app
- Android → Bluesky Android app
🚀 How do I find people?
Rebuilding all the folks you know or finding people with similar interests take time. Here are some neat ways to bootstrap the process.
✅ Starter Packs
These are lists of Bluesky users that you can bulk follow. The starter packs help you quickly get started on Bluesky and follow folks in the Microsoft community.
See bluesky.ms/starterpacks for the latest list.
You can also find other non-Microsoft starter packs over at blueskydirectory.com/starter-packs
✅ Help others find you
If you write about Microsoft content and want others to find you, add your profile over at bluesky.ms.
This crowd sourced database welcomes everyone. If you find it’s missing anyone please feel free to add them in.
Let’s build some awesome open communities!
Share on
TwitterGraph API: Minimal permissions to read user group membership
Here’s an interesting question I received today.
What are the minimal permissions required to read group membership for a user?
The ask was for an application so we need to grant Application permissions and the first attempt was with User.Read.All
permission.
When you run this query you do get the groups the user is a member of but it is limited to just the group id. The permission is not enough to get the name of the group.
Invoke-GraphRequest -Uri 'https://graph.microsoft.com/v1.0/users/[email protected]/memberOf/microsoft.graph.group?$select=displayName' | ConvertTo-Json
Now this would be perfectly valid if your app needed just the ID of the group.
However if the app needs the name and other details of the group then you will need to grant additional permissions.
My immediate thought was to grant Group.Read.All but this is a scary permissions, especially when it is an application permission. This will grant tenant wide access to read any information stored in a Group or Team. This includes files and messages in a channel.
So what’s the least privilege permission that will grant access to just the display name?
As of today, the answer is GroupMember.Read.All
permission. The reason I say “as of today” is because the permissions are constantly being updated and new permissions are being added, so it is always a good idea to check the docs for the least privilege permissions. Since I did this frequently I built a site to easily show the least privilege permissions Microsoft Graph permissions reference.
Share on
TwitterFiltering members in Entra groups and admin units
Here’s a recent Graph query-related issue I helped troubleshoot.
The request was to find all the members in an Administrative Unit with a specific value in the extensionAttribute10
property.
However this query errored out as an unsupported query.
❌
/directory/administrativeUnits/<guid>/members?$filter=onPremisesExtensionAttributes/extensionAttribute10 eq 'ABC'&$count=true
code: "Request_UnsupportedQuery",
message: "Property 'extensionAttribute10' does not exist as a declared property or extension property."
The fix was fairly simple, just add /microsoft.graph.user
at the end of the url path.
✅
/directory/administrativeUnits/<guid>/members/microsoft.graph.user?$filter=onPremisesExtensionAttributes/extensionAttribute10 eq 'ABC'&$count=true
So let’s break down the fix.
Adding /microsoft.graph.user
at the end of url path tells Graph API to only return members that are of type user
. You can then apply all the available user object property filters including filtering by extensionAttribute10
.
Why did the original query fail?
The administrativeUnit
object like the group
object can contain different types of directory objects.
Here’s a visual representation of the directory object inheritance hierarchy.
When you create a group or an administrative unit, you can add users, devices, and other groups to it. Each of these objects will have their unique set of properties.
Not all object types inheriting from
DirectoryObject
can be added to groups and administrative units.
When you query for members in a group or an administrative unit, you are querying against all the objects in the container.
So while you can query against special properties like id
and displayName
you cannot directly query against any of the other properties.
This explains why a query for displayName
will work without qualifying the query with the object type.
✅
/groups/<guid>/members?$filter=displayName eq 'John'&$count=true
In our original query, not all the member object types in the administrativeUnit
object would have a declared property called onPremisesExtensionAttributes
. Instead it is a declared property of the user
object.
Once you qualify the query to filter by the microsoft.graph.user
object, the query works as expected.
To close it off with another example, this query for groups
will fail for the same reason.
❌
/groups/<guid>/members?$filter=onPremisesExtensionAttributes/extensionAttribute10 eq 'ABC'&$count=true
Which can be fixed by qualifying the query with the microsoft.graph.user
object type.
✅
/groups/<guid>/members/microsoft.graph.user?$filter=onPremisesExtensionAttributes/extensionAttribute10 eq 'ABC'&$count=true
Here’s the TLDR;
Hope this helps!
Share on
TwitterInvoke-MgGraphRequest -OutputFilePath vs Out-File Performance Comparison
In case you were wondering which is faster.
Invoke-GraphRequest -Uri 'beta/users' -OutputFilePath ./user.json
or
Invoke-GraphRequest -Uri 'beta/users' | Out-File -FilePath ./user.json
Surprisingly, the answer is that Out-File is the fastest
Here’s what I got when I ran the two commands:
Measure-Command { Invoke-GraphRequest -Uri 'beta/users' -OutputFilePath ./user.json }
TotalSeconds : 3.1084942
Invoke-GraphRequest -Uri 'beta/users' | Out-File -FilePath ./user.json
TotalSeconds : 0.5016927
That got me thinking. Does the performance change much when using different output types?
Measure-Command { Invoke-GraphRequest -Uri 'beta/users' -OutputType Json | Out-File ./user.json }
TotalSeconds : 0.4035508
Here’s what I get when I ran the same command with different output types. There was no clear winner and they all ranged between 0.2 and 0.5 seconds.
OutputType | TotalSeconds |
---|---|
Json | 0.4035508 |
Hashtable | 0.3948994 |
PSObject | 0.4495978 |
HttpResponseMessage | 0.5539832 |
Share on
TwitterDevice filter > Device platform
When designing a conditional access policy and have the choice between using device filter and device platform always use device filter.
The catch is that device filter can only be applied to managed or hybrid joined devices.
It’s a limitation since you can’t use it with unmanaged devices, but that is exactly the reason why it is better to use it over device platform when your CA policy is targeting managed devices.
The device platform relies on the user agent string which can be easily spoofed. Nicola has a good write up on this over at Bypassing Conditional Access Device Platform Policies