Showing posts with label M365. Show all posts
Showing posts with label M365. Show all posts

Monday, May 2, 2022

Mailbox moves between Exchange Online tenants

For the last several years I've been involved mostly in tenant to tenant migration projects. These projects typically include migration of email, teams, and SharePoint. My focus is on email migration.

For migration, there are a number of third party tools such as MigrationWiz (Bit Titan) and On Demand Migration for Email (Quest ODME) that can be used. I don't know all of the details for all tools, but at least in the case of ODME, the migration is copies mailbox data, but doesn't synchronize it. So, if you migrate data from source to target and the user later deleted data from the source, it is not deleted in the target. Occasionally, this is annoying and causes confusion for users.

Microsoft has tenant-to-tenant mailbox moves in Preview. This type of move is a migration batch like moving mailboxes between on-premises Exchange and Exchange Online. In this case, you get true syncing which is a better quality move. It looks like a bit of a pain to setup and manage, but I expect the experience will improve over time.

The documentation claims that mailbox permissions like Full Access are maintained as long as users are part of the same migration batch. It does not mention inbox rules.

Another interesting item that's coming up is Microsoft 365 cross-tenant SMTP domain sharing. This  allows an email domain to exist in two Microsoft 365 tenants simultaneously and will make it easier to manage incremental migrations between tenants when you want to maintain the same email address. The feature is currently in private preview, but is scheduled to be moved into public preview in June 2022.

You can monitor the development status at:

Wednesday, November 17, 2021

Managing Microsoft 365 Licenses by using Microsoft Graph

Microsoft has announced that after June 2022, the MSOL and AzureAD cmdlets for managing user licenses in Microsoft 365 will cease working. These cmdlets rely on management functionality that is being retired. To manage licenses programmatically, you need to start using Microsoft Graph.

Here is the announcement:

Microsoft Graph is a web-API that you can use to manage Microsoft 365 users, groups, and services. If you're a programmer, then perhaps the idea of building a web request to perform administrative tasks sounds like a good idea. However, for an admin guy like me that typically uses PowerShell cmdlets for management tasks, building web requests is a bit painful. Fortunately, the Microsoft Graph PowerShell SDK has been released that provides PowerShell cmdlets to access Microsoft Graph features.

To get more information about the Microsoft Graph PowerShell SDK:

 Connecting with Microsoft Graph

Just like you use Connect-AzureAD or Connect-MsolService, for Microsoft Graph, you use Connect-MgGraph. When you connect, you need to specify a scope that defines your permissions. So, unlike previous versions, the connection does not automatically gain the full permissions based on your roles like Global Admin. I haven't experimented with exactly which scopes are required to manage user licenses. However, I can confirm that the following example does work.

Connect-MgGraph -Scopes "User.ReadWrite.All","Directory.ReadWrite.All"

To get more information about Microsoft Graph scopes:

License Structure and Naming

If you've been managing licenses through the web interface in Microsoft 365 or the MSOL cmdlets, you're used to seeing license names such as Office 365 E3. When you manage licenses by using Microsoft Graph, you need to know the SkuId property of the licenses available in your tenant. You can obtain the SkuId for a license by using Get-MgSubscribedSku as shown in the following figure. The SkuPartNumber property is a more user friendly name that you can recognize.

Within each licenses type, there are also service plans. These correlate with apps provided by a license such as Exchange Online (Plan 2). To enable or disable the service plans, you need to use the ServicePlanId for a  service plan. If you place your subscribed SKUs in a variable, you can view the service plans included in the SKU as shown in the following figure.

To get a list of generally available SKUs and their service plans:

Viewing Assigned Licenses

You can view the licenses assigned to a user by using the Get-MgUserLicenseDetail cmdlet as shown in the following example:

Get-MgUserLicenseDetail -UserId user@domain.com

The results of this command return the users license assigned to the user. An array of licenses is returned if multiple licenses have been assigned. Within each license returned, you can view the ServicePlans property to see if any service plans have been disabled for a user.

You can also query assigned license information by using Get-MgUser. The licensing information isn't returned by default and you need to specify that the AssignedLicenses property will be retrieved as shown in the figure below. Notice that these results list the service plans that are disabled for a license.

Querying Users with Assigned Licenses

If you want to query all of the users with a specific license, you can do this by using Get-MgUser with a filter for a specific SkuId. The example below shows the syntax.

Get-MgUser -Filter "assignedLicenses/any(x:x/skuId eq 78e66a63-337a-4a9a-8959-41c6654dfb56)" -Property AssignedLicenses,UserPrincipalName,Id

When you are filtering based on AssignedLicenses there are some limitations on the results returned. By default, only 100 results are returned. If you use the PageSize parameter, you can specify up to 999 results are returned as shown below.

Get-MgUser -Filter "assignedLicenses/any(x:x/skuId eq 78e66a63-337a-4a9a-8959-41c6654dfb56)" -PageSize 999

If you have a larger tenant, and you try to use the All parameter to return results larger than these limits, you will be the following error Get-MgUser : The specified page token value has expired and can no longer be included in your request.To avoid this error, you need to query a list of all users and then filter by using Where-Object as shown below.

$allusers = Get-MgUser -All -Property AssignedLicenses,UserPrincipalName,Id,DisplayName
$A1plusUsers = $allusers | Where-Object {$_.AssignedLicenses.SkuId -contains "78e66a63-337a-4a9a-8959-41c6654dfb56"}

Modifying Assigned Licenses

To add or remove licenses for a user, you use the Set-MgUserLicense cmdlet. When you run the cmdlet, you need to provide the following parameters:

  • UserId. The user being modified. You can specify the user by the object Id or UserPrincipalName.
  • AddLicenses. A hash table that specifies the SkuId of a license being added and the ServicePlanId of any service plans that are being disabled.
  • RemoveLicenses. A string that identifies the SkuId of a license being removed.

The following code shows how to build a hash table for the AddLicenses parameter. This specifies a license SkuId and a service plan that's disabled in the license.

$A1FacultySku = @{
    SkuID = "94763226-9b3c-4e75-a931-5c89701abe66"
    DisabledPlans = "9aaf7827-d63c-4b61-89c3-182f06f82e5c"
}

The following code lists a SkuID that will be disabled.

$A1PlusFacultySku = "78e66a63-337a-4a9a-8959-41c6654dfb56"

The command that modifies the user license is below. Note that the UserId parameter will accept a UPN also.

Set-MgUserLicense -UserId $User.Id -AddLicenses $A1FacultySku -RemoveLicenses $A1PlusFacultySku

The RemoveLicenses and AddLicenses parameters are mandatory. If you don't provide an empty array, you'll get an error such as Set-MgUserLicense : One or more parameters of the function import 'assignLicense' are missing from the request payload. The missing parameters are: removeLicenses. If you don't want to remove any licenses, you need to provide an empty array for RemoveLicenses as shown below. If you are only removing licenses, you need to provide an empty array for the AddLicenses parameter.

Set-MgUserLicense -UserId $User.id -AddLicenses $A1FacultySku -RemoveLicenses @()

If you want to modify the disabled plans for a licenses, you build a new hash table with the license and all of the plans you want disabled. Then you apply the new hash table with the AddLicenses parameter. The new license assignment overwrites the existing license assignment.

If you want to add multiple licenses, you can provide a comma separated list of hash tables. I have not explicitly tested, but I think providing an array with the hash tables would also work.

If you want to remove multiple licenses, create an array with the SkuIDs that you want to remove.