How to Use Azure AD Privileged Identity Management PowerShell and Graph API

A while back I wrote a blog post on how you could download, install and use a separate Azure AD PIM PowerShell Module for managing Privileged Roles, https://gotoguy.blog/2018/05/22/getting-started-with-azure-ad-pim-powershell-module/. With the recent update of the AzureADPreview Module, the cmdlets for managing Privileged Roles are now included in the module, so there is no longer required to install a separate module for this.

In this blog post I will explain and show how these commands can be used.

Install or Update AzureADPreview Module

First you need to either install or update the AzureADPreview Module, so that you are running on version 2.0.2.27 or newer. The AzureADPreview Module can be installed from PowerShellGallery using Install-Module or Update-Module, and you can verify which version you have installed using Get-Module <modulename> –ListAvailable like this:

image

With that requirement out of the way we can proceed to look at the commands.

Privileged Role Management Commands

Currently in the AzureADPreview Module, there are 13 commands related to Privileged Roles:

Get-Command -Module AzureADPreview | Where-Object {$_.Name -like “*privileged*”}

image

The new cmdlets in AzureADPreview Module 2.0.2.27 are documented here, https://docs.microsoft.com/en-us/powershell/module/azuread/?view=azureadps-2.0-preview#privileged_role_management.

Note that some of the above commands are not in that documentation, all the new commands have a *MS* which means it is mapped to equvivalent Microsoft Graph API’s.

In the interest of this blog post, here is a quick explanation of each of the available commands:

  • Add-AzureADMSPrivilegedResource. Use this API to add a new azure AD MS privileged resource.
  • Close-AzureADMSPrivilegedRoleAssignmentRequest. Cancel a AzureADMSPrivilegedRoleAssignmentRequest.
  • Get-AzureADMSPrivilegedResource. Get azure AD MS privileged resource.
  • Get-AzureADMSPrivilegedRoleAssignment. Get role assignments for a specific provider and resource.
  • Get-AzureADMSPrivilegedRoleAssignmentRequest. Get role assignment request for a specific resource.
  • Get-AzureADMSPrivilegedRoleDefinition. Get role definitions.
  • Get-AzureADMSPrivilegedRoleSetting. Get role settings.
  • Open-AzureADMSPrivilegedRoleAssignmentRequest. Create a role assignment request.
  • Set-AzureADMSPrivilegedRoleAssignmentRequest. Update a role assignment request.
  • Set-AzureADMSPrivilegedRoleSetting. Update role setting.

The other 3 Privileged Role commands that are still available to use, but currently are not documented are:

  • Get-AzureADPrivilegedRole. List all Directory Roles available for Privileged Roles assignments.
  • Get-AzureADPrivilegedRoleAssignment. List active and eligable privileged role assignments.
  • New-AzureADPrivilegedRoleAssignment. Creates a new privileged role assignment for specified role and user.

Further on in this blog post I will provide some more examples and usage scenarios for the new Azure AD Privileged Role Management commands, and their equivalent Microsoft Graph API methods, but first something on the difference between Azure Resources and Azure AD, upcoming changes and current limitations in the PowerShell commands.

Azure Resources vs. Azure AD

As you might know, Azure AD PIM can be used for managing privileged role assignments to both Azure AD roles and Azure Resources:

image

The new PowerShell commands that follows the syntax verb-AzureADMSPrivilegedRole…. all require a parameter called ProviderId, which as per today only support “AzureResources”. This means that currently you can only use the new Azure AD PowerShell commands for managing PIM for Azure resources, not for Azure AD roles yet! I had this confirmed with the Microsoft Program Manager for Azure AD PIM, as you can see from the conversation https://twitter.com/stevemsft/status/1143977432690466816?s=20 and shown in the following image:

image

This is related to the following notice from the Azure AD PIM Microsoft Graph documentation, stating that Azure AD roles will move to the Azure resource API in the coming months:

image

As the new PowerShell commands are built on Microsoft Graph, this also means that they will not work for Azure AD roles until the move to the Azure resources API!

Explore Privileged Azure Resources

image

There are two commands for exploring and adding Privileged Azure Resources:

  • Get-AzureADMSPrivilegedResource –ProviderId AzureResources
  • Add-AzureADMSPrivilegedResource –ProviderId AzureResources

g-raph

Their equivalent Microsoft Graph API methods are (Beta endpoint only as per july 2019):

  • List: GET /privilegedAccess/azureResources/resources
  • Get: GET /privilegedAccess/azureResources/resources/{id}
  • Register: POST /privilegedAccess/azureResources/resources/register

Getting or listing Privileged Resources is based on that you have onboarded to Azure AD PIM for Azure Resources, as explained here: https://docs.microsoft.com/en-us/azure/active-directory/privileged-identity-management/pim-resource-roles-discover-resources. You can also add a privileged resource by ExternalId, which I will show an example of later.

To get a list over all privileged Azure resources, just run:

Get-AzureADMSPrivilegedResource –ProviderId AzureResources

This will return a list (capped at max 200 results), with the Id, ExternalId, Type, DisplayName and more for each resource that have been registered to Azure AD PIM:

image

As mentioned above the list is capped at max 200 results, which is a Microsoft Graph limitation for this privileged resource API. You can use the –Top parameter to specify a lower number of returned results, like –Top 50,  but it will just ignore and cap at 200 if you for example type –Top 300.

So to return fewer results we can use the –Filter parameter which support Odata query. I have tried some different combinations, and not all will work as expected. Some examples of working filters:

Get-AzureADMSPrivilegedResource -ProviderId AzureResources -Filter “Type eq ‘resourcegroup'”

Get-AzureADMSPrivilegedResource -ProviderId AzureResources -Filter “Type eq ‘subscription'”

Get-AzureADMSPrivilegedResource -ProviderId AzureResources -Filter “DisplayName eq ‘elvsabootdiag001”

Get-AzureADMSPrivilegedResource -ProviderId AzureResources -Filter “startswith(DisplayName,’rg-‘)”

image

What I found DON’T work is filters for specific resource types like:

Get-AzureADMSPrivilegedResource -ProviderId AzureResources -Filter “Type eq ‘Microsoft.Compute/virtualMachines'”

..or any other specific resource type like Microsoft.Network/loadBalancers, Microsoft.Network/networkSecurityGroups, etc., which I find is a bit strange as I would like to filter on those as well.

If you know the specific resource id you can also get that privileged resource object directly:

Get-AzureADMSPrivilegedResource -ProviderId AzureResources –Id <resource id>

Lets compare this to the Microsoft Graph API methods, using Graph Explorer. To list all managed Azure Resources I run the GET /privilegedAccess/azureResources/resources like this:

image

Which pretty much returns the same list of resources and attributes as I did when running the PowerShell command. There is one important change though, and I mentioned earlier that the PowerShell command would only return max 200 results. In the Graph response I will receive a skip token from where if I run that I will get the next set of potentially 200 more results, and so on.

Graph can also handle filters of course, so lets try that. Here are some variations you can try out:

GET /privilegedAccess/azureResources/resources?$filter=type eq ‘resourcegroup’&$top=5

GET /privilegedAccess/azureResources/resources?$filter=displayName eq ‘rg-auth-dc’

GET /privilegedAccess/azureResources/resources?$filter=startswith(displayName,’rg-‘)

And if you want to get a specific resource with Graph, just specify the id like this:

GET /privilegedAccess/azureResources/resources/ad7327ba-50f4-4f03-a4ee-029f310b6775

Which will return the specific resource in the response:

image

Last, to add a resource as a managed resource to Azure AD PIM, using PowerShell can be done like this:

Add-AzureADMSPrivilegedResource -ProviderId AzureResources -ExternalId “/subscriptions/<your-subscription-id>”

And via Graph:

POST /privilegedAccess/azureResources/resources/register

Request Body:

{
“externalId”: “/subscriptions/<your-subscription-id>”
}

These commands are just to get a list of and adding managed Azure resources for Azure AD PIM, i the next parts we will look into actually managing assignments and settings.

Explore Role Assignments

image

The following command can be used for listing or getting specific role assignments for Azure resources:

  • Get-AzureADMSPrivilegedRoleAssignment –ProviderId AzureResources –ResourceId <resource id>

g-raph

The equivalent Microsoft Graph API methods:

  • List: GET /privilegedAccess/azureResources/resources/{resourceId}/roleAssignments
  • List: GET /privilegedAccess/azureResources/roleAssignments?$filter=resourceId+eq+'{resourceId}’
  • List (Mine): GET /privilegedAccess/azureResources/roleAssignments?$filter=subjectId+eq+'{myId}’
  • Get: GET /privilegedAccess/azureResources/resources/{resourceId}/roleAssignments/{id}
  • Get: GET /privilegedAccess/azureResources/roleAssignments/{id}?$filter=resourceId+eq+'{resourceId}’
  • Get (Mine): GET /privilegedAccess/azureResources/roleAssignments/{id}?$filter=subjectId+eq+'{myId}’

Now that seems a lot of different variations for Graph calls for the one PowerShell command, but as you will see later Graph can be a little more flexible in querying in different ways.

Lets see some samples for PowerShell first. Now we need to supply a resource id, that can be subscription object, a resource group object, any type of resource objects like virtual machines, virtual networks and so on, and even management group objects. So based on the commands previously shown in the blog post, we should be able to get out the resource id’s first, for example like this:

$myResource = Get-AzureADMSPrivilegedResource -ProviderId AzureResources -Filter “DisplayName eq ‘NetworkWatcherRG'”

Get-AzureADMSPrivilegedRoleAssignment –ProviderId AzureResources –ResourceId $myResource.Id

So this returns a list of role assignments for the specified resource, each assignment has its own id, as well as the ResourceId, the RoleDefinitionId (which role that has been assigned, like reader, contributor, owner, etc), SubjectId (which user, service principal, group, etc has been assigned the role). In addition we can get info on any linked eligible assignements, start and end time for assignements, assignment state and if the assignment is active or not, or if the type is inherited or assigned directly to the resource.

image

Basically the above command returns the same as this blade in the Azure Portal:

image

Explore and Manage Role Assignment Requests

image.png

The following command can be used for exploring and managing role assignment requests:

    • Get-AzureADMSPrivilegedRoleAssignmentRequest. Get role assignment request for a specific resource.
    • Open-AzureADMSPrivilegedRoleAssignmentRequest. Create a role assignment
      request.
    • Set-AzureADMSPrivilegedRoleAssignmentRequest. Update a role assignment
      request.
    • Close-AzureADMSPrivilegedRoleAssignmentRequest. Cancel a
      AzureADMSPrivilegedRoleAssignmentRequest.

g-raph

The equivalent Microsoft Graph API methods:

  • GET /privilegedAccess/azureResources/resources/{resourceId}/roleAssignmentRequests
  • GET /privilegedAccess/azureResources/roleAssignmentRequests?$filter=resourceId+eq+'{resourceId}’
  • GET /privilegedAccess/azureResources/roleAssignmentRequests?$filter=subjectId+eq+'{myId}’
  • GET /privilegedAccess/azureResources/roleAssignmentRequests/{id}
  • POST /privilegedAccess/azureResources/roleAssignmentRequests
  • POST /privilegedAccess/azureResources/roleAssignmentRequests/{id}/updateRequest
  • POST /privilegedAccess/azureResources/roleAssignmentRequests/{id}/cancel

Explore and Manage Role Definitions and Settings

image.png

The following commands can be used to get and manage role definitions and settings:

  • Get-AzureADMSPrivilegedRoleDefinition. Get role
    definitions.
  • Get-AzureADMSPrivilegedRoleSetting. Get role settings.
  • Set-AzureADMSPrivilegedRoleSetting. Update role
    setting.

g-raph

The equivalent Microsoft Graph API methods:

  • GET /privilegedAccess/azureResources/resources/{resourceId}/roleDefinitions
  • GET /privilegedAccess/azureResources/roleDefinitions?$filter=resourceId+eq+'{resourceId}’
  • GET /privilegedAccess/azureResources/resources/{resourceId}/roleDefinitions/{id}
  • GET /privilegedAccess/azureResources/roleDefinitions/{id}?$filter=resourceId+eq+'{resourceId}’
  • GET /privilegedAccess/azureResources/resources/<resourceId>/roleSettings
  • GET /privilegedAccess/azureResources/roleSettings?$filter=resourceId+eq+'<resourceId>’
  • GET /privilegedAccess/azureResources/roleSettings/{id}
  • PATCH /privilegedAccess/azureResources/roleSettings/{id}

Speaking at Experts Live Europe 2019

I’m very happy to announce that I will back speaking at Experts Live Europe 2019! I got 2 sessions to present this year, and I really look forward to go to Prague in November and be a part of this great community conference for the 7th time in a row! Read on for some more details, history and perspectives on why you should attend this conference also.

Experts Live Europe, SCU Europe & Me

This years event is the third edition under the name “Experts Live Europe”. Before that it was known as System Center Universe Europe, or just SCU Europe. The first edition of SCU Europe was in Bern, Switzerland, 2013. I went there because I wanted to attend a European based conference with focus on Datacenter Management after it was announced that Microsoft Management Summit (MMS) would be no more. I was immediately blown away by the community experience, great speakers and content. I made some contacts and friends I’m still happy to see and meet again today.

Of course I had to make it to the 2nd and 3rd editions of SCU Europe that were held in Basel, Switzerland, in 2014 and 2015. Speakers, sessions and community was as good or better as the first conference in Bern, and content was starting to focus more on Microsoft Cloud. Myself I was intrigued and motivated to be a bigger part of this community, and started submitting sessions proposals to be a speaker myself.

In 2016, SCU Europe moved out of Switzerland and to Berlin, Germany. And this year I was one of the speakers! I was so proud when the opening keynote video introduced this speaker from Norway, you can by the way see all those opening videos at https://www.expertslive.eu/history. The conference being in Berlin meant a much easier travel from Norway, and this time around I was able to bring some colleagues and customers along. We had a great time:

At the end of the 2016 conference it was announced that the name SCU Europe will be no more, from 2017 the conference would be named Experts Live Europe, being part of the Experts Live network of global community conferences (www.expertslive.org).

In 2017 we were back in Berlin and I was accepted as a speaker again :). Once again we traveled as a group of colleagues and customers, and had a great time enjoying the excellent content and community.

In 2018, after all 5 previous conferences had been in a city that started with the letter “B”, it was announced that Experts Live Europe 2018 was to be held in the beautiful city of Prague, Czech Republic. For the third time in a row I would be one of the Experts again. Prague is also easy to travel to from Norway so we once again went as a group.

And that brings us to 2019, where we go back to the excellent Prague Congress Center, 20-22nd of November! I’m once again very proud to be speaking at this great community conference, and my record of 100% attendance for every SCU/ExpertsLive Europe record is intact, 7th time in a row now :).

My Sessions

As previously mentioned, I will be delivering two sessions at this years Experts Live Europe. And for the first time I will hold a session together with the one and only Samuel Erskine. This session should be both informative and entertaining!

My session with Sam is titled “Same old System Center.. but how can we hook up the Cloud and make it hot again!”. As System Center 2019 was released earlier this year, and with updates from Ignite, we will look into how System Center 2019 can go from “same old, same old..” to integrating with the Azure Cloud Platform and make System Center hot again!

My second session is named “Manage Identity Lifecycle and Access Control with Azure AD Identity Governance”, in which we will focus on how to manage identity lifecycle and make sure users have the right access at every time using Identity Governance solutions in Azure AD.

You can see the complete agenda for all the sessions here: https://www.expertslive.eu/agenda.

Community Rocks!

The thing I love most with ExpertsLive Europe is the really strong community of experts, sponsors and attendees. Beside the sessions and pre-conference itself, a big part of the value of attending is getting to know new people, connect again with people you have met before, learn, discuss and ask questions, get answers and be followed up with the best of the MVP’s and Community leaders out there. I started out standing in the corner and slowly started interacting with the community members, and now feel I have made many friends and being part of this great community myself. I really want to say hi to you in Prague, either if you are attending one of my sessions or you see me in the expo area, at the VIP party or attendee party.

Hope to see you there, and if you’re still not signed up, you can still register at http://www.expertslive.eu!

Speaking at Evolve Conference!

I’m delighted to announce that I will be speaking at Evolve Conference in Birmingham, 21st of October! Evolve Conference is a free Microsoft 365 and Azure Conference, and if you live near or are able to travel to Birmingham on monday there is still time to register: https://www.evolveconf.co.uk/.

Evolve, previously known as UC Day UK, is a one day conference covering a range of interesting topics on Microsoft 365 and Azure, delivered by top notch speakers from UK and abroad.

I have been lucky enough to present at this event on 2 occasions before, and I have been blown away at both the amount of sponsors and attendants making this a great community event.

I will be presenting a session on “Mastering Azure AD B2B Guests”, which will provide you with all the info you need for taking control of B2B collaboration in your organization.

Hope to see you there, and please come and say hi if you do!

Explore Microsoft Graph as a B2B Guest Account

The Microsoft Graph Explorer (https://aka.ms/ge) is always a great learning source and useful tool for querying the Microsoft Graph, especially as you can use your own Azure AD work account or Microsoft account to query for real data. Another advantage with the Graph Explorer is that you can use it without requiring an App Registration in your tenant, something most users are not able to do themselves as they don’t have the administrator rights for registering apps.

However, sometimes working with Microsoft Graph, I find myself in a scenario where I want to use my own work account, but where the Microsoft Graph resources I want to query is in another Organization’s tenant. These kind of scenarios is usually where my account is invited as a B2B guest user to the resource tenant, and currently there are no way to use the Graph Explorer tool to do that.

So I need another tool, and as an IT pro I could easily write myself some PowerShell code, or as a developer I could create a Web app or Console app querying Graph, and run the queries against the other tenant from there.

On the other hand, I want to do this more inline with the Graph Explorer experience, so the most logical choice for me is to use a tool where I can just run the REST API queries I like. And the most popular tool, both by me and many others are the “Postman” client. You can get it yourself for free at https://www.getpostman.com, both Windows, Linux and Mac downloads are supported!

So in this blog post I will show how I use Postman to query Microsoft Graph in a B2B Guest User scenario.

Requirements & Preparation

So, first you will need to get invited to another tenant where the resources you wan’t to query is. You might already have this Azure AD B2B invitation accepted previously. If you aren’t an administrator in that tenant, you will have to ask someone that has the rights to invite to do that for you.

Next, you will need to get assigned permissions to the resources you want to query in the resource tenant. This is all dependent on what kind of queries you want to run, whether it is for reading, writing or deleting resources for example.

Then, you will need an Global Administrator in that resource tenant to create an App Registration. The following instructions and screenshots can be used as a guide:

First, under the Azure AD experience in the Azure Portal, go to App Registrations and create a new Registration, type a name and a redirect URI like shown below:

image

You can type any name you like, for example in this scenario I want to use it for querying identities. I choose to support accounts in this organizational directory only, as this app registration is for members or guests from this tenant. And since I will be using the Postman i specify the redirect URI of “https://www.getpostman.com/oauth2/callback”. This is important as when I authenticate from Postman later the response will be returned to the Postman client.

When accessing Microsoft Graph you have to authenticate using one of the Oauth2 flows, and the most common is using authorization code flow, (https://developer.microsoft.com/en-us/graph/blogs/30daysmsgraph-day-12-authentication-and-authorization-scenarios/), which is exactly this scenario is all about as I will authenticate on behalf of my guest user account in the resource tenant. That means that I will have to add some delegated Graph permissions to the app registration, and in this example I add User.ReadBasic.All, this will make me able to query users via Graph:

image

After granting Admin consent for the permissions, I can verify that the permissions are added correctly:

image

Next I will need to create a client secret to be used in the request to get an access token, go to certificates & secrets and add a client secret of chosen time expiry:

image

Copy and make sure you save the client secret for later next:

image

Then copy the application id and tenant id and save for later:

image

Click on endpoints (see arrow above), and note the Oauth2 authorization and token endpoints (v2), this endpoints contains the tenantid:

image

With these steps the requirements are complete and we can move on to the postman client.

Authenticating and Querying Graph with Postman

There are a lot of features in the Postman client that you should look into when working with REST API’s. You can organize your queries in Collections, save variables in Environments, synchronize your requests across devices and so on. But for now we will start simple and easy.

In the main canvas at your workspace, create a new query, and for example start with https://graph.microsoft.com/v1.0/me:

image

Don’t push Send just yet, we need to authenticate first. Go to the Authorization section under the request, and select Oauth2, then click Get New Access Token:

image

In the following dialog fill in your details as shown below, where:

  • Token Name: You can type what you want here, this is named reference to the Access Token you will aquire.
  • Grant Type: As earlier mentioned, running graph queries as the logged in user (delegated) use the Authorization Code Oauth2 flow.
  • Callback URL: This is the URL that you specified earlier for Redirect URI under the Azure AD App Registration.
  • Auth URL and Access Token URL: These are the URL’s you saw earlier from the Endpoints setting for the Azure AD App Registration (contains the Tenant ID).
  • Client ID: This is the Application ID for the App Registration.
  • Client Secret: This is the secret key you generated under the App Registration.
  • Scope: Provide a default scope, use the default.
  • State: Scope is used for creating application logic that prevents cross use of Access Tokens. You can type anything you want here.
  • Client Authentication: Select to Send client credentials in body when working with Graph requests.
image

When you click request token, you will be taken to the resource tenant for authenticating with your delegated user. Type in your username and click next. This username can either be a normal user that belongs to this resource tenant, or in this case you can log in with your B2B Guest Account:

image

Now dependent on any Conditional Access policies and settings you might be required to approve sign in:

image

After successfully authenticating I should receive a valid Access Token:

image

If you get an error here, please verify your App Registration settings and that the account you logged in as is correct.

Scroll down and click Use Token:

image

You will see that the Access Token is now filled in the Access Token textbox. Next click Preview Request down to the left, this will add the Access Token to the request as a authorization header:

image

If you click on Headers, you can see the Authorization header has been added with the access token as a Bearer Token value:

image

This Access Token is now valid for 1 hour, and you can run as many requests you like as long as you are inside the delegated permissions (for the App Registration) and the logged in users actual permissions. After 1 hour you can request a new Access Token.

PS! Postman will save the authenticated user session in cookies, if you want to log in as a different user clear those cookies, se “cookies” link right below the Send button.

So, now lets run this query, click Send at verify the response, you should get details for your guest user. From the screenshot below you can clearly see that this user is a Guest account looking at the userPrincipalName attribute:

image

Lets try another query, in this query I list all users that has userPrincipalName that starts with “Jan”, and showing only the displayName and userPrincipalName attributes:

image

As you can see from the result above, I have several guest accounts in this tenant (Microsoft Account, Google, Azure AD) as well as a normal user account. You can also see that the Postman client is helpful in specifying my parameters.

Inspecting a B2B Guest Access Token

If you copy the Access Token you got earlier, and paste it into a site like jwt.ms or jwt.io, we can take a look at the access token contents and claims:

image

If I scroll down a little I see the displayname of the App Registration, but the most important info is the mail claim, which for Guest users will be the external e-mail address. Idp is the source authority for the Guest account, in this case another Azure AD tenant with the Tenant Id as shown below:

image

Working with Environments

Chances are that you might work with several environments in Postman, and that where it’s useful to create environment variables. For example create an environment like shown below:

image

That way you can select which environment you want to work with when running queries, and when referencing variables use “{{ .. }}”. For example under Get New Access Token, change to this:

image

Now, lets finalize this blog post by logging in with another guest account, I will choose my Gmail account, I’ve already set up Google Federation and invited this user to my tenant.

First I need to clear the cookies:

image

Next I will click to Get a New Access Token again, and then authenticate as my Google account, which directs me to the Google login page:

image

After successfully authenticating, and using the new Access Token in the Authorization Header, I can run the basic /me query again, this time showing me that I’m now authenticated to Graph with my Gmail user:

image

And looking inside the Access Token again, I can see that the e-mail address is my gmail and the idp is now google.com:

image

If I had logged on with a Microsoft Account the idp value would have been “live.com”.

Next steps

So, now you know how to authenticate to and query Microsoft Graph with an Azure AD B2B Guest User. I really hope this functionality will come to Graph Explorer eventually, but for now Postman is already an awesome free tool for organizing and running your Microsoft Graph queries that I use a lot myself.

The Microsoft Graph Team has also published this source for a lot of useful collections of Graph queries:

https://github.com/microsoftgraph/microsoftgraph-postman-collections

Get and Set Automatic Replies like OOF with Microsoft Graph

Hi, a short blog post this time as Summer Vacation 2019 is here shortly! And on that note, the topic of the post is to show how you can get and set automatic replies with Microsoft Graph. Automatic replies, previously known as Out of Office (OOF) messages is a mailbox setting for each Exchange Online enabled user.

The Microsoft Graph API documentation for mailbox settings is located here: https://docs.microsoft.com/en-us/graph/api/user-get-mailboxsettings?view=graph-rest-1.0&tabs=http, and besides automatic replies you can also get and set locale (language and country/region), time zone, and working hours.

But for now we will only focus on automatic replies, using the automaticRepliesSetting resource type: https://docs.microsoft.com/en-us/graph/api/resources/automaticrepliessetting?view=graph-rest-1.0.

This resource type has the following settings:

{
   "externalAudience": "String",
   "externalReplyMessage": "string",
   "internalReplyMessage": "string",
   "scheduledEndDateTime": {"@odata.type": "microsoft.graph.dateTimeTimeZone"},
   "scheduledStartDateTime": {"@odata.type": "microsoft.graph.dateTimeTimeZone"},
   "status": "String"
 }

Let’s look at some different samples, and I will use Graph Explorer (https://aka.ms/ge). Please note that every end user already have user permission to get or set their own mailbox settings, but you need an Exchange Admin role to get or set the settings for other users in your organization. In addition, if you create your own app registration for Microsoft Graph, you need to make sure the app has either MailboxSettings.Read or MailboxSettings.ReadWrite permission.

In Graph Explorer, after you sign in with your work account, you can modify these permissions if needed:

image

After signing out and in again you will be prompted to consent, if you havent already:

image

Get Current Mailbox Settings

To get your own current settings you can run the following:

GET /me/mailboxSettings

In Graph Explorer this would look like this, and you might have some previous values set here. In my example automatic replies have a status of disabled:

image

To get another users mailbox settings you can run the following (but then you must be an Exchange Admin):

GET /users/{id|userPrincipalName}/mailboxSettings

Simple Update of Status

Lets see how Microsoft Graph can be used to change the status value, there are 3 different settings:

  • disabled. No automatic replies are sent.
  • alwaysEnabled. Automatic replies are sent as specified.
  • scheduled. Automatic replies are sent if between a specific time period.

First, change the method i Graph Explorer to PATCH:

image

Then you need to supply a request body. This sample is just for enabling automatic replies:

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Me/mailboxSettings",
    "automaticRepliesSetting": {
        "status": "alwaysEnabled",
    }
}

So paste that body to Graph Explorer and then Run Query:

image

You should then get a successful response. Likewise you can set the status to “disabled” again, or to “scheduled”. But using scheduled means that you must set some datetime values as well.

Set Scheduled Automatic Replies

To set scheduled automatic replies, in your request body include the resource types scheduledStartDateTime and scheduledEndDateTime. You can read more about that resource type here, including available time zones: https://docs.microsoft.com/en-us/graph/api/resources/datetimetimezone?view=graph-rest-1.0. This is a sample specifying a scheduled automatic replies:

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Me/mailboxSettings",
    "automaticRepliesSetting": {
        "status": "scheduled",
        "scheduledStartDateTime": {
            "dateTime": "2019-07-15T08:00:00.0000000",
            "timeZone": "Europe/Berlin"
        },
        "scheduledEndDateTime": {
            "dateTime": "2019-08-09T16:00:00.0000000",
            "timeZone": "Europe/Berlin"
        }
    }
}

Customize internal and external reply messages

The last part is where we put it all together, specifying the following values:

  • internalReplyMessage: Plain text or HTML formatted message sent to all internal users in your organization as the automatic reply.
  • externalReplyMessage: Plain text or HTML formatted message sent to all external users as the automatic reply, but depending on this value:
  • externalAudience: If “none”, no external users will get automatic replies, if “contactsOnly” replies will only be sent to users in your contacts, and if “all” every external user will get a reply.

So this is a working sample of a complete request body:

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Me/mailboxSettings",
"automaticRepliesSetting": {
        "status": "scheduled",
        "externalAudience": "contactsOnly",
        "internalReplyMessage": "<html>\n<body>\n<div></div>\n<div>Hi, I'm enjoying summer vacation 2019. I'm back at work August 12th!</div>\n<div><br>\n</div>\n<div>Kindly Regards</div>\n<div>Jan Vidar Elven</div>\n<div></div>\n</body>\n</html>\n",
        "externalReplyMessage": "<html>\n<body>\n<div></div>\n<div>Hi, I'm enjoying summer vacation 2019. I'm back at work August 12th!</div>\n<div><br>\n</div>\n<div>I'll only read e-mails intermittently, and rarely respond before I'm back. Please contact management if anything urgent business needs follow up. Contact info on our website.</div>\n<br>\n</div>\n<div>Kindly Regards</div>\n<div>Jan Vidar Elven</div>\n</div>\n<div></div>\n</body>\n</html>\n",
        "scheduledStartDateTime": {
            "dateTime": "2019-07-15T08:00:00.0000000",
            "timeZone": "Europe/Berlin"
        },
        "scheduledEndDateTime": {
            "dateTime": "2019-08-09T16:00:00.0000000",
            "timeZone": "Europe/Berlin"
        }
    }
}

In Graph Explorer:

image

And I can verify in my Outlook settings:

image

Summary and Usage Scenarios

Beside that it is always fun to learn something new about the Microsoft Graph, and automation, the reality is that for many users they will just click to enable or disable automatic replies directly in their Outlook client, both Office Outlook, Outlook Mobile and Outlook on the Web supoorts this. Finding out how to do it with Graph took me just under 2 hours, including writing this blog post. But then again, I learned something new! And I picked up a couple of more tips and tricks on different JSON Request Body constructs 😉

Anyway, in a bigger picture, Graph API is great for customizing, integrating, reporting and automating, so if your organization maybe have create a vacation calendar, you could use the Graph API to automatically enable or disable out of office replies, this is just one example, many more will exist. Please share with me in the comments if you have done or plan to do something with this or similar.

Smile

But first: Summer Vacation 2019! And I’m all set with automatic replies !

Speaking at Microsoft Ignite – The Tour Stockholm

It is with great pride I can announce that I will speak at Microsoft Ignite – The Tour, in Stockholm April 24-25 2019. This conference, which already is sold out and now only available on waiting list, will be held at Stockholmsmassan, and you can read more about it here: https://www.microsoft.com/sv-se/ignite-the-tour/Stockholm.

Ignite The Tour Stockholm will have over 150 different breakout sessions, theatre sessions, modules and workshops, covering 10 learning paths. Speakers will be from Microsoft, and from the MVP and Regional Director community, which I’m so fortunate and honored to be part of 😉

I will be speaking at the following sessions:

In addition to this I will participate on the following hands-on workshop as a proctor:

You will also find me at the experts area and doing booth duty, I will be at the following demo stations at the Hub and Microsoft Showcase area, covering topics on Azure and Azure AD:

  • Demo Station #5 (Azure): Day 1 1600-1800: “Getting Started with Azure Log Analytics and Azure Monitor using Azure AD Activity Logs
  • Demo Station #6 (Azure): Day 2 0800-1200: “Getting Started with Azure Log Analytics and Azure Monitor using Azure AD Activity Logs

I’m really excited for presenting these sessions and being part of the Ignite The Tour Community! Hope to see you there 🙂

Exploring Azure MFA sign-in failures using Log Analytics

Most IT admins, pros and end users from organizations that use Office 365 and Azure AD will by now have heard about the big Azure MFA outage on Monday November 19. When something like this happens, it is important to get insights on which users that were affected, and in what type of scenarios they most experienced the problem. Microsoft MVP Tony Redmond wrote a useful blog post (https://office365itpros.com/2018/11/21/reporting-mfa-enabled-accounts/) on how to report on possible/potentially affected and MFA enabled users, and how to disable and re-enable those users. But many organizations are now using Conditional Access policies using Azure AD Premium, so this will be of limited help for those.

If I could wish one thing from Microsoft for Christmas this year, it would be to be able to manage MFA and Conditional Access policies with Azure AD PowerShell and Microsoft Graph! Admins could then run “break-the-glass” administrative users (or even “break-the-glass service principals”) to disable/re-enable policies when big MFA outages happens. A good CA policy design, trusting compliant devices and secure locations could also go a long way in mitigating such big outage problems.

Tony’s blog post made me think about the feature I recently activated, on integrating Azure AD Activity Logs to Azure Log Analytics, you can read more about this here: https://gotoguy.blog/2018/11/06/get-started-with-integration-of-azure-ad-activity-logs-to-azure-log-analytics/

By exploring the Sign-in logs in Log Analytics I could get some more insights into how my organization was affected by the MFA outage on November 19. Please see the above blog post on how to get started setting up this integration, the rest of this blog post will show some sample queries for the SigninLogs.

Querying the SigninLogs for failed and interrupted sign-ins

All the queries seen below are shown in screenshot images, but I have listed all them for you to copy at the end of the blog post.

First I can take a look at the SigninLogs for the specific day of 19th November, and the grouping on the result type and description of the sign-in events. For example I can see that there is a high number of event 50074: User did not pass the MFA challenge. Interestingly there is also a relatively high number of invalid username or password, that could be a separate issue but could also be that users that fails MFA sign-ins tries to log in again thinking they had wrong password first time.

image

Changing that query a little, I can exclude the successful sign-ins (ResultType 0), and sort on the most count of failures. Two of the events of most interest here is 50074 and 50076:

image

In this next query I focus on the “50074: User did not pass the MFA challenge” error. By increasing the time range to last 31 days, and adding a bin(TimeGenerated, 1d) to the summarize group, I’ll be able to see the count of this error on each day in the last month. This will give me a baseline, and we can see that on the 19th this number spikes. I have added a render to timechart for graphical display. There are also some other days where this number increases, I can look into more insights for that if I want as well, but for now I will focus on the 19th.

image

Back to the time range of the 19th of November, I can modify the summarize to group by each hour, by using bin(TimeGenerated, 1h). This will show me how the problems evolved during the day. Must errors occurred about 10 am in the morning:

image

Lets look at some queries for how this error affected my environment. First I can group on the Users and how many errors they experience. Some users were really persistent in trying to get through the MFA error. I have masked the real names. We also see some admin accounts but admins quickly recognized that something was wrong, and actively sought information on the outage. By midday most users were notified on the on-going outage and the number of errors slowly decrease during the day.

image

In this next query, I group on the Apps the users tried to reach:

image

And in this following query, what kind of Client App they used. It would be normal that Browser is quite high, as mobile apps and desktop clients are more likely to have valid refresh tokens.

image

In this query I can look into the device operating system the users tried to sign in from:

2018-11-21_22-05-27

In the following query I can look at which network the users tried to log in from, by identifying IP address:

image

And in this query we can get more location details from where users tried to sign in:

2018-11-21_22-07-17

Summary

Querying Log Analytics for Sign-in events as shown above can provide valuable insights into how such an outage can affect users. This can also give me some input on how to design Conditional Access policies. Querying this data over time can also provide a baseline for normal operations in your environment, and make it easier to set alert thresholds if you want to get alerts when number of failures inside a time interval gets higher than usual. Using Azure Monitor and action groups you can be pro-active and be notified if something similar should occur again.

Here are all the queries shown above:

// Look at SigninLogs for a custom date time interval and group by sign in results
SigninLogs
| where TimeGenerated between(datetime("2018-11-19 00:00:00") .. datetime("2018-11-19 23:59:59")) 
| summarize count() by ResultType, ResultDescription

// Exclude successful signins and format results with sorting
SigninLogs
| where TimeGenerated between(datetime("2018-11-19 00:00:00") .. datetime("2018-11-19 23:59:59")) 
| where ResultType != "0" 
| summarize FailedSigninCount = count() by ResultDescription, ResultType 
| sort by FailedSigninCount desc

// Look at User did not pass the MFA challenge error last month to see trend
// and present to line chart group by each 1 day
SigninLogs
| where TimeGenerated >= ago(31d)
| where ResultType == "50074"
| summarize FailedSigninCount = count() by ResultDescription, bin(TimeGenerated, 1d)
| render timechart

// Look at User did not pass the MFA challenge error on the MFA outage day
// and present to line chart group by each 1 hour to see impact during day
SigninLogs
| where TimeGenerated between(datetime("2018-11-19 00:00:00") .. datetime("2018-11-19 23:59:59")) 
| where ResultType == "50074" 
| summarize FailedSigninCount = count() by ResultDescription, bin(TimeGenerated, 1h)
| render timechart

// Look at User did not pass the MFA challenge error on the MFA outage day
// and group on users to see affected users
SigninLogs
| where TimeGenerated between(datetime("2018-11-19 00:00:00") .. datetime("2018-11-19 23:59:59")) 
| where ResultType == "50074"
| summarize FailedSigninCount = count() by UserDisplayName
| sort by FailedSigninCount desc

// Look at User did not pass the MFA challenge error on the MFA outage day
// and group on Apps to see affected applications the users tried to sign in to
SigninLogs
| where TimeGenerated  between(datetime("2018-11-19 00:00:00") .. datetime("2018-11-19 23:59:59")) 
| where ResultType == "50074"
| summarize FailedSigninCount = count() by AppDisplayName
| sort by FailedSigninCount desc

// Look at User did not pass the MFA challenge error on the MFA outage day
// and group on client apps to see affected apps the users tried to sign in from
SigninLogs
| where TimeGenerated between(datetime("2018-11-19 00:00:00") .. datetime("2018-11-19 23:59:59")) 
| where ResultType == "50074"
| summarize FailedSigninCount = count() by ClientAppUsed
| sort by FailedSigninCount desc

// Look at User did not pass the MFA challenge error on the MFA outage day
// and group on device operating system to see affected platforms
SigninLogs
| where TimeGenerated between(datetime("2018-11-19 00:00:00") .. datetime("2018-11-19 23:59:59")) 
| where ResultType == "50074"
| summarize FailedSigninCount = count() by tostring(DeviceDetail.operatingSystem)
| sort by FailedSigninCount desc

// Look at User did not pass the MFA challenge error on the MFA outage day
// and group on IP address to see from which network users tried to sign in from
SigninLogs
| where TimeGenerated between(datetime("2018-11-19 00:00:00") .. datetime("2018-11-19 23:59:59")) 
| where ResultType == "50074"
| summarize FailedSigninCount = count() by IPAddress
| sort by FailedSigninCount desc

// Look at User did not pass the MFA challenge error on the MFA outage day
// and group on users location details to see which country, state and city users tried to sign in from
SigninLogs
| where TimeGenerated between(datetime("2018-11-19 00:00:00") .. datetime("2018-11-19 23:59:59")) 
| where ResultType == "50074"
| summarize FailedSigninCount = count() by tostring(LocationDetails.countryOrRegion), tostring(LocationDetails.state), tostring(LocationDetails.city)
| sort by FailedSigninCount desc