PowerShell scripts to automate the migration from direct license assignment (in Office 365/Azure AD) to group based licensing. This assumes you have on-prem AD and will be adding users to AD based groups, although the scripts could be tweaked to use AAD groups.
I wrote these scripts to automate the migration of users, they are a bit rough and ready so make sure you understand and test these well before using in a live environment.
The scripts will do the following:
1st script assigns users to AD groups for individual plans e.g. components of E3 such as Teams or Planner. It will check which direct assigned licenses the user has, and add them to the appropriate group.
2nd script assigns users to AD groups for complete SKUs e.g. EMS.
3rd script removes any direct assigned SKUs, leaving just the group assigned plans and SKUs (make sure replication has completed, I would leave at least an hour for AAD to make the license changes).
You may only need script 1 or 2 depending on how you organised your AD groups, I would normally recommend one group per service in the plan e.g. Exchange P2, Teams etc, but sometimes it also make sense to have a complete SKU e.g. Visio or EMS.
Firstly, the scripts use a connection script:
write-host "Checking connection"
try
{ $var = Get-AzureADTenantDetail }
catch [Microsoft.Open.Azure.AD.CommonLibrary.AadNeedAuthenticationException]
{ Write-Host "Not connected, authenticate in other window"; Connect-AzureAD}
Script 1:
# This script is used when there are groups for complete SKUs e.g. EMS or VISIO
# Wait for AD sync before removing the direct assigned license for the SKU
# Check if a user has both direct and inherited license see https://docs.microsoft.com/en-us/azure/active-directory/users-groups-roles/licensing-ps-examples
# For Service plan IDs see https://docs.microsoft.com/en-us/azure/active-directory/users-groups-roles/licensing-service-plan-reference
$ErrorActionPreference = "Continue"
# Variables
$CSVfile = "$PSScriptRoot\LicenseToGroupUsers.csv"
# Connect to AzureAD
."$PSScriptRoot\..\Connections\ConnectAzureAD.ps1"
# Enter planID and group, use plan IDs from below.
# For Dynamics choose DYN365_ENTERPRISE_SALES or DYN365_ENTERPRISE_TEAM_MEMBERS (plans not SKUs with same name)
# $SKUs = Get-AzureADSubscribedSku | Where-Object {$_.SkuPartNumber -eq "ENTERPRISEPACK"}
# $SKUs.ServicePlans
#Hash table to map service plans to AD groups, these are just the SKUs that users should be allowed to use. Any others not in this list will be removed.
$SKUToGroup = @{
"1e1a282c-9c54-43a2-9310-98ef728faace"="O365_Dynamics_Sales"
"53818b1b-4a27-454b-8896-0dba576410e6"="O365_ProjectP3_Users"
"8e7a3d30-d97d-43ab-837c-d7701cef83dc"="O365_Dynamics_Team"
"c5928f49-12ba-48f7-ada3-0d743a3601d5"="O365_VisioP2_Users"
"efccb6f7-5641-4e0e-bd10-b4976e1bf68e"="O365_EMS_Users"
"f8a1db68-be16-40ed-86d5-cb42ce701560"="O365_PowerBIPro_Users"
"a403ebcc-fae0-4ca2-8c8c-7a907fd6c235"="O365_PowerBIFree_Users"
}
#These SKUs are licensed using individual components (plans) so ignoring here. Should have been done in 1st script.
$SKUToIgnore = @{
"6fd2c87f-b296-42f0-b197-1e91e994b900"="ENTERPRISEPACK"
}
# $SKUs = Get-AzureADSubscribedSku | Where-Object {$_.SkuPartNumber -eq "ENTERPRISEPACK"}
# $SKUs.ServicePlans
# Build a hash table with SkuPartNumber and SkuId
$AllSKUs = @{
"c5928f49-12ba-48f7-ada3-0d743a3601d5"="VISIOCLIENT"
"1f2f344a-700d-42c9-9427-5cea1d5d7ba6"="STREAM"
"f8a1db68-be16-40ed-86d5-cb42ce701560"="POWER_BI_PRO"
"6fd2c87f-b296-42f0-b197-1e91e994b900"="ENTERPRISEPACK"
"f30db892-07e9-47e9-837c-80727f46fd3d"="FLOW_FREE"
"726a0894-2c77-4d65-99da-9775ef05aad1"="MICROSOFT_BUSINESS_CENTER"
"dcb1a3ae-b33f-4487-846a-a640262fadf4"="POWERAPPS_VIRAL"
"74fbf1bb-47c6-4796-9623-77dc7371723b"="MS_TEAMS_IW"
"e06abcc2-7ec5-4a79-b08b-d9c282376f72"="CRMTESTINSTANCE"
"6070a4c8-34c6-4937-8dfb-39bbc6397a60"="MEETING_ROOM"
"a403ebcc-fae0-4ca2-8c8c-7a907fd6c235"="POWER_BI_STANDARD"
"efccb6f7-5641-4e0e-bd10-b4976e1bf68e"="EMS"
"078d2b04-f1bd-4111-bbd4-b4b1b354cef4"="AAD_PREMIUM"
"9d776713-14cb-4697-a21d-9a52455c738a"="CRMINSTANCE"
"53818b1b-4a27-454b-8896-0dba576410e6"="PROJECTPROFESSIONAL"
"29a2f828-8f39-4837-b8ff-c957e86abe3c"="TEAMS_COMMERCIAL_TRIAL"
"1e1a282c-9c54-43a2-9310-98ef728faace"="DYN365_ENTERPRISE_SALES"
"8e7a3d30-d97d-43ab-837c-d7701cef83dc"="DYN365_ENTERPRISE_TEAM_MEMBERS"
}
# Import the CSV file
try {
$users = import-csv $CSVfile
}
catch {
$errorZero = $Error[0]
write-host "Error: " $errorZero -ForegroundColor Red #Writes the latest error
Break
}
write-warning "About to add the following users to license groups for complete SKU:"
foreach ($user in $users){
write-host $user.UserPrincipalName
}
Read-Host -Prompt "Press Enter to continue or CTRL+C to quit"
foreach ($user in $users){
$groupsToAdd = @()
$groupsToRemove = @()
write-host "Processing" $user.UserPrincipalName
# Get licensed SKUs for the user
$aaduser = get-azureaduser -objectID $user.UserPrincipalName
$SKUs = $aaduser | Select UserPrincipalName,ImmutableID -ExpandProperty AssignedLicenses
#Get the AD ObjectGuid for the group add (cannot use UPN)
$ImmutableID = "" #Null these out otherwise gets reused from previous
#Have to match using the guid
$ImmutableID = $aaduser.ImmutableID
if ($ImmutableID) {$objectGUID = ([GUID][System.Convert]::FromBase64String($ImmutableID)).Guid}
else {
write-warning "Error getting ImmutableID for $UPN, user is likely cloud only, skipping"
Break
}
foreach ($SKU in $SKUs) {
if($ADGroup = $SKUToGroup[$SKU.SkuId]) {
write-host "Adding" $user.UserPrincipalName"to $ADGroup" -ForegroundColor Green
try {
Add-ADGroupMember -Identity $ADGroup -members $objectGUID #-whatif #Note that this will not warn if existing member if the domain is server 2008 R2 or lower
}
catch {
$errorZero = $Error[0]
write-host "Error: " $errorZero -ForegroundColor Red #Writes the latest error
}
}
elseif($ADGroup = $SKUToIgnore[$SKU.SkuId]) {
write-host "Should have already been assigned individual plans:"$SKUToIgnore[$SKU.SkuId]
}
else {
write-host "SKU not used so will be removed during next script:"$AllSKUs[$SKU.SkuId] $SKU.SkuId -ForegroundColor Yellow
}
}
}
Script 2:
# This script is used when there are groups for complete SKUs e.g. EMS or VISIO
# Wait for AD sync before removing the direct assigned license for the SKU
# Check if a user has both direct and inherited license see https://docs.microsoft.com/en-us/azure/active-directory/users-groups-roles/licensing-ps-examples
# For Service plan IDs see https://docs.microsoft.com/en-us/azure/active-directory/users-groups-roles/licensing-service-plan-reference
$ErrorActionPreference = "Continue"
# Variables
$CSVfile = "$PSScriptRoot\LicenseToGroupUsers.csv"
# Connect to AzureAD
."$PSScriptRoot\..\Connections\ConnectAzureAD.ps1"
# Enter planID and group, use plan IDs from below.
# For Dynamics choose DYN365_ENTERPRISE_SALES or DYN365_ENTERPRISE_TEAM_MEMBERS (plans not SKUs with same name)
# $SKUs = Get-AzureADSubscribedSku | Where-Object {$_.SkuPartNumber -eq "ENTERPRISEPACK"}
# $SKUs.ServicePlans
#Hash table to map service plans to AD groups, these are just the SKUs that users should be allowed to use. Any others not in this list will be removed.
$SKUToGroup = @{
"1e1a282c-9c54-43a2-9310-98ef728faace"="O365_Dynamics_Sales"
"53818b1b-4a27-454b-8896-0dba576410e6"="O365_ProjectP3_Users"
"8e7a3d30-d97d-43ab-837c-d7701cef83dc"="O365_Dynamics_Team"
"c5928f49-12ba-48f7-ada3-0d743a3601d5"="O365_VisioP2_Users"
"efccb6f7-5641-4e0e-bd10-b4976e1bf68e"="O365_EMS_Users"
"f8a1db68-be16-40ed-86d5-cb42ce701560"="O365_PowerBIPro_Users"
"a403ebcc-fae0-4ca2-8c8c-7a907fd6c235"="O365_PowerBIFree_Users"
}
#These SKUs are licensed using individual components (plans) so ignoring here. Should have been done in 1st script.
$SKUToIgnore = @{
"6fd2c87f-b296-42f0-b197-1e91e994b900"="ENTERPRISEPACK"
}
# $SKUs = Get-AzureADSubscribedSku | Where-Object {$_.SkuPartNumber -eq "ENTERPRISEPACK"}
# $SKUs.ServicePlans
# Build a hash table with SkuPartNumber and SkuId
$AllSKUs = @{
"c5928f49-12ba-48f7-ada3-0d743a3601d5"="VISIOCLIENT"
"1f2f344a-700d-42c9-9427-5cea1d5d7ba6"="STREAM"
"f8a1db68-be16-40ed-86d5-cb42ce701560"="POWER_BI_PRO"
"6fd2c87f-b296-42f0-b197-1e91e994b900"="ENTERPRISEPACK"
"f30db892-07e9-47e9-837c-80727f46fd3d"="FLOW_FREE"
"726a0894-2c77-4d65-99da-9775ef05aad1"="MICROSOFT_BUSINESS_CENTER"
"dcb1a3ae-b33f-4487-846a-a640262fadf4"="POWERAPPS_VIRAL"
"74fbf1bb-47c6-4796-9623-77dc7371723b"="MS_TEAMS_IW"
"e06abcc2-7ec5-4a79-b08b-d9c282376f72"="CRMTESTINSTANCE"
"6070a4c8-34c6-4937-8dfb-39bbc6397a60"="MEETING_ROOM"
"a403ebcc-fae0-4ca2-8c8c-7a907fd6c235"="POWER_BI_STANDARD"
"efccb6f7-5641-4e0e-bd10-b4976e1bf68e"="EMS"
"078d2b04-f1bd-4111-bbd4-b4b1b354cef4"="AAD_PREMIUM"
"9d776713-14cb-4697-a21d-9a52455c738a"="CRMINSTANCE"
"53818b1b-4a27-454b-8896-0dba576410e6"="PROJECTPROFESSIONAL"
"29a2f828-8f39-4837-b8ff-c957e86abe3c"="TEAMS_COMMERCIAL_TRIAL"
"1e1a282c-9c54-43a2-9310-98ef728faace"="DYN365_ENTERPRISE_SALES"
"8e7a3d30-d97d-43ab-837c-d7701cef83dc"="DYN365_ENTERPRISE_TEAM_MEMBERS"
}
# Import the CSV file
try {
$users = import-csv $CSVfile
}
catch {
$errorZero = $Error[0]
write-host "Error: " $errorZero -ForegroundColor Red #Writes the latest error
Break
}
write-warning "About to add the following users to license groups for complete SKU:"
foreach ($user in $users){
write-host $user.UserPrincipalName
}
Read-Host -Prompt "Press Enter to continue or CTRL+C to quit"
foreach ($user in $users){
$groupsToAdd = @()
$groupsToRemove = @()
write-host "Processing" $user.UserPrincipalName
# Get licensed SKUs for the user
$aaduser = get-azureaduser -objectID $user.UserPrincipalName
$SKUs = $aaduser | Select UserPrincipalName,ImmutableID -ExpandProperty AssignedLicenses
#Get the AD ObjectGuid for the group add (cannot use UPN)
$ImmutableID = "" #Null these out otherwise gets reused from previous
#Have to match using the guid
$ImmutableID = $aaduser.ImmutableID
if ($ImmutableID) {$objectGUID = ([GUID][System.Convert]::FromBase64String($ImmutableID)).Guid}
else {
write-warning "Error getting ImmutableID for $UPN, user is likely cloud only, skipping"
Break
}
foreach ($SKU in $SKUs) {
if($ADGroup = $SKUToGroup[$SKU.SkuId]) {
write-host "Adding" $user.UserPrincipalName"to $ADGroup" -ForegroundColor Green
try {
Add-ADGroupMember -Identity $ADGroup -members $objectGUID #-whatif #Note that this will not warn if existing member if the domain is server 2008 R2 or lower
}
catch {
$errorZero = $Error[0]
write-host "Error: " $errorZero -ForegroundColor Red #Writes the latest error
}
}
elseif($ADGroup = $SKUToIgnore[$SKU.SkuId]) {
write-host "Should have already been assigned individual plans:"$SKUToIgnore[$SKU.SkuId]
}
else {
write-host "SKU not used so will be removed during next script:"$AllSKUs[$SKU.SkuId] $SKU.SkuId -ForegroundColor Yellow
}
}
}
Script 3:
# This script will remove a complete SKU e.g. EMS, E3 etc, along with all components, unless they have been assigned via group.
# This will remove all direct licenses leaving only inherited (group based) licenses, so make sure the users have been added all relevant groups for the SKU first
# Will have no effect if license is group assigned (inherited) only
# Connect to AzureAD
."$PSScriptRoot\..\Connections\ConnectAzureAD.ps1"
#
#$ErrorActionPreference = "SilentlyContinue"
# Variables
$CSVfile = "$PSScriptRoot\LicenseToGroupUsers.csv"
# Enter SKUID and group, use plan IDs from below.
# Get-AzureADSubscribedSku | select SkuID,SKuPartNumber
# Build a hash table with SkuPartNumber and SkuId
$AllSKUs = @{
"c5928f49-12ba-48f7-ada3-0d743a3601d5"="VISIOCLIENT"
"1f2f344a-700d-42c9-9427-5cea1d5d7ba6"="STREAM"
"f8a1db68-be16-40ed-86d5-cb42ce701560"="POWER_BI_PRO"
"6fd2c87f-b296-42f0-b197-1e91e994b900"="ENTERPRISEPACK"
"f30db892-07e9-47e9-837c-80727f46fd3d"="FLOW_FREE"
"726a0894-2c77-4d65-99da-9775ef05aad1"="MICROSOFT_BUSINESS_CENTER"
"dcb1a3ae-b33f-4487-846a-a640262fadf4"="POWERAPPS_VIRAL"
"74fbf1bb-47c6-4796-9623-77dc7371723b"="MS_TEAMS_IW"
"e06abcc2-7ec5-4a79-b08b-d9c282376f72"="CRMTESTINSTANCE"
"6070a4c8-34c6-4937-8dfb-39bbc6397a60"="MEETING_ROOM"
"a403ebcc-fae0-4ca2-8c8c-7a907fd6c235"="POWER_BI_STANDARD"
"efccb6f7-5641-4e0e-bd10-b4976e1bf68e"="EMS"
"078d2b04-f1bd-4111-bbd4-b4b1b354cef4"="AAD_PREMIUM"
"9d776713-14cb-4697-a21d-9a52455c738a"="CRMINSTANCE"
"53818b1b-4a27-454b-8896-0dba576410e6"="PROJECTPROFESSIONAL"
"29a2f828-8f39-4837-b8ff-c957e86abe3c"="TEAMS_COMMERCIAL_TRIAL"
"1e1a282c-9c54-43a2-9310-98ef728faace"="DYN365_ENTERPRISE_SALES"
"8e7a3d30-d97d-43ab-837c-d7701cef83dc"="DYN365_ENTERPRISE_TEAM_MEMBERS"
}
$users = @()
# Import the CSV file
try {
$users = import-csv $CSVfile
}
catch {
$errorZero = $Error[0]
write-host "Error: " $errorZero -ForegroundColor Red # Writes the latest error
Break
}
# Could add a check here to check for AD group membership
foreach ($user in $users){
write-host $user.UserPrincipalName
}
write-warning "These users will have all SKU licenses removed, make sure they have been added to AD groups"
write-host "Make sure you have waited 30 minutes after adding to groups!" -ForegroundColor Red
write-host "Note: This will not affect licenses which have been assigned by group"
Read-Host -Prompt "Press Enter to continue or CTRL+C to quit"
foreach ($user in $users){$user.UserPrincipalName}
$userSKUs = @()
$userSKUIDs = @()
foreach ($user in $users){
$aaduser = get-azureaduser -ObjectId $user.UserPrincipalName
$SKUs = $aaduser | Select UserPrincipalName,ImmutableID -ExpandProperty AssignedLicenses
# Get all the SKUs
foreach ($SKU in $SKUs) {
if($SKUname = $AllSKUs[$SKU.SkuId]) {
# Add the users SKUs to an array
write-host "Removing" $SKUname $SKU.SkuId "from" $user.UserPrincipalName
$LicensesToAssign = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicenses
$LicensesToAssign.AddLicenses = @() # We aren't adding any
$LicensesToAssign.RemoveLicenses = $SKU.SkuId # Get the ID from the hash table.
try {
$aaduser | Set-AzureADUserLicense -AssignedLicenses $LicensesToAssign
}
catch {
$errorZero = $Error[0]
write-host "Error: " $errorZero -ForegroundColor Yellow # Writes the latest error
}
}
else {
write-host $SKU.SkuId "is not listed in the hash table" -ForegroundColor Red
exit
}
}
}