Last active
June 7, 2023 04:23
-
-
Save mrik23/2ed37ce0c7c4a79605bdcf052e29b391 to your computer and use it in GitHub Desktop.
Remove in bulk direct assigned license to users who have group assigned license with Azure AD PowerShell v1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
Modified version of the script from Microsoft Documentation. | |
Removed the part that checks if the users is assigned more products than the group assigned license. | |
Added connection part and help to find Sku and Group Object ID. | |
This script requires Azure AD (aks MSOL) PowerShell v1. It doesn't seem possible to do so with v2. | |
Ref: https://docs.microsoft.com/en-us/azure/active-directory/active-directory-licensing-ps-examples | |
#> | |
Import-Module MSOnline | |
$UserCredential = Get-Credential | |
Connect-MsolService -Credential $UserCredential | |
#Get License Sku for the tenant | |
Get-MsolAccountSku | |
#license to be removed | |
$skuId = "Contoso:STANDARDPACK" | |
#add here the group with license assignment to be processed | |
$LicensedGroup = "Licensed_Group" | |
#Get the group Object ID | |
$groupId = (Get-MsolGroup -SearchString $LicensedGroup).ObjectId | |
#Helper functions used by the script | |
#Returns TRUE if the user has the license assigned directly | |
function UserHasLicenseAssignedDirectly | |
{ | |
Param([Microsoft.Online.Administration.User]$user, [string]$skuId) | |
$license = GetUserLicense $user $skuId | |
if ($license -ne $null) | |
{ | |
#GroupsAssigningLicense contains a collection of IDs of objects assigning the license | |
#This could be a group object or a user object (contrary to what the name suggests) | |
#If the collection is empty, this means the license is assigned directly - this is the case for users who have never been licensed via groups in the past | |
if ($license.GroupsAssigningLicense.Count -eq 0) | |
{ | |
return $true | |
} | |
#If the collection contains the ID of the user object, this means the license is assigned directly | |
#Note: the license may also be assigned through one or more groups in addition to being assigned directly | |
foreach ($assignmentSource in $license.GroupsAssigningLicense) | |
{ | |
if ($assignmentSource -ieq $user.ObjectId) | |
{ | |
return $true | |
} | |
} | |
return $false | |
} | |
return $false | |
} | |
#Returns TRUE if the user is inheriting the license from a specific group | |
function UserHasLicenseAssignedFromThisGroup | |
{ | |
Param([Microsoft.Online.Administration.User]$user, [string]$skuId, [Guid]$groupId) | |
$license = GetUserLicense $user $skuId | |
if ($license -ne $null) | |
{ | |
#GroupsAssigningLicense contains a collection of IDs of objects assigning the license | |
#This could be a group object or a user object (contrary to what the name suggests) | |
foreach ($assignmentSource in $license.GroupsAssigningLicense) | |
{ | |
#If the collection contains at least one ID not matching the user ID this means that the license is inherited from a group. | |
#Note: the license may also be assigned directly in addition to being inherited | |
if ($assignmentSource -ieq $groupId) | |
{ | |
return $true | |
} | |
} | |
return $false | |
} | |
return $false | |
} | |
#Returns the license object corresponding to the skuId. Returns NULL if not found | |
function GetUserLicense | |
{ | |
Param([Microsoft.Online.Administration.User]$user, [string]$skuId, [Guid]$groupId) | |
#we look for the specific license SKU in all licenses assigned to the user | |
foreach($license in $user.Licenses) | |
{ | |
if ($license.AccountSkuId -ieq $skuId) | |
{ | |
return $license | |
} | |
} | |
return $null | |
} | |
#process staging removal for only 20 members in the group first | |
Get-MsolGroupMember -MaxResults 20 -GroupObjectId $groupId | | |
#get full info about each user in the group | |
Get-MsolUser -ObjectId {$_.ObjectId} | | |
Foreach { | |
$user = $_; | |
$operationResult = ""; | |
#check if Direct license exists on the user | |
if (UserHasLicenseAssignedDirectly $user $skuId) | |
{ | |
#check if the license is assigned from this group, as expected | |
if (UserHasLicenseAssignedFromThisGroup $user $skuId $groupId) | |
{ | |
#remove the direct license from user | |
Set-MsolUserLicense -ObjectId $user.ObjectId -RemoveLicenses $skuId | |
$operationResult = "Removed direct license from user." | |
} | |
else | |
{ | |
$operationResult = "User does not inherit this license from this group. License removal was skipped." | |
} | |
} | |
else | |
{ | |
$operationResult = "User has no direct license to remove. Skipping." | |
} | |
#format output | |
New-Object Object | | |
Add-Member -NotePropertyName UserId -NotePropertyValue $user.ObjectId -PassThru | | |
Add-Member -NotePropertyName OperationResult -NotePropertyValue $operationResult -PassThru | |
} | Format-Table | |
<#You can then process all members in the group if the result of staging is OK | |
Get-MsolGroupMember -All -GroupObjectId $groupId | | |
#get full info about each user in the group | |
Get-MsolUser -ObjectId {$_.ObjectId} | | |
Foreach { | |
$user = $_; | |
$operationResult = ""; | |
#check if Direct license exists on the user | |
if (UserHasLicenseAssignedDirectly $user $skuId) | |
{ | |
#check if the license is assigned from this group, as expected | |
if (UserHasLicenseAssignedFromThisGroup $user $skuId $groupId) | |
{ | |
#remove the direct license from user | |
Set-MsolUserLicense -ObjectId $user.ObjectId -RemoveLicenses $skuId | |
$operationResult = "Removed direct license from user." | |
} | |
else | |
{ | |
$operationResult = "User does not inherit this license from this group. License removal was skipped." | |
} | |
} | |
else | |
{ | |
$operationResult = "User has no direct license to remove. Skipping." | |
} | |
#format output | |
New-Object Object | | |
Add-Member -NotePropertyName UserId -NotePropertyValue $user.ObjectId -PassThru | | |
Add-Member -NotePropertyName OperationResult -NotePropertyValue $operationResult -PassThru | |
} | Format-Table | |
#> | |
if someone test the above out and it works please report back
Hey Neil,Thanks a lot for the tweaking. I will give it a test and provide update.RegardsNitin On 7 Jun 2023 01:41, Neil Sabol ***@***.***> ***@***.*** commented on this gist.Hello Nitin - yeah, understand the pain. Even a one second sleep adds A LOT of delay when processing thousands of users. If you are brave, feel free to give this a whirl:https://gist.github.com/neil-sabol/250d43047c6c061067fca274588a4ce8That is my first pass at porting this script to use the MgGraph (Microsoft Graph) PowerShell module instead of MSOnline. My very cursory testing suggests it works, but I cannot say 100%. Additional testing is needed. If you are able to do that, it would be appreciated.Pending the outcome, @mrik23, would you mind incorporating the updates into your Gist since you clearly have the SEO advantage here? ;)Thank you all,-N—Reply to this email directly, view it on GitHub or unsubscribe.You are receiving this email because you commented on the thread.Triage notifications on the go with GitHub Mobile for iOS or Android.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello Nitin - yeah, understand the pain. Even a one second sleep adds A LOT of delay when processing thousands of users. If you are brave, feel free to give this a whirl:
https://gist.github.com/neil-sabol/250d43047c6c061067fca274588a4ce8
That is my first pass at porting this script to use the MgGraph (Microsoft Graph) PowerShell module instead of MSOnline. My very cursory testing suggests it works, but I cannot say 100%. Additional testing is needed. If you are able to do that, it would be appreciated.
Pending the outcome, @mrik23, would you mind incorporating the updates into your Gist since you clearly have the SEO advantage here? ;)
Thank you all,
-N