Graph API – Office 365 for IT Pros https://office365itpros.com Mastering Office 365 and Microsoft 365 Fri, 13 Sep 2024 09:05:56 +0000 en-US hourly 1 https://i0.wp.com/office365itpros.com/wp-content/uploads/2024/06/cropped-Office-365-for-IT-Pros-2025-Edition-500-px.jpg?fit=32%2C32&ssl=1 Graph API – Office 365 for IT Pros https://office365itpros.com 32 32 150103932 Copilot Usage Report APIs Available https://office365itpros.com/2024/09/13/copilot-usage-report-api/?utm_source=rss&utm_medium=rss&utm_campaign=copilot-usage-report-api https://office365itpros.com/2024/09/13/copilot-usage-report-api/#comments Fri, 13 Sep 2024 07:00:00 +0000 https://office365itpros.com/?p=66347

Copilot Usage Reports Weak on Detail

Announced in message center notification MC877369 (29 August 2024, Microsoft 365 roadmap item 396562), the Microsoft Graph beta usage reports API now includes support for Copilot for Microsoft 365 tenant usage data. All tenants that use Copilot for Microsoft 365 should now have access to the usage data.

Microsoft says that the availability of this information will “facilitate the creation of customized reporting and analytics,” but the fact is that the data exposed by the API is bare-bones. On the upside, the data matches what’s available in the report section of the Microsoft 365 admin center (Figure 1).

  • Tenant-level summary of Copilot-enabled (licensed) users and active users.
  • Adoption trend (tenant summary) over time.
  • Last activity date for Copilot interaction in different apps for each user.
Copilot usage reports in the Microsoft 365 admin center.
Figure 1: Copilot usage reports in the Microsoft 365 admin center

Accounts accessing the Graph data must have a Copilot for Microsoft 365 license.

User Count Summary

The user count summary report returns a count of the user accounts licensed for Copilot for Microsoft 365 (enabled users) and a count of the users with an active interaction with Copilot in each app during the reporting period (7, 30, 90, or 180 days). Unsurprisingly, when someone is enabled for Copilot in one app, they’re usually enabled for all:

  • Teams
  • Outlook (classic, new Outlook for Windows, OWA).
  • Excel.
  • PowerPoint.
  • Copilot Graph-grounded chat (aka Copilot Chat).
  • OneNote.
  • Loop.
$Uri = "https://graph.microsoft.com/beta/reports/getMicrosoft365CopilotUserCountSummary(period='D90')"
$Data = Invoke-GraphRequest -Uri $Uri -Method Get
$Data.value.adoptionByProduct

Name                           Value
----                           -----
loopEnabledUsers               100
reportPeriod                   90
oneNoteActiveUsers             3
wordEnabledUsers               100
powerPointEnabledUsers         100
microsoftTeamsActiveUsers      97
oneNoteEnabledUsers            100
excelActiveUsers               43
loopActiveUsers                2
copilotChatEnabledUsers        100
outlookEnabledUsers            100
anyAppEnabledUsers             100
anyAppActiveUsers              97
microsoftTeamsEnabledUsers     100
excelEnabledUsers              100
wordActiveUsers                61
powerPointActiveUsers          12
copilotChatActiveUsers         73
outlookActiveUsers             18

User Activity Detail

This report is the most interesting because it details the last activity date for Copilot interaction by users with each of the various Copilot-enabled apps. In addition, the last activity date for any Copilot interaction with any of the supported apps is published (lastActivityDate). An array (value) holds a separate usage report for each Copilot-enabled account.

The user principal name and display name is obfuscated if the tenant data privacy control is enabled. In the following extract, we see that the user has never used Copilot for Loop and OneNote and hasn’t used Copilot with PowerPoint since April 11, 2024:

$Uri = https://graph.microsoft.com/beta/reports/getMicrosoft365CopilotUsageUserDetail(period='D90')
$Data = Invoke-GraphRequest -Uri $Uri -Method Get
$Data.value[0]

Name                           Value
----                           -----
copilotActivityUserDetailsByP… {System.Collections.Hashtable}
microsoftTeamsCopilotLastActi… 2024-09-05
outlookCopilotLastActivityDate 2024-08-29
lastActivityDate               2024-09-05
reportRefreshDate              2024-09-08
excelCopilotLastActivityDate   2024-09-05
loopCopilotLastActivityDate
oneNoteCopilotLastActivityDate
copilotChatLastActivityDate    2024-09-05
powerPointCopilotLastActivity… 2024-04-11
userPrincipalName              Tony.Redmond@office365itpros.com
displayName                    Tony Redmond
wordCopilotLastActivityDate    2024-09-05

Adoption Trend over Time

This report returns an array called adoptionByDate with entries for each day during the reporting period (7, 30, 90, or 180 days). The purpose of the report is to track progress in Copilot adoption over time and to note if any specific action had an effect. For instance, you might run an education campaign to teach users how to generate effective results using Copilot in Excel. Over the weeks following the campaign, you’d expect to see the number of users who use Copilot in Excel to grow.

$Uri = "https://graph.microsoft.com/beta/reports/getMicrosoft365CopilotUserCountTrend(period='D90')"
$Data = Invoke-GraphRequest -Uri $Uri -Method Get
$Data.Value.copilotActivityUserDetailsByPeriod

reportDate                     2024-06-17
excelEnabledUsers              100
wordActiveUsers                51
powerPointActiveUsers          11
copilotChatActiveUsers         66
outlookActiveUsers             15
loopEnabledUsers               100
oneNoteActiveUsers             1
wordEnabledUsers               100
powerPointEnabledUsers         100
microsoftTeamsActiveUsers      86
oneNoteEnabledUsers            1
excelActiveUsers               21
loopActiveUsers                1
copilotChatEnabledUsers        100
outlookEnabledUsers            100
anyAppEnabledUsers             100
anyAppActiveUsers              86
microsoftTeamsEnabledUsers     100

Track Copilot Activity Using Audit Records instead of Copilot Usage Reports

Although it’s nice to have Copilot usage reports included in the Graph API, the information exposed isn’t very informative in terms of how people use Copilot. The data tells you that someone used Copilot in an app during a day. At least, they clicked a Copilot button. The information doesn’t reveal any more insight than that. Any enterprise who invests large sums of money in expensive Copilot for Microsoft 365 licenses will find a dearth of detail here in terms of understanding whether the investment is justified. In many cases, you will be better off analyzing the audit records captured for Copilot interactions to figure out what’s really going on.


Insight like this doesn’t come easily. You’ve got to know the technology and understand how to look behind the scenes. Benefit from the knowledge and experience of the Office 365 for IT Pros team by subscribing to the best eBook covering Office 365 and the wider Microsoft 365 ecosystem.

]]>
https://office365itpros.com/2024/09/13/copilot-usage-report-api/feed/ 1 66347
Planner User Policy Stops Task and Plan Deletions https://office365itpros.com/2024/06/21/set-planneruserpolicy-effects/?utm_source=rss&utm_medium=rss&utm_campaign=set-planneruserpolicy-effects https://office365itpros.com/2024/06/21/set-planneruserpolicy-effects/#respond Fri, 21 Jun 2024 07:00:00 +0000 https://office365itpros.com/?p=65202

Running the Set-PlannerUserPolicy Cmdlet Has an Unexpected Effect

Although Planner supports a Graph API, the API focuses on management of plans, tasks, buckets, categories, and other objects used in the application rather than plan settings like notifications or backgrounds. It’s good at reporting plans and tasks or populating tasks in a plan, but the API also doesn’t include any support for tenant-wide application settings. In most cases, these gaps don’t matter. The Planner UI has the necessary elements to deal with notification and background settings, neither of which are likely changed all that often. But tenant-wide settings are a dirty secret of Planner. Let me explain why.

The Planner Tenant Admin PowerShell Module

In 2018, Microsoft produced the Planner Tenant Admin PowerShell module. With such a name, you’d expect this module to manage important settings for Planner. That is, until you read the instructions about how to use the module, which document the odd method chosen by the Planner development group distribute and install the software.

Even the Microsoft Commerce team, who probably have the reputation for the worst PowerShell module in Microsoft 365, manage to publish their module through the PowerShell Gallery. But Planner forces tenant administrators to download a ZIP file, “unblock” two files, and manually load the module. The experience is enough to turn off many administrators from interacting with Planner PowerShell.

But buried in this unusual module is the ability to block users from being able to delete tasks created by other people. Remember that most plans are associated with Microsoft 365 Groups. The membership model for groups allows members to have the same level of access to group resources, including tasks in a plan. Anyone can delete tasks in a plan, and that’s not good when Planner doesn’t support a recycle bin or another recovery mechanism.

What the Set-PlannerUserPolicy Cmdlet Does

The Set-PlannerUserPolicy cmdlet from the Planner Tenant Admin PowerShell module allows tenant administrators to block users from deleting tasks created by other people. It’s the type of function that you’d imagine should be in plan settings where a block might apply to plan members. Or it might be a setting associated with a sensitivity label that applied to all plans in groups assigned the label. Alternatively, a setting in the Microsoft 365 admin center could impose a tenant-wide block.

In any case, none of those implementations are available. Instead, tenant administrators must run the Set-PlannerUserPolicy cmdlet to block individual users with a command like:

Set-PlannerUserPolicy -UserAadIdOrPrincipalName Kim.Akers@office365itpros.com -BlockDeleteTasksNotCreatedBySelf $True

The Downside of the Set-PlannerUserPolicy Cmdlet

The point of this story is that assigning the policy to a user account also blocks the ability of the account to delete plans, even if the account is a group owner. This important fact is not mentioned in any Microsoft documentation.

I discovered the problem when investigating how to delete a plan using PowerShell. It seemed a simple process. The Remove-MgPlannerPlan cmdlet from the Microsoft Graph PowerShell SDK requires the planner identifier and its “etag” to delete a plan. This example deletes the second plan in a set returned by the Get-MgPlannerPlan cmdlet:

[array]$Plans = Get-MgPlannerPlan -GroupId $GroupId
$Plan = $Plans[1]
$Tag = $Plan.additionalProperties.'@odata.etag' 
Remove-MgPlannerPlan -PlannerPlanId $Plan.Id -IfMatch $Tag

The same problem occurred when running the equivalent Graph API request:

$Headers = @{}
$Headers.Add("If-Match", $plan.additionalproperties['@odata.etag'])
$Uri = ("https://graph.microsoft.com/v1.0/planner/plans/{0}" -f $Plan.Id)
Invoke-MgGraphRequest -uri $Uri -Method Delete -Headers $Headers

In both cases, the error was 403 forbidden with explanatory text like:

{"error":{"code":"","message":"You do not have the required permissions to access this item, or the item may not exist.","innerError":{"date":"2024-06-13T17:10:10","request-id":"d5bf922c-ea9b-48c6-9629-d9749ab7ec51","client-request-id":"6a533cf8-4396-4743-acf1-a40c32dd11bc"}}}

Even more bafflingly, the Planner browser client refused to let me delete a plan too. At least, the client accepted the request but then failed with a very odd error (Figure 1). After dismissing the error, my access to the undeleted plan continued without an issue.

The Planner browser app declines to delete a plan because of the effect of the Set-PlannerUserPolicycmdlet.
Figure 1: The Planner browser app declines to delete a plan

A Mystery Solved

Fortunately, I have some contacts inside Microsoft that were able to check why my attempts to delete plans failed and report back that the deletion policy set on my account blocked the removal of both tasks created by other users and plans. The first block was expected, the second was not. I’m glad that the mystery is solved but underimpressed that Microsoft does not document this behavior. They might now…

The moral of the story is not to run PowerShell cmdlets unless you know what their effect would be. I wish someone told me that a long time ago.

]]>
https://office365itpros.com/2024/06/21/set-planneruserpolicy-effects/feed/ 0 65202
Problems Retrieving SharePoint Online Usage Data with Graph APIs https://office365itpros.com/2024/02/19/sharepoint-usage-data-issue/?utm_source=rss&utm_medium=rss&utm_campaign=sharepoint-usage-data-issue https://office365itpros.com/2024/02/19/sharepoint-usage-data-issue/#comments Mon, 19 Feb 2024 01:00:00 +0000 https://office365itpros.com/?p=63767

Longstanding Service Issue Retrieving SharePoint Usage Data

The Microsoft 365 ecosystem is so large that it’s hard to keep track of everything that changes that show up in different workloads. We’ve always known about the difficulties of tracking new features, deprecations, and other issues, but sometimes it takes a user to report something to focus on a specific problem.

An example is when a reader noted that the Graph-based script to report the storage quota used by SharePoint sites no longer included site URLs in the output (Figure 1). The original script (from 2020) used a registered Entra ID app to authenticate and use the Graph getSharePointSiteUsageDetail API to fetch site detail data.

No SharePoint Site URL available in usage data.

SharePoint Usage Data
Figure 1: No SharePoint Site URL available in usage data

Problems in the Graph APIs Accessing SharePoint Usage Data

When I investigated the problem, I decided to update the script code to use the Microsoft Graph PowerShell SDK instead. The update did nothing to retrieve the missing data. This isn’t surprising because the problem lies in the Graph API rather than the way the API is called.

The Microsoft 365 admin center uses the same Graph API for its SharePoint site usage report and the same problem of no site URL data is seen there (Figure 2).

No SharePoint Site URL in the Microsoft 365 admin center usage reports
Figure 1: No SharePoint Site URL in the Microsoft 365 admin center usage reports

Even worse, the SharePoint site activity report in the Microsoft 365 admin center displays no data (Figure 3).

No SharePoint user activity available in the Microsoft 365 admin center.
Figure 3: No SharePoint usage data for user activity is available in the Microsoft 365 admin center

This problem is because the getSharePointActivityUserDetail API returns no data whatsoever. Here’s an example of using the API in PowerShell in an attempt to retrieve SharePoint Online user activity for the last 180 days. The retrieved data should end up in the SPOUserDetail.CSV file.

$Uri = "https://graph.microsoft.com/v1.0/reports/getSharePointActivityUserDetail(period='D180')"
Invoke-MgGraphRequest -Uri $Uri -Method GET -OutputFilePath SPOUserDatail.CSV

However, the output file is perfectly empty apart from the column headers (Figure 4).

Blank user activity data downloaded from the Graph
Figure 4: Blank user activity data downloaded from the Graph

The same approach works perfectly with other usage data. For instance, this query works nicely to fetch Exchange Online usage data:

$Uri = "https://graph.microsoft.com/v1.0/reports/getEmailActivityUserDetail(period='D180')"
Invoke-MgGraphRequest -Uri $Uri -Method GET -OutputFilePath $EmailUsage.CSV

A Known Service Issue with SharePoint Usage Data

It’s not surprising that an API should have a problem. The APIs haven’t changed recently, so the root cause is more likely due to a change in the SharePoint Online back end. This feeling is reinforced by service health report SP676147 filed on 21 September 2023 (last updated 9 February 2024) that blithely says that “SharePoint and OneDrive URLs may not be displayed in some usage reports.”

Microsoft goes on to note that:

We’re continuing our work through the validation of multiple potential mitigation strategies to display the URLs of the affected site usage reports. Due to the complexity of the scenarios involved we anticipate this may take additional time.”

The next update for the service health announcement is due on 1 March 2024. What I’m struggling with is that the usage reports included site URLs without any difficulty for years. Why it should suddenly become an issue is inexplicable. And taking over six months to find a solution is even more so.

Microsoft suggests that developers use the Graph Sites API to retrieve the site URL. For example:

$Uri = ("https://graph.microsoft.com/v1.0/sites/{0}" -f $Site.'Site Id')
$SiteData = Invoke-MgGraphRequest -Uri $Uri -Method GET

This works, but only when using an application permission. Using delegated permissions restricts access to sites that the signed-in user is a member of.

SharePoint PowerShell Still Works

Fortunately, it’s possible to get the site storage quota information using the SharePoint Online management PowerShell module. The Graph APIs read from a usage data warehouse that’s populated using background processes. The data is always at least two days old, but it’s much faster to access than using PowerShell to check the storage for each site. But needs must, and at least the old method still works.

I admit forgetting about the service health announcement, perhaps because it’s been ongoing for so long. I’m genuinely surprised that Microsoft is still working on something that seems so innocuous. And I’m even more surprised that customers aren’t making more of a fuss because the URL is the fundamental way to identify a SharePoint site.


Learn how to exploit the data available to Microsoft 365 tenant administrators through the Office 365 for IT Pros eBook. We love figuring out how things work.

]]>
https://office365itpros.com/2024/02/19/sharepoint-usage-data-issue/feed/ 2 63767
How to Update Tenant Corporate Branding for the Entra ID Sign-in Screen with PowerShell https://office365itpros.com/2024/01/25/corporate-branding-for-entra-id/?utm_source=rss&utm_medium=rss&utm_campaign=corporate-branding-for-entra-id https://office365itpros.com/2024/01/25/corporate-branding-for-entra-id/#comments Thu, 25 Jan 2024 01:00:00 +0000 https://office365itpros.com/?p=63398

Use Graph SDK Cmdlets to Apply Annual Updates to Corporate Branding for Entra ID Sign-in Screen

Back in 2020, I took the first opportunity to apply corporate branding to a Microsoft 365 tenant and added custom images to the Entra ID web sign-in process. Things have moved on and company branding has its own section in the Entra ID admin center with accompanying documentation. Figure 1 shows some custom branding elements (background screen, banner logo, and sign-in page text) in action.

Corporate branding applied to the Entra ID sign-in screen.

Corporate Branding for Entra ID.
Figure 1: Corporate branding applied to the Entra ID sign-in screen

Entra ID displays the custom elements after the initial generic sign-in screen when a user enters their user principal name (UPN). The UPN allows Entra ID to identify which tenant the account comes from and if any custom branding should be displayed.

Company branding is available to any tenant with Entra ID P1 or P2 licenses. The documentation mentions that Office 365 licenses are needed to customize branding for the Office apps. This mention is very non-specific. I assume it means Office 365 E3 and above enterprise tenants can customize branding to appear in the web Office apps. Certainly, no branding I have attempted has ever affected the desktop Office apps.

Scripting the Annual Branding Refresh

Every year, I like to refresh the custom branding elements, if only to update the sign-in text to display the correct year. It’s certainly easy to make the changes through the Entra ID admin center (Figure 2), but I like to do it with PowerShell because I can schedule an Azure Automation job to run at midnight on January 1 and have the site customized for the year.

Editing corporate branding settings in the Entra ID admin center.
Figure 2: Editing corporate branding settings in the Entra ID admin center

The Graph APIs include the organizational branding resource type to hold details of a tenant’s branding (either default or custom). Updating the properties of the organizational branding resource type requires the Organization.Rewrite.All permission. Properties are divided into string types (like the sign-in text) and stream types (like the background image).

The script/runbook executes the following steps:

  • Connects to the Graph using a managed identity.
  • Retrieves details of the current sign-in text using the Get-MgOrganizationBranding cmdlet.
  • Checks if the sign-in text has the current year. If not, update the sign-in text and run the Update-MgOrganizationBranding cmdlet to refresh the setting. The maximum size of the sign-in text is 1024 characters. The new sign-in text should be displayed within 15 minutes.
  • Checks if a new background image is available. The code below uses a location on a local disk to allow the script to run interactively. To allow the Azure Automation runbook to find the image, it must be stored in a network location like a web server. The background image should be sized 1920 x 1080 pixels and must be less than 300 KB. Entra ID refuses to upload larger files.
  • If a new image is available, update the branding configuration by running the Invoke-MgGraphRequest cmdlet. I’d like to use the Set-MgOrganizationBrandingLocalizationBackgroundImage cmdlet from the SDK, but it has many woes (issue #2541), not least the lack of a content type parameter to indicate the type of image being passed. A new background image takes longer to distribute across Microsoft’s network but should be available within an hour of the update.

Connect-MgGraph -Scopes Organization.ReadWrite.All -NoWelcome 
# If running in Azure Automation, use Connect-MgGraph -Scopes Organization.ReadWrite.All -NoWelcome -Identity

$TenantId = (Get-MgOrganization).Id
# Get current sign-in text
[string]$SignInText = (Get-MgOrganizationBranding -OrganizationId $TenantId -ErrorAction SilentlyContinue).SignInPageText 
If ($SignInText.Length -eq 0) {
   Write-Host "No branding information found - exiting" ; break
}
[string]$CurrentYear = Get-Date -format yyyy
$DefaultYearImage = "c:\temp\DefaultYearImage.jpg"
$YearPresent = $SignInText.IndexOf($CurrentYear)
If ($YearPresent -gt 0) {
    Write-Output ("Year found in sign in text is {0}. No update necessary" -f $CurrentYear)
} Else {
    Write-Output ("Updating copyright date for tenant to {0}" -f $CurrentYear )
    $YearPosition = $SignInText.IndexOf('202')
    $NewSIT = $SignInText.SubString(0, ($YearPosition)) + $CurrentYear
    # Create hash table for updated parameters
    $BrandingParams = @{}
    $BrandingParams.Add("signInPageText",$NewSIT)
    Update-MgOrganizationBranding -OrganizationId $TenantId -BodyParameter $BrandingParams
    If (Test-Path $DefaultYearImage) {
        Write-Output "Updating background image..."
        $Uri = ("https://graph.microsoft.com/v1.0/organization/{0}/branding/localizations/0/backgroundImage" -f $TenantId)
        Invoke-MgGraphRequest -Method PUT -Uri $Uri -InputFilePath $DefaultYearImage -ContentType "image/jpg"
    } Else {
        Write-Output "No new background image available to update"
    }
}

The script is available in GitHub.

Figure 2 shows the updated sign-in screen (I deliberately updated the year to 2025).

The refreshed corporate branding for the Entra ID sign-in screen.

Corporate branding Entra Id
Figure 3: The refreshed corporate branding for the Entra ID sign-in screen.

If you run the code in Azure Automation, the account must have the Microsoft.Graph.Authentication and Microsoft.Graph.Identity.DirectoryManagement modules loaded as resources in the automation account to use the cmdlets in the script.

Full Corporate Branding Possible

The documentation describes a bunch of other settings that can be tweaked to apply full custom branding to a tenant. Generally, I prefer to keep customization light to reduce ongoing maintenance, but I know that many organizations are strongly attached to corporate logos, colors, and so on.

Corporate Branding for Entra ID Isn’t Difficult

Applying customizations to the Entra ID sign-in screens is not complicated. Assuming you have some appropriate images to use, updating takes just a few minutes with the Entra ID admin center. I only resorted to PowerShell to process the annual update, but you could adopt it to have different sign-in screens for various holidays, company celebrations, and so on.


Learn about using Entra ID and the rest of the Microsoft 365 ecosystem by subscribing to the Office 365 for IT Pros eBook. Use our experience to understand what’s important and how best to protect your tenant.

]]>
https://office365itpros.com/2024/01/25/corporate-branding-for-entra-id/feed/ 1 63398
How to Use PowerShell to Retrieve Permissions for Entra ID Apps https://office365itpros.com/2024/01/24/app-permissions-report/?utm_source=rss&utm_medium=rss&utm_campaign=app-permissions-report https://office365itpros.com/2024/01/24/app-permissions-report/#respond Wed, 24 Jan 2024 01:00:00 +0000 https://office365itpros.com/?p=63376

Keeping an Eye on App Permissions

The recent Midnight Blizzard attack on Microsoft corporate email accounts emphasized the world of threat that exists today. If attackers can compromise the world’s biggest software company, repeat with thousands of skilled security professionals, what hope have the rest of us? Even if attackers might consider your tenant to be unworthy of their attention, the answer lies in paying attention to what happens in the tenant.

In the past, I’ve written about checking consent grants to apps for high-priority permissions. The script used in that article posts the results to a team channel in the hope that administrators read and respond to anything untoward. Recent events make it useful to discuss how to retrieve the set of permissions held by apps.

Hash Tables for Fast Permission Lookup

To find consents for high-priority permissions (like Mail.Send or Exchange.ManageAsApp), the script builds a set of hash tables to hold the role identifiers and their display names. For instance, this code builds a hash table of all Microsoft Graph roles (also called permissions or scopes):

$GraphApp = Get-MgServicePrincipal -Filter "AppId eq '00000003-0000-0000-c000-000000000000'"
$GraphRoles = @{}
ForEach ($Role in $GraphApp.AppRoles) { $GraphRoles.Add([string]$Role.Id, [string]$Role.Value) }

$GraphRoles

Name                           Value
----                           -----
b0c13be0-8e20-4bc5-8c55-963c2… TeamsAppInstallation.ReadWriteAndConsentForTeam.All
926a6798-b100-4a20-a22f-a4918… ThreatSubmissionPolicy.ReadWrite.All
c2667967-7050-4e7e-b059-4cbbb… CustomAuthenticationExtension.ReadWrite.All
(etc.)

The script uses separate hash tables for Graph API permissions, Teams permissions. Entra ID permissions, Exchange Online permissions, and so on.

Having hash tables containing role identifiers and names makes it very easy to resolve the roles assigned to registered apps or the service principals used for enterprise apps. As you’ll recall, an enterprise app is an app created by Microsoft or an ISV that is preconfigured to authenticate against Entra ID and is capable of being used in any tenant. When an enterprise app is installed in a tenant, its service principal becomes the tenant-specific instantiation of the app and holds the permissions assigned to the app.

Finding App Permissions from an App’s Service Principal

To find the permissions assigned to an app, we find the service principal identifier for the app and use it to fetch the set of role assignments. Each role assignment allows the use of a Graph API permission or a administrative role (like Exchange.ManageAsApp) allowing the app to behave as if it was an administrator account holding the role:

$App = Get-MgApplication | Where DisplayName -match 'My registered app'
$SP = Get-MgServicePrincipal -All | Where-Object AppId -match $App.Appid 
[array]$AppRoleAssignments = Get-MgServicePrincipalAppRoleAssignment -All -ServicePrincipalId $SP.id

A role assignment looks like this:

AppRoleId            : 5b567255-7703-4780-807c-7be8301ae99b
CreatedDateTime      : 05/04/2023 16:55:55
DeletedDateTime      :
Id                   : HkiavhziPkSBG_2Lh0ibAx1voO6aUiJCm3NQwDYWeu8
PrincipalDisplayName : My registered app
PrincipalId          : be9a481e-e21c-443e-811b-fd8b87489b03
PrincipalType        : ServicePrincipal
ResourceDisplayName  : Microsoft Graph
ResourceId           : 14a3c489-ed6c-4005-96d1-be9c5770f7a3
AdditionalProperties : {}

We’re interested in the resource identifier (ResourceId) and AppRoleId properties. Together, these tell us the resource (like the Microsoft Graph or SharePoint Online) and role that the permission relates to. Because an app can hold many permissions, we loop through the array to retrieve each permission:

ForEach ($AppRoleAssignment in $AppRoleAssignments) {
  $Permission = (Get-MgServicePrincipal -ServicePrincipalId `
  $AppRoleAssignment.resourceId).appRoles | `
  Where-Object id -match $AppRoleAssignment.AppRoleId | Select-Object -ExpandProperty Value
  Write-Host ("The app {0} has the permission {1}" -f ` 
  $AppRoleAssignment.PrincipalDisplayName, $Permission)
}

The advantage of the hash tables is that the script doesn’t need to keep running the Get-MgServicePrincipal cmdlet to fetch the set of app roles owned by a resource. Thus, we can simply say:

$Permission = $GraphRoles[$AppRoleAssignment.AppRoleId]

Other Methods to Report App Permissions

As is often the case with PowerShell, other methods exist to resolve the names of app permissions from role identifiers. To explore the possibilities, I suggest you look at Sean Avinue’s article about how to generate a risk report or Vasil Michev’s application service principal inventory script.

One of the great things about PowerShell is the ease of repurposing code written by other people to expand and enhance functionality. In this context, “ease” means that it is easier to reuse code than write it from scratch. Some effort is often necessary to shoehorn the code into your scripts.

Vasil’s inventory script handles both delegate and application permissions. As an example of what I mean about code reuse, I took some of his code and fitted it into my script to generate a report about expiring app credentials (secrets and certificates). Figure 1 shows the output email with the high-priority permissions asterisked. Perhaps having highly-permissioned apps brought to the attention of administrators on a regular basis will force them to review what’s happening with apps.

Email containing an app permissions report.
Figure 1: Email containing an app permission report

You can download the updated script from GitHub. Happy coding!


Learn more about how the Microsoft 365 applications and the Graph really work on an ongoing basis by subscribing to the Office 365 for IT Pros eBook. Our monthly updates keep subscribers informed about what’s important across the Office 365 ecosystem.

]]>
https://office365itpros.com/2024/01/24/app-permissions-report/feed/ 0 63376
A New Approach to Reporting Exchange Mailbox Statistics https://office365itpros.com/2023/11/21/graph-usage-data-mailboxes/?utm_source=rss&utm_medium=rss&utm_campaign=graph-usage-data-mailboxes https://office365itpros.com/2023/11/21/graph-usage-data-mailboxes/#respond Tue, 21 Nov 2023 01:00:00 +0000 https://office365itpros.com/?p=62520

Exploit Graph Usage Data Instead of PowerShell Cmdlets

The first report generated by Exchange administrators as they learn PowerShell is often a list of mailboxes. The second is usually a list of mailboxes and their sizes. A modern version of the code used to generate such a report is shown below.

Get-ExoMailbox -RecipientTypeDetails UserMailbox -ResultSize Unlimited | Sort-Object DisplayName | Get-ExoMailboxStatistics | Format-Table DisplayName, ItemCount, TotalItemSize -AutoSize

I call the code “modern” because it used the REST-based cmdlets introduced in 2019. Many examples persist across the internet that use the older Get-Mailbox and Get-MailboxStatistics cmdlets.

Instead of piping the results of Get-ExoMailbox to Get-ExoMailboxStatistics, a variation creates an array of mailboxes and loops through the array to generate statistics for each mailbox.

[array]$Mbx = Get-ExoMailbox -RecipientTypeDetails UserMailbox -ResultSize Unlimited
Write-Host ("Processing {0} mailboxes..." -f $Mbx.count)

$OutputReport = [System.Collections.Generic.List[Object]]::new()

ForEach ($M in $Mbx) {
  $MbxStats = Get-ExoMailboxStatistics -Identity $M.ExternalDirectoryObjectId -Properties LastUserActionTime
  $DaysSinceActivity = (New-TimeSpan $MbxStats.LastUserActionTime).Days
  $ReportLine = [PSCustomObject]@{
    UPN               = $M.UserPrincipalName
    Name              = $M.DisplayName
    Items             = $MbxStats.ItemCount
    Size              = $MbxStats.TotalItemSize.Value.toString().Split("(")[0]
    LastActivity      = $MbxStats.LastUserActionTime
    DaysSinceActivity = $DaysSinceActivity
   } 
   $OutputReport.Add($ReportLine)
 }
$OutputReport | Format-Table Name, UPN, Items, Size, LastActivity

In both cases, the Get-ExoMailboxStatistics cmdlet fetches information about the number of items in a mailbox, their size, and the last recorded user interaction. There’s nothing wrong with this approach. It works (as it has since 2007) and generates the requested information. The only downside is that it’s slow to run Get-ExoMailboxStatistics for each mailbox. You won’t notice the problem in small tenants where a script only needs to process a couple of hundred mailboxes, but the performance penalty mounts as the number of mailboxes increases.

Graph Usage Data and Microsoft 365 Admin Center Reports

Microsoft 365 administrators are probably familiar with the Reports section of the Microsoft 365 admin center. A set of usage reports are available to help organizations understand how active their users are in different workloads, including email (Figure 1).

Email usage reports in the Microsoft 365 admin center

Graph usage data
Figure 1: Email usage reports in the Microsoft 365 admin center

The basis of the usage reports is the Graph Reports API, including the email activity reports and mailbox usage reports through Graph API requests and Microsoft Graph PowerShell SDK cmdlets. Here are examples of fetching email activity and mailbox usage data with the SDK cmdlets. The specified period is 180 days, which is the maximum:

Get-MgReportEmailActivityUserDetail -Period 'D180' -Outfile EmailActivity.CSV
[array]$EmailActivityData = Import-CSV EmailActivity.CSV
Get-MgReportMailboxUsageDetail -Period 'D180' -Outfile MailboxUsage.CSV
[array]$MailboxUsage = Import-CSV MailboxUsage.CSV

I cover how to use Graph API requests in the Microsoft 365 user activity report. This is a script that builds up a composite picture of user activity across different workloads, including Exchange Online, SharePoint Online, OneDrive for Business, and Teams. One difference between the Graph API requests and the SDK cmdlets is that the cmdlets download data to a CSV file that must then be imported into an array before it can be used. The raw API requests can fetch data and populate an array in a single call. It’s just another of the little foibles of the Graph SDK.

The combination of email activity and mailbox usage allows us to replace calls to Get-ExoMailboxStatistics (or Get-MailboxStatistics, if you insist on using the older cmdlet). The basic idea is that the script fetches the usage data (as above) and references the arrays that hold the data to fetch the information about item count, mailbox size, etc.

You can download a full script demonstrating how to use the Graph usage data for mailbox statistics from GitHub.

User Data Obfuscation

To preserve user privacy, organizations can choose to obfuscate the data returned by the Graph and replace user-identifiable data with MD5 hashes. We obviously need non-obfuscated user data, so the script checks if the privacy setting is in force. If this is true, the script switches the setting to allow the retrieval of user data for the report.

$ObfuscatedReset = $False
If ((Get-MgBetaAdminReportSetting).DisplayConcealedNames -eq $True) {
    $Parameters = @{ displayConcealedNames = $False }
    Update-MgBetaAdminReportSetting -BodyParameter $Parameters
    $ObfuscatedReset = $True
}

At the end of the script, the setting is switched back to privacy mode.

Faster but Slightly Outdated

My tests (based on the Measure-Command cmdlet) indicate that it’s much faster to retrieve and use the email usage data instead of running Get-ExoMailboxStatistics. At times, it was four times faster to process a set of mailboxes. Your mileage might vary, but I suspect that replacing cmdlets that need to interact with mailboxes with lookups against arrays will always be faster. Unfortunately the technique is not available for Exchange Server because the Graph doesn’t store usage data for on-premises servers.

One downside is that the Graph usage data is always at least two days behind the current time. However, I don’t think that this will make much practical difference because it’s unlikely that there will be much variation in mailbox size over a couple of days.

The point is that old techniques developed to answer questions in the earliest days of PowerShell might not necessarily still be the best way to do something. New sources of information and different ways of accessing and using that data might deliver a better and faster outcome. Always stay curious!


Insight like this doesn’t come easily. You’ve got to know the technology and understand how to look behind the scenes. Benefit from the knowledge and experience of the Office 365 for IT Pros team by subscribing to the best eBook covering Office 365 and the wider Microsoft 365 ecosystem.

]]>
https://office365itpros.com/2023/11/21/graph-usage-data-mailboxes/feed/ 0 62520
Microsoft 365 Groups with Long Names Cause Graph Errors https://office365itpros.com/2023/10/16/group-display-name-error-120/?utm_source=rss&utm_medium=rss&utm_campaign=group-display-name-error-120 https://office365itpros.com/2023/10/16/group-display-name-error-120/#respond Mon, 16 Oct 2023 01:00:00 +0000 https://office365itpros.com/?p=61883

Keep Group Display Names Short to Avoid Problems

A recent discussion revealed that Graph API requests against the Groups endpoint for groups with display names longer than 120 characters generate an error. As you might know, The Groups Graph API supports group display names up to a maximum of 256 characters, so an error occurring after 120 seems bizarre. Then again, having extraordinarily long group names is also bizarre (Figure 1).

Teams settings for a group with a very long name
Figure 1: Teams settings for a group with a very long name

Group display names longer than 30 or so characters make it difficult for clients to list groups or teams, which is why Microsoft’s recommendation for Teams clients says that between 30 and 36 characters is a good limit for a team name. Using very long group names also creates formatting and layout issues when generating output like the Teams and Groups activity report, especially if only one or two groups have very long names.

Replicating the Problem

In any case, here’s an example. After creating a group with a very long name, I populated a variable with the group’s display name (146 characters). I then created a URI to request the Groups endpoint to return any Microsoft 365 group that has the display name. Finally, I executed the Invoke-MgGraphRequest cmdlet to issue the request, which promptly failed with a “400 bad request” error:

$Name = "O365Grp-Team with an extraordinary long name that makes it much more than 120 characters so that we can have some fun with it  with Graph requests"
$Uri = "https://graph.microsoft.com/v1.0/groups?`$filter= displayName eq '${name}'"
$Data = Invoke-MgGraphRequest -Uri $Uri -Method Get

Invoke-MgGraphRequest: GET Invoke-MgGraphRequest: GET https://graph.microsoft.com/v1.0/groups?$filter=%20displayName%20eq%20'O365Grp-Team%20with%20an%20extraordinary%20long%20name%20that%20makes%20it%20much%20more%20than%20120%20characters%20so%20that%20we%20can%20have%20some%20fun%20with%20it%20%20with%20Graph%20requests'
HTTP/1.1 400 Bad Request
Cache-Control: no-cache

The Get-MgGroup cmdlet also fails. This isn’t at all surprising because the Graph SDK cmdlets run the underlying Graph API requests, so if those requests fail, the cmdlets can’t apply magic to make everything work again:

Get-MgGroup -Filter "displayName eq '$Name'"

Get-MgGroup_List: Unsupported or invalid query filter clause specified for property 'displayName' of resource 'Group'.

The same happens if you try to use the Get-MgTeam cmdlet from the Microsoft Graph PowerShell SDK.

Get-MgTeam -Filter "displayName eq '$Name'"

Get-MgTeam_List: Unsupported or invalid query filter clause specified for property 'displayName' of resource 'Group'.
Status: 400 (BadRequest)
ErrorCode: BadRequest
Date: 2023-10-06T04:53:39
 

The Workaround for Group Display Name Errors

But here’s the thing. The Get-MgGroup cmdlet (and the underlying Graph API request) work if you add the ConsistencyLevel header and an output variable to accept the count of returned items. The presence of the header makes the request into an advanced query against Entra ID.

Get-MgGroup -ConsistencyLevel Eventual -Filter "displayName eq '$Name'" -CountVariable X | Format-Table DisplayName
 
DisplayName
-----------
O365Grp-Team with an extraordinary long name that makes it much more than 120 characters so that we can have some fun …

Oddly, the Get-MgTeam cmdlet doesn’t support the ConsistencyLevel header so this workaround isn’t possible using this cmdlet. Given that Teams (the app) finds its teams through Graph requests, this inconsistency is maddening, and it’s probably due to a flaw in the metadata read by the ‘AutoREST’ process Microsoft runs regularly to generate the SDK cmdlets and build new versions of the SDK modules.

None of the Teams clients that I’ve tested have any problem displaying team names longer than 120 characters, so I suspect that the clients do the necessary magic when fetching lists of teams.

Inconsistency in Entra ID Admin Center

The developers of the Entra ID admin center must know about the 120 character limit (and not about the workaround) because they restrict group names (Figure 2).

The Entra ID admin center wants to restrict group names to 120 characters

Group display name error
Figure 2: The Entra ID admin center wants to restrict group names to 120 characters

A StackOverflow thread from 2017 reported that attempts to use the Graph to create new groups with display names longer than 120 characters resulted in errors. However, it’s possible to now use cmdlets like New-MgGroup to create groups with much longer names.

Given that the Groups Graph API allows for 256 characters, it’s yet another oddity that the Entra ID admin center focuses on a lower limit – unless the developers chose to emphasize to administrators that it’s a really bad idea to use overly long group names.

Time to Update SDK Foibles

I shall have to add this issue to my list of Microsoft Graph PowerShell SDK foibles (aka, things developers should know before they try coding PowerShell scripts using the SDK). The fortunate thing is that you’re unlikely to meet this problem in real life. At least, I hope that you are. And if you do, you’ll know what to do now.


Make sure that you’re not surprised about changes that appear inside Microsoft 365 applications or the Graph by subscribing to the Office 365 for IT Pros eBook. Our monthly updates make sure that our subscribers stay informed.

]]>
https://office365itpros.com/2023/10/16/group-display-name-error-120/feed/ 0 61883
The Right Way for Scripts to Request Permissions with the Microsoft Graph PowerShell SDK https://office365itpros.com/2023/06/02/connect-mggraph-scopes/?utm_source=rss&utm_medium=rss&utm_campaign=connect-mggraph-scopes https://office365itpros.com/2023/06/02/connect-mggraph-scopes/#respond Fri, 02 Jun 2023 01:00:00 +0000 https://office365itpros.com/?p=60314

Pass Permissions with the Connect-MgGraph Scopes

Connect-MgGraph Scopes

Now that we’re in June 2023, the need to migrate PowerShell scripts from using the old and soon-to-be-deprecated Azure AD, AzureADPreview, and Microsoft Online Services (MSOL) modules becomes intense. Microsoft is already throttling cmdlets like New-MsOlUser that perform license assignments. These cmdlets will stop working after June 30, 2023. The other cmdlets in the affected modules will continue to work but lose support after that date. Basing operational automation on unsupported modules isn’t a great strategy, which is why it’s time to replace the cmdlets with cmdlets from the Microsoft Graph PowerShell SDK or Graph API requests.

Graph Permissions

Graph permissions are an element that people often struggle with during the conversion. After you get to know how the Graph works and how Microsoft documentation is laid out, figuring out what permissions a script needs to run is straightforward.

Understanding the difference between delegated and application permissions is a further complication that can lead developers to make incorrect assumptions. Essentially, if a script uses delegated permissions, it can only access data available to the signed-in user. Application permissions are more powerful because they allow access to data across the tenant. For example, the Planner Graph API was limited to delegated permissions for about four years. Microsoft recently upgraded the API to introduce application permission support, which now means that developers can do things like report the details about every plan in an organization.

PowerShell scripts that need to process data drawn from all mailboxes, all sites, all teams, or other sets of Microsoft 365 objects should use application permissions. RBAC for applications is available to limit script access to mailboxes, but it doesn’t extend past mailboxes.

Defining Permissions for a Script with Connect-MgGraph Scopes

All of which brings me to the topic of how to define Graph permissions (scopes) in scripts that use the Microsoft Graph PowerShell SDK. Two choices exist:

I do not recommend the second option. It is preferable to be precise about the permissions needed for a script and to state those permissions when connecting to the Graph.

Examples for Connect-MgGraph Scopes

My script to report the user accounts accessing Teams shared channels in other tenants depends on the CrossTenantUserProfileSharing.Read.All permission. Thus, the script connects with this command:

Connect-MgGraph -Scopes CrossTenantUserProfileSharing.Read.All

If multiple permissions are needed, pass them in a comma-separated list.

If the service principal used by the Graph SDK doesn’t already hold the permission, the SDK prompts the user to grant access. They can grant user access or consent on behalf of the organization (which is needed to get to other users’ data).

The alternative is to check the required permissions against the set of permissions already possessed by the service principal for the Graph SDK. For example:

Connect-MgGraph
[array]$CurrentPermissions = (Get-MgContext).Scopes
[array]$RequiredPermissions = "CrossTenantUserProfileSharing.Read.All"
ForEach ($Permission in $RequiredPermissions) {
   If ($Permission -notin $CurrentPermissions)  {
      Write-Host ("This script needs the {0} permission to run. Please have an administrator consent to the permission and try again" -f $Permission) 
      Break
   }
}

After connecting, the first command fetches the set of current permissions. After stating the set of required permissions in an array, we loop through the set of current permissions to check that each of the required permissions are present. It’s a lot of bother and extra code, which is why I think the simplicity of stating required permissions when connecting to the Microsoft Graph PowerShell SDK is the only way to proceed. Either way works – it’s up to you to decide what you prefer.

Good luck with converting those scripts!


Support the work of the Office 365 for IT Pros team by subscribing to the Office 365 for IT Pros eBook. Your support pays for the time we need to track, analyze, and document the changing world of Microsoft 365 and Office 365.

]]>
https://office365itpros.com/2023/06/02/connect-mggraph-scopes/feed/ 0 60314
Mastering the Foibles of the Microsoft Graph PowerShell SDK https://office365itpros.com/2023/02/13/microsoft-graph-powershell-sdk-prob/?utm_source=rss&utm_medium=rss&utm_campaign=microsoft-graph-powershell-sdk-prob https://office365itpros.com/2023/02/13/microsoft-graph-powershell-sdk-prob/#comments Mon, 13 Feb 2023 01:00:00 +0000 https://office365itpros.com/?p=59070
He looks happy, but he hasn't hit some of the Microsoft Graph PowerShell SDK foibles yet...
He looks happy, but he hasn’t hit some of the SDK foibles yet…

Translating Graph API Requests to PowerShell Cmdlets Sometimes Doesn’t Go So Well

The longer you work with a technology, the more you come to know about its strengths and weaknesses. I’ve been working with the Microsoft Graph PowerShell SDK for about two years now. I like the way that the SDK makes Graph APIs more accessible to people accustomed to developing PowerShell scripts, but I hate some of the SDK’s foibles.

This article describes the Microsoft Graph PowerShell SDK idiosyncrasies that cause me most heartburn. All are things to look out for when converting scripts from the Azure AD and MSOL modules before their deprecation (speaking of which, here’s an interesting tool that might help with this work).

No Respect for $Null

Sometimes you just don’t want to write something into a property and that’s what PowerShell’s $Null variable is for. But the Microsoft Graph PowerShell SDK cmdlets don’t like it when you use $Null. For example, let’s assume you want to create a new Azure AD user account. This code creates a hash table with the properties of the new account and then runs the New-MgUser cmdlet.

$NewUserProperties = @{
    GivenName = $FirstName
    Surname = $LastName
    DisplayName = $DisplayName
    JobTitle = $JobTitle
    Department = $Null
    MailNickname = $NickName
    Mail = $PrimarySmtpAddress
    UserPrincipalName = $UPN
    Country = $Country
    PasswordProfile = $NewPasswordProfile
    AccountEnabled = $true }
$NewGuestAccount = New-MgUser @NewUserProperties

New-MgUser fails because of an invalid value for the department property, even though $Null is a valid PowerShell value.

New-MgUser : Invalid value specified for property 'department' of resource 'User'.
At line:1 char:2
+  $NewGuestAccount = New-MgUser @NewUserProperties
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: ({ body = Micros...oftGraphUser1 }:<>f__AnonymousType64`1) [New-MgUser
   _CreateExpanded], RestException`1
    + FullyQualifiedErrorId : Request_BadRequest,Microsoft.Graph.PowerShell.Cmdlets.NewMgUser_CreateExpanded

One solution is to use a variable that holds a single space. Another is to pass $Null by running the equivalent Graph request using the Invoke-MgGraphRequest cmdlet. Neither are good answers to what should not happen (and we haven’t even mentioned the inability to filter on null values).

Ignoring the Pipeline

The pipeline is a fundamental building block of PowerShell. It allows objects retrieve by a cmdlet to pass to another cmdlet for processing. But despite the usefulness of the pipeline, the SDK cmdlets don’t support it and the pipeline stops stone dead whenever an SDK cmdlet is asked to process incoming objects. For example:

Get-MgUser -Filter "userType eq 'Guest'" -All | Update-MgUser -Department "Guest Accounts"
Update-MgUser : The pipeline has been stopped

Why does this happen? The cmdlet that receives objects must be able to distinguish between the different objects before it can work on them. In this instance, Get-MgUser delivers a set of guest accounts, but the Update-MgUser cmdlet does not know how to process each object because it identifies an object is through the UserId parameter whereas the inbound objects offer an identity in the Id property.

The workaround is to store the set of objects in an array and then process the objects with a ForEach loop.

Property Casing and Fetching Data

I’ve used DisplayName to refer to the display name of objects since I started to use PowerShell with Exchange Server 2007. I never had a problem with uppercasing the D and N in the property name until the Microsoft Graph PowerShell SDK came along only to find that sometimes SDK cmdlets insist on a specific form of casing for property names. Fail to comply, and you don’t get your data.

What’s irritating is that the restriction is inconsistent. For instance, both these commands work:

Get-MgGroup -Filter "DisplayName eq 'Ultra Fans'"
Get-MgGroup -Filter "displayName eq 'Ultra Fans'"

But let’s say that I want to find the group members with the Get-MgGroupMember cmdlet:

[array]$GroupMembers = Get-MgGroupMember -GroupId (Get-MgGroup -Filter "DisplayName eq 'Ultra Fans'" | Select-Object -ExpandProperty Id)

This works, but I end up with a set of identifiers pointing to individual group members. Then I remember from experience gained from building scripts to report group membership that Get-MgGroupMember (like other cmdlets dealing with membership like Get-MgAdministrationUnitMember) returns a property called AdditionalProperties holding extra information about members. So I try:

$GroupMembers.AdditionalProperties.DisplayName

Nope! But if I change the formatting to displayName, I get the member names:

$GroupMembers.AdditionalProperties.displayName
Tony Redmond
Kim Akers
James Ryan
Ben James
John C. Adams
Chris Bishop

Talk about frustrating confusion! It’s not just display names. Reference to any property in AdditionalProperties must use the same casing as used the output, like userPrincipalName and assignedLicenses.

Another example is when looking for sign-in logs. This command works because the format of the user principal name is the same way as stored in the sign-in log data:

[array]$Logs = Get-MgAuditLogSignIn -Filter "UserPrincipalName eq 'james.ryan@office365itpros.com'" -All

Uppercasing part of the user principal name causes the command to return zero hits:

[array]$Logs = Get-MgAuditLogSignIn -Filter "UserPrincipalName eq 'James.Ryan@office365itpros.com'" -All

Two SDK foibles are on show here. First, the way that cmdlets return sets of identifiers and stuff information into AdditionalProperties (something often overlooked by developers who don’t expect this to be the case). Second, the inconsistent insistence by cmdlets on exact matching for property casing.

I’m told that this is all due to the way Graph APIs work. My response is that it’s not beyond the ability of software engineering to hide complexities from end users by ironing out these kinds of issues.

GUIDs and User Principal Names

Object identification for Graph requests depends on globally unique identifiers (GUIDs). Everything has a GUID. Both Graph requests and SDK cmdlets use GUIDs to find information. But some SDK cmdlets can pass user principal names instead of GUIDs when looking for user accounts. For instance, this works:

Get-MgUser -UserId Tony.Redmond@office365itpros.com

Unless you want to include the latest sign-in activity date for the account.

Get-MgUser -UserId Tony.Redmond@office365itpros.com -Property signInActivity
Get-MgUser :
{"@odata.context":"http://reportingservice.activedirectory.windowsazure.com/$metadata#Edm.String","value":"Get By Key
only supports UserId and the key has to be a valid Guid"}

The reason is that the sign-in data comes from a different source which requires a GUID to lookup the sign-in activity for the account, so we must pass the object identifier for the account for the command to work:

Get-MgUser -UserId "eff4cd58-1bb8-4899-94de-795f656b4a18" -Property signInActivity

It’s safer to use GUIDs everywhere. Don’t depend on user principal names because a cmdlet might object – and user principal names can change.

No Fix for Problems in V2 of the Microsoft Graph PowerShell SDK

V2.0 of the Microsoft Graph PowerShell SDK is now in preview. The good news is that V2.0 delivers some nice advances. The bad news is that it does nothing to cure the weaknesses outlined here. I’ve expressed a strong opinion that Microsoft should fix the fundamental problems in the SDK before doing anything else.

I’m told that the root cause of many of the issues is the AutoRest process Microsoft uses to generate the Microsoft Graph PowerShell SDK cmdlets from Graph API metadata. It looks like we’re stuck between a rock and a hard place. We benefit enormously by having the SDK cmdlets but the process that makes the cmdlets available introduces its own issues. Let’s hope that Microsoft gets to fix (or replace) AutoRest and deliver an SDK that’s better aligned with PowerShell standards before our remaining hair falls out due to the frustration of dealing with unpredictable cmdlet behavior.


Insight like this doesn’t come easily. You’ve got to know the technology and understand how to look behind the scenes. Benefit from the knowledge and experience of the Office 365 for IT Pros team by subscribing to the best eBook covering Office 365 and the wider Microsoft 365 ecosystem.

]]>
https://office365itpros.com/2023/02/13/microsoft-graph-powershell-sdk-prob/feed/ 10 59070
Reporting Operating System Versions for Registered Devices https://office365itpros.com/2023/01/31/entra-id-registered-devices-os/?utm_source=rss&utm_medium=rss&utm_campaign=entra-id-registered-devices-os https://office365itpros.com/2023/01/31/entra-id-registered-devices-os/#comments Tue, 31 Jan 2023 01:00:00 +0000 https://office365itpros.com/?p=58916

Know What Operating System Used by Entra ID Registered Devices

After reading an article about populating extension attributes for registered devices, a reader asked me how easy it would be to create a report about the operating systems used for registered devices. Microsoft puts a lot of effort into encouraging customers to upgrade to Windows 11 and it’s a good idea to know what’s the device inventory. Of course, products like Intune have the ability to report this kind of information, but it’s more fun (and often more flexible) when you can extract the information yourself.

As it turns out, reporting the operating systems used by registered devices is very easy because the Microsoft Graph reports this information in the set of properties retrieved by the Get-MgDevice cmdlet from the Microsoft Graph PowerShell SDK.

PowerShell Script to Report Entra ID Registered Devices

The script described below creates a report of all registered devices and sorts the output by the last sign in date. Microsoft calls this property ApproximateLastSignInDateTime. As the name indicates, the property stores the approximate date for the last sign in. Entra ID doesn’t update the property every time someone uses the device to connect. I don’t have a good rule for when property updates occur. It’s enough (and approximate) that the date is somewhat accurate for the purpose of identifying if a device is in use, which is why the script sorts devices by that date.

Any Windows device that hasn’t been used to sign into Entra ID in the last six months is likely not active. This isn’t true for mobile phones because they seem to sign in once and never appear again. The report generated for my tenant still has a record for a Windows Phone which last signed in on 2 December 2015. I think I can conclude that it’s safe to remove this device from my inventory.

Figuring Out Device Owners

In the last script I wrote using the Get-MgDevice cmdlet, I figured out the owner of the device by extracting the user identifier from the PhysicalIds property. While this approach works, it’s complicated. A much better approach is to use the Get-MgDeviceRegisteredOwner cmdlet which returns the user identifier for the user account of the registered owner. With this identifier, we can retrieve any account property that makes sense, such as the display name, user principal name, department, city, and country. You could easily add other properties that make sense to your organization. See this article for more information about using the Get-MgUser cmdlet to interact with user accounts.

The Big Caveat About Operating System Information

The problem that exists in using registered devices to report operating system information is that it’s not accurate. The operating system details noted for a device are accurate at the point of registration but degrade over time. If you want to generate accurate reports, you need to use the Microsoft Graph API for Intune.

With that caveat in mind, here’s the code to report the operating system information for Entra ID registered devices:

Connect-MgGraph -Scope User.Read.All, Directory.Read.All

Write-Host "Finding registered devices"
[array]$Devices = Get-MgDevice -All -PageSize 999
If (!($Devices)) { Write-Host "No registered devices found - exiting" ; break }
Write-Host ("Processing details for {0} devices" -f $Devices.count)
$Report = [System.Collections.Generic.List[Object]]::new() 
$i = 0
ForEach ($Device in $Devices) {
  $i++
  Write-Host ("Reporting device {0} ({1}/{2})" -f $Device.DisplayName, $i, $Devices.count)
  $DeviceOwner = $Null
  Try {
    [array]$OwnerIds = Get-MgDeviceRegisteredOwner -DeviceId $Device.Id
    $DeviceOwner = Get-MgUser -UserId $OwnerIds[0].Id -Property Id, displayName, Department, OfficeLocation, City, Country, UserPrincipalName} 
 } Catch {}

  $ReportLine = [PSCustomObject][Ordered]@{
   Device             = $Device.DisplayName
   Id                 = $Device.Id
   LastSignIn         = $Device.ApproximateLastSignInDateTime
   Owner              = $DeviceOwner.DisplayName
   OwnerUPN           = $DeviceOwner.UserPrincipalName
   Department         = $DeviceOwner.Department
   Office             = $DeviceOwner.OfficeLocation
   City               = $DeviceOwner.City
   Country            = $DeviceOwner.Country
   "Operating System" = $Device.OperatingSystem
   "O/S Version"      = $Device.OperatingSystemVersion
   Registered         = $Device.RegistrationDateTime
   "Account Enabled"  = $Device.AccountEnabled
   DeviceId           = $Device.DeviceId
   TrustType          = $Device.TrustType }
  $Report.Add($ReportLine)

} #End Foreach Device

# Sort in order of last signed in date
$Report = $Report | Sort-Object {$_.LastSignIn -as [datetime]} -Descending

$Report | Out-GridView

Figure 1 is an example of the report as viewed through the Out-GridView cmdlet.

Reporting operating system information for Entra ID registered devices
Figure 1: Reporting operating system information for Entra ID registered devices

You can download the latest version of the script from GitHub.

An Incomplete Help

I’ve no idea whether this script will help anyone. It’s an incomplete answer to a question. However, even an incomplete answer can be useful in the right circumstances. After all, it’s just PowerShell, so use the code as you like.


Learn how to exploit the data available to Microsoft 365 tenant administrators through the Office 365 for IT Pros eBook. We love figuring out how things work.

]]>
https://office365itpros.com/2023/01/31/entra-id-registered-devices-os/feed/ 1 58916
Fetching Group Membership Information for an Entra ID User Account https://office365itpros.com/2023/01/30/group-membership-with-the-graph/?utm_source=rss&utm_medium=rss&utm_campaign=group-membership-with-the-graph https://office365itpros.com/2023/01/30/group-membership-with-the-graph/#comments Mon, 30 Jan 2023 01:00:00 +0000 https://office365itpros.com/?p=58839

Discover Group Membership with the Graph SDK

Group membership with the Graph SDK

I’ve updated some scripts recently to remove dependencies on the Azure AD and Microsoft Online Services (MSOL) modules, which are due for deprecation on June 30, 2023 (retirement happens at the end of March for the license management cmdlets). In most cases, the natural replacement is cmdlets from the Microsoft Graph PowerShell SDK.

One example is when retrieving the groups a user account belongs to. This is an easy task when dealing with the membership of individual groups using cmdlets like:

  • Get-DistributionGroupMember (fetch distribution list members).
  • Get-DynamicDistributionGroupMember (fetch dynamic distribution group members).
  • Get-UnifiedGroupLinks (fetch members of a Microsoft 365 group).
  • Get-MgGroupMember (fetch members of an Entra ID group).

Things are a little more complex when answering a question like “find all the groups that Sean Landy belongs to.” Let’s see how we can answer the request.

The Exchange Online Approach

One method of attacking the problem often found in Exchange scripts is to use the Get-Recipient cmdlet with a filter based on the distinguished name of the mailbox belonging to an account: For example, this code reports a user’s membership of Microsoft 365 groups:

$User = Get-EXOMailbox -Identity Sean.Landy
$DN = $User.DistinguishedName
$Groups = (Get-Recipient -ResultSize Unlimited -RecipientTypeDetails GroupMailbox -Filter "Members -eq '$DN'" )
Write-Host (“User is a member of {0} groups” -f $Groups.count)

The method works if the distinguished name doesn’t include special characters like apostrophes for users with names like Linda O’Shea. In these cases, extra escaping is required to make PowerShell handle the name correctly. This problem will reduce when Microsoft switches the naming mechanism for Exchange Online objects to be based on the object identifier instead of mailbox display name. However, there’s still many objects out there with distinguished names based on display names.

The Graph API Request

As I go through scripts, I check if I can remove cmdlets from other modules to make future maintenance easier. Using Get-Recipient means that a script must connect to the Exchange Online management module, so let’s remove that need by using a Graph API request. Here’s what we can do, using the Invoke-MgGraphRequest cmdlet to run the request:

$UserId = $User.ExternalDirectoryObjectId
$Uri = ("https://graph.microsoft.com/V1.0/users/{0}/memberOf/microsoft.graph.group?`$filter=groupTypes/any(a:a eq 'unified')&`$top=200&$`orderby=displayName&`$count=true" -f $UserId)
[array]$Data = Invoke-MgGraphRequest -Uri $Uri
[array]$Groups = $Data.Value
Write-Host (“User is a member of {0} groups” -f $Groups.count) 

We get the same result (always good) and the Graph request runs about twice as fast as Get-Recipient does.

Because the call is limited to Microsoft 365 groups, I don’t have to worry about transitive membership. If I did, then I’d use the group transitive memberOf API.

Using the SDK Get-MgUserMemberOf Cmdlet

The Microsoft Graph PowerShell SDK contains cmdlets based on Graph requests. The equivalent cmdlet is Get-MgUserMemberOf. This returns memberships of all group types known to Entra ID, so it includes distribution lists and security groups. To return the set of Microsoft 365 groups, apply a filter after retrieving the group information from the Graph.

[array]$Groups = Get-MgUserMemberOf -UserId $UserId -All | Where-Object {$_.AdditionalProperties["groupTypes"] -eq "Unified"}
Write-Host (“User is a member of {0} groups” -f $Groups.count) 

Notice that the filter looks for a specific type of group in a value in the AdditionalProperties property of each group. If you run Get-MgUserMemberOf without any other processing. the cmdlet appears to return a simple list of group identifiers. For example:

$Groups

Id                                   DeletedDateTime
--                                   ---------------
b62b4985-bcc3-42a6-98b6-8205279a0383
64d314bb-ea0c-46de-9044-ae8a61612a6a
87b6079d-ddd4-496f-bff6-28c8d02e9f8e
82ae842d-61a6-4776-b60d-e131e2d5749c

However, the AdditionalProperties property is also available for each group. This property contains a hash table holding other group properties that can be interrogated. For instance, here’s how to find out whether the group supports private or public access:

$Groups[0].AdditionalProperties['visibility']
Private

When looking up a property in the hash table, remember to use the exact form of the key. For instance, this works to find the display name of a group:

$Groups[0].AdditionalProperties['displayName']

But this doesn’t because the uppercase D creates a value not found in the hash table:

$Groups[0].AdditionalProperties['DisplayName']

People starting with the Microsoft Graph PowerShell SDK are often confused when they see just the group identifiers apparently returned by cmdlets like Get-MgUserMemberOf, Get-MgGroup, and Get-MgGroupMember because they don’t see or grasp the importance of the AdditionalProperties property. It literally contains the additional properties for the group excepting the group identifier.

Here’s another example of using information from AdditionalProperties. The details provided for a group don’t include its owners. To fetch the owner information for a group, run the Get-MgGroupOwner cmdlet like this:

$Group = $Groups[15]
[array]$Owners = Get-MgGroupOwner -GroupId $Group.Id | Select-Object -ExpandProperty AdditionalProperties
$OwnersOutput = $Owners.displayName -join ", "
Write-Host (“The owners of the {0} group are {1}” -f $Group.AdditionalProperties[‘displayName’], $OwnersOutput)

If necessary, use the Get-MgGroupTransitiveMember cmdlet to fetch transitive memberships of groups.

The Graph SDK Should be More Intelligent

It would be nice if the Microsoft Graph PowerShell SDK didn’t hide so much valuable information in AdditionalProperties and wasn’t quite so picky about the exact format of property names. Apparently, the SDK cmdlets behave in this manner because it’s how Graph API requests work when they return sets of objects. That assertion might well be true, but it would be nice if the SDK applied some extra intelligence in the way it handles data.


Insight like this doesn’t come easily. You’ve got to know the technology and understand how to look behind the scenes. Benefit from the knowledge and experience of the Office 365 for IT Pros team by subscribing to the best eBook covering Office 365 and the wider Microsoft 365 ecosystem.

]]>
https://office365itpros.com/2023/01/30/group-membership-with-the-graph/feed/ 4 58839
Reporting Group Membership for Azure AD Guest Accounts with the Microsoft Graph PowerShell SDK https://office365itpros.com/2023/01/18/old-azure-ad-guest-accounts/?utm_source=rss&utm_medium=rss&utm_campaign=old-azure-ad-guest-accounts https://office365itpros.com/2023/01/18/old-azure-ad-guest-accounts/#comments Wed, 18 Jan 2023 01:00:00 +0000 https://office365itpros.com/?p=58742

Finding Azure AD Guest Accounts in Microsoft 365 Groups

The article explaining how to report old guest accounts and their membership of Microsoft 365 Groups (and teams) in a tenant is very popular and many people use its accompanying script. The idea is to find guest accounts above a certain age (365 days – configurable in the script) and report the groups these guests are members of. Any old guest accounts that aren’t in any groups are candidates for removal.

The script uses an old technique featuring the distinguished name of guest accounts to scan for group memberships using the Get-Recipient cmdlet. The approach works, but the variation of values that can exist in distinguished names due to the inclusion of characters like apostrophes and vertical lines means that some special processing is needed to make sure that lookups work. Achieving consistency in distinguished names might be one of the reasons for Microsoft’s plan to make Exchange Online mailbox identification more effective.

In any case, time moves on and code degrades. I wanted to investigate how to use the Microsoft Graph PowerShell SDK to replace Get-Recipient. The script already uses the SDK to find Azure AD guest accounts with the Get-MgUser cmdlet.

The Graph Foundation

Graph APIs provide the foundation for all SDK cmdlets. Graph APIs provide the foundation for all SDK cmdlets. The first thing to find is an appropriate API to find group membership. I started off with getMemberGroups. The PowerShell example for the API suggests that the Get-MgDirectoryObjectMemberGroup cmdlet is the one to use. For example:

$UserId = (Get-MgUser -UserId Terry.Hegarty@Office365itpros.com).id 
[array]$Groups = Get-MgDirectoryObjectMemberGroup  -DirectoryObjectId $UserId -SecurityEnabledOnly:$False

The cmdlet works and returns a list of group identifiers that can be used to retrieve information about the groups that the user belongs to. For example:

Get-MgGroup -GroupId $Groups[0] | Format-Table DisplayName, Id, GroupTypes

DisplayName                     Id                                   GroupTypes
-----------                     --                                   ----------
All Tenant Member User Accounts 05ecf033-b39a-422c-8d30-0605965e29da {DynamicMembership, Unified}

However, because Get-MgDirectoryObjectMemberGroup returns a simple list of group identifiers, the developer must do extra work to call Get-MgGroup for each group to retrieve group properties. Not only is this extra work, calling Get-MgGroup repeatedly becomes very inefficient as the number of guests and their membership in groups increase.

Looking Behind the Scenes with Graph X-Ray

The Azure AD admin center (and the Entra admin center) both list the groups that user accounts (tenant and guests) belong to. Performance is snappy and it seemed unlikely that the code used was making multiple calls to retrieve the properties for each group. Many of the sections in these admin centers use Graph API requests to fetch information, and the Graph X-Ray tool reveals those requests. Looking at the output, it’s interesting to see that the admin center uses the beta Graph endpoint with the groups memberOf API (Figure 1).

Using the Graph X-Ray tool to find the Graph API for group membership

Azure AD Guest Accounts
Figure 1: Using the Graph X-Ray tool to find the Graph API for group membership

We can reuse the call used by the Azure AD center to create the query (containing the object identifier for the user account) and run the query using the SDK Invoke-MgGraphRequest cmdlet. One change made to the command is to include a filter to select only Microsoft 365 groups. If you omit the filter, the Graph returns all the groups a user belongs to, including security groups and distribution lists. The group information is in an array that’s in the Value property returned by the Graph request. For convenience, we put the data into a separate array.

$Uri = ("https://graph.microsoft.com/beta/users/{0}/memberOf/microsoft.graph.group?`$filter=groupTypes/any(a:a eq 'unified')&`$top=200&$`orderby=displayName&`$count=true" -f $Guest.Id)
[array]$Data = Invoke-MgGraphRequest -Uri $Uri
[array]$GuestGroups = $Data.Value

Using the Get-MgUserMemberOf Cmdlet

The equivalent SDK cmdlet is Get-MgUserMemberOf. To return the set of groups an account belongs to, the command is:

[array]$Data = Get-MgUserMemberOf -UserId $Guest.Id -All
[array]$GuestGroups = $Data.AdditionalProperties

The format of returned data marks a big difference between the SDK cmdlet and the Graph API request. The cmdlet returns group information in a hash table in the AdditionalProperties array while the Graph API request returns a simple array called Value. To retrieve group properties from the hash table, we must enumerate through its values. For instance, to return the names of the Microsoft 365 groups in the hash table, we do something like this:

[Array]$GroupNames = $Null
ForEach ($Item in $GuestGroups.GetEnumerator() ) {
   If ($Item.groupTypes -eq "unified") { $GroupNames+= $Item.displayName }
}
$GroupNames= $GroupNames -join ", "

SDK cmdlets can be inconsistent in how they return data. It’s just one of the charms of working with cmdlets that are automatically generated from code. Hopefully, Microsoft will do a better job of ironing out inconsistencies when they release V2.0 of the SDK sometime later in 2023.

A Get-MgUserTransitiveMemberOf cmdlet is also available to return the membership of nested groups. We don’t need to do this because we’re only interested in Microsoft 365 groups, which don’t support nesting. The cmdlet works in much the same way:

[array]$TransitiveData = Get-MgUserTransitiveMemberOf -UserId Kim.Akers@office365itpros.com -All

The Script Based on the SDK

Because of the extra complexity in accessing group properties, I decided to use a modified version of the Graph API request from the Azure AD admin center. It’s executed using the Invoke-MgGraphRequest cmdlet, so I think the decision is justified.

When revising the script, I made some other improvements, including adding a basic assessment of whether a guest account is stale or very stale. The assessment is intended to highlight if I should consider removing these accounts because they’re obviously not being used. Figure 2 shows the output of the report.

Report highlighting potentially obsolete guest accounts
Figure 2: Report highlighting potentially obsolete Azure AD guest accounts

You can download a copy of the script from GitHub.

Cleaning up Obsolete Azure AD Guest Accounts

Reporting obsolete Azure AD guest accounts is nice. Cleaning up old junk from Azure AD is even better. The script generates a PowerShell list with details of all guests over a certain age and the groups they belong to. To generate a list of the very stale guest accounts, filter the list:

[array]$DeleteAccounts = $Report | Where-Object {$_.StaleNess -eq "Very Stale"}

To complete the job and remove the obsolete guest accounts, a simple loop to call Remove-MgUser to process each account:

ForEach ($Account in $DeleteAccounts) {
   Write-Host ("Removing guest account for {0} with UPN {1}" -f $Account.Name, $Account.UPN) 
   Remove-MgUser -UserId $Account.Id }

Obsolete or stale guest accounts are not harmful, but their presence slows down processing like PowerShell scripts. For that reason, it’s a good idea to clean out unwanted guests periodically.


Learn about mastering the Microsoft Graph PowerShell SDK and the Microsoft 365 PowerShell modules by subscribing to the Office 365 for IT Pros eBook. Use our experience to understand what’s important and how best to protect your tenant.

]]>
https://office365itpros.com/2023/01/18/old-azure-ad-guest-accounts/feed/ 2 58742
Microsoft Clarifies How It Plans to Charge for APIs https://office365itpros.com/2022/12/14/microsoft-365-api-tiers/?utm_source=rss&utm_medium=rss&utm_campaign=microsoft-365-api-tiers https://office365itpros.com/2022/12/14/microsoft-365-api-tiers/#comments Wed, 14 Dec 2022 01:00:00 +0000 https://office365itpros.com/?p=58245

Pay as You Go Model for Microsoft 365 APIs

Microsoft 365 APIs

About fifteen months ago, Microsoft introduced the notion of metered APIs where those who consumed the APIs would pay for the resources they consume. The pay-as-you-go (PAYG) model evolved further in July 2022 when Microsoft started to push ISVs to use the new Teams export API instead of Exchange Web Services (EWS) for their backup products. The Teams export API is a metered API and is likely to the test case to measure customer acceptance of the PAYG model.

So far, I haven’t heard many positive reactions to the development. Some wonder how Microsoft can force ISVs to use an API when they don’t know how high the charges metering will rack up. Others ask how Microsoft can introduce an export API for backup when they don’t have an equivalent import API to allow tenants to restore data to Teams. I don’t understand this either as it seems logical to introduce export and import capabilities at the same time. We live in interesting times!

PAYG with Syntex Backup

To be fair to Microsoft, they plan to go down the same PAYG route with the new backup service they plan to introduce in 2023 as part of the Syntex content management suite. Customers will have to use an Azure subscription to pay for backups of SharePoint Online, OneDrive for Business, and Exchange Online (so far, Microsoft is leaving Teams backup to ISVs).

All of which brings me to the December 2 post from the Microsoft Graph development team where Microsoft attempts to describe what they’re doing with different Microsoft 365 APIs. Like many Microsoft texts, too many words disguise the essential facts of the matter.

Three Microsoft 365 API Tiers

Essentially, Microsoft plans to operate three Microsoft 365 API tiers:

  • Standard: The regular Graph-based and other APIs that allow Microsoft 365 tenants to access and work with their data.
  • High-capacity: Metered APIs that deal with high-volume operations like the streaming of data out of Microsoft 365 for backups or the import of data into Microsoft 365.
  • Advanced: APIs developed by Microsoft to deliver new functionality. Microsoft points to Azure Communications Services as an example. These APIs allow developers to add the kind of communication options that are available in Teams to their applications.

My reading of the situation is that Microsoft won’t charge for standard APIs because this would interfere with customer access to their data. Microsoft says that standard APIs will remain the default endpoint.

However, Microsoft very much wants to charge for high-capacity APIs used by “business-critical applications with high usage patterns.” The logic here is that these APIs strain the resources available within the service. To ensure that Microsoft can meet customer expectations, they need to deploy more resources to meet the demand and someone’s got to pay for those resources. By using a PAYG model, Microsoft will charge for actual usage of resources.

Microsoft also wants customers to pay for advanced APIs. In effect, this is like an add-on license such as Teams Premium. If you want to use the bells and whistles enabled by an advanced API, you must pay for the privilege. It’s a reasonable stance.

Problem Areas for Microsoft 365 APIs

I don’t have a problem with applying a tiered model for APIs, especially if the default tier continues with free access. The first problem here is in communications, where Microsoft has failed to sell their approach to ISVs and tenants. The lack of clarity and obfuscation is staggering for an organization that employs masses of marketing and PR staff.

The second issue is the lack of data about how much PAYG is likely to cost. Few want to write an open-ended check to Microsoft for API usage. Microsoft is developing the model and understands how the APIs work, so it should be able to give indicative pricing for different scenarios. For instance, if I have 100 teams generating 35,000 new channel conversations and 70,000 chats monthly, how much will a backup cost? Or if my tenant generates new and updated documents at the typical rate observed by Microsoft across all tenants of a certain size, how much will a Syntex backup cost?

The last issue is the heavy-handed approach Microsoft has taken with backup ISVs. Being told that you must move from a working, well-sorted, and totally understood API to a new, untested, and metered API is not a recipe for good ISV relationships. Microsoft needs its ISVs to support its API tiered model. It would be so much better if a little less arrogance and a little more humility was obvious in communication. Just because you’re the big dog who owns the API bone doesn’t mean that you need to fight with anyone who wants a lick.


Make sure that you’re not surprised about changes that appear inside Office 365 applications by subscribing to the Office 365 for IT Pros eBook. Our monthly updates make sure that our subscribers stay informed.

]]>
https://office365itpros.com/2022/12/14/microsoft-365-api-tiers/feed/ 3 58245
Tips for Working with the Graph Usage Reports API https://office365itpros.com/2022/09/09/graph-usage-report-tips/?utm_source=rss&utm_medium=rss&utm_campaign=graph-usage-report-tips https://office365itpros.com/2022/09/09/graph-usage-report-tips/#respond Fri, 09 Sep 2022 01:00:00 +0000 https://office365itpros.com/?p=56909

Understanding What the Graph Usage Reports API Generates

Last month, I discussed a new version of the Microsoft 365 user activity report based on the 180-day lookback period now supported by the Graph usage reports API. This provoked some questions about the API that are worth clarifying.

Single Point of Reference for Reports

Microsoft 365 administrative interfaces (like the Teams admin center) generate their usage reports from the data retrieved via the API. The API is a single point of contact for any usage report found inside Microsoft 365. This used not to be the case, but it is now. The Teams admin center currently limits reports to the previous 90 days while the Microsoft 365 admin center supports 180 days.

Concealing Display Names

Because the API is used everywhere, the setting to conceal user, group, and site display names available in the Reports section of Org Settings in the Microsoft 365 admin center controls all data retrieved with the API, including any reports that you create. If you want to see display names in usage data, the setting to obfuscate this information must be turned off (Figure 1).

Setting the concealed display names option for the Graph usage report API
Figure 1: Setting the concealed display names option for the Graph usage reports API

Teams was the last workload to apply concealment to usage data. All workloads that generate usage data now support this feature.

Switching Concealment On and Off

In a script, you can check if concealment is active and disable it temporarily to allow display names to be fetched using the Graph Usage Reports API. For example:

# first, find if the data is obscured
$Display = Invoke-MgGraphRequest -Method Get -Uri 'https://graph.microsoft.com/beta/admin/reportSettings'
If ($Display['displayConcealedNames'] -eq $True) { # data is obscured, so let's reset it to allow the report to run
   $ObscureFlag = $True
   Write-Host "Setting tenant data concealment for reports to False" -foregroundcolor red
   Invoke-MgGraphRequest -Method PATCH -Uri 'https://graph.microsoft.com/beta/admin/reportSettings' -Body (@{"displayConcealedNames"= $false} | ConvertTo-Json) }

To reverse the process, update the setting to True:

# And reset obscured data if necessary
If ($ObscureFlag -eq $True) {
   Write-Host "Resetting tenant data concealment for reports to True" -foregroundcolor red
   Invoke-MgGraphRequest -Method PATCH -Uri 'https://graph.microsoft.com/beta/admin/reportSettings' -Body (@{"displayConcealedNames"= $true} | ConvertTo-Json) }

Teams Data Needs Interpretation

If you look at the information returned by the Graph Usage Reports API for Teams usage, the activity level reported for teams needs some interpretation. For instance, here’s some information I extracted for the team we use to manage the Office 365 for IT Pros eBook. I used a 180-day lookback to extract the data with this script.

Name            : Ultimate Guide to Office 365
LastActivity    : 2022-09-06
AccessType      : Private
Id              : 33b07753-efc6-47f5-90b5-13bef01e25a6
IsDeleted       : False
ActiveUsers     : 17
ActiveExtUsers  : 7
Guests          : 8
ActiveChannels  : 11
SharedChannels  : 1
Posts           : 51
Replies         : 511
Channelmessages : 674
Reactions       : 241
Mentions        : 185
UrgentMessages  : 0
Meetings        : 0

The report data shows that:

  • The last activity noted for the team was September 6, 2022. The usage report data is always a couple of days behind.
  • The team has private membership and is not deleted. Usage data includes deleted teams that have logged some activity within the lookback period.
  • The number of active users is 17. However, the team has 2 owners and 9 members, so this figure is odd.
  • The number of active external users is 7. This matches the 7 guest members in the team.
  • The number of guest accounts is 8. Currently, the team has only 7 guests, but a team owner or administrator could have removed a guest account during the lookback period. The shortest lookback period is 7 days. When I use this to query the usage data, it reports 7 guests.
  • The number of active channels is 11. There are currently 10 channels in the team plus one deleted channel. Deleted channels remain available for restore for a 21-day period. In this case, the deleted channel is a shared channel that I deleted many months ago, so that’s worth investigating. The Graph API for channels doesn’t list deleted channels, so I can’t check its deletion status or deletion date. What’s also weird is that no audit records exist for the deletion of this channel…
  • There is one other shared channel in the team.

Message Counts are Even More Confusing

The data reported for message volume within a team is where things get interesting. The numbers of reactions and mentions are easy to understand (and you can validate the reactions number through audit records). Things are less clear with posts, replies, and channel messages. The Microsoft 365 admin center avoids confusion by only reporting the count of channel messages (674). The Teams admin center reports posts (51), replies (511), and channel messages.

According to Microsoft documentation, “Channel messages is the number of unique messages that the user posted in a team channel during the specified time period.” I think the reference to “the user” should be “users” as this makes more sense. However, adding posts and replies only gets me to 562, which is 122 less than the channel message count. Reactions could be considered as a form of reply, but 241 reactions is more than the 122 gap, so there’s a mystery as to how Microsoft calculates the number of channel messages.

The counted messages include only those posted by users. They don’t include messages posted by applications or those that come through the inbound webhook connector.

The Groups and Teams activity report script reads the usage data for a team to know if it is active or potentially obsolete. In that instance, a precise measurement of message activity isn’t a real problem because we accept that if a team has some messaging activity it’s not obsolete. However, if you’re interested in tracking the exact number of messages generated per team, it’s best to use the total of posts and replies.

Seeking Understanding from the Graph Usage Reports API

The old rule applies of not accepting data until you understand its meaning. It’s nice that Teams usage data is available for tenants to browse and download. It would be even nicer if the meaning of the data was clearer.


Learn how to exploit the data available to Microsoft 365 tenant administrators through the Office 365 for IT Pros eBook. We love figuring out how things work.

]]>
https://office365itpros.com/2022/09/09/graph-usage-report-tips/feed/ 0 56909
Microsoft Graph Support for SharePoint Online Tenant Settings https://office365itpros.com/2022/07/19/sharepoint-graph-settings/?utm_source=rss&utm_medium=rss&utm_campaign=sharepoint-graph-settings https://office365itpros.com/2022/07/19/sharepoint-graph-settings/#comments Tue, 19 Jul 2022 01:00:00 +0000 https://office365itpros.com/?p=56125

Introducing the Tenant Admin Namespace for SharePoint Graph Settings

SharePoint Graph settings

Despite being the two basic Microsoft 365 workloads, one of the notable gaps in Microsoft Graph API coverage has been administrative interfaces for SharePoint Online and Exchange Online. A small but valuable step in the right direction happened with the appearance of the settings resource type in the TenantAdmin namespace. For now, the coverage for tenant settings is sparse and only deals with some of the settings that administrators can manage using the Set-SPOTenant PowerShell cmdlet, but it’s a start, and you can see how Microsoft might develop the namespace to handle programmatic access to settings that currently can only be managed through an admin portal.

Options to Manage SharePoint Online Settings

SharePoint Online tenant-wide settings apply to SharePoint Online sites and OneDrive for Business accounts. Like all Graph APIs, apps must have permissions to be able to make requests. The read-only permission is SharePointTenantSettings.Read.All while you’ll need the SharePointTenantSettings.ReadWrite.All permission to update settings.

Three methods are available to use the new API:

  • The Graph Explorer.
  • A dedicated app registered in Entra ID
  • The Microsoft Graph PowerShell SDK.

The Graph Explorer is acceptable for testing or one-off commands. However, given that the Set-SPOTenant cmdlet is available, it’s unlikely that you’d use the Graph Explorer as your preferred method to update settings.

Creating a dedicated app just to manage SharePoint Online settings is unlikely too unless you use the same app to manage multiple tenants. This points to the most likely use of the TenantAdmin API, which is to allow MSPs to create apps to manage multiple tenants on behalf of customers.

The Microsoft Graph PowerShell SDK could be used to replace the SharePoint Online management module. An organization might want to do this to rationalize the number of PowerShell modules its developers work with and maintain. I can see this happening in the future when Microsoft has developed the TenantAdmin API to match the capabilities available today through the Set-SPOTenant cmdlet. For now, I’d stay with the SharePoint module and keep a close eye on what happens with the API.

Updating SharePoint Online Settings with the Microsoft Graph PowerShell SDK

As an example of using the new API, let’s update the setting controlling Loop components in Microsoft 365 apps. This seems appropriate given the recent appearance of Loop components in OWA. The setting controlling the availability of Loop components is IsLoopEnabled, which is True by default. Here’s the code to retrieve the current setting:

Connect-MgGraph -Scopes SharePointTenantSettings.ReadWrite.All
$Uri = "https://graph.microsoft.com/V1.0/admin/sharepoint/settings"
$SPOSettings = Invoke-MgGraphRequest -Uri $Uri -Method Get
$SPOSettings['IsLoopEnabled']
True

To change the setting to False (and disable Loop components), we use the same URI and run a Patch request. To make the command slightly more interesting, we’ll also update the SharePoint News feed setting at the same time and set a new default time zone for new sites created in the tenant. The time zone for new sites is an example of a setting that cannot be set using the Set-SPOTenant cmdlet. Currently, the time zone can only be set in the SharePoint admin center, so this is an example of how the Graph API will expose new settings.

First, we create a payload object.

$NewSettings = @{
    "isLoopEnabled" = "false"
    "isSharePointNewsFeedEnabled" = "true"
    "tenantDefaultTimezone" = "(UTC) Dublin, Edinburgh, Lisbon, London"
}

Then, we patch the settings.

Invoke-MgGraphRequest -Uri $Uri -Method Patch -Body $NewSettings

SharePoint responds by listing all the settings available to the API: You can see that the two settings have the values contained in the payload.

Name                           Value
----                           -----
isFileActivityNotificationE... True
isCommentingOnSitePagesEnabled True
sharingBlockedDomainList       {Gmail.com}
sharingAllowedDomainList       {hotmail.com, live.com, locklan.com.au, Microsoft.com...}
siteCreationDefaultManagedPath /sites/
deletedUserPersonalSiteRete... 60
isSiteCreationUIEnabled        True
isSyncButtonHiddenOnPersona... False
isSitePagesCreationEnabled     False
tenantDefaultTimezone         (UTC) Dublin, Edinburgh, Lisbon, London
isLoopEnabled                  False
personalSiteDefaultStorageL... 5242880
allowedDomainGuidsForSyncApp   {}
isSiteCreationEnabled          True
availableManagedPathsForSit... {/sites/, /teams/, /containers/}
isResharingByExternalUsersE... False
isSharePointMobileNotificat... True
sharingDomainRestrictionMode   none
sharingCapability              externalUserAndGuestSharing
isMacSyncAppEnabled            True
imageTaggingOption             basic
isUnmanagedSyncAppForTenant... False
isSitesStorageLimitAutomatic   True
isSharePointNewsfeedEnabled    False
excludedFileExtensionsForSy... {*.exe, *.zip, *.rar, *.pst...}
@odata.context                 https://graph.microsoft.com/beta/$metadata#admin/sharepoint/settings/$entity
siteCreationDefaultStorageL... 26214400

Something to Monitor

I suspect that the new API will not be heavily used for now and won’t until it attains feature comparability with the Set-SPOTenant cmdlet. But that’s not the important thing to take away. This is the start of the development of Graph API support for tenant administrative settings, and that’s certainly something to welcome.


Insight like this doesn’t come easily. You’ve got to know the technology and understand how to look behind the scenes. Benefit from the knowledge and experience of the Office 365 for IT Pros team by subscribing to the best eBook covering Office 365 and the wider Microsoft 365 ecosystem.

]]>
https://office365itpros.com/2022/07/19/sharepoint-graph-settings/feed/ 2 56125
Use the Debug Parameter for Microsoft Graph PowerShell SDK Cmdlets to Expose Graph API Requests https://office365itpros.com/2022/07/11/debug-microsoft-graph-powershell-sdk/?utm_source=rss&utm_medium=rss&utm_campaign=debug-microsoft-graph-powershell-sdk https://office365itpros.com/2022/07/11/debug-microsoft-graph-powershell-sdk/#comments Mon, 11 Jul 2022 01:00:00 +0000 https://office365itpros.com/?p=56010

Debug Microsoft Graph PowerShell SDK Cmdlets to Gain Insights into What They Do

A comment for my article about recent enhancements for the Microsoft Graph Explorer noted that while it was great to see the Graph Explorer generate PowerShell code snippets for requests it executes, it would be even nicer if the Graph Explorer supported “round tripping.” In this instance, that means users could send PowerShell commands to the Graph Explorer, which would then interpret the commands and generate the appropriate Graph API requests. It sounds like a great idea.

I contacted some of the folks working on the Microsoft Graph to see if this is possible. Although they couldn’t commit on such an implementation appearing in the future, I was told about a nice feature in the Microsoft Graph PowerShell SDK cmdlets. It doesn’t address the round-tripping request, but it’s a good thing to know none the less, especially if you’re grappling to understand how the SDK cmdlets work for tasks like user, group, or license management.

In a nutshell, if you add the –Debug parameter to any Microsoft Graph PowerShell SDK cmdlet, you’ll see exactly what the cmdlet does, including the Graph API request it runs to execute the command. This is a great way to gain insight into how these cmdlets work and also understand how to leverage Graph API requests.

Because many of its cmdlets are built on Graph APIs, the Debug parameter also works in the same manner for cmdlets from the Microsoft Teams PowerShell module. However, the Teams cmdlets do not output details about permissions and the older policy cmdlets that originated from the Skype for Business Online connector do not display Graph API URIs when they run.

Running with the Debug Parameter

Let’s take a basic example and run the Get-MgUser cmdlet to fetch details of all user accounts in a tenant:

Get-MgUser -All -Debug -Filter "userType eq 'Member'"

When the cmdlet starts, it shows the context it will run under, including whether it’s an interactive session and the scopes (permissions) available to the command. You can get the same information by running the Get-MgContext cmdlet, but this is useful up-front knowledge.

In Figure 1 you can see that the service principal used by the Microsoft Graph PowerShell SDK has many permissions. This is the result of permission creep, the tendency of the service principal to accrue permissions over time due to testing different cmdlets. The existence of so many permissions makes it a bad idea to use the Microsoft Graph PowerShell SDK cmdlets interactively unless you know what you’re doing. In production, it’s best to use certificate-based authentication and a registered Azure AD app to limit the permissions available.

Debug Microsoft Graph PowerShell SDK cmdlets - the execution context
Figure 1: Debug Microsoft Graph PowerShell SDK cmdlets – the execution context

The Graph API request is now displayed. We can see that it looks for the top 100 matching items that satisfy the filter. In other words, return the first 100 Azure AD member accounts (Figure 2).

Debug Microsoft Graph PowerShell SDK cmdlets - the HTTP GET request
Figure 2: Debug Microsoft Graph PowerShell SDK cmdlets – the HTTP GET request

As you can see, running with Debug set, the cmdlet halts frequently to allow you to read what’s happened and understand if the command has any problems. If you want to see the cmdlet run as normal but with the diagnostic information, set the SDebugPreference variable from its default (SilentlyContinue) to Continue.

$DebugPreference="Continue"

To revert to normal operation, set $DebugPreference back to SilentlyContinue.

Pagination

Pagination is a concept that doesn’t really exist in PowerShell. Some cmdlets have a ResultSize parameter to control the number of items retrieved by a command, and some have an All parameter to tell the command to fetch everything. The Get-MgUser and Get-MgGroup cmdlets are examples of cmdlets that support an -All parameter.

Graph API requests limit the retrieval of data (usually to 100 or 200 items) to avoid issues caused by requests that might mistakenly look for tens of thousands of items. If more items exist, the application must make additional requests to fetch more pages of data until it has fetched all available items. Applications do this by following a nextlink (or skiptoken) link.

In Figure 3, we see a nextlink for the cmdlet to run to retrieve the next page of data. In this instance, I ran the Get-MgUser cmdlet with no filter, so more than 100 accounts are available, and this is what caused the Graph to respond with the first 100 accounts and the nextlink. In debug mode, you can pause after each page to see the results retrieved from the Graph.

Debug Microsoft Graph PowerShell SDK cmdlets - a nextlink to more data
Figure 3: Debug Microsoft Graph PowerShell SDK cmdlets – a nextlink to more data

Another Thing for the Administrator Toolbox

Facilities like the Debug parameter and the Graph X-ray tool help people to understand how the Graph APIs work. Knowing how the Graph functions is invaluable. Having an insight into how cmdlets work helps people develop better code and hopefully avoid bugs. At least, that’s the theory. Try out the Debug parameter with some Microsoft Graph PowerShell SDK cmdlets and see what you think.


Learn how to exploit the data available to Microsoft 365 tenant administrators like how to debug Microsoft Graph PowerShell SDK cmdlets through the Office 365 for IT Pros eBook. We love figuring out how things work.

]]>
https://office365itpros.com/2022/07/11/debug-microsoft-graph-powershell-sdk/feed/ 2 56010
Microsoft Launches IMAP4 and POP3 Application Access to Exchange Online Mailboxes https://office365itpros.com/2022/07/04/exchange-online-imap4-pop3/?utm_source=rss&utm_medium=rss&utm_campaign=exchange-online-imap4-pop3 https://office365itpros.com/2022/07/04/exchange-online-imap4-pop3/#comments Mon, 04 Jul 2022 01:00:00 +0000 https://office365itpros.com/?p=55861

Good Tactical Move but Not the Right Strategy

Although I understand the tactical necessity for Microsoft to enable OAuth 2.0 authorization for programs wishing to use the POP3 and IMAP4 protocols to retrieve emails from Exchange Online, I don’t think it is the correct strategy for Exchange Online IMAP4 and POP3 access. Essentially, Microsoft is facilitating the continued use of antique messaging protocols instead of forcing developers to change to the Microsoft Graph APIs. I think that’s wrong, but I know why Microsoft is doing it.

The big basic authentication turnoff for Exchange Online is now just 88 days away. October 1 marks the point when Microsoft begins to disable seven email connectivity protocols for Exchange Online. It will take time for Microsoft to process all tenants, but eventually those wishing to use protocols like POP3, IMAP4, and Exchange ActiveSync will have no choice but to use modern authentication. In other words, a username and password won’t be enough.

Microsoft has been preparing to remove basic authentication from Exchange for over two years. The scale of Exchange Online, the product’s history, and the number of protocols and devices combine to create a myriad of complexities. Automatically upgrading the profiles for Apple’s device Mail app on iOS and iPadOS devices is a good example of the kind of detailed planning and technical execution involved in this project.

I don’t know how many companies have applications that use POP3 or IMAP4 to programmatically retrieve messages from mailboxes. Enough must exist for Microsoft’s fabled telemetry to detect a potential customer satisfaction problem should the turnoff proceed without an answer released with sufficient time for customers to prepare.

Registered Apps and IMAP4/POP3 Permissions

Microsoft’s solution is to allow customers to create Azure AD registered apps and assign the necessary permissions to allow the apps to use IMAP4 or POP3 to interact with mailboxes. Figure 1 shows the assignment of the IMAP.AccessAsApp permission to an app. The equivalent permission for POP3 access is POP.AccessAsApp.

Granting the IMAP4 access permission to an Azure AD registered app

Exchange Online IMAP4
Figure 1: Granting the Exchange Online IMAP4 access permission to an Azure AD registered app

After assigning the permissions, an administrator must grant consent to allow the app to use the permissions to access user mailboxes.

There’s nothing strange here. Apps follow the same process to allow them to use Graph API permissions to access other kinds of information from user accounts to Microsoft 365 groups. The only difference is that two specific permissions exist to control access via the IMAP4 and POP3 protocols.

Service Principals and Exchange Online

Registered apps have service principals, which are, in the words of a Graph API architect, “convenient holders for permissions.” Exchange Online boasts a new PowerShell cmdlet called New-ServicePrincipal to make the service principal of an app holding IMAP4 or POP3 permissions known. In this example, the $ClientId variable holds the application or client identifier for the app, the $ServiceId variable holds the object identifier for the service principal, and the $TenantId variable holds the tenant identifier.

$ServiceId = (Get-MgServicePrincipal -All | ? {$_.displayname -eq "POP3 and IMAP4 OAuth 2.0 Authorization"} | Select -ExpandProperty Id)
$TenantId = (Get-MgOrganization).Id
$ClientId = (Get-MgServicePrincipal -All | ? {$_.displayname -eq "POP3 and IMAP4 OAuth 2.0 Authorization"} | Select -ExpandProperty AppId)
New-ServicePrincipal -AppId $ClientId -ServiceId $ServiceId -Organization $TenantId -DisplayName "OAuth for POP3 and IMAP4"

Once a service principal is registered with Exchange Online, administrators can run the Add-MailboxPermission cmdlet to assign receive permissions to the service principal, just like the granting of regular delegate access to mailboxes.

Add-MailboxPermission -Identity "Kim.Akers@office365itpros.com" -User $ServiceId -AccessRights FullAccess

In passing, I should note that this is all theoretical on my part because the New-ServicePrincipal cmdlet is not available yet in any tenant that I have access to. In any case, the theory is clear:

  • Create a registered app.
  • Assign the appropriate permission(s) to the app.
  • Make the app’s service principal known to Exchange Online.
  • Use the app’s service principal to gain delegate access to specific mailboxes.

Application Access

I have no need to use IMAP4 or POP3 to access Exchange Online mailboxes, but I did want to test that I could get an OAuth 2.0 access token containing the necessary permissions. In production use, an app should use a certificate for authentication. To test, I used a client secret and ran this PowerShell code:

$Uri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
$Body = @{
    client_id     = $ClientId
    scope         = "https://ps.outlook.com/.default"
    client_secret = $AppSecret
    grant_type    = "client_credentials" }

# Get OAuth 2.0 Token
$TokenRequest = Invoke-WebRequest -Method Post -Uri $Uri -ContentType "application/x-www-form-urlencoded" -Body $Body -UseBasicParsing
# Unpack Access Token
$Token = ($tokenRequest.Content | ConvertFrom-Json).access_token

I then checked the access token and found that the expected permissions were present (Figure 2). All is well and the app has authorization to access the mailboxes.

Checking an OAuth 2,0 access token for the IMAP4 and POP3 permissions
Figure 2: Checking an OAuth 2,0 access token for the Exchange Online IMAP4 and POP3 permissions

Pragmatic, but Wrong Strategy

Developers will probably welcome Microsoft’s approach because it means minimal change for their code. All they need to do is replace the code to sign into a mailbox using basic authentication with code to get an access token. Afterward, the rest of the app code to access messages in a mailbox should work.

Pragmatic as it is, I think Microsoft’s approach is a short-term tactical win. The long-term solution is to move to the Outlook Graph API to access mailboxes. This uses the same registered app approach with different permissions, but it’s more functional. And anyway, app developers will have to embrace the Graph sooner or later to send email via SMTP. The SMTP AUTH protocol is a current exception to Microsoft’s effort to remove basic authentication for email connectivity, but that exception won’t last forever.

I guess that October 1 date is just too close to ask developers to recode their applications. But if you’re in the position where your tenant has some apps that exploit Exchange Online IMAP4 or POP3 mailbox access , consider dumping these old protocols and laying a better foundation for the future. If you have the time…


Insight like this doesn’t come easily. You’ve got to know the technology and understand how to look behind the scenes. Benefit from the knowledge and experience of the Office 365 for IT Pros team by subscribing to the best eBook covering Office 365 and the wider Microsoft 365 ecosystem.

]]>
https://office365itpros.com/2022/07/04/exchange-online-imap4-pop3/feed/ 34 55861
Microsoft Graph Explorer Boosted by New Features https://office365itpros.com/2022/06/27/graph-explorer-resources/?utm_source=rss&utm_medium=rss&utm_campaign=graph-explorer-resources https://office365itpros.com/2022/06/27/graph-explorer-resources/#comments Mon, 27 Jun 2022 01:00:00 +0000 https://office365itpros.com/?p=55692

The Growing Importance of the Graph and the Graph Explorer

As time goes by, the importance of the Microsoft Graph APIs increases. Administrators who once were happy to know how to run PowerShell cmdlets to retrieve information about Microsoft 365 workloads now need to understand how to access data through the Graph. First, Graph requests are faster to retrieve data. Second, some workloads (like Planner) don’t have PowerShell modules, meaning that the Graph is the only way to interact with data programmatically.

The Microsoft Graph PowerShell SDK is helpful in terms of bridging the gap between PowerShell and the Graph. The SDK cmdlets are essentially wrappers around Graph requests generated automatically using a process called AutoRest. The upside of the process is that literally thousands of cmdlets are available. The downside is that the documentation is poor unless humans get involved to improve the automatic text by correcting errors, adding examples, and so on. Sometimes the automatic documentation is so obscure and convoluted that it’s easier to read the documentation for the underlying Graph API to get an idea of how a cmdlet works.

Having a variety of solid code examples for each cmdlet is the big difference between traditional PowerShell cmdlet documentation and the automatic documentation. The lack of good working examples in what people regard as official documentation is a barrier to adoption for those who’d like to use the Graph but just can’t make head or tail about how the Graph works.

Enter the Explorer

Which is where the Microsoft Graph Explorer comes in. The Graph Explorer is an invaluable tool when it comes to understanding how to put together Graph API requests and the responses (and errors) that come back. When researching articles like those covering basic Azure AD group management with the Graph SDK or Basic Azure AD User management with the Graph SDK, I’ve often resorted to the Graph Explorer to make sure of syntax or to check the data returned from a call. The Graph X-Ray add-in for the Azure AD admin center is also useful for figuring out requests for user and group data.

The basis of the Graph Explorer web application has remained the same since its inception about four years ago.

  • Open the application.
  • Sign into your organization account to work with tenant data or use sample queries against test data.
  • Perform GET, POST, PATCH, PUT, and DELETE requests against the V1.0 (production) or beta endpoints. You can input your own query or select from the set of sample queries available in the Explorer. This set grows over time to add coverage of Graph APIs and the requests supported by the APIs. After selecting a sample query, the Graph Explorer copies it into the query box. You can amend the request before running it.
  • The Explorer takes care of fetching the necessary access tokens to authorize requests. You’ll probably need to give consent for permissions to be used for either your data or at a tenant level. It’s important to understand that the Explorer can only use delegated Graph permissions (this article explains how the Graph calculates the set of effective permissions from those consented to the app and those held by the signed-in account).

In Figure 1, I’ve used the request to fetch the items in a drive for a Microsoft 365 group. Drives is the Graph term for SharePoint Online sites, so the query is for items in the SharePoint site belonging to the group. The response preview in the response pane shows the data returned for the request, and the different items are listed, including a document library.

 Running a Graph API request with the Microsoft Graph Explorer
Figure 1: Running a Graph API request with the Microsoft Graph Explorer

The form of the request (https://graph.microsoft.com/v1.0/groups/33b07753-efc6-47f5-90b5-13bef01e25a6/drive/items/root/children) shows that the request is against the Groups API for drive information for the group with the identifier 33b07753-efc6-47f5-90b5-13bef01e25a6.

Becoming acquainted with the syntax for Graph requests is one big reason why the tool is so useful. The syntax is consistent but is harder to understand at first than most PowerShell cmdlets are, but time and repetition makes the Graph syntax more familiar.

PowerShell Code Snippets

Some recent changes have made the Graph Explorer even more useful. First, PowerShell has joined the set of supported languages for code snippets. These are segments of code showing the query run by the Explorer in the selected language. Figure 2 shows the PowerShell code generated for the query https://graph.microsoft.com/v1.0/users?$count=true&$search=”displayName:room”&$filter=endsWith(mail,’microsoft.com&#8217;)&$orderBy=displayName&$select=id,displayName,mail.

PowerShell code snippet generated by the Microsoft Graph Explorer
Figure 2: PowerShell code snippet generated by the Microsoft Graph Explorer

PowerShell code snippets are not available for every Graph API. However, good coverage exists for users and groups, which are the two APIs that might be of most interest to administrators.

The Resources Tab

The second recent change is that Microsoft has added a Resources tab to the Graph Explorer. These aren’t fully-baked requests ready to run. Instead, they’re more like starting points for Graph requests to make you aware of the APIs that exist and how to begin using the APIs. For example, there’s no sample query for the SKUs (products) licensed within a tenant, but a resource exists, and you can use it without altering the query to return the set of licensed products. In Figure 3, you can see the details of the EnterprisePack SKU (Office 365 E3) and some of its service plans. It’s possible to navigate from this point to access an individual SKU and so on.

Viewing tenant SKUs (products) in the Microsoft Graph Explorer
Figure 3: Viewing tenant SKUs (products) in the Microsoft Graph Explorer

Given that Microsoft 365 will soon (August 26) move to a new licensing platform that will stop the Azure AD licensing cmdlets from working, it’s a good idea to check any scripts that perform functions like licensing reports and update the code to use Microsoft Graph SDK cmdlets (or direct Graph queries to the licensing API).

Embrace the Graph

The Graph Explorer is getting better and better. It’s a tool I now use every day. That might be just because I know how to use the Graph Explorer better than I did in the past, but I suspect it’s also an indication of just how important it is for Microsoft 365 tenant administrators to understand how to use the Graph APIs to get work done.


Learn how to exploit the data available to Microsoft 365 tenant administrators through the Office 365 for IT Pros eBook. We love figuring out how things work.

]]>
https://office365itpros.com/2022/06/27/graph-explorer-resources/feed/ 3 55692
Graph X-Ray Tool Helps PowerShell Developers Master the Graph https://office365itpros.com/2022/05/23/graph-x-ray-powershell/?utm_source=rss&utm_medium=rss&utm_campaign=graph-x-ray-powershell https://office365itpros.com/2022/05/23/graph-x-ray-powershell/#comments Mon, 23 May 2022 01:00:00 +0000 https://office365itpros.com/?p=55170

Even in First Release, Graph X-Ray Proves Its Worth

When Microsoft decided to build the administrative tools for Exchange Server 2007 around PowerShell, they realized that it would take time for administrators to become accustomed to PowerShell. Sensibly, Microsoft included a cmdlet logging facility in the Exchange Management Console (EMC) to allow administrators to see the PowerShell code used to execute different actions, such as creating a new mailbox. Cmdlet logging gave administrators prototype code to build scripts around and it’s still the best learning tool I have encountered in a Microsoft server product.

Roll on sixteen years and cmdlet logging doesn’t exist in the modern Exchange Admin Center (EAC). Many, including me, have moaned at Microsoft about this deficiency. One response is that EAC is no longer built on PowerShell, which makes it very difficult to generate and display PowerShell code for administrators to copy and reuse. It’s a great pity.

All of this brings me to a new browser extension called Graph X-Ray. Created by Microsoft employees but not a formal product, Graph X-Ray displays the Graph API commands run to execute actions in consoles like the Azure AD admin center and the Intune admin center. Not every action in these consoles depends on Graph APIs, but enough do in important areas like users, groups, and device management to make this an interesting facility.

Raw Graph or Graph SDK

Anyone developing code for Microsoft 365 can get value from Graph X-ray, whether you’re using compiled languages like C# or JavaScript or writing PowerShell scripts. Using Graph APIs in PowerShell normally means that scripts run faster, especially if the code must process more than a few objects. Scripters have the choice to include “raw API calls” or use cmdlets from the Microsoft Graph PowerShell SDK. The script to create a tenant configuration report is a good example of using raw API calls while the script to generate an Office 365 licensing report uses the SDK cmdlets. In either case, you need to understand how Graph API queries are formed and executed, and that’s where the Graph X-Ray extension proves its worth.

Restoring Deleted Microsoft 365 Groups

Take the example of restoring a deleted Microsoft 365 group. Before you can restore a group, you need to know what groups are in a soft-deleted state. Groups remain in the soft-deleted state for 30 days after deletion to allow administrators to restore groups using options in the Microsoft 365 and Azure AD admin centers. After the 30-day retention period lapses, Azure AD removes the groups permanently and they become irrecoverable.

In a large tenant, many groups might be waiting for permanent deletion, including inactive groups removed by the Microsoft 365 Groups Expiration policy. The Get-UnifiedGroup cmdlet can generate a list of soft-deleted groups using a command like this:

Get-UnifiedGroup -ResultSize Unlimited -IncludeSoftDeletedGroups:$True | ? {$_.WhenSoftDeleted -ne $Null} | Sort WhenSoftDeleted | Format-Table DisplayName, PrimarySmtpAddress, WhenSoftDeleted

The cmdlet works, but it’s slow. To speed things up, I tried using the Get-MgDirectoryDeletedItem SDK cmdlet. The cmdlet works when listing deleted user accounts, but no matter what I did, I couldn’t find a way to make it return a list of deleted groups.

Using the Graph X-Ray

I downloaded the Graph X-Ray extension for the Edge browser add-on (other versions are available for Chrome and a Microsoft Store app). To load the add-on, I opened the Developer Tools option in Edge and selected Graph X-Ray. A new blade opened in the browser to display the Graph API commands used for actions performed in the Azure AD admin center (Figure 1).

Viewing Graph commands with the Graph X-Ray extension
Figure 1: Viewing Graph commands with the Graph X-Ray extension

It’s important to emphasize that this is very much an MVP release. Things are by no means perfect, but enough value is present to allow Graph X-Ray to be very helpful. For example, the command reported when the Azure AD admin center lists deleted groups is:

Get-MgDirectoryDeletedItem -DirectoryObjectId $directoryObjectId -Property "id,displayName,mailEnabled,securityEnabled,groupTypes,onPremisesSyncEnabled,deletedDateTime,isAssignableToRole" -Sort "displayName%20asc" -Top 20

This is fine, but nowhere does it tell you how to populate the $directoryObjectId variable. On a more positive note, the raw Graph API query showed the structure needed to return deleted groups, and I was able to use that information to submit the query with the Invoke-MgGraphRequest SDK cmdlet, which worked. It’s worth noting that the Invoke-MgGraphRequest cmdlet exists to allow scripts to execute raw Graph API queries when an SDK cmdlet isn’t available (or doesn’t work).

Equipped with new-found knowledge about how to find deleted groups, I coded this script to report the set of soft-deleted groups including when each group is due for permanent deletion.

Connect-MgGraph
Select-MgProfile Beta
$uri = "https://graph.microsoft.com/beta/directory/deleteditems/microsoft.graph.group?`$select=id,displayName,groupTypes,deletedDateTime&`$orderBy=displayName%20asc&`$top=100"
[array]$Groups = (Invoke-MgGraphRequest -Uri $Uri).Value
If (!($Groups)) { write-Host "No deleted groups available for recovery" ; break }
$Report = [System.Collections.Generic.List[Object]]::new() # Create output file for report
$Now = Get-Date
ForEach ($Group in $Groups) {
     $PermanentRemovalDue = Get-Date($Group.deletedDateTime).AddDays(+30)
     $TimeTillRemoval = $PermanentRemovalDue - $Now
     $ReportLine = [PSCustomObject]@{ 
          Group                = $Group.DisplayName
          Id                   = $Group.Id
          Deleted              = $Group.deletedDateTime
          PermanentDeleteOn    = Get-Date($PermanentRemovalDue) -format g
          DaysRemaining        = $TimeTillRemoval.Days        } 
       $Report.Add($ReportLine) 
}
$Report | Sort {$_.PermanentDeleteOn -as [datetime]} | Out-GridView

The add-on includes the facility to download the commands it captures in a script (GraphXRaySession.PS1). There’s likely to be some duplication of commands in the downloaded script, but it’s great to have such an easy method to copy the commands for later use.

More Insight from Graph X-Ray

Moving on to restoring a soft-deleted group, Microsoft’s documentation for the Restore-MgDirectoryObject cmdlet is woefully deficient in terms of useful examples. An attempt to pass the identifier of a deleted group to the cmdlet failed:

Restore-MgDirectoryObject -DirectoryObjectId $GroupId
Restore-MgDirectoryObject : Resource '2eea84f2-eda3-4a72-8054-5b52c063ee3a' does not exist or one of its queried reference-property objects are not present.

Once again, I turned to Graph X-Ray to find out what command powered the restore deleted group option in the Azure AD admin center. The raw API reported by Graph X-Ray is a POST (update) query like this:

POST /directory/deleteditems/2eea84f2-eda3-4a72-8054-5b52c063ee3a/restore

It’s easy to take this command and repurpose it for use with the Invoke-MgGraphRequest cmdlet:

$uri = “https://graph.microsoft.com/beta/directory/deleteditems/8783e3dd-66fc-4841-861d-49976f0617c0/restore”
Invoke-MgGraphRequest -Method Post -Uri $Uri

More Please!

I wish Microsoft would provide similar insight across all the Microsoft 365 admin consoles. Being able to see the Graph API commands used to perform real-life actions is a powerful learning aid. If Microsoft is serious about driving the adoption of the Graph and the Graph SDK, they could do worse than invest in this kind of tooling. I hope that they do.


Keep up to date with developments like the Graph API commands by subscribing to the Office 365 for IT Pros eBook. Our monthly updates make sure that our subscribers understand the most important changes happening across Office 365.

]]>
https://office365itpros.com/2022/05/23/graph-x-ray-powershell/feed/ 1 55170
Microsoft Sets New Deprecation Schedule for Azure AD PowerShell https://office365itpros.com/2022/03/17/azure-ad-powershell-deprecation/?utm_source=rss&utm_medium=rss&utm_campaign=azure-ad-powershell-deprecation https://office365itpros.com/2022/03/17/azure-ad-powershell-deprecation/#comments Thu, 17 Mar 2022 01:00:00 +0000 https://office365itpros.com/?p=54064

What You Need to Do Before Azure AD and MSOL Modules Retire

Azure AD PowerShell retirement

Microsoft has recently been beating the drum about the retirement of the Azure AD PowerShell module and its older Microsoft Online Services (MSOL) counterpart. On March 3, the Azure AD team posted in the Microsoft Technical Community to say that they had listened to customer feedback and pushed the termination of support out from the end of June to the end of 2022. On September 30, Microsoft set a new retirement date for the Azure AD and MSOL modules for June 30, 2023. Things tend to happen around the end of June to align with the end of Microsoft’s financial year and allow everyone to start the new year afresh.

The salient points in message center notification MC281145 are:

  • Reaffirmation that Microsoft will not retire the Azure AD Graph API on June 30, 2022. The Azure AD Graph is the component which underpins the Azure AD and MSOL modules. It’s a Graph API built especially for Azure AD before the Microsoft Graph established its position as the common API for Microsoft 365. The Azure AD team wants to deprecate their Graph API and embrace the Microsoft Graph, which is the basic reason for the planned deprecation of the Azure AD and MSOL modules.
  • Confirmation that the subset of the cmdlets in the Azure AD and MSOL modules which deal with user licensing will stop working earlier than the rest of the other cmdlets. Quite apart from the desire to move to the Microsoft Graph, these cmdlets are affected because Microsoft is moving to a new licensing management platform. Originally, the scheduled date for the transition was June 30, 2022. Microsoft pushed the date out eight weeks to August 26, 2022 and now it’s March 31, 2023. After this date, license management cmdlets like Get-AzureADSubscribedSKU won’t work.
  • Reconfirmation that the Microsoft Graph PowerShell SDK is the way forward.

Shifting Dates

The deprecation date for the Azure AD and MSOL modules is shifting. Originally, this was June 2022, then the end of 2022, and now it’s June 2023. Clearly, customer feedback has told Microsoft that it’s going to be difficult to update PowerShell scripts before Microsoft wants to retire these modules. ISV products which use the modules or the Azure AD Graph API must also be updated before the axe descends. See Microsoft’s FAQ for help in identifying other applications which use the Azure AD Graph API.

Update (July 29): Microsoft has pushed out the retirement of the Azure AD and MSOL license management cmdlets to 31 March 2023.

No matter which way you turn, the basic fact is that Microsoft will eventually retire the Azure AD and MSOL modules. It’s time to update scripts now, with the priority order being:

  • Scripts that manage licenses for Azure AD accounts (before August 26, 2022). This example of creating a license management report might help get you started.
  • Scripts that perform other Azure AD management operations (ideally before the end of 2022).

Microsoft Documents Its Migration Approach

To help, Microsoft has created some documentation for steps to migrate scripts. The most important statement is “There is currently no tool to automatically converts scripts in Azure AD PowerShell to Microsoft Graph PowerShell.” I doubt that any automatic script migration tool will appear. There are just too many variations in how people code with PowerShell to guarantee that a tool could handle even moderately complex scripts. The potential to create a support nightmare is one reason why I think Microsoft won’t produce a migration tool.

Which leaves us with Microsoft’s simple three-step approach to script migration:

  • Find the Microsoft Graph equivalent of your Azure AD PowerShell cmdlets from the Cmdlet map.
  • Select the Microsoft Graph cmdlet to view the reference documentation and get the new syntax for the cmdlet.
  • Update your existing scripts using the new syntax.

Testing might be a good fourth step to add. And before you start, you need to create an inventory of scripts which use Azure AD or MSOL cmdlets.

Migration Tips

At first glance, the process seems straightforward. In many cases, it is, and you won’t have huge difficulty in converting Get-AzureADUser with Get-MgUser. Microsoft notes some limitations, to which I add:

  • Don’t depend on the Microsoft Graph PowerShell SDK documentation for help with basic information like the format of input parameters. The documentation is machine-created and is shockingly bad in terms of its ability to guide people with real-life examples.
  • The SDK cmdlets are based on Graph API queries and often the documentation for those queries will help you understand how cmdlets work and the parameter values they expect.
  • The Graph Explorer is an excellent tool to help understand how Graph queries run and what they return.
  • Pay attention to parameters and switches. Some parameters of SDK cmdlets require a colon between the parameter and the value where an Azure AD or MSOL cmdlet does not. Some parameter and switch names change.
  • Don’t plan to run SDK scripts interactively. It will only lead to an accumulation of permissions for the service principal used by the SDK.
  • The SDK cmdlets handle pagination when necessary to retrieve all matching objects. Usually, there’s an -All parameter to help (like Get-MgGroup -All).
  • Sometimes you’ll need to use certificate-based authentication and a separate Azure AD registered to gain administrative access to data. The Teams tags report is a good example of when this technique is necessary.

We Feel Your Pain

The Office 365 for IT Pros eBook writers are busy converting script examples to use the Microsoft Graph PowerShell SDK. We plan to have everything done over the next few months. On one level, it’s a pain to be forced to find and upgrade scripts. On another, it’s an opportunity to revamp scripts to make them work better. Perhaps you might even consider moving some of your long-running scripts to Azure Automation?


So much change, all the time. It’s a challenge to stay abreast of all the updates Microsoft makes across Office 365. Subscribe to the Office 365 for IT Pros eBook to receive monthly insights into what happens, why it happens, and what new features and capabilities mean for your tenant.

]]>
https://office365itpros.com/2022/03/17/azure-ad-powershell-deprecation/feed/ 6 54064
Understanding How App Certification for Microsoft 365 Apps Works https://office365itpros.com/2022/02/23/app-certification-microsoft365/?utm_source=rss&utm_medium=rss&utm_campaign=app-certification-microsoft365 https://office365itpros.com/2022/02/23/app-certification-microsoft365/#comments Wed, 23 Feb 2022 01:00:00 +0000 https://office365itpros.com/?p=53581

The Many Ways to Stress the Need for Modern Authentication

By now, all Microsoft 365 tenant administrators should be aware that Microsoft is removing support for basic authentication for many Exchange Online connectivity protocols. The aim is to complete the process by October 2022. SMTP AUTH is an exception, but Microsoft will deal with it in time.

What you might not be aware of is that access to Microsoft 365 data using modern authentication requires that the developers must register their app with Azure AD. This applies to any Microsoft API, including the Outlook add-in model and the Graph APIs. If you’ve written PowerShell scripts which make Graph queries, you know that you must register an app to receive consent for the Graph permissions necessary to access the target data. This is a basic registration. Registrations for more sophisticated apps like those sourced from ISVs contain more information about the app, such as a redirect URL for the app. Registration for ISV apps usually happens during the app installation, including the creation of a service principal to allow the app to run with API permissions consented to by tenant administrators.

Previously, I’ve written about the need for tenants to clean out application crud from Azure AD. The crud is composed of unwanted apps and their service principals accumulated over time in Azure AD. Being able to fetch sign-in data for service principals via Graph queries makes it easier to add context to this exercise by knowing what service principals are active.

After cleaning out obsolete applications, Azure AD might be tidy, but do you know much about the apps which remain? The application governance add-on for Microsoft Defender for Cloud Apps might help, but only if your tenant has the necessary licenses.

Microsoft’s App Compliance Program

Fortunately, Microsoft has an App Compliance Program, part of their Zero Trust initiative to help customers verify apps they might want to run in their tenant. App developers go through the process to achieve app certification by providing information about the app and the data it accesses. The program has three phrases or levels:

Publisher verification: The app developer has a Microsoft developer network identity. The app supports modern authentication and is capable of multi-tenant activity. This is the entry-level participation in certification.

Publisher attestation: The app developer completes a questionnaire covering security, data handling, and compliance.

Microsoft 365 certification: Instead of the app developer reporting details of their app, third-party assessors audit the assertions to validate that the app meets Microsoft standards for security and compliance. The process occurs annually, and details gathered during the audit is available online. Figure 1 shows details of a Microsoft certified app in AppSource. The audit information is available through the Microsoft 365 certification link for the app.

App certification information in AppSource
Figure 1: App certification information in AppSource

The app certification information available online (Figure 2) includes detail of the app permissions, including the reason why the app developers need administrator consent to use the permission.

App certification includes documenting API permissions
Figure 2: App certification includes documenting API permissions

Obviously, app developers must invest time and effort to satisfy Microsoft criteria for app certification. However, once completed, they should reap the benefits gained by increased customer confidence in their product. At least, that’s the theory.

Downgraded Certification

In April 2020, I reviewed the new Manage Apps section in the Teams admin center and commented on the Microsoft 365 certified status of the Wrike app. The number of apps available for Teams continues to expand (from 462 in April 2020 to 1,402 as I write this in February 2022, or roughly 44 new apps monthly). Checking the online list of Teams apps, it looks like very few apps are Microsoft 365 certified. This begs the question why app developers feel it unnecessary to go through Microsoft’s audit process – or why publishers of apps like Wrike downgraded their apps from certified to publisher attestation.

I’m sure cost has something to do with it, along with a feeling that customers don’t go looking for apps which are Microsoft 365 certified. If a developer gains no business advantage by completing the full certification process for their apps, why bother? It’s a reasonable perspective. Microsoft would obviously like developers to go the whole hog, but this might be an uphill battle.

One way that customers might help persuade developers that app certification is worthwhile is to allow users to grant consent for apps from verified publishers when apps require only “low-impact” permissions. The idea is that if less friction exists to deploy and use an app, it will be more popular and profitable.

The consent settings for a tenant are available in the Azure AD admin center (Figure 3) and include the ability to define what you consider to be low-impact permissions. In this case, the selected option allows users to grant consent, but only for three low-impact permissions such as the ability to read a user’s profile. Tenants can define what they consider to be low-impact permissions through the Permissions Classifications option shown in Figure 3.

Azure AD Consent and Permissions settings
Figure 3: Azure AD Consent and Permissions settings

Some will be uneasy about the prospect of users granting consents to apps. The safeguard is that consent is only possible for verified publishers; the counterargument is that developers can attain verification too easily to make this status truly valuable. If Microsoft 365 certified apps were the threshold, a different story might ensue. Microsoft recommends that it’s OK to allow users to grant consent to apps, but without stronger controls, this might be a stretch for many organizations.

The Rocky Road to App Certification

The situation is complex. Microsoft wants everyone to use modern authentication to access Microsoft 365. Getting to that position means a great deal of change for clients, apps, users, and organizations. Certification helps customers understand and control the access apps have to data in their tenant. That’s goodness, but only if ISVs co-operate and certify their products. Time enables change. While that happens, keep your app repository clean and tidy. You know it makes sense.


Learn more about how Office 365 really works on an ongoing basis by subscribing to the Office 365 for IT Pros eBook. Our monthly updates keep subscribers informed about what’s important across the Office 365 ecosystem.

]]>
https://office365itpros.com/2022/02/23/app-certification-microsoft365/feed/ 1 53581
How Microsoft Teams Displays Local Time in Profile Cards https://office365itpros.com/2022/02/21/teams-time-zone-profile-cards/?utm_source=rss&utm_medium=rss&utm_campaign=teams-time-zone-profile-cards https://office365itpros.com/2022/02/21/teams-time-zone-profile-cards/#comments Mon, 21 Feb 2022 01:00:00 +0000 https://office365itpros.com/?p=53593

Teams Local Time a Useful Feature Dependent on Exchange Calendar Settings

On February 14, Microsoft updated the Teams profile card to display the profile owner’s local time and the time difference between you and them. To see a profile card, hover over a user’s thumbnail photo anywhere they appear in Teams, like a chat or channel conversation. As you can see in Figure 1, the local time for the chosen person and the difference to your time appear.

Local time information appears on a Teams profile card
Figure 1: Local time information appears on a Teams profile card

Obviously, if you’re trying to contact someone or arrange a meeting with them, knowing when they work is valuable information. It’s also a feature requested several times in the Teams feedback forum (here’s one example).

Administrators don’t have to do anything to make local time appear in profile cards. Like other information shown in the profile card, the local time depends on information already known about users. In this case, the working hours defined for user calendars. Apart from tenant accounts, local time information is also displayed for guest accounts, if an Exchange Online organization relationship exists to share calendar information between the tenant and the guest’s home domain. Local time information is not available for federated chat including chats with Teams consumer users.

Puzzled by Time Zones

All of this is wonderful until I saw some puzzling results, like people showing up as being in odd time zones or with time differences that just didn’t work. Take the local time shown for Vasil Michev’s guest account (Figure 2). I am in Dublin, Ireland and Vasil is in Sofia, Bulgaria. At 9:51am, Teams told me that Vasil’s local time was 07:51am and that he is two hours behind me. In other words, his time zone is somewhere in the mid-Atlantic. This is upsetting, because the technical editor for the Office 365 for IT Pros eBook shouldn’t be stuck in mid-ocean.

Teams displays an interesting local time zone for a Bulgarian user
Figure 2: Teams displays an interesting local time zone for a Bulgarian user

Why would someone’s time zone be four hours off? To answer the question, we need to know where Teams obtains its time zone information.

Two Time Zones for Outlook

It’s logical that Teams would use someone’s location to determine their time zone. Looking at the data, it seems OK.

Get-User -Identity Vasil.Michev | fl city, stateorprovince, countryorregion

City            : Sofia
StateOrProvince : Sofia
CountryOrRegion : Bulgaria

However, when you think about things a little more, mailboxes have a regional configuration. Perhaps this is where Teams fetches the time zone from. The problem with this theory is that guest accounts have only special cloud-only mailboxes used to store compliance records and other system data. You can’t query these mailboxes using the Get-MailboxRegionalConfiguration cmdlet, like you can for tenant mailboxes:

Get-MailboxRegionalConfiguration -Identity Ken.Bowers | fl

DateFormat                            : dd/MM/yyyy
Language                              : en-GB
DefaultFolderNameMatchingUserLanguage : False
TimeFormat                            : HH:mm
TimeZone                              : Eastern Standard Time

The answer therefore must be data that remote tenants can access, such as the calendar configuration. Each calendar has a time zone for working hours, which is used when scheduling meetings and to publish free/busy information. Organizations can share free/busy data with other Microsoft 365 tenants, and as it turns out, this is the data used by Teams.

Users can set the time zone for their calendar through Outlook or OWA settings. OWA is more intelligent about time zone settings than Outlook desktop is and detects if a difference exists between the regional time zone configured in the General tab and the calendar time zone. In Figure 3, we see that OWA offers to fix a mismatch detected between a user’s regional time zone (Eastern Standard Time or UTC -5) and  the time zone for their calendar meeting hours (UTC).

OWA calendar settings detect a time zone mismatch
Figure 3: OWA calendar settings detect a time zone mismatch

Although the two time zones are usually the same, they don’t need to be. By default, the time zone for the calendar is set to the tenant time zone during the creation of a new mailbox. Afterwards, the user can update the time zone to match their location, or an administrator can update the time zone using the Set-MailboxCalendarConfiguration cmdlet. For example:

Set-MailboxCalendarConfiguration -Identity Nikki.Patia -WorkingHoursTimeZone "FLE Standard Time"

Like any change to a mailbox or account setting, it can take some time before Teams clients refresh their cache to pick up the change. In testing, I found it could take several days before Teams reflected calendar adjustments in profile cards.

Checking Calendar Settings

Apart from running the Get-MailboxCalendarConfiguration cmdlet, administrators can use the Graph Explorer to check calendar settings for a user by running a Calendar API query, which is how Teams fetches user time zone information.

To run the query, open the Graph Explorer and sign into your account. In the request body, enter the request you want the Graph to process. In this case, we want to know about the calendar settings for two users defined in the schedules section. The start and end time can be any date.

{
    "schedules": [
        "jane.smith@office365itpros.com",
        "john.hopper@office365itpros.com"
    ],
    "startTime": {
        "dateTime": "2023-03-15T09:00:00",
        "timeZone": "GMT Standard Time"
    },
    "endTime": {
        "dateTime": "2023-03-15T18:00:00",
        "timeZone": "GMT Standard Time"
    },
    "availabilityViewInterval": 60
}

After populating the request body, run this POST query:

https://graph.microsoft.com/v1.0/me/calendar/getSchedule

The response gives the availability of the users for the requested time slot. However, we’re interested only in the time zone included in the response for each user. In this instance, we see that the user’s calendar time zone is Eastern Standard Time.

                ],
                "startTime": "08:00:00.0000000",
                "endTime": "17:00:00.0000000",
                "timeZone": {
                    "name": "Eastern Standard Time"
                }

We started off by reporting problems with the profile card for Vasil Michev’s guest account. My tenant has an Exchange Online federated relationship with Vasil’s tenant, so we can use the Calendar API to perform a free/busy lookup. This is how the Outlook scheduling assistant finds free time slots for meetings.

The lookup returned the following result shows that Vasil’s calendar uses a custom time zone to apply a four-hour time offset from UTC. This is why his profile card reports such an odd local time.

"startTime": "09:00:00.0000000",
                "endTime": "18:00:00.0000000",
                "timeZone": {
                    "@odata.type": "#microsoft.graph.customTimeZone",
                    "bias": -120,
                    "name": "Customized Time Zone",
                    "standardOffset": {
                        "time": "04:00:00.0000000",
                        "dayOccurrence": 5,
                        "dayOfWeek": "sunday",
                        "month": 10,
                        "year": 0

You might decide that it’s up to users to make sure that their calendars have the correct time zones set and administrators have no part to play to ensure no mismatches exist. However, if you decide that you’d like to know if any mailboxes have mismatched time zones, we can detect the condition with some PowerShell code. The script below:

  • Fetches user mailboxes.
  • Checks the regional configuration to get the time zone set for the user location.
  • Checks the calendar configuration to get the time zone set for meeting hours.
  • Checks the user account properties to get their physical address.
  • Reports any mismatches found between the two time zones.

$ModulesLoaded = Get-Module | Select Name
If (!($ModulesLoaded -match "ExchangeOnlineManagement")) {Write-Host "Please connect to the Exchange Online Management module and then restart the script"; break}
Write-Host "Finding user mailboxes..."
# OK, we seem to be fully connected and ready to go...
[array]$Mbx = Get-ExoMailbox -RecipientTypeDetails UserMailbox -ResultSize Unlimited
If (!($Mbx)) { Write-Host "Something happened and we found no user mailboxes - exiting" ; break }

$Report = [System.Collections.Generic.List[Object]]::new()
ForEach ($M in $Mbx) {
   Write-Host "Processing" $M.DisplayName
   $RegionalConfiguration = Get-MailboxRegionalConfiguration -Identity $M.UserPrincipalName
   $CalendarConfiguration = Get-MailboxCalendarConfiguration -Identity $M.UserPrincipalName
   $UserInfo = Get-User -Identity $M.UserPrincipalName
   $Status = $Null
   If ($CalendarConfiguration.WorkingHoursTimeZone -ne $RegionalConfiguration.TimeZone) {$Status = "Time zone mismatch"}

   $DataLine= [PSCustomObject][Ordered]@{ 
        Status          = $Status
        User            = $M.DisplayName
        UPN             = $M.UserPrincipalName
        CalendarZone    = $CalendarConfiguration.WorkingHoursTimeZone
        RegionalZone    = $RegionalConfiguration.TimeZone
        Language        = $RegionalConfiguration.Language
        DateFormat      = $RegionalConfiguration.DateFormat
        TimeFormat      = $RegionalConfiguration.TimeFormat
        Office          = $UserInfo.Office
        StreetAddress   = $UserInfo.StreetAddress
        City            = $UserInfo.City
        StateOrProvince = $UserInfo.StateOrProvince
        CountryOrRegion = $UserInfo.CountryOrRegion
        PostalCode      = $UserInfo.PostalCode
      
    }
    $Report.Add($DataLine)
}
$Report | Out-GridView

Figure 4 shows example output from the script.

Time zone mismatches reported by PowerShell
Figure 4: Time zone mismatches reported by PowerShell

The cmdlets to fetch regional and calendar configurations are not quick and the script can take between ten and fifteen seconds to process a mailbox. In fact, this is a great example of an Exchange Online script suitable for processing by Azure Automation.

Adjustments Best Left to Users

Although it would be easy to insert an extra line of PowerShell to fix time zone mismatches by adjusting the calendar time zone to match the regional time zone, that’s a call better left to users. They know their calendar and they know how they work. But with the information to hand, you can advise users with mismatched time zones to update their settings as necessary to make sure that Teams profile cards reflect accurate data. After all, you wouldn’t want people to see bad local times, would you?


Insight like this doesn’t come easily. You’ve got to know the technology and understand how to look behind the scenes. Benefit from the knowledge and experience of the Office 365 for IT Pros team by subscribing to the best eBook covering Office 365 and the wider Microsoft 365 ecosystem.

]]>
https://office365itpros.com/2022/02/21/teams-time-zone-profile-cards/feed/ 27 53593
Understanding What’s in an Entra ID Access Token https://office365itpros.com/2022/02/17/understanding-entra-id-access-token/?utm_source=rss&utm_medium=rss&utm_campaign=understanding-entra-id-access-token https://office365itpros.com/2022/02/17/understanding-entra-id-access-token/#comments Thu, 17 Feb 2022 01:00:00 +0000 https://office365itpros.com/?p=53497

Critical Piece When Connecting to the Microsoft Graph

By now, most people who write PowerShell code to interact with Microsoft 365 workloads understand that sometimes it’s necessary to use Microsoft Graph API queries instead of “pure” PowerShell cmdlets. The Graph queries are usually faster and more reliable when retrieving large quantities of data, such as thousands of Microsoft 365 Groups. Over the last few years, as people have become more familiar with the Microsoft Graph, an increased number of scripts have replaced cmdlets with Graph queries. All these scripts use Entra ID (Azure AD) access tokens, as does any utility which interacts with the Microsoft Graph, like the Graph Explorer (Figure 1).

The Graph Explorer displays its Azure AD access token
Figure 1: The Graph Explorer displays its access token

In the remainder of this article, I explore what an Entra ID access token contains.

The Need for Access Tokens

Graph queries need authentication before they can run and the Graph API uses modern authentication. Entra ID registered applications bridge the gap between PowerShell and the Graph. The apps hold details used during authentication such as the app name, its identifier, the tenant identifier, and some credentials (app secret or certificate. The app also holds permissions granted to access data through Graph APIs and other APIs. When the time comes to authenticate, the service principal belonging to an app uses this information to request an access token from Entra ID. Once Entra ID issues the access token, requests issued to the Invoke-RestMethod or Invoke-WebRequest cmdlets can include the access token to prove that the app has permission to access information.

At first glance, an access token is a confused mass of text. Here’s how PowerShell reports the content of an access token:

eyJ0eXAiOiJKV1QiLCJub25jZSI6IlFQaVN1ck1VX3gtT2YzdzA1YV9XZzZzNFBZRFUwU2NneHlOeDE0eVctRWciLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1yNS1BVWliZkJpaTdOZDFqQmViYXhib1hXMCIsImtpZCI6Ik1yNS1BVWliZkJpaTdOZDFqQmViYXhib1hXMCJ9.eyJhdWQiOiJodHRwczovL2dyYXBoLm1pY3Jvc29mdC5jb20iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9iNjYyMzEzZi0xNGZjLTQzYTItOWE3YS1kMmUyN2Y0ZjM0NzgvIiwiaWF0IjoxNjQ0ODQ1MDc3LCJuYmYiOjE2NDQ4NDUwNzcsImV4cCI6MTY0NDg0ODk3NywiYWlvIjoiRTJaZ1lEaW1McEgwTSt5QTk5NmczbWZUUXlYN0FBPT0iLCJhcHBfZGlzcGxheW5hbWUiOiJHZXRUZWFtc0xpc3QiLCJhcHBpZCI6IjgyYTIzMzFhLTExYjItNDY3MC1iMDYxLTg3YTg2MDgxMjhhNiIsImFwcGlkYWNyIjoiMSIsImlkcCI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0L2I2NjIzMTNmLTE0ZmMtNDNhMi05YTdhLWQyZTI3ZjRmMzQ3OC8iLCJpZHR5cCI6ImFwcCIsIm9pZCI6IjM4NTRiYjA4LTNjMmMtNGI1Ny05NWZjLTI0ZTA3OGQzODY4NSIsInJoIjoiMC5BVndBUHpGaXR2d1Vva09hZXRMaWYwODBlQU1BQUFBQUFBQUF3QUFBQUFBQUFBQmNBQUEuIiwicm9sZXMiOlsiVGVhbVNldHRpbmdzLlJlYWRXcml0ZS5BbGwiLCJUZWFtTWVtYmVyLlJlYWQuQWxsIiwiR3JvdXAuUmVhZC5BbGwiLCJEaXJlY3RvcnkuUmVhZC5BbGwiLCJUZWFtLlJlYWRCYXNpYy5BbGwiLCJUZWFtU2V0dGluZ3MuUmVhZC5BbGwiLCJPcmdhbml6YXRpb24uUmVhZC5BbGwiLCJBdWRpdExvZy5SZWFkLkFsbCJdLCJzdWIiOiIzODU0YmIwOC0zYzJjLTRiNTctOTVmYy0yNGUwNzhkMzg2ODUiLCJ0ZW5hbnRfcmVnaW9uX3Njb3BlIjoiRVUiLCJ0aWQiOiJiNjYyMzEzZi0xNGZjLTQzYTItOWE3YS1kMmUyN2Y0ZjM0NzgiLCJ1dGkiOiI3RVkyWnVXV2JFYVF0T3piVVlwOUFBIiwidmVyIjoiMS4wIiwid2lkcyI6WyIwOTk3YTFkMC0wZDFkLTRhY2ItYjQwOC1kNWNhNzMxMjFlOTAiXSwieG1zX3RjZHQiOjEzMDI1NDMzMTB9.N9yvmkCedti2fzT44VfBkN7GvuCInrIgiMgNxdyZeAyxnbdZjEhxHmNdU6HLLHQ3J-GonpPdt28dKwYxgLcrSibGzSPVHddh6MDPYutSwfIxh2oRanxhgFOWVJADfbFoCxsRFDhKJNT39bsauIUiRNzGzbb6dvWuZQ8LrgWjZzjae2qxVxj9jvYgjXEypeYZgLvPOzJiBCuluAMH3TjPuS-CuglFK_edn4CS-ztCwM0hmDFD5BLNZqng5P2KqGTEgjkMKoyIJ8yTGBJpASfdqqEFqWzQwcQ9ese924qNC3hJR_5TWHp2Fl73bpdhwBHRL5UwGTPi9_ysYdndKhXwgA

Deciphering an Access Token

Access tokens issued by Entra ID comply with the OAuth 2.0 bearer token standard (RFC6750) and are structured as JSON-formatted Web Tokens. We can’t see the JSON content because it is base64Url encoded and signed. However, if you paste the token into a site like https://jwt.ms/, the site will decrypt the list of claims included in the token and we’ll see something like the details shown below for the access token featured above:

{ "typ": "JWT", 
"nonce": "gq3zmJhybfXGDGqt6RO2PX9s0cimmRpSRrTO90sQ4w4", 
"alg": "RS256",
 "x5t": "Mr5-AUibfBii7Nd1jBebaxboXW0", 
"kid": "Mr5-AUibfBii7Nd1jBebaxboXW0" 
}.
{ "aud": "https://graph.microsoft.com", 
"iss": "https://sts.windows.net/a662313f-14fc-43a2-9a7a-d2e27f4f3478/", 
"iat": 1644833772, 
"nbf": 1644833772,
 "exp": 1644837672,
 "aio": "E2ZgYJif1+eocevtzqRIrgDGA2V3AQ==",
 "app_displayname": "ReportDLs", 
"appid": "76c31534-ca1f-4d46-959a-6159fcb2f77a", 
"appidacr": "1",
 "idp": "https://sts.windows.net/a662313f-14fc-43a2-9a7a-d2e27f4f3478/", 
"idtyp": "app",
 "oid": "4449ce36-3d83-46fb-9045-2d1721e8f032",
 "rh": "0.AVwAPzFitvwUokOaetLif080eAMAAAAAAAAAwAAAAAAAAABcAAA.",
 "roles": 
[ "Group.Read.All", "Directory.Read.All", "User.Read.All" ],
 "sub": "4449ce36-3d83-46fb-9045-2d1721e8f032", 
"tenant_region_scope": "EU", 
"tid": "a662313f-14fc-43a2-9a7a-d2e27f4f3478",
 "uti": "BU1RVc7mHkmBq2FMcZdTAA", 
"ver": "1.0", 
"wids": [ "0997a1d0-0d1d-4acb-b408-d5ca73121e90" ],
 "xms_tcdt": 1302543310 
}
.[Signature]

The deciphered token divides into three parts: header, payload, and signature. The aim of a token is not to hide information, so the signature is not protected by encryption. Instead, it’s signed using a private key by the issuer of the token. Details of the algorithm and private key used to sign an access token are in its header. An application can validate the signature of an access token if necessary, but this is not usually done when running a PowerShell script. The payload is the location for the claims made by the token and is the most interesting place to check.

Another way to check what’s in an access token is to use the JWTDetails PowerShell module, which is available in the PowerShell Gallery. To install this (very small) module, run:

Install-Module -Name JWTDetails -RequiredVersion 1.0.0 -Scope AllUsers

Afterward, you can examine a token with the Get-JWTDetails cmdlet. Here’s an example revealing that the access token issued to an app allows it to access Exchange Online using the IMAP4 or POP3 protocols:

Get-JWTDetails -Token $Token

aud             : https://outlook.office.com
iss             : https://sts.windows.net/b662313f-14fc-43a2-9a7a-d2e27f4f3478/
iat             : 1671891468
nbf             : 1671891468
exp             : 1671895368
aio             : E2ZgYDAQS/prW6b0Zsah6KMXtnTEAQA=
app_displayname : POP3 and IMAP4 OAuth 2.0 Authorization
appid           : 6a90af02-6ac1-405a-85e6-fb6ede844d92
appidacr        : 1
idp             : https://sts.windows.net/a662313f-14fc-43a2-9a7a-d2e27f4f3478/
oid             : b7483867-51b6-4fdf-8882-0c43aede8dd5
rh              : 0.AVwAPzFitvwUokOaetLif080eAIAAAAAAPEPzgAAAAAAAABcAAA.
roles           : {POP.AccessAsApp, IMAP.AccessAsApp}
sid             : 1475d8e7-2671-47e9-b538-0ea7b1d43d0c
sub             : b7483867-51b6-4fdf-8882-0c43aede8dd5
tid             : a662313f-14fc-43a2-9a7a-d2e27f4f3478
uti             : COCw22GGpESVXvfdhmEVAQ
ver             : 1.0
wids            : {0997a1d0-0d1d-4acb-b408-d5ca73121e90}
sig             : PdScMpYqwA25qJL1z8q589sz/Ma5CGQ4ea9Bi0lnO2yByrIs530emYPnFPfQNN9EPBIvv4EaAoTLomrw4RMBWYoQSAgkBUXVrYGnC
                  jzAU6a2ZNZgo7+AORHk4iyLO0FpbLEaMJvCvI5vWhP9PHOxnGLcIsCbOmyrCK6lxxIKtBx851EpLrhpyvJ3p05NSw0D/mKzXPRKtc
                  rzQcUwECxOUugbm1zdq8JaE/PmSggBb87VZy7p1S2BXhxQZ5QU17JeIADyhCGm1Ml+avuIHsVS2iat/LPEi/nktbrXMcOzROpUKyZ
                  /7uVhxQ0cscJ6WGxbd+zJm36s25Yp1vMzSHaRxQ==
expiryDateTime  : 24/10/2022 15:22:48
timeToExpiry    : 00:59:34.7611307

Claims and Scopes

The list of claims in the access token includes simple claims and scopes (groups of claims). A claim is an assertion about something related to the token. In this case, the claims tell us details like:

  • The tenant (tid).
  • The intended consumer of the token (aud): https://graph.microsoft.com.
  • The app name (app_displayname).
  • The app identifier (appid).
  • The security token service (STS) responsible for issuing the token (iss): https://sts.windows.net/a662313f-14fc-43a2-9a7a-d2e27f4f3478/.
  • The generation time for the token (iat).
  • The time when the token expires (exp). All dates are in Unix epoch time, so 1644837672 means 11:21:12 GMT on February 14, 2022. By default, access tokens issued by Entra ID last one hour, except those used by applications which support continual access evaluation (CAE), where Entra ID issues 28-hour access tokens because it can terminate access at any time and force the user to reauthenticate should a critical user event (like a password change) happen.
  • The identifier for the object in the Microsoft identity system used for authentication (oid). In this case, the script uses a registered Entra ID app, so the value is the service principal for the app. You can test this by running the Get-MgServicePrincipal cmdlet from the Microsoft Graph PowerShell SDK:

Get-MgServicePrincipal -Filter "Id eq '4449ce36-3d83-46fb-9045-2d1721e8f032'"

DisplayName Id                                   AppId                                SignInAudience ServicePrincipalTy
                                                                                                     pe
----------- --                                   -----                                -------------- ------------------
ReportDLs   4449ce36-3d83-46fb-9045-2d1721e8f032 77c31534-ca1f-4d46-959a-6159fcb2f77a AzureADMyOrg   Application

Scopes are a logical grouping of claims, and they can serve as a mechanism to limit access to resources. The roles claim contains a scope of Graph API permissions starting with Group.Read.All and ending with User.Read.All. We therefore know that this app has consent from the organization to use the permissions stated in the scope when it executes Graph API queries. The list of permissions is enough to allow the PowerShell script (in this case, one to generate a report of distribution list memberships) to query the Graph for a list of all groups and read the membership of each group.

From bitter experience, I know how easy it is to get Graph permissions wrong. One way to check is sign into the Graph Explorer and run the query (here’s an example) to check what permissions the Explorer uses to execute the query. However, you can also dump the access token to check that the set of permissions in the access token matches what you expect. It’s possible that you might have requested some application permissions for the app and failed to gain administrator consent for the request, meaning that the access token issued to the app by Entra ID won’t include the requested permissions.

Using the Access Token

Once we’re happy that we have a good access token, we can use it with Graph queries. Here’s how to fetch the list of distribution groups in a tenant. The access token is included in the $Headers variable passed to the Invoke-RestMethod cmdlet.

$Headers = @{Authorization = "Bearer $token"}

$Uri = "https://graph.microsoft.com/V1.0/groups?`$filter=Mailenabled eq true and not groupTypes/any(c:c+eq+'Unified')&`$count=true"
[array]$DLs = (Invoke-RestMethod -Uri $Uri -Headers $Headers -Method Get -ContentType "application/json")
$DLs = $DLs.Value

And if everything goes to plan, we should have a set of distribution lists to process. If not, it’s bound to be a problem with your access token, so it’s time to return to square one and restart the acquisition process.


Learn more about how Office 365 really works on an ongoing basis by subscribing to the Office 365 for IT Pros eBook. Our monthly updates keep subscribers informed about what’s important across the Office 365 ecosystem.

]]>
https://office365itpros.com/2022/02/17/understanding-entra-id-access-token/feed/ 6 53497
How Microsoft 365 Notifications Show Active User Data for Workloads Affected by Service Updates https://office365itpros.com/2022/01/20/microsoft-365-notifications-users/?utm_source=rss&utm_medium=rss&utm_campaign=microsoft-365-notifications-users https://office365itpros.com/2022/01/20/microsoft-365-notifications-users/#comments Thu, 20 Jan 2022 01:00:00 +0000 https://office365itpros.com/?p=53127

Microsoft 365 Notifications User Counts Come from the Graph

Message center notification MC315739 (January 18, roadmap item 83946) brings news of a big change for the information included in notifications. Soon, along with the text describing new features or changes to existing Microsoft 365 features, notifications will include service usage data relevant to the change. Deployment starts for targeted release tenants in mid-January and should be complete worldwide for all tenants by mid-February.

Let’s take the change announced in MC302456 as an example. This notification describes how users can maintain their guest accounts in other tenants from Teams. To help administrators understand how many people will be affected by the change, the service communications API queries the Microsoft Graph reports API to retrieve the monthly active user data for Teams and reports this information in the notification.

Figure 1 shows a mock-up included in MC315739 to illustrate how Microsoft 365 notifications highlight user data. On the left, you see a notification for a change affecting multiple workloads together with the usage data for each workload (Outlook is really Exchange Online, but obviously non-Outlook clients can connect to Exchange Online mailboxes). On the right, you see a notification for Kaizala, which doesn’t store its usage data in the Microsoft Graph, so it’s impossible to display this information.

Usage data shows up in message center notifications (source: Microsoft)

Microsoft 365 Notifications
Figure 1: Microsoft 365 notifications with user data (source: Microsoft)

Editorial comment: The need for Kaizala is possibly now much reduced by the general availability of the Teams Walkie-Talkie feature.

The Problem with Microsoft Graph Usage Data

The Microsoft Graph reports API allows access to usage data about some Microsoft 365 services. Coverage is good for base workloads (SharePoint Online, Exchange Online, Teams, and OneDrive for Business) and not so good elsewhere (Planner, Stream, Forms, Whiteboard, etc.). Nevertheless, the usage data is detailed enough to build a picture of user activity over the last ninety days. If you’d like to know how to use the API with PowerShell, consider running the User Activity Analysis script to see how to make calls against the reports API and the kind of data the API returns. For example, this code creates a query to retrieve Teams activity data for users over the last 30 days. Data returned by the reports API is always a few days behind the actual date.

$TeamsUserReportsURI = "https://graph.microsoft.com/v1.0/reports/getTeamsUserActivityUserDetail(period='D30')"

[array]$TeamsUserData = (Invoke-RestMethod -Uri $TeamsUserReportsURI -Headers $Headers -Method Get -ContentType "application/json") -Replace "...Report Refresh Date", "Report Refresh Date" | ConvertFrom-Csv

The data returned by the API is in an array. Here’s the item in the area for an account:

Report Refresh Date        : 2022-01-16
User Principal Name        : Jane.Smith@office365itpros.org
Last Activity Date         : 2022-01-15
Is Deleted                 : False
Deleted Date               :
Assigned Products          : POWER BI (FREE)+ENTERPRISE MOBILITY + SECURITY E5+BUSINESS APPS (FREE)+MICROSOFT POWER AUTOMATE FREE+MICROSOFT VIVA TOPICS+MICROSOFT DEFENDER FOR CLOUD APPS – APP GOVERNANCE+OFFICE 365 E5 WITHOUT AUDIO CONFERENCING
Team Chat Message Count    : 58
Private Chat Message Count : 14
Call Count                 : 1
Meeting Count              : 5
Has Other Action           : No
Report Period              : 30

The data looks good and is useful. However, some workloads (like Teams) return data for both tenant and guest accounts, so the numbers reported in message center notifications will reflect that data. You might be concerned about how a change will affect guest users, but I hazard a guess that most tenant administrators will focus on the effect on tenant users.

Another issue (acknowledged in MC315739) is the non-specific nature of the report. Usage across all clients and all features is included into one workload figure. For instance, a change affecting Microsoft Lists in SharePoint Online and OneDrive for Business might affect just the five people who create and manage Lists, but the notification will say that the change affects everyone who has used SharePoint Online or OneDrive for Business in the last month. You won’t know either if a change is specific to a client platform, like Android or iOS.

Counting all and sundry who use a workload isn’t such a big problem for new features. It is more important for updated features and becomes even more critical when Microsoft deprecates some functionality. You then want to know precisely who is affected, or at least, how many are affected.

Another aspect of an all-up number is that it doesn’t take account of multi-geo deployments. You’ll know that some people in the organization might need to be informed about a change, but not their location.

Still a Good Change

Even with the caveats listed above, including user data in Microsoft 365 notifications is still a good change. If you see a notification where a low number of users will experience an impact, you can probably spend less time preparing for that change and more on changes affecting large user populations. The availability of data through Graph APIs limit what the developers can do to slice and dice usage data to make it more precise and informative. This will probably happen over time. In the interim, take the user information presented in Microsoft 365 notifications as a starting point to help you understand the likely impact of individual changes on users. Use this data in conjunction with your knowledge of the tenant and how people work within the organization, and the monthly active user data for affected workloads will be helpful. Taken as an exact guide, it won’t be.

I guess I might have to update my script to extract and report information from message center notifications to accommodate this change…


Insight like this doesn’t come easily. You’ve got to know the technology and understand how to look behind the scenes. Benefit from the knowledge and experience of the Office 365 for IT Pros team by subscribing to the best eBook covering Office 365 and the wider Microsoft 365 ecosystem.

]]>
https://office365itpros.com/2022/01/20/microsoft-365-notifications-users/feed/ 2 53127
How to Determine the Age of a Microsoft 365 Tenant https://office365itpros.com/2022/01/14/find-age-microsoft-365-tenant/?utm_source=rss&utm_medium=rss&utm_campaign=find-age-microsoft-365-tenant https://office365itpros.com/2022/01/14/find-age-microsoft-365-tenant/#comments Fri, 14 Jan 2022 01:00:00 +0000 https://office365itpros.com/?p=53007

Use Teams, PowerShell, or the Graph

Vasil Michev, the Technical Editor of the Office 365 for IT Pros eBook, comes up with all sorts of weird and wonderful insights into Microsoft 365. A recent question he discussed on his blog was how to find the creation date for a tenant. It’s a good question because it forces respondents to know where to look for this information and is exactly the kind of poser we like to tease out as we write content for the book.

As Vasil points out, the obvious answer is to fire up the Teams admin center because the tenant creation date appears on a card displayed on its home screen (Figure 1). The Teams admin center is the only Microsoft 365 portal which shows this information. Why the Teams developers thought that it was useful to highlight the tenant creation date is unknown. After all, the date won’t change over time and static information is not usually featured by workload dashboards.

Viewing the tenant creation date in the Teams admin center
Figure 1: Viewing the tenant creation date in the Teams admin center

Opening an administrative portal is no challenge. Vasil suggests several alternate methods to retrieve the tenant creation date. It seemed like fun to try some of these methods against my tenant. Here’s what I found.

Using Exchange Online Data

If you’ve used Exchange Online from the start, you can check the creation date of the Exchange organization configuration object, created when an administrator enables Exchange Online for the first time.

(Get-OrganizationConfig).WhenCreated

Monday 27 January 2014 20:28:45

It’s an interesting result. Exchange Online reports its initiation in January 2014 while Teams is quite sure that the tenant existed in April 2011. I’ve used Exchange Online for email ever since I had a tenant, so the disconnect between Exchange Online and the tenant creation date is interesting.

Another way of checking Exchange data is to look at the creation dates for mailboxes. This PowerShell snippet finds all user mailboxes and sorts them by creation date. The first mailbox in the sorted array is the oldest, so we can report its creation date:

[array]$Mbx = Get-ExoMailbox -ResultSize Unlimited -Properties WhenCreated -RecipientTypeDetail UserMailbox | Sort {$_.WhenCreated -as [datetime]} 
Write-Host ("The oldest mailbox found in this tenant is {0} created on {1}" -f $Mbx[0].DisplayName, $Mbx[0].WhenCreated)

The oldest mailbox found in this tenant is Tony Redmond created on 27/01/2014 20:36:38

(Dates shown are in Ireland local format. The equivalent U.S. format date is 01/27/2014).

Grabbing all mailboxes to check their creation date will not be a fast operation. Even using the REST-based Get-ExoMailbox cmdlet from the Exchange Online management module, it will take time to retrieve all the user mailboxes in even a medium size tenant.

As it turns out, the oldest mailbox is my own, created about eight minutes after the initiation of Exchange Online. However, we’re still in 2014 when the tenant proclaims its creation in 2011, so what happened?

A search through old notes revealed that Microsoft upgraded my original Office 365 tenant created in 2011 to an enterprise version in 2014. It seems that during the tenant upgrade, Microsoft recreated the instance of Exchange Online. That explanation seems plausible.

Administrator Accounts

Another method is to examine the creation dates of administrator accounts to find the oldest account. This is usually the administrator account created during tenant setup. In other words, when you create a new tenant, you’re asked to provide the name for an account which becomes the first global administrator. If we look at the administrator accounts in the tenant and find the oldest, it should be close to the tenant creation date shown in the Teams admin center. That is, unless someone deleted the original administrator account.

Azure AD is the directory of record for every Microsoft 365 tenant, so we should check Azure AD for this information. The steps are:

  • Find the set of accounts which currently hold the global administrator role. We omit the account returned with the object id 25cbf210-02e5-4a82-9f5c-f41befd2681a as this is a service principal used by Microsoft Rights Management services (you can confirm this by running Get-AzureADServicePrincipal -ObjectId 25cbf210-02e5-4a82-9f5c-f41befd2681a).
  • Check each account to find the creation date. This is slightly complicated when using the Azure AD PowerShell module because the creation date is part of the extension properties. We therefore use the Get-AzureADUserExtension cmdlet to extract the date and then store it in the array used to hold details about tenant administrators.
  • Sort the accounts by creation date and report the oldest.

Here’s the code I used:

# Find the identifier for the Azure AD Global Administrator role
$TenantAdminRole = Get-AzureADDirectoryRole | Where-Object {$_.DisplayName -eq ‘Global Administrator’} | Select ObjectId
# Get the set of accounts holding the global admin role. We omit the account used by
# the Microsoft Rights Management Service
$TenantAdmins = Get-AzureADDirectoryRoleMember -ObjectId $TenantAdminRole.ObjectId | ? {$_.ObjectId -ne "25cbf210-02e5-4a82-9f5c-f41befd2681a"} | Select-Object ObjectId, UserPrincipalName
# Get the creation date for each of the accounts
$TenantAdmins | ForEach-Object { $_ | Add-Member -MemberType NoteProperty -Name "Creation Date" -Value (Get-AzureADUserExtension -ObjectId $_.ObjectId ).Get_Item("createdDateTime") }
# Find the oldest account
$FirstAdmin = ($TenantAdmins | Sort-Object {$_."Creation Date" -as [datetime]} | Select -First 1)
Write-Host ("First administrative account created on {0}" -f $FirstAdmin."Creation Date")

The older Microsoft Online PowerShell module doesn’t require such a complicated approach to retrieve account creation data. Taking the code shown above and replacing the Get-AzureADUserExtension cmdlet with Get-MsOlUser, we get:

$TenantAdmins | ForEach-Object { $_ | Add-Member -MemberType NoteProperty -Name "Creation Date" -Value ((Get-MsOlUser -ObjectId $_.ObjectId ).WhenCreated) }

Using either cmdlet, the result is:

First administrative account created on 11/04/2011 17:35:11

The Teams admin center also reports April 11, 2011, so using administrator accounts might be a viable way to determine tenant age.

Use the Graph

Microsoft 365 stores information for each tenant in the Microsoft Graph, and it’s the Graph which is the source for the Teams admin center. We can retrieve the same information by running the https://graph.microsoft.com/V1.0/organization Graph query. The createdDateTime property returned in the organization settings is what we need.

Here’s the PowerShell code to run after obtaining the necessary access token for a registered app, which must have consent to use the Organization.Read.All Graph permission. Vasil used the beta endpoint when he showed how to fetch tenant organization settings using the Graph Explorer (which saves the need to write any code), but the V1.0 endpoint works too.

$Uri = "https://graph.microsoft.com/V1.0/organization"
$OrgData = Invoke-RESTMethod -Method GET -Uri $Uri -ContentType "application/json" -Headers $Headers
If ($OrgData) {
  Write-Host ("The {0} tenant was created on {1}" -f $Orgdata.Value.DisplayName, (Get-Date($Orgdata.Value.createdDateTime) -format g)) }

The Redmond & Associates tenant was created on 11/04/2011 18:35

The first administrator account appears to date from 17:35 while the tenant creation time is an hour later. This is easily explained because all dates stored in the Graph are in UTC whereas the dates extracted from Azure AD and reported by PowerShell reflect local time. In April 2011, local time in Ireland was an hour ahead of UTC.

An Old Tenant

After all the checks, it’s clear that I created my tenant in the early evening of April 11, 2011. Given that this was ahead of Microsoft’s formal launch of Office 365 in July 2011, I can claim to use an old tenant, for what that’s worth.

]]>
https://office365itpros.com/2022/01/14/find-age-microsoft-365-tenant/feed/ 2 53007
Microsoft Flags Need to Upgrade PowerShell Scripts to Use TLS 1.2 https://office365itpros.com/2021/11/12/microsoft-flags-need-upgrade-powershell-scripts-use-tls-12/?utm_source=rss&utm_medium=rss&utm_campaign=microsoft-flags-need-upgrade-powershell-scripts-use-tls-12 https://office365itpros.com/2021/11/12/microsoft-flags-need-upgrade-powershell-scripts-use-tls-12/#respond Fri, 12 Nov 2021 01:00:00 +0000 https://office365itpros.com/?p=52336

Failure Will Break Ability to Send Email via Exchange Online

Message center notification MC297438 arrived in the Microsoft 365 admin center on November 10 to inform me that Microsoft was about to enforce version 1.2 of the Transport Layer Security (TLS) for Direct Routing SIP interfaces. I have no problem with this proposal. It seems perfectly splendid to enforce TLS 1.2 for all manner of communications.

The note then said: “You are receiving this message because our reporting indicates that your organization is still connecting using SMTP Auth client submission via smtp.office365.com with TLS1.0 or TLS1.1 to connect to Exchange Online.”

Deprecating Old TLS

The problem here is that PowerShell uses the system default for TLS unless you specify otherwise. Although Microsoft is excluding SMTP AUTH from the set of connection protocols they will block for basic authentication in all tenants in October 2022, this doesn’t mean that SMTP AUTH is immune from other efforts within Microsoft 365 to remove older, less secure protocols. As Microsoft notes in MC297438, they communicated their intention to remove TLS 1.0 and 1.1 from Microsoft 365 as far back as December 2017, so this development shouldn’t come as a shock to anyone.

I covered this topic in January 2021 and noted that script developers who use the Send-MailMessage cmdlet to send email via Exchange Online should include a line in their scripts to force PowerShell to use TLS 1.2. If you don’t, the deprecation of TLS 1.0 and 1.1 in Exchange Online will prevent scripts being able to send messages.

For the record, the command to force TLS 1.2 connections from PowerShell is:

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

Hopefully, components like multi-function devices which use basic authentication with SMTP AUTH today can use TLS 1.2 connections. If they can’t, those connections will stop working even while basic authentication for SMTP AUTH persists.

Moving to the Graph

Forcing PowerShell to use TLS 1.2 is effective, but it’s a short-term fix. Microsoft will come back to the topic of SMTP AUTH once the dust settles after the removal of basic authentication for the other connection protocols next year. The time will come when Exchange Online ceases support for basic authentication with SMTP AUTH connections.

Microsoft’s preferred method for sending secure email with Exchange Online is to use the Graph APIs. You can do this in two ways by upgrading scripts to replace calls to the Send-MailMessage cmdlet with:

Graph APIs use modern authentication, so the basic authentication issue doesn’t arise.

It’s time to inventory the scripts in your tenant which send email via Exchange Online to know what needs to be done, make sure that TLS 1.2 is used by all scripts, and consider the best option for future upgrades.


Insight like this doesn’t come with hard work and experience. You’ve got to know the technology and understand how to look behind the scenes. Benefit from the knowledge and experience of the Office 365 for IT Pros team by subscribing to the best eBook covering Office 365 and the wider Microsoft 365 ecosystem.

]]>
https://office365itpros.com/2021/11/12/microsoft-flags-need-upgrade-powershell-scripts-use-tls-12/feed/ 0 52336
How to Find When Azure AD User Accounts Receive Microsoft 365 Licenses https://office365itpros.com/2021/11/10/find-when-azure-ad-user-accounts-receive-microsoft-365-licenses/?utm_source=rss&utm_medium=rss&utm_campaign=find-when-azure-ad-user-accounts-receive-microsoft-365-licenses https://office365itpros.com/2021/11/10/find-when-azure-ad-user-accounts-receive-microsoft-365-licenses/#comments Wed, 10 Nov 2021 01:00:00 +0000 https://office365itpros.com/?p=52291

Licensing Report Has No Dates

I recently published a Practical365.com article explaining how to create a licensing report for an Office 365 tenant using cmdlets from the Microsoft Graph SDK for PowerShell.

A reader asked: “I am trying to determine when a specific license, in this case an E3 Security and Mobility license, was added for all users.”

No Dates for Licenses

It’s an interesting question. As written, my script generates a report based on the licenses and service plans assigned to user accounts. However, it doesn’t do anything to tell you when a license for a product like Enterprise Security and Mobility E3 (EMS E3) is assigned to a user. This is because Azure AD does not record assignment dates for the product license information held for user accounts. Instead, license information for an account is presented as a table of SKU identifiers with any disabled service plans in a SKU noted:

$User,AssignedLicenses

DisabledPlans                          SkuId
-------------                          -----
{bea4c11e-220a-4e6d-8eb8-8ea15d019f90} b05e124f-c7cc-45a0-a6aa-8cf78c946968
{}                                     8c4ce438-32a7-4ac5-91a6-e22ae08d9c8b
{}                                     4016f256-b063-4864-816e-d818aad600c9
{a23b959c-7ce8-4e57-9140-b90eb88a9e97} 6fd2c87f-b296-42f0-b197-1e91e994b900

However, date information is included in the service plan information for an account:

$User.AssignedPlans

AssignedDateTime    CapabilityStatus Service                       ServicePlanId
----------------    ---------------- -------                       -------------
09/11/2021 10:33:37 Enabled          AzureAdvancedThreatAnalytics  14ab5db5-e6c4-4b20-b4bc-13e36fd2227f
09/11/2021 10:33:37 Enabled          AADPremiumService             eec0eb4f-6444-4f95-aba0-50c24d67f998
09/11/2021 10:33:37 Enabled          RMSOnline                     5689bec4-755d-4753-8b61-40975025187c
09/11/2021 10:33:37 Enabled          SCO                           c1ec4a95-1f05-45b3-a911-aa3fa01094f5
09/11/2021 10:33:37 Enabled          AADPremiumService             41781fb2-bc02-4b7c-bd55-b576c07bb09d
09/11/2021 10:33:37 Enabled          MultiFactorService            8a256a2b-b617-496d-b51b-e76466e88db0
09/11/2021 10:33:37 Enabled          RMSOnline                     bea4c11e-220a-4e6d-8eb8-8ea15d019f90
09/11/2021 10:33:37 Enabled          RMSOnline                     6c57d4b6-3b23-47a5-9bc9-69f17b4947b3
09/11/2021 10:33:37 Enabled          Adallom                       2e2ddb96-6af9-4b1d-a3f0-d6ecfd22edb2

Checking Dates for Service Plan Assignments

Given that date information is available for service plans, it should therefore be possible to check against the service plan information for user accounts to find assignments of a service plan belonging to a product (SKU). Looking at the Product names and service plan identifiers for licensing page , we find the list of service plans included in EMS E3 (SKU identifier efccb6f7-5641-4e0e-bd10-b4976e1bf68e). The set of service plans are:

  • Azure Active Directory Premium P1: 41781fb2-bc02-4b7c-bd55-b576c07bb09d
  • Azure Information Protection Premium P1: 6c57d4b6-3b23-47a5-9bc9-69f17b4947b3
  • Cloud App Security Discovery: 932ad362-64a8-4783-9106-97849a1a30b9
  • Exchange Foundation: 113feb6c-3fe4-4440-bddc-54d774bf0318
  • Microsoft Azure Active Directory Rights: bea4c11e-220a-4e6d-8eb8-8ea15d019f90
  • Microsoft Azure Multi-Factor Authentication: 8a256a2b-b617-496d-b51b-e76466e88db0
  • Microsoft Intune: c1ec4a95-1f05-45b3-a911-aa3fa01094f5

The theory is that you should be able to check accounts assigned EMS E3 to retrieve information about one of the service plans in the SKU and retrieve and report the assigned date. I don’t have EMS E3 in my tenant, but I do have EMS E5. I therefore checked the theory by running this PowerShell code:

# Check the date when a service plan belonging to a product like EMS E3 is assigned to an account
$EMSE3 = "efccb6f7-5641-4e0e-bd10-b4976e1bf68e" # Product SKU identifier for Enterprise Mobility and Security E3
$EMSE5 = "b05e124f-c7cc-45a0-a6aa-8cf78c946968" # Product SKU identifier for Enterprise Mobility and Security E5
$TestSP = "41781fb2-bc02-4b7c-bd55-b576c07bb09d" # Azure Active Directory Premium P1
$Report = [System.Collections.Generic.List[Object]]::new()
# Find tenant accounts
Write-Host "Finding Azure AD accounts..."
[Array]$Users = Get-MgUser -Filter "UserType eq 'Member'" -All | Sort DisplayName
ForEach ($User in $Users) {
  ForEach ($SP in $User.AssignedPlans) {
   If (($User.AssignedLicenses.SkuId -contains $EMSE5) -and ($SP.ServicePlanId -eq $TestSP -and $SP.CapabilityStatus -eq "Enabled")) {
        $ReportLine = [PSCustomObject][Ordered]@{  
          User            = $User.DisplayName
          UPN             = $User.UserPrincipalName
          ServicePlan     = $SP.Service
          ServicePlanId   = $SP.ServicePlanId 
          Assigned        = Get-Date($SP.AssignedDateTime) -format g
         }
        $Report.Add($ReportLine)
    } #End if
  } #End ForEach Service plans
} #End ForEach Users

After defining some variables, the code calls the Get-MgUser cmdlet to find the Azure AD accounts in the tenant (I used the script described in this article as the basis; see this article for more information about the Microsoft Graph SDK for PowerShell). Make sure that you connect to the beta endpoint as license information is not available with the V1.0 endpoint (run Select-MgProfile beta after connecting to the Graph).

Next, the code checks the assigned plans and if the desired plan belongs to the right product and is enabled, we report it. Each line in the report is like this:

User          : Kim Akers
UPN           : Kim.Akers@office365itpros.com
ServicePlan   : AADPremiumService
ServicePlanId : 41781fb2-bc02-4b7c-bd55-b576c07bb09d
Assigned      : 11/11/2017 16:52

This is a quick and dirty answer to the problem of discovering when a product license is assigned to user accounts. It might serve to fill in while Microsoft improves matters.

As reported by Vasil Michev, Microsoft recently added a licenseAssignmentState resource to the Graph API. This isn’t yet available for PowerShell, but the date information can be retrieved using the Graph. In this snippet, we find user accounts and examine their assignment state for EMS E5 to discover when the license was assigned. The code assumes that you’ve already used a registered app to authenticate and fetch an access token to interact the Graph APIs. Remember that you might need to use pagination to fetch all the pages of user data available in the tenant. Anyway, here’s my quick and dirty code to prove the point:

# Use the Graph API to check license assignment states
Write-Host "Fetching user information from Azure AD..."
$Uri = "https://graph.microsoft.com/v1.0/users?&`$filter=userType eq 'Member'"
[Array]$Users = (Invoke-RestMethod -Uri $Uri -Headers $Headers -Method Get -ContentType "application/json")
$Users = $Users.Value

Write-Host “Processing users…”
ForEach ($User in $Users) {
    $Uri = "https://graph.microsoft.com/beta/users/" + $User.UserPrincipalName + "?`$select=licenseAssignmentStates"
    [Array]$Assignments = Get-GraphData -Uri $Uri -AccessToken $Token
    ForEach ($License in $Assignments.LicenseAssignmentStates) {
        $LicenseUpdateDate = $Null
        If ($License.SkuId -eq $EMSE5 -and $License.State -eq "Active") {
           If ([string]::IsNullOrWhiteSpace(($License.lastUpdatedDateTime)) -eq $False ) {
              $LicenseUpdateDate = Get-Date($License.lastUpdatedDateTime) -format g }
           Else {
              $LicenseUpdateDate = "Not set" }
           Write-Host ("Last update for EMS for {0} on {1}" -f $User.DisplayName, $LicenseUpdateDate) }
    } # End ForEach License
} # End ForEach User

Last update for EMS for Tony Redmond on 15/07/2021 15:28
Last update for EMS for Andy Ruth (Director) on Not set
Last update for EMS for Kim Akers on 26/10/2021 16:58
Last update for EMS for Jack Hones on Not set
Last update for EMS for Oisin Johnston on 03/10/2020 13:18

The dates retrieved using this method differ to the values you get from service plans because Microsoft is populating these values using the last licensing change made to the account. However, in the future, the dates will be more accurate and usable because they will capture changes, hopefully when PowerShell access is possible.

No Audit Data

In passing, I note that the Office 365 audit log captures a “Change user license” audit record when an administrator updates the licenses for an account. However, the audit record doesn’t include details of what licenses were added, changed, or removed. The Azure AD team could do a better job of capturing audit information about license updates. I’m sure they’ll be happy to hear that.


Keep up with the changing world of the Microsoft 365 ecosystem by subscribing to the Office 365 for IT Pros eBook. Monthly updates mean that our subscribers learn about new development as they happen.

]]>
https://office365itpros.com/2021/11/10/find-when-azure-ad-user-accounts-receive-microsoft-365-licenses/feed/ 3 52291
API Deprecations Signal the Demise of Exchange Web Services https://office365itpros.com/2021/10/11/api-deprecations-signal-demise-exchange-web-services/?utm_source=rss&utm_medium=rss&utm_campaign=api-deprecations-signal-demise-exchange-web-services https://office365itpros.com/2021/10/11/api-deprecations-signal-demise-exchange-web-services/#comments Mon, 11 Oct 2021 01:00:00 +0000 https://office365itpros.com/?p=51882

EWS Sunset Will Impact Office 365 Tenants and ISVs

Microsoft’s October 5 announcement that they will decommission 25 Exchange Web Services (EWS) APIs by March 31, 2022, contains some clear signals that EWS is on a short runway to oblivion. Microsoft says that the set of 25 APIs selected to go first (Figure 1) are those least used with Exchange Online.

Exchange Web Services APIs due for deprecation by March 31, 2022
Figure 1: Exchange Web Services APIs due for deprecation by March 31, 2022

Microsoft also says that they’ll remove the ability to create new EWS apps starting on September 30, 2022. Both steps are part of the sunset process to ease EWS out of Microsoft 365, with Microsoft noting that “EWS is a legacy API surface that has served us well, but no longer meets the security and manageability needs of modern app development.”

Figuring Out the Impact

Microsoft created EWS as a SOAP-based protocol to allow ISVs access to Exchange Server content. In fact, when it appeared in Exchange 2007, EWS was the first successful API for Exchange that Microsoft delivered since MAPI. Since then, EWS has been used by Microsoft in the Entourage and Outlook for Mac clients and by ISVs for many different purposes, including migration of mailbox and PST data to Exchange Online and as the foundation for backup products.

The introduction of the Microsoft Graph APIs as the preferred access method for data across Microsoft 365 marked the beginning of the end for EWS. The first sign was Microsoft’s July 2018 announcement that EWS would receive no further feature updates. In many cases, a static API is a dead API, especially when its owner’s attention is directed firmly at another API.

It’s hard to deny that Microsoft is wrong to force people towards the Microsoft Graph APIs at this point. The Graph APIs are newer, used across Microsoft 365, support standards like OAuth and OData, and its REST-based model is familiar to many programmers. The wide range of tools like the Graph Explorer and SDKs in different programming languages make it easier for developers. It’s also true that the Graph APIs have more granular security policies. But the biggest thing is that the Graph is where the puck is going.

The second sign was the inclusion of EWS in the set of connectivity protocols which Microsoft plans to disable for basic authentication. The latest turn-off date is October 1, 2022.

All of which brings us to the need for organizations to figure out how EWS is used inside their tenants and draw up plans for its replacement. The focus areas to look for EWS include:

  • Custom home-built programs and PowerShell scripts.
  • Third-party clients and client add-ons.
  • Third-party products.

Every organization is different and EWS has been around for a long time. The two factors make it difficult to give a definitive set of guidelines for what to look. Three obvious areas are migration, connectors, and backup. Any product which streams information into or out of Exchange Online should be questioned to establish what API is in use. Most backup products for Exchange Online use EWS. The protocol was never intended as the foundation for backup, but nothing else is available so ISVs have used what they can.

Searching for a Graph Replacement

As they digest the news that EWS is in terminal decline, ISVs will naturally look for a replacement. Right now, the Microsoft Graph APIs cover Outlook (mail) and other objects stored inside mailboxes like tasks, notes, and contacts. However, there isn’t any obvious Graph API to handle large-scale streaming of mailbox content of the type usually experienced in backup and restore operations.

Microsoft has just released a Graph export API for Teams which is deemed suitable for backup and restore scenarios. However, that API comes with consumption meters and a per-message charging model which creates a whole new economic model for pricing for backup data. The volume of Teams messages is lower than Exchange Online email and the size of Exchange Online mailboxes is larger (even with the new 1.5 TB limit for auto-expanding archives). If Microsoft decides to impose the same consumption pricing model on an export/import API for Exchange Online, it could create many headaches for customers and ISVs alike to deal with the costs associated with scenarios like:

  • Moving data from legacy archive solutions to Exchange Online.
  • Importing data from third-party systems into Exchange Online (connectors).
  • Tenant-to-tenant migrations of Exchange mailboxes and archives.
  • Backup processing for Exchange Online mailboxes (user, shared, group, public folder, and other) to external third-party datacenters.

Although all the signs are that EWS will slip away soon, Microsoft hasn’t set a final (public) date for the removal of EWS from Exchange Online. You could put your head into the sand and hope that the protests of customers and ISVs will be sufficient to force Microsoft to keep EWS going for longer. I don’t think that’s a great strategy. It’s better to accept that the days of EWS are numbered (in the low hundreds) and start working on replacement components for your IT infrastructure. You know it makes sense.


Learn about Exchange Online and the rest of the Office 365 ecosystem by subscribing to the Office 365 for IT Pros eBook. Use our experience to understand what’s importance and how changes affect your tenant.

]]>
https://office365itpros.com/2021/10/11/api-deprecations-signal-demise-exchange-web-services/feed/ 6 51882
How to Use /Any Filters in Microsoft Graph API Queries with PowerShell https://office365itpros.com/2021/09/17/graph-lambda-operators-powershell/?utm_source=rss&utm_medium=rss&utm_campaign=graph-lambda-operators-powershell https://office365itpros.com/2021/09/17/graph-lambda-operators-powershell/#comments Fri, 17 Sep 2021 01:00:00 +0000 https://office365itpros.com/?p=51564

Why Lambda Operators are Sometimes Needed

A reader asked about the meaning of x:x in a Graph API query included in the article about upgrading Office 365 PowerShell scripts to use the Graph. You see this construct (a Lambda operator) in queries like those necessary to find the set of accounts assigned a certain license. For example, to search for accounts assigned Office 365 E3 (its SKU or product identifier is always 6fd2c87f-b296-42f0-b197-1e91e994b900):

https://graph.microsoft.com/beta/users?$filter=assignedLicenses/any(s:s/skuId eq 6fd2c87f-b296-42f0-b197-1e91e994b900)

Find the set of Microsoft 365 Groups in the tenant:

https://graph.microsoft.com/v1.0/groups?$filter=groupTypes/any(a:a eq 'unified')

Find the set of Teams in the tenant:

https://graph.microsoft.com/beta/groups?$filter=resourceProvisioningOptions/Any(x:x eq 'Team')

As you might expect, because the cmdlets in the Microsoft Graph SDK for PowerShell essentially are wrappers around Graph API calls, these cmdlets use the same kind of filters. For example, here’s how to find accounts with the Office 365 licenses using the Get-MgUser cmdlet:

[array]$Users = Get-MgUser -Filter "assignedLicenses/any(x:x/skuId eq 6fd2c87f-b296-42f0-b197-1e91e994b900)" -all

Lambda Operators and Advanced Graph Queries

All these queries use lambda operators to filter objects using values applied to multi-valued properties. For example, the query to find users based on an assigned license depends on the data held in the assignedLicenses property of Azure AD accounts, while discovering the set of Teams in a tenant relies on checking the resourceProvisioningOptions property for Microsoft 365 groups. These properties hold multiple values or multiple sets of values rather than simple strings or numbers. Because this is a query against a multivalue property for an Entra ID directory object, it’s called an advanced query.

Accessing license information is a good example to discuss because Microsoft is deprecating the Azure AD cmdlets for license management at the end of 2022, forcing tenants to upgrade scripts which include these cmdlets to replace them with cmdlets from the Microsoft Graph SDK for PowerShell or Graph API calls. This Practical365.com article explains an example of upgrading a script to use the SDK cmdlets.

If we look at the value of assignedLicenses property for an account, we might see something like this, showing that the account holds three licenses, one of which has a disabled service plan.

disabledPlans                          skuId
-------------                          -----
{33c4f319-9bdd-48d6-9c4d-410b750a4a5a} 6fd2c87f-b296-42f0-b197-1e91e994b900
{}                                     1f2f344a-700d-42c9-9427-5cea1d5d7ba6
{}                                     8c4ce438-32a7-4ac5-91a6-e22ae08d9c8b

It’s obvious that assignedLicenses is a more complex property than a single-value property like an account’s display name, which can be retrieved in several ways. For instance, here’s the query with a filter to find users whose display name starts with Tony.

https://graph.microsoft.com/v1.0/users?$filter=startswith(displayName,'Tony')

As we’re discussing PowerShell here, remember that you must escape the dollar character in filters. Taking the example above, here’s how it is passed in PowerShell:

$Uri = "https://graph.microsoft.com/v1.0/users?`$filter=startswith(displayName,'Tony')"
[array]$Users = Invoke-WebRequest -Method GET -Uri -ContentType "application/json" -Headers $Headers | ConvertFrom-Json

The data returned by the query is in the $Users array and can be processed like other PowerShell objects.

Using Any and All

Getting back to the lambda operators, while OData defines two (any and all), it seems like the all operator, which “applies a Boolean expression to each member of a collection and returns true if the expression is true for all members of the collection (otherwise it returns false)” is not used. At least, Microsoft’s documentation says it “is not supported by any property.”

As we’ve seen from the examples cited above, the any operator is used often. This operator “iteratively applies a Boolean expression to each member of a collection and returns true if the expression is true for any member of the collection, otherwise it returns false.”

If we look at the filter used to find accounts assigned a specific license:

filter=assignedLicenses/any(s:s/skuId eq 6fd2c87f-b296-42f0-b197-1e91e994b900)

My interpretation of the component parts (based on Microsoft documentation) of the filter is:

  • assignedLicenses is the parameter, or the property the filter is applied to. The property can contain a collection of values or a collection of entities. In this case, the assignedLicenses property for an account contains a collection of one or more license entities. Each license is composed of the SkuId and any disabled plans unavailable to the license holders.
  • s:sis a range variable that holds the current element of the collection during iteration.” The interesting thing is that you can give any name you like to the range variable. In this case, it could be license:license or even rubbish:debris. It’s just a name for a variable.
  • SkuId is the subparam, or value within the property being checked. When there’s only one value in a collection (as when you check for team-enabled groups), you don’t need to specify a subparam. In the case of assignedLicenses, it is needed because we want to match against the SkuId within the collection of entities in the assignedLicenses property.
  • 6fd2c87f-b296-42f0-b197-1e91e994b900 is the value to match against items.

I’m Not a Developer

All of this is second nature to professional developers but not so much to tenant administrators who want to develop some PowerShell scripts to automate operations. This then poses the question about how to discover when lambda qualifiers are needed. I don’t have a great answer except to look for examples in:

  • Microsoft’s Graph API documentation.
  • Code posted online as others describe their experiences working with the Graph APIs.

And when you find something which might seem like it could work, remember that the Graph Explorer is a great way to test queries against live data in your organization. Figure 1 shows the results of a query for license information.

Running a query with a lambda qualifier in the Graph Explorer
Figure 1: Running a query with a lambda qualifier in the Graph Explorer

Exploring the Mysteries of the Graph

One complaint often extended about Microsoft’s documentation for the Graph APIs is that it pays little attention to suitable PowerShell examples. The Graph SDK developers say that they understand this situation must change and they plan to improve their documentation for PowerShell over the next year. Although understandable that languages like Java and C# have been priorities up to now, Microsoft can’t expect the PowerShell community to embrace the Graph and learn its mysteries (like lambda qualifiers) without help. Let’s hope that the Graph SDK developers live up to their promise!


Learn how to exploit the Office 365 data available to tenant administrators through the Office 365 for IT Pros eBook. We love figuring out how things work.

]]>
https://office365itpros.com/2021/09/17/graph-lambda-operators-powershell/feed/ 4 51564
How to Manage Anonymized User Data in Microsoft 365 Usage Reports https://office365itpros.com/2021/09/09/manage-anonymized-user-data-in-microsoft-365-usage-reports/?utm_source=rss&utm_medium=rss&utm_campaign=manage-anonymized-user-data-in-microsoft-365-usage-reports https://office365itpros.com/2021/09/09/manage-anonymized-user-data-in-microsoft-365-usage-reports/#respond Thu, 09 Sep 2021 01:00:00 +0000 https://office365itpros.com/?p=51433

From September 1, Pseudonymized by Default

MC275344 (published August 3, updated August 31, Microsoft 365 roadmap item 81959) deals with the topic of anonymization of user information in Microsoft 365 usage reports. Until now, the situation has been that the usage reports show full usage data, including details of user principal names and group names with an option for the tenant to choose pseudonymized information. In this situation, anonymized values like A6968D016DB2256910FD3B85B4B0457B replace user or group identifiable information in the reports. You can still understand the overall context of the report and what it tells you about the usage pattern for a workload like SharePoint or Teams, but you can’t dive down into the detail at user level.

Microsoft says that de-identifying user data will help tenants support local privacy laws. The changeover to use anonymized data by default came into effect on September 1, 2021. Users with access to report data now see values like those shown in Figure 1.

Anonymized usage data reported by the Microsoft 365 admin center
Figure 1: Anonymized usage data reported by the Microsoft 365 admin center

Reverting to Real User Data

If you want to revert to see real user information in usage reports, a global administrator can switch through the Reports section of Org-wide settings by clearing the checkbox shown in Figure 2.

The tenant-wide setting controlling anonymization of user information in usage reports
Figure 2: The tenant-wide setting controlling anonymization of user information in usage reports

Updating the setting captures an UpdatedCFRPrivacySettings audit record. For instance, here’s an edited version of the audit record captured when I enabled identifiable user information in usage reports.

RecordType   : CoreReportingSettings
CreationDate : 06/09/2021 19:37:55
UserIds      : Tony.Redmond@office365itpros.com
Operations   : UpdatedCFRPrivacySettings
AuditData    : {
                 "ModifiedProperties": [
                   {
                     "Name": "PrivacyEnabled",
                     "OldValue": "True",
                     "NewValue": "False"
                   }
                 ],
                 "Id": "639e2bcc-eba9-4146-8885-333622ffb4b0",
                 "RecordType": "CoreReportingSettings",
                 "CreationTime": "2021-09-06T19:37:55",
                 "Operation": "UpdatedCFRPrivacySettings",
               }

Access to User Information Limited to Certain Roles

In the past, this would have been sufficient to let any account holding an administrative role with access to usage data to see user information. This is not now the case as Microsoft has made a further change to confine the ability to see user information to “administrative and report reader roles.

In effect, this means that roles like:

  • Global administrator.
  • Exchange administrator.
  • SharePoint administrator.
  • Teams administrator.
  • User administrator.
  • Helpdesk admin.
  • Service support admin, and:
  • Reports reader.

Can see user information (anonymized or real as selected by the tenant setting), but other administrative roles such as Usage summary reports reader or Global reader, which used to be able to see user information, no longer have access. Users with these roles see only summary graphs (Figure 3).

What a user with the Reports Reader role sees for usage data
Figure 3: What a user with the Reports Reader role sees for usage data

Governs Programmatic Access Too

The change affects usage reports in the Microsoft 365 admin center and the Teams admin center. It also affects programmatic access to usage data through the Microsoft Graph usage reports API, including SharePoint site detail. This is because the usage reports API is the basis for reporting across Microsoft 365.

As noted when Microsoft originally introduced anonymized user data for reports, if the organization generates its own version of usage reports like my Office 365 User Activity Report, you’ll need to make sure to generate the report using an account with a suitable administrative role. Identifiable user data makes these kinds of reports much more valuable, especially if you use the reports to analyze usage patterns based on departments, locations, and workloads, and if you want the reports to contain this information, the org-wide setting to allow identifiable user data must be enabled when the report runs. Arranging for this to be done if the organization decides to use anonymized user information for reporting could be a challenge!

Good for Privacy

There’s no doubt that this is a good step from the perspective of privacy advocates. However, I wonder if obscuring information about how people use technology at the level of detail available in the Graph (like the number of emails sent and read, or Yammer conversations created) will make it harder for administrators to do their job. I agree with the move to restrict access to detailed information to the more highly privileged administrative roles, but wonder how many organizations will try to use anonymized user information before reverting because good reason exists to access detailed data.


Insight like this doesn’t come easily. You’ve got to know the technology and understand how to look behind the scenes. Benefit from the knowledge and experience of the Office 365 for IT Pros team by subscribing to the best eBook covering Office 365 and the wider Microsoft 365 ecosystem.

]]>
https://office365itpros.com/2021/09/09/manage-anonymized-user-data-in-microsoft-365-usage-reports/feed/ 0 51433
How to Access the TEC 2021 Session Videos https://office365itpros.com/2021/09/06/tec-2021-presentations-online/?utm_source=rss&utm_medium=rss&utm_campaign=tec-2021-presentations-online https://office365itpros.com/2021/09/06/tec-2021-presentations-online/#comments Mon, 06 Sep 2021 08:25:47 +0000 https://office365itpros.com/?p=51399

Videos and Slides Available to All

The Experts Conference (TEC) 2021 took place using a mixture of Teams Live Events and online meetings over September 1-2. The TEC organizers have posted videos and slides of the presentations online for all to access.

Naturally, you’ll check out my session on Leveraging the Graph to Manage Microsoft 365 (video and slides). While many of the topics covered in the session have also appeared in articles, there’s nothing like making a pitch on important topics like this to force you to think through the value of the subject.

Many other useful sessions are also available – certainly enough for anyone to find some nuggets of information. I’ve pointed to a set of sessions that I like here, including interesting talks about the Microsoft 365 substrate, Azure AD futures, and defending your company against sophisticated cyberattacks.

Looking Forward to TEC 2022 in Atlanta

The Experts Conference 2022 takes place September 20-21, 2022, in the Loews Atlanta Hotel. Although Teams has served as a worthy platform for TEC 2020 and TEC 2021, it will be great to get back to an in-person conference. Many great speakers have already been lined up, and the combination of talent and in-person interaction should make TEC 2022 a fantastic event to attend. Super early bird registration for TEC 2022 ends Feb 28, 2022, and costs $350. The registration gradually increases to $799, so this is a good example of getting in early to save money.

I look forward to meeting many Office 365 for IT Pros subscribers at TEC 2021 in Atlanta.

]]>
https://office365itpros.com/2021/09/06/tec-2021-presentations-online/feed/ 2 51399
How to Find Delve Accounts with Disabled Document Insights https://office365itpros.com/2021/09/03/find-delve-accounts-with-disabled-document-insights/?utm_source=rss&utm_medium=rss&utm_campaign=find-delve-accounts-with-disabled-document-insights https://office365itpros.com/2021/09/03/find-delve-accounts-with-disabled-document-insights/#respond Fri, 03 Sep 2021 01:00:00 +0000 https://office365itpros.com/?p=51311

Controlling Document Insights

The Microsoft Graph Insights API proves different views of users and documents:

  • Trending: Documents a user has access to which are popular with other users.
  • Used: Documents a user has accessed recently.
  • Shared: Documents shared with a user, including email and attachments in calendar appointments.

Insights are consumed by many apps and Microsoft 365 components such as MyAnalytics, Workplace Analytics, Viva Insights for Teams, and the Office 365 profile card. Figure 1 is a Microsoft graphic to explain the use of the Insights API and its value to “drive productivity and creativity in businesses.”

The Microsoft Graph Insights API (source: Microsoft)
Figure 1: The Microsoft Graph Insights API (source: Microsoft)

Delve and Sharing

Delve was the first app to surface insights, but used Office Graph settings to allow users to decide if they wanted to reveal information about their document-centric activities. Some users never want details of their work exposed, even to people who have access to documents, because they either don’t see the need or because they wish to preserve the confidential nature of the information they work with. They can protect content by assigning a sensitivity label with encryption to confidential documents, but this won’t stop document metadata like titles showing up in insights. The feature settings for Delve therefore have a slider to control showing documents in Delve (trending, used, and shared). When the slider is Off, Delve blocks insights based on documents (Figure 2).

Delve feature settings prevent the display of documents associated with a user
Figure 2: Delve feature settings prevent the display of documents associated with a user

Moving to the New Graph Sharing Controls

In April, I wrote about how Microsoft is replacing Office Graph controls over Item Insights with Microsoft Graph controls. The change is now effective in Microsoft 365 tenants and mean that instead of user-driven control over how the Insights API reveals information, a tenant has:

  • An organization-wide setting to control insights privacy (isEnabledInOrganization). This is True if controls are active in the organization or False if not.
  • An Azure AD group to control the set of accounts who do not want to use insights (disabledForGroup). Individual users can disable insights through the Privacy options of their MyAccount page. However, this does not add their account to the group.

Access to these settings is available through the Search & Intelligence section of the Microsoft 365 admin center (Figure 3).

Settings to control item insights exposed in the Microsoft 365 admin center
Figure 3: Settings to control item insights exposed in the Microsoft 365 admin center

The question arises how to find the current set of accounts with the option disabled in Delve so that you can add the accounts to the Azure AD group. As it happens, I was asked this question by a Microsoft customer engineer who wanted to help their customer move to the new Microsoft Graph controls.

The first step is to find the set of accounts with Delve insights disabled. This cannot be done with PowerShell only because no cmdlet exists to retrieve the value of the Delve setting. Instead, we can combine PowerShell with a call to the Graph Users API. Here are the steps:

  • Get a set of accounts to check. This can be done with Get-ExoMailbox or Get-AzureADUser. We need the object identifier to make the Graph call. Either of these cmdlets wll return the necessary identifiers. I like the mailbox cmdlet because it has better filtering capabilities.
  • Loop through the set of accounts and check the value of the Delve setting (contributionToContentDiscoveryDisabled). If True (insights are disabled), report the account.
  • Generate whatever output is required (I usually create a CSV file because of its flexibility).

You can download the script I used to report users with Delve insights disabled from GitHub.

Updating the Group

The next step is to review the report and decide which accounts to add to the Azure AD group used to control item insights. To review the data, open the CSV file generated by the script (Figure 4), and remove any accounts which should not be added to the control group.

CSV file for accounts with Delve item insights disabled
Figure 4: CSV file for accounts with Delve item insights disabled

We can then use the updated CSV file as the input for a script which:

  • Retrieves some tenant details.
  • Retrieves the current privacy control settings from the Graph.
  • Fetches the current membership of the group (we assume here that one is specified).
  • Import the set of accounts from the CSV file.
  • Loop through the accounts and add them to the Azure AD group if not already a member.

The essential code to fetch the settings from the Graph and update the membership of the control group looks like this:

$InputCSV = "c:\temp\DelveDisabledAccounts.csv"
$TenantDetails = Get-AzureADTenantDetail
$TenantId = $TenantDetails.ObjectId
$TenantName = $TenantDetails.DisplayName
$Uri = "https://graph.microsoft.com/beta/organization/" + $TenantId + "/settings/iteminsights"
$Settings = Invoke-RestMethod -Uri $Uri -Method Get -ContentType "application/JSON" -Headers $Headers -UseBasicParsing

If ($Settings.isEnabledInOrganization -ne $True) {
   Write-Host "Insights control setting not set for" $TenantName ; break }
Else {
   $DisabledGraphInsightsGroup = $Settings.disabledForGroup }

[array]$CurrentMembers = Get-AzureADGroupMember -ObjectId $DisabledGraphInsightsGroup | Select -ExpandProperty ObjectId

Write-Host "Adding users to the Disabled Graph Insights Group"
$Users = Import-CSV $InputCSV
ForEach ($User in $Users) {

   If ($User.ObjectId -notin $CurrentMembers) {
      Write-Host "Adding" $User.Name
      Add-AzureADGroupMember -ObjectId $DisabledGraphInsightsGroup -RefObjectId $User.ObjectId }
}

I haven’t published a script to GitHub for this purpose because the code is straightforward and simple to plug into an existing script (or add to the bottom of the script mentioned above). Happy Insights!


Learn more about how Office 365 really works on an ongoing basis by subscribing to the Office 365 for IT Pros eBook. Our monthly updates keep subscribers informed about what’s important across the Office 365 ecosystem.

]]>
https://office365itpros.com/2021/09/03/find-delve-accounts-with-disabled-document-insights/feed/ 0 51311
Microsoft Introduces Data Privacy Tag for Message Center Notifications https://office365itpros.com/2021/07/27/microsoft-365-privacy-message/?utm_source=rss&utm_medium=rss&utm_campaign=microsoft-365-privacy-message https://office365itpros.com/2021/07/27/microsoft-365-privacy-message/#comments Tue, 27 Jul 2021 00:36:00 +0000 https://office365itpros.com/?p=50860

Microsoft 365 Privacy Messages in Case of Data Compromise

Microsoft posts notifications to the message center in the Microsoft 365 admin center to inform tenant administrators about a variety of different updates made to its service. MC272885 posted on Jul 24, 2021, has the title Attachments for messages with Data Privacy Tag, which might leave you scratching your head to understand what Microsoft means. At first glance, the combination of attachments and messages points to email and tag could mean a sensitivity or retention label. But that’s not what it means.

Reading the detail reveals that Microsoft is introducing a new tag for service update messages. Let’s explore what this means.

Tagging Service Messages

When Microsoft publishes a service update message, it applies tags to help tenant administrators understand the importance and potential impact of the change (Figure 1).

Microsoft assigns two tags to service update MC272885

Microsoft 365 privacy message
Figure 1: Microsoft assigns two tags to service update MC272885

The tags shown in the message center include:

  • Admin Impact: The change impacts the management of some aspect of the tenant. For example, a new API is available. MC272885 (described here) is deemed a change with administrator impact.
  • Feature Update: Microsoft has changed the way a feature works. For example, MC264095 describes how the default setting for guest access in Teams changes from off to on.
  • Major Update: The change described is considered major. For example, the retirement of Skype for Business Online on July 31 (MC266078) is obviously a big change in Office 365. Other updates tagged as major are debatable, but you can consider this tag to be a way for Microsoft to highlight important changes. Note: Unlike the other tags, this tag is marked by setting the IsMajorChange property of a message to $True.
  • New Feature: A new feature is on its way for an app. For example, MC230680 describes the introduction of reactions in Teams meetings. Microsoft often misses the date for feature introductions and republishes the update, which is what happened on MC230680 on June 30 when they published new dates for availability of the feature in the GCC and DOD clouds.
  • Retirement: Microsoft is removing a feature from the service. Skype for Business Online is an example, so is the final removal of Site mailboxes (MC266256). Not many shed tears when site mailboxes shuffled off into the great byte wastebasket.
  • User Impact: Many changes impact users in some way. For example, MC271629 advises administrators that Project Moca is moving its spaces to the OWA calendar.
  • Updated Message: This tag does not appear in the Microsoft 365 admin center. It’s used to flag service messages which have been updated since the original publication. This is usually due when Microsoft needs to clarify the meaning of the text.

Many updates have multiple tags. For instance, MC264095 has the major update, feature update, and user impact tags.

Analyzing Service Update Tags

Using the Graph API for Service Communications, we can fetch the messages currently available in the Microsoft 365 admin center to see what tags are in use. As you’ll recall, this API spans both incidents (outages) reported in the admin center and service updates. I took the example script I created for service updates and used some of the code to pull all update messages into an array.

$Uri = "https://graph.microsoft.com/beta/admin/serviceAnnouncement/messages"
[array]$Messages = Get-GraphData -AccessToken $Token -Uri $uri

I then used some simple code to analyze the tags placed on each message.

$TagAdmin = 0; $TagUpdate = 0; $TagMajor = 0; $TagNew = 0; $TagRetirement = 0; $TagUser = 0; $TagUpdatedMessage = 0; $TagDataPrivacy = 0
ForEach ($Message in $Messages) {
    ForEach ($Tag in $Message.Tags) {
      Switch ($Tag) 
        {
        "Admin impact"    {$TagAdmin++}
        "Feature update"  {$TagUpdate++}
        "New feature"     {$TagNew++}
        "Retirement"      {$TagRetirement++}
        "User impact"     {$TagUser++}
        "Updated message" {$TagUpdatedMessage++}
        "Data privacy"    {$TagDataPrivacy++}
       } # End Switch
    }  # End Foreach tag
   If ($Message.IsMajorChange -eq $True) {$TagMajor++}
} # End ForEach message 
Write-Host "Admin impact messages:  " $TagAdmin
Write-Host "Feature update messages:" $TagUpdate
Write-Host "Major update messages:  " $TagMajor
Write-Host "New feature messages:   " $TagNew
Write-Host "Retirement messages:    " $TagRetirement
Write-Host "User impact messages:   " $TagUser
Write-Host "Updated messages:       " $TagUpdatedMessage
Write-Host "Data privacy messages:  " $TagDataPrivacy

Admin impact messages:   165
Feature update messages: 65
Major update messages:   76
New feature messages:    119
Retirement messages:     31
User impact messages:    191
Updated messages:        96
Data privacy messages    0

The total count of messages was 266. You can see that:

  • The most popular tag is user impact (191) followed by admin impact (165).
  • There’s a surprising number of retirement messages (31).
  • Many updates are issued (96).

Your mileage might vary because Microsoft issues service updates to tenants based on the feature set licensed by the tenant.

What’s Changing

Microsoft is introducing a new Data Privacy tag to indicate messages which need administrator attention because they potentially impact sensitive data. The change is due to roll out by the end of July.

Microsoft says that messages might also contain one or more downloadable attachments (if multiple, the attachments are in a zip file) to help administrators “gain additional insight into the described scenario.” For instance, an attachment might be a PowerShell script to report data or users affected by a service update.

Only accounts holding the Global administrator and Privacy reader roles can access the downloadable attachments.

It’s hard to be certain about how Microsoft will use the new Data Privacy tag and what kind of service update messages they will tag. I guess we will see when some messages appear with the tag (none are found in the messages in my tenant) and the kind of attachments available for the messages.


So much change, all the time. It’s a challenge to stay abreast of all the updates Microsoft makes across Office 365. Subscribe to the Office 365 for IT Pros eBook to receive monthly insights into what’s happening.

]]>
https://office365itpros.com/2021/07/27/microsoft-365-privacy-message/feed/ 1 50860
How to Decide When to Use the Microsoft Graph API to Speed Up PowerShell Scripts https://office365itpros.com/2021/07/13/use-microsoft-graph-powershell/?utm_source=rss&utm_medium=rss&utm_campaign=use-microsoft-graph-powershell https://office365itpros.com/2021/07/13/use-microsoft-graph-powershell/#comments Tue, 13 Jul 2021 01:00:00 +0000 https://office365itpros.com/?p=50655

Combining the Power of the Shell and the Graph

Over the last few months, I’ve written many times about using Microsoft Graph API calls in PowerShell scripts to get real work done. Among the many examples are:

In addition, Microsoft has stirred the pot by announcing that they won’t support the Azure AD Graph from June 2022. This affects the Azure AD PowerShell module, one of the most heavily used modules for Office 365 tenant management. And we have examples where Microsoft introduces new features, like tenant privacy controls, which can be controlled only through Graph API calls.

As a result of this activity, I’ve received several questions about how to decide when to use Graph API calls in scripts. And as I due to speak about combining PowerShell and the Graph at the (free) TEC 2021 event in September, it seemed like a good idea to formulate some thoughts about how I approach the issue.

Four Steps to Consider

I use a simple four-step process when writing scripts to automate some aspect of Office 365:

Sketch out the solution: Understand what source data is available and how to access it. Define the expected output and the processing needed to achieve the result. Make an initial selection of PowerShell modules and Graph APIs which might be useful, understanding that some data is only accessible to the Graph (and might need a beta API). Do an internet search to see if anyone has already written code to do what you want or something similar. Never reinvent the wheel if someone else has one to use.

Code in PowerShell first: It’s often wise to write the initial code in PowerShell before introducing any Graph APIs. The code you write might work well enough to be the solution you need without doing any further work. This is often the case when a small amount of data is involved, in which case you don’t need the additional overhead necessary to introduce Graph API calls.

Speed Things Up: Usually, the biggest advantage gained through using Graph APIs is speed, especially when fetching large numbers of objects like user accounts or groups. The next step is to find places in your code where large delays occur to run calls like Get-UnifiedGroup and replace those cmdlets with Graph APIs.

Adjust for Production: Every tenant has their own idea of how to run PowerShell scripts in production. After developing a script which can run interactively, you might need to change it to run as a background process and deal with issues like certificate-based authentication (never store passwords in scripts). Because of the need to adjust scripts for production usage, the code I write for books and articles is to illustrate principles rather than being fully worked-out answers.

The most important point in the checklist is the internet search for code. If you don’t find a suitable script to remove the need to create anything new, you’ll probably find the basis or starting point for what you want to do. It astounds me that people post questions in forums when it is perfectly obvious that they haven’t done the basic research to uncover details which can help solve their problem. Unfortunately, too many people expect answers to be handed to them on a plate and aren’t prepared to learn through experimentation and failure. I spend most of my time in the latter state.

Take Things Slowly

The Microsoft Graph isn’t scary. It’s there to be used and like any other tool, it should be used at the right time. PowerShell gets most things done really well when it comes to tenant management. It has its limitations, some of which the Graph can fill in. Starting with simple tasks and moving forward to more complex issues is a great way to learn how to use the Graph with PowerShell. Your task is to provide the brainpower to combine the two to get things done most effectively.

https://theexpertsconference.cventevents.com/7yq3Er?RefId=P365

Learn how to exploit the Office 365 data available to tenant administrators through the Office 365 for IT Pros eBook. We love figuring out how things work.

]]>
https://office365itpros.com/2021/07/13/use-microsoft-graph-powershell/feed/ 5 50655
Speeding Up the Groups and Teams Activity Report by Replacing PowerShell with Graph API Calls https://office365itpros.com/2021/06/09/speed-access-large-group-sets-graph/?utm_source=rss&utm_medium=rss&utm_campaign=speed-access-large-group-sets-graph https://office365itpros.com/2021/06/09/speed-access-large-group-sets-graph/#comments Wed, 09 Jun 2021 02:31:00 +0000 https://office365itpros.com/?p=50174

Grappling with PowerShell

Sometimes I hate PowerShell. Not the language itself, just my ineptitude, or my inability to remember how to do things, or the speed of some cmdlets which deal with objects like mailboxes and groups. It’s not that the cmdlets are inefficient. They do a lot of work to retrieve information about objects, so they are slow.

This is fine for ad-hoc queries or where you only need to process a couple of hundred mailboxes or groups. The problem is accentuated as numbers grow, and once the need exists to process thousands of objects, some significant time is spent waiting for cmdlets to complete, meaning that scripts can take hours to run.

Microsoft has made significant progress in the Exchange Online PowerShell module to introduce faster cmdlets like Get-ExoMailbox and Get-ExoMailboxStatistics. These REST-based cmdlets are faster and more robust than their remote PowerShell cousins and these improvements are ample justification for the work needed to revisit and upgrade scripts. The module also supports automatic renewal of sessions to Exchange Online and the Security and Compliance endpoints, so it’s all good.

The Sloth of Get-UnifiedGroup

Things aren’t so impressive with Get-UnifiedGroup, which retrieves details about Microsoft 365 Groups. Reflecting the use of Microsoft 365 groups, Get-UnifiedGroup is a complex cmdlet which assembles details from Azure AD, Exchange Online, and SharePoint Online to give a full picture of group settings. Running Get-UnifiedGroup to fetch details of 200 groups is a slow business; running the cmdlet to fetch details of 10,000 groups is a day-long task. The Get-Team cmdlet is no speedster either. In their defense, Microsoft designed these cmdlets for general-purpose interaction with Groups and Teams and not to be the foundation for reporting thousands of objects over a short period.

If you only need a list of Microsoft 365 Groups, it’s also possible to create the list using the Get-Recipient cmdlet.

Get-Recipient -RecipientTypeDetails GroupMailbox -ResultSize Unlimited

Creating a list of groups with Get-Recipient is usually much faster than creating it with Get-UnifiedGroup. However, although you end up with a list of groups, Get-Recipient doesn’t return any group-related properties, so you usually end up running Get-UnifiedGroup to retrieve settings for an individual group before you can process it. Still, that overhead can be spread out over the processing of a script and might only be needed for some but not all groups.

The Graph is Quicker

Which brings me to the Microsoft Graph API for Groups. As I’ve pointed out for some years, using Graph APIs with PowerShell is a nice way to leverage the approachability of PowerShell and the power of the Graph. The script to create a user activity report from Graph data covering Exchange, SharePoint, OneDrive, Teams, and Yammer is a good example of how accessible the Graph is when you get over the initial learning curve.

Three years ago, I wrote about a script to find obsolete Teams and Groups based on the amount of activity observed in a group across Exchange Online, SharePoint Online, and Teams. In turn, that script was based on an earlier script which processed only Office 365 Groups. Since then, I have tweaked the script in response to comments and feedback and everything worked well. Except that is, once the script ran in large environments supporting thousands of groups. The code worked, but it was slow, and prone to time-outs and failures.

Speeding Up the Report

The solution was to dump as many PowerShell cmdlets as possible and replace them with Graph calls. The script (downloadable from GitHub) now uses the Graph to retrieve:

  • SharePoint Online site usage data for the last 90 days.
  • A list of Microsoft 365 Groups and a list of team-enabled groups.
  • The owners of a group.
  • The display name of the owners (because the first call returns their Azure AD identifier).
  • Some extended properties of a group not fetched when the group list is returned.
  • Counts for group members and guests (easier to do since Microsoft added advanced queries for directory objects in October 2020).
  • Archived status for teams.

The result is that the script is much faster than before and can deal with thousands of groups in a reasonable period. Fetching the group list still takes time as does fetching all the bits that Get-UnifiedGroup returns automatically. On a good day when the service is lightly loaded, the script takes about six seconds per group. On a bad day, it could be eight seconds. Even so, the report (Figure 1) is generated about three times faster.

Results - Teams and Microsoft 365 Groups Activity Report V5.1
--------------------------------------------------------------   
Number of Microsoft 365 Groups scanned                          : 199    
Potentially obsolete groups (based on document library activity): 121    
Potentially obsolete groups (based on conversation activity)    : 130      
Number of Teams-enabled groups                                  : 72    
Percentage of Teams-enabled groups                              : 36.18%

Total Elapsed time:  1257.03 seconds
Summary report in c:\temp\GroupsActivityReport.html and CSV in c:\temp\GroupsActivityReport.csv
Sample output from the Teams and Groups activity report
Figure 1: Sample output from the Teams and Groups activity report

The only remaining use of an “expensive” cmdlet in the script is when Get-ExoMailboxFolderStatistics fetches information about compliance items for Teams stored in Exchange Online mailboxes. The need for this call might disappear soon when Microsoft eventually ships the Teams usage report described in message center notification MC234381 (no sign so far despite a promised delivery of late February). Hopefully, that report will include an update to the Teams usage report API to allow fetching of team activity data like the number of conversations over a period. If this happens, I can eliminate calling Get-ExoMailboxFolderStatistics and gain a further speed boost.

Some Extra Work but Not Onerous

The downsides of using the Graph with PowerShell are that you need to register an app in Azure Active Directory and make sure that the app has the required permissions to access the data. This soon becomes second nature, and once done, being able to process data faster than is possible using the general-purpose Get-UnifiedGroup and Get-Team cmdlets is a big benefit when the time comes to process more than a few groups at one time.

]]>
https://office365itpros.com/2021/06/09/speed-access-large-group-sets-graph/feed/ 9 50174
How to Decrypt Protected SharePoint Files Using PowerShell and the Graph API https://office365itpros.com/2021/03/25/decrypt-protected-sharepoint-files/?utm_source=rss&utm_medium=rss&utm_campaign=decrypt-protected-sharepoint-files https://office365itpros.com/2021/03/25/decrypt-protected-sharepoint-files/#comments Thu, 25 Mar 2021 00:43:00 +0000 https://office365itpros.com/?p=48786

Unlocking Protected SharePoint Documents

In my article about how to decrypt SharePoint Online documents with PowerShell, I explained how to use the Unlock-SPOSensitivityLabelEncryptedFile cmdlet to decrypt protected SharePoint files by removing the sensitivity labels protecting the files. The example script uses cmdlets from the SharePoint PnP module to return a set of files from a folder in a document library for processing, and the unlock cmdlet then removes protection from any file with a sensitivity label.

The script works, but it’s not as flexible as I would like. For instance, because PnP can’t distinguish files with labels, every document in the folder is processed whether it is labelled or not. This does no harm, but it’s not something that you might want to do in the case of something like a tenant-to-tenant migration where thousands of protected documents might need to be processed.

Update May 10, 2021: The latest version of the SharePoint Online PowerShell module contains the Get-FileSensitivityLabelInfo cmdlet. This can be run to return the label status of a file, including if the label assigned to the file encrypts the file. The existence of this cmdlet removes some of the need to use the Graph to find and remove labels from protected files, but the Graph is still the fastest way to get the job done.

Using the Sites Microsoft Graph API

Which brings me to an updated version of the script (available from GitHub), which uses the Sites API from the Microsoft Graph to navigate through SharePoint Online and find labelled documents to process. Apart from being able to search for documents with sensitivity labels, a Graph API is usually the fastest way to deal with large numbers of objects.

Because we’re making Graph calls from PowerShell, we need to create a registered app in Azure AD to use as the entry point to the Graph (the same steps as outlined in this post are used). The app needs to be able to read site data, so I assigned it Sites.Read.All and Sites.ReadWrite.All permissions (Figure 1).

Setting API permissions for the Graph app
Figure 1: Setting API permissions for the Graph app

Finding Protected Documents

The script accepts two parameters: the name of the site to search (not the URL) and an optional folder. If multiple matching sites are found, the user is asked to choose which one to search (Figure 2).

Choosing a SharePoint Online site to investigate for protected documents
Figure 2: Choosing a SharePoint Online site to investigate for protected documents

Once a target site is confirmed, the script figures out if a folder is specified and if that folder exists in the chosen site. In Graph terms, we’re now dealing with drive objects. The default drive is the root folder of a document library and each folder is a different drive. To find folders, we need to find the child objects in the root, identify the right folder, find its drive identifier, and use that to find the files in the folder. All good, clean Graph fun.

The Drive API returns a maximum of 200 items at a time, so some Nextlink processing is needed to fetch the complete set of files in a folder. Each file is examined to figure out if it has a sensitivity label with protection, and if so, the display name of the label. After processing all the files, we tell the user what we’ve found and ask permission to go ahead and decrypt the files (Figure 3). If the user chooses not to proceed, the script writes details of the protected files out to a CSV file.

Reporting the protected files found in a folder in a SharePoint Online document library

Decrypt protected SharePoint files
Figure 3: Reporting the protected files found in a folder in a SharePoint Online document library

Decrypting Files

Files are decrypted by calling the Unlock-SPOSensitivityLabelEncryptedFile cmdlet. There’s no native Graph API call to decrypt SharePoint documents. In any case, we’re running a PowerShell script so it’s easy to call the cmdlet.

An Example to Build On

The script is an example of what’s possible with a combination of PowerShell and Graph API calls. I’m sure that the code and the functionality can be improved (feel free to suggest changes and improvements via GitHub). I’m just happy to demonstrate how things work and how including the Graph enables some extra flexibility.


Read the Office 365 for IT Pros eBook to find much more information about how sensitivity labels work – and many PowerShell examples too!

]]>
https://office365itpros.com/2021/03/25/decrypt-protected-sharepoint-files/feed/ 20 48786