I just launched 🦋 Bluesky.ms!

1 minute read

🚀 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

🚀 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!

Graph API: Minimal permissions to read user group membership

1 minute read

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

screenshot showing querying by group member with user.read permission

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.

screenshot showing querying by group member with user.read permission

Filtering members in Entra groups and admin units

1 minute read

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.

Illustration showing directory object inheritance hierarchy with the DirectoryObject base type and child types

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.

Screenshot of an Entra group that contains users, groups and devices

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;

alt text

Hope this helps!

Invoke-MgGraphRequest -OutputFilePath vs Out-File Performance Comparison

less than 1 minute read

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

Device filter > Device platform

less than 1 minute read

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

He man skeleton recommends using device filter over device platform