Office 365 PowerShell tip: Automatically assign licenses based on AD attribute

Important note: The end of an era with licensing scripts is near… and the beginning of a new one with Azure AD Group Based Licensing is here. Group Based Licensing is now in preview and currently requires a paid Azure AD Subscription. Try it out and give Microsoft your feedback on how they can make it even better! 

Using DirSync in combination with Office 365 / Windows Azure Active Directory is great.
It automates user creation and makes you able to master all user creation changes from on premises.
There is just one(or two) things you need to do manually, assign licenses…
This can be done both in the portal or with PowerShell.

Depending on how your license structure looks like and how large environment you have, you might want to automate this in a more convenient way. There is a Wiki article on TechNet with a few examples on how to automate it as well (both with Security Groups and AD attributes). http://social.technet.microsoft.com/wiki/contents/articles/15905.how-to-use-powershell-to-automatically-assign-licenses-to-your-office365-users.aspx

Until Microsoft has come up with an integrated solution for this in DirSync or something else, we have to stick with PowerShell…

The case:
In my case we want to assign Office 365 licenses based on a local AD attribute of your choice fully automated and minimal input.
We also want a bit of logging so we are able to find and fix errors as easy as possible.

In order to assign a license in Office 365, we need to assign two attributes on a user(of course the user must exist…):
UsageLocation and Licenses
Also, if a user has a valid license assigned, the boolean IsLicensed will be set to True.
2013-12-30 15-26-24

The ‘Licenses’ attribute contains “tenantname:LICENSESKU”, which in my case above is “mstlegacy:ENTERPRISEPACK” for an E3 license.
So, when assigning a license with PowerShell we need to know the SKUID of the particular license we are using.

We can also disable specific parts of a license, for example SharePoint when we assign the license. More details about ‘manual’ PowerShell assignment of licenses in Office 365 you’ll find here.

Solution

My script activates Office 365 users based on the AD attribute of your choise.
It requires you to populated the AD attribute with a string that identifies the license type for the particular user.
Default AD attribute used in the script is employeeType.

Supported license types with AD attributes as below (attribute to the left):
E1 – Office 365 for Enterprises E1
E3 – Office 365 for Enterprises E3
K1 – Deskless user without Office
E2 – Deskless user with Office
A1S – Office 365 for Education A1 (Students)
A2S – Office 365 for Education A2 (Students)
A3S – Office 365 for Education A3 (Students)
A1F – Office 365 for Education A1 (Faculty)
A2F – Office 365 for Education A2 (Faculty)
A3F – Office 365 for Education A3 (Faculty)
2014-01-01 15-31-22
In case the AD attribute Country is populated in your AD, it will automatically use that attribute to populate UsageLocation of the user in Office 365, otherwise it will default back to the parameter $DefaultCountry.

It will log all changes and errors to the logfile MSOL-UserActivation.log within the same folder you run the script.
Running the script
Prereqs:

Example – first time use:
As a preparation have to change the default value of $AdminUser parameter or use the parameter -AdminUser to an actual adminuser of your tenant.
On first use it’ll then ask you for the password and then store encrypted with DPAPI to a file in the same folder where you run the script. This so you can run without user interaction in the future.

.\ActivateMSOLUser.ps1 -AdminUser DirSync@mstlegacy.onmicrosoft.com

2013-12-30 16-31-16
(Hopefully you will type in the correct password… 🙂 )

After you’ve finished with the first time configuration, you are ready to actually start assigning licenses.
Example 1 – Activate all K1,K2 and E3 licenses with default AD attribute (employeeType)

.\ActivateMSOLUser.ps1 -AdminUser dirsync@mstlegacy.onmicrosoft.com -Licenses K1,K2,E3

2013-12-30 16-45-58
As seen above, 2 unlicensed users were found in Office 365, but only one of them had the required local AD attribute (employeeType in this case), set to ‘E3’.
I also go two errors since I didn’t have any K1 or K2 licenses in my tenant.

Example 2 – Activate all E3 licenses with custom AD attribute (msDS-cloudExtensionAttribute1) and MasterOnPremise

.\ActivateMSOLUser.ps1 -AdminUser dirsync@mstlegacy.onmicrosoft.com -Licenses E3 -LicenseAttribute msDS-cloudExtensionAttribute1 -MasterOnPremise

2013-12-30 16-57-28
In the above example we found one user to activate, but with the switch -MasterOnPremise we looked in to our local ad instead checking Office 365 for unlicensed users, and reported back to the attribute when the license was successfully assigned.
This can be useful if you for some reason have a lot of unlicensed users in Office 365 that you intend to keep that way.

Note: Since the -MasterOnPremise function writes back to your AD, the account that runs the script will in that case need write permissions to that AD attribute.

2013-12-30 17-03-59

Next Step
In order to make this fully automated, you will also need to schedule this as a task that runs (preferably) as often as your DirSync goes which by default is every 3 hours, an article on how to do that is here.

I’ve been running the script for a while and it works very well, of course some parts can be done more efficient. If you find any bugs or other issues, let me know!

The script can be downloaded from here, or cut’n’paste it from below…

Happy Licensing!
/Johan

ActivateMSOLUsers.ps1

<# .SYNOPSIS     The script automatically assigns licenses in Office 365 based on AD-attributes of your choice. .DESCRIPTION     Fetches your Office 365 tenant Account SKU's in order to assign licenses based on them.     Sets mandatory attribute UsageLocation for the user in Office 365 based on local AD attribute Country (or default)     If switch -MasterOnPremise has been used:     Assigns Office 365 licenses to the user based on an AD-attribute and reports back to the same attribute if a license was successfully assigned.     If not:     Looks after unlicensed users in Office365, checks if the unlicensed user has the right attribute in ad to be verified, otherwise not. .PARAMETER MasterOnPremise     Switch, If used, it  Assigns Office 365 licenses to the user based on an AD-attribute and reports back to the same attribute if a license was successfully assigned.     .PARAMETER Licenses     Array, defines the licenses used and to activate, specific set of licenses supported. "K1","K2","E1","E3","A1S","A2S","A3S","A1F","A2F","A3F" .PARAMETER LicenseAttribute     String, the attribute  used on premise to identify licenses .PARAMETER AdminUser     Adminuser in tenant .PARAMETER DefaultCountry     Defaultcountry for users if not defined in active directory              .NOTES     File Name: ActivateMSOLUsers.ps1     Author   : Johan Dahlbom, johan[at]dahlbom.eu     The script are provided “AS IS” with no guarantees, no warranties, and they confer no rights.     #>
param(
[parameter(Mandatory=$false)]
[Switch]
$MasterOnPremise = $false,

[parameter(Mandatory=$false,HelpMessage="Please provide the SKU's you want to activate")]
[ValidateSet("K1","K2","E1","E3","A1S","A2S","A3S","A1F","A2F","A3F")]
[ValidateNotNullOrEmpty()]
[array]
$Licenses,

[parameter(Mandatory=$false)]
[string]
$LicenseAttribute = "employeeType",

[parameter(Mandatory=$false)]
[string]
$AdminUser = "admin@tenant.onmicrosoft.com",

[parameter(Mandatory=$false)]
[string]
$DefaultCountry = "SE"
)
#Define variables
$PSScriptRoot = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition
$Logfile = ($PSScriptRoot + "\MSOL-UserActivation.log")
$TimeDate = Get-Date -Format "yyyy-MM-dd-HH:mm"
$SupportedSKUs = @{
                    "K1" = "DESKLESSPACK"
                    "K2" = "DESKLESSWOFFPACK"
                    "E1" = "STANDARDPACK"
                    "E3" = "ENTERPRISEPACK"
                    "A1S" = "STANDARDPACK_STUDENT"
                    "A2S" = "STANDARDWOFFPACK_STUDENT"
                    "A3S" = "ENTERPRISEPACK_STUDENT"
                    "A1F" = "STANDARDPACK_FACULTY"
                    "A2F" = "STANDARDWOFFPACK_FACULTY"
                    "A3F" = "ENTERPRISEPACK_FACULTY"

                    }

################################################
##### Functions

#Function to Import Modules with error handling
Function Import-MyModule
{
Param([string]$name)
    if(-not(Get-Module -name $name))
    {
        if(Get-Module -ListAvailable $name)
        {
            Import-Module -Name $name
            Write-Host "Imported module $name" -ForegroundColor Yellow
        }
        else
        {
            Throw "Module $name is not installed. Exiting..."
        }
    }
    else
    {
        Write-Host "Module $name is already loaded..." -ForegroundColor Yellow }
    }

#Function to log to file and write output to host
Function LogWrite
{
Param ([string]$Logstring)
Add-Content $Logfile -value $logstring -ErrorAction Stop
Write-Host $logstring
}
#Function to activate your users based on ad attribute
Function Activate-MsolUsers
{
Param([string]$SKU)

    begin {
        #Set counter to 0
        $i = 0
    }

        process {

            #Catch and log errors
            trap {
                $ErrorText = $_.ToString()
		        LogWrite "Error: $_"
                $error.Clear()
                Continue
            }

            #If the switch -MasterOnPremise has been used, start processing licenses from the AD-attribute

            if ($MasterOnPremise) {

		    $UsersToActivate = Get-ADUser -filter {$LicenseAttribute -eq $SKU} -Properties $LicenseAttribute,Country -ErrorAction Stop

                if ($UsersToActivate)
			    {
			    $NumUsers = ($UsersToActivate | Measure-Object).Count

			    LogWrite "Info: $NumUsers user(s) to activate with $SKU"
                foreach($user in $UsersToActivate) {

                          trap {
                                $ErrorText = $_.ToString()
		                        LogWrite "Error: $_"
                                $error.Clear()
                                Continue
                            }
                        $UPN = $user.userprincipalname
                        $Country = $user.Country
                        LogWrite "Info: Trying to assign licenses to: $UPN"
                                if (!($country)) {
                                    $Country = $DefaultCountry }

                        if ((Get-MsolUser -UserPrincipalName $UPN -ErrorAction Stop)) {

                                Set-MsolUser -UserPrincipalName $UPN -UsageLocation $country -Erroraction Stop
                                Set-MsolUserLicense -UserPrincipalName $UPN -AddLicenses $SKUID -Erroraction Stop
									#Verify License Assignment
									if (Get-MsolUser -UserPrincipalName $UPN | Where-Object {$_.IsLicensed -eq $true}) {
										Set-ADUser $user -Replace @{$LicenseAttribute=$SKU+' - Licensed at ' + $TimeDate}
										LogWrite "Info: $upn successfully licensed with $SKU"
                                        $i++;
									}
									else
									{
										LogWrite "Error: Failed to license $UPN with $SKU, please do further troubleshooting"

									}
                        }
				    }
			    }
            }
	#If no switch has been used, process users and licenses from MSOnline
            else {
			    $UsersToActivate = Get-MsolUser -UnlicensedUsersOnly -All
				    if ($Userstoactivate)
				    {
				    $NumUsers = $UsersToActivate.Count
				    LogWrite "Info: $NumUsers unlicensed user(s) in tenant: $($SKUID.ToLower().Split(':')[0]).onmicrosoft.com"
				    foreach ($user in $UsersToActivate) {
                                trap {
                                        $ErrorText = $_.ToString()
		                                LogWrite "Error: $_"
                                        $error.Clear()
                                        Continue
                                }

					        $UPN = $user.UserPrincipalName
					        $ADUser = Get-Aduser -Filter {userPrincipalName -eq $UPN} -Properties $LicenseAttribute,Country -ErrorAction Stop
					        $Country = $ADUser.Country
							    if (!($Country)) {
								$Country = $DefaultCountry
							    }
					        if ($ADUser.$LicenseAttribute -eq $SKU) {
						        LogWrite "Info: Trying to assign licenses to: $UPN"
						        Set-MsolUser -UserPrincipalName $UPN -UsageLocation $country -Erroraction Stop
						        Set-MsolUserLicense -UserPrincipalName $UPN -AddLicenses $SKUID -Erroraction Stop

						        #Verify License Assignment
						        if (Get-MsolUser -UserPrincipalName $UPN | Where-Object {$_.IsLicensed -eq $true}) {
							    LogWrite "Info: $upn successfully licensed with $SKU"
                                $i++;
						        }
						   else
						        {

                                LogWrite "Error: Failed to license $UPN with $SKU, please do further troubleshooting"

						        }
				        }
			        }
		        }
	        }
        }

    End{
    LogWrite "Info: $i user(s) was activated with $license ($SKUID)"
	}
}
################################################
#Import modules required for the script to run
Import-MyModule MsOnline
Import-MyModule ActiveDirectory

	#Start logging and check logfile access
	try
	{
		LogWrite -Logstring "**************************************************`r`nLicense activation job started at $timedate`r`n**************************************************"
	}
	catch
	{
		Throw "You don't have write permissions to $logfile, please start an elevated PowerShell prompt or change NTFS permissions"
	}

    #Connect to Azure Active Directory
	try
	{
		$PasswordFile = ($PSScriptRoot + "\$adminuser.txt")
		if (!(Test-Path  -Path $passwordfile))
			{
				Write-Host "You don't have an admin password assigned for $adminuser, please provide the password followed with enter below:" -Foregroundcolor Yellow
				Read-Host -assecurestring | convertfrom-securestring | out-file $passwordfile
			}
		$password = get-content $passwordfile | convertto-securestring -ErrorAction Stop
		$credentials = new-object -typename System.Management.Automation.PSCredential -argumentlist $adminuser,$password -ErrorAction Stop
		Connect-MsolService -credential $credentials -ErrorAction Stop
	}
	catch [system.exception]
	{
        $err = $_.Exception.Message
		LogWrite "Error: Could not Connect to Office 365, please check connection, username and password.`r`nError: $err"
        exit
	}

if (!$licenses) {
    LogWrite "Error: No licenses specified, please specify a supported license`r`nInfo: Supported licenses are: K1,K2,E1,E3,A1S,A2S,A3S,A1F,A2F,A3F!"
}
else
{

#Start processing license assignment
    foreach($license in $Licenses) {

        $SKUID = (Get-MsolAccountSku | Where-Object {$_.AccountSkuId -like "*:$($SupportedSKUs.Get_Item($license))"}).AccountSkuId

            if ($SKUID)
	        {
		        Activate-MsolUsers -SKU $license
	        }
	        else
	        {
		    LogWrite "Error: No $license licenses in your tenant!"
	        }
    }
}
LogWrite -Logstring "**************************************************"
Advertisements

33 thoughts on “Office 365 PowerShell tip: Automatically assign licenses based on AD attribute

  1. Andreas Gidlöf

    Thanks for this nice script!
    I only have one problem with it..I can’t get usagelocation to work, it sets the default usagelocation for everyone, I tried removing the “default” but then I get an error saying I must provide a required property “Usagelocation”.
    Do u have any idea on how to fix this?

    Thanks

    Reply
    1. Johan Dahlbom Post author

      Hi Andreas,
      Thanks for reading!
      I looked in to it and it seems like there is a small “bug” in the script, which prevents you from using anything but $defaultcountry. 🙂
      Row 122 and (variable $userstoactivate) and row 178 (variable $aduser) has been updated accordingly.
      Please let me know if you have any other questions, or feature requests!

      /Johan

      Reply
  2. Pingback: Office 365: Assign licenses based on groups using PowerShell | Tailspintoys – 365lab.net

  3. Lee Allen

    Brilliant script…. has really helped me a lot! I would say that I needed to edit lines 59 and 62 as they had one “pack” to many!!!!

    Before
    “A2S” = “STANDARDWOFFPACKPACK_STUDENT”
    “A2F” = “STANDARDWOFFPACKPACK_FACULTY”

    After
    “A2S” = “STANDARDWOFFPACK_STUDENT”
    “A2F” = “STANDARDWOFFPACK_FACULTY”

    Reply
  4. Steffen

    Hi,

    Thanks for the script…it looks promising:-)

    I’ve started to look into how we can implement automatic license assignment now that we’re moving to Office 365 in a near future. I have a few questions to the script that I hope you can answer.

    I do not see any difference in using the option MasterOnPremise. If I use it the script will check the local AD for users with a valid value in $LicenseAttribute, and then assign the license in Office 365. If I do not use it, then the script finds all unlicensed users in Office365, but only assigns a license to users with a valid value in $LicenseAttribute in local AD. It seems to me that the end result will be the same (not considering that MasterOnpremise also report back to local AD)?

    You write that it is possible to disable specific parts of a license. I cannot find any code that does that in the script, so I guess that’s not implemented in the current script at the moment?

    /Steffen

    Reply
    1. Johan Dahlbom Post author

      Hi Steffen,
      Thanks for Reading!

      The –MasterOnPremise switch was actually built for a special use case at a Customer. They had lots and lots of users synced to O365 that were not to be licensed. So, before adding the masteronpremise-switch, it took quite an extra amount of time to go through all unlicensed users. So you are right, the end result will be the same, except for the write-back to the attribute.
      But using the -MasterOnPremise switch is better in the particular case as above.

      Regarding assigning parts of an SKU, just as written in the post, it would possible, but not implemented in this particular script. To disable specific options for each plan, look into the following site: http://blogs.technet.com/b/treycarlee/archive/2013/11/01/list-of-powershell-licensing-sku-s-for-office-365.aspx.

      /Johan

      Reply
      1. Pradeep

        Hi

        Thanks a lot for this nice script . Everything works great but when I run the script the usagelocation gets set to default. Can you help with this?

        Pradeep

  5. Michael

    Great Script. Does anyone have an idea on how to check the box for
    SKU OFFICESUBSCRIPTION_STUDENT
    I’ve assigned the students the A2S attribute and that works but I also need them to have this license.
    Thanks

    Reply
  6. Richard

    Hi Johan,
    thanks a lot for this script.
    I would like ask you, if it’s possible modify it with possibility changing license?
    For example changing licence E1 -> E3 when I will change it in AD.
    This PS script is too complex to modify it by myself.

    Reply
  7. K

    Great script – just found it and it works flawlessly – may i suggest that you should be able to specify the string to match in AD? (Say you allready tagged with anoither string) and also be able to specify what lisence to attach? (ex OFFICESUBSCRIPTION_STUDENT) – those two features would make it much more flexible and future-proof 🙂

    Reply
    1. Johan Dahlbom Post author

      Hi, Thanks for Reading! You can actually do that already. If you change the parameter e.g. “E3” to ENTERPRISEPACK, it’ll work.

      Just let me know if you have further questions.

      /Johan

      Reply
  8. Michael Clementin

    I’m trying to run this, but now sure what SkuID applies to “STANDARDWOFFPACK_IW_FACULTY” which is “Office 365 Education for Faculty” or “Office 365 Education for Students”.

    Reply
  9. Michael Clementin

    Thank you Johan, I was able to update the SKU’s accordingly.

    Now, dumb question…It will not accept the password for my @tenant.onmicrosoft.com password. I’ve verified the password by logging directly into the Office portal, cut/paste, but nothing works, same error every time: Error: The server has rejected the client credentials.

    Reply
  10. Michael Clementin

    Johan, yes I can connect manually. Just can’t figure out where I’m suppose enter the license types. When I run the script, I get the following error:

    DefaultUsageLocation: : US
    Configuring Scripts
    The script Get-LicensingInputFromAD.ps1 has already been configured. Do you want
    to reset the configuration (Y/N)?
    Y
    Please enter the ldapDisplayName of the Attribute you will be using to search fo
    r users in AD.
    ldapdisplayName: : userPrinicipalName
    Please enter the Filter Value for the attribute you entered above.
    The default value is * so all objects will be returned having the attribute set
    regardless of the value in the attribute.
    to limit the objects returned you can use any valid LDAP Filter syntax.
    Attribute Filter: employeeType
    You have selected an attribute different then employeeType
    Please specify the ldapDisplayName of the attribute of the user object containin
    g the license information.
    ldapDisplayName: extensionAttribute14
    0
    Exception calling “FindAll” with “0” argument(s): “Unknown error (0x80005000)”
    At C:\O365LicenseScriptsStudents\SetupScript.ps1:261 char:1
    + $results=$searcher.FindAll()
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : COMException

    Configuring Folders
    To configure the correct License Information in your on premises Active Director
    y use the following information
    EXCHANGESTANDARD_STUDENT has the following options: , INTUNE_O365, EXCHANGE_S_ST
    ANDARD
    STANDARDWOFFPACK_IW_FACULTY has the following options: , YAMMER_EDU, OFFICESUBSC
    RIPTION, SHAREPOINTWAC_EDU, SHAREPOINTSTANDARD_EDU, EXCHANGE_S_STANDARD, MCOSTAN
    DARD
    EOP_ENTERPRISE_FACULTY has the following options: No options available.
    STANDARDWOFFPACK_STUDENT has the following options: , INTUNE_O365, YAMMER_EDU, S
    HAREPOINTWAC_EDU, MCOSTANDARD, SHAREPOINTSTANDARD_EDU, EXCHANGE_S_STANDARD
    STANDARDWOFFPACK_IW_STUDENT has the following options: , YAMMER_EDU, SHAREPOINTW
    AC_EDU, SHAREPOINTSTANDARD_EDU, EXCHANGE_S_STANDARD, OFFICESUBSCRIPTION, MCOSTAN
    DARD
    EXCHANGESTANDARD_ALUMNI has the following options: , INTUNE_O365, EXCHANGE_S_STA
    NDARD
    EOP_ENTERPRISE_STUDENT has the following options: No options available.
    STANDARDWOFFPACK_FACULTY has the following options: , INTUNE_O365, YAMMER_EDU, S
    HAREPOINTWAC_EDU, MCOSTANDARD, SHAREPOINTSTANDARD_EDU, EXCHANGE_S_STANDARD
    License Information has been stored in file licenseinformation.txt for your refe
    rence.
    Setup Complete.

    Reply
  11. Michael Clementin

    Johan, disregard my last post, I that error was for a different script. Still stuck on: Error: The server has rejected the client credentials.

    Reply
  12. Pingback: References: Add Office 365 Licenses with PowerShell | ODDYTEE

  13. Michael Clementin

    Okay, finally figured out that I must be logged to the server with my user account, NOT the local administrator.

    So, I’ve previously updated the AD user attributes (i.e., extensionAttribute14 = EXCHANGESTANDARD_ALUMNI) to contain the appropriate license AccountSku’s for faculty, students, and alumni.

    Updated the script to reflect those AccountSku’s (i.e., A1S = “EXCHANGESTANDARD_ALUMNI”, but now…it runs, but does not assign (activate) any users.

    What am I missing?

    Reply
  14. Jedediah Wilson

    I have a question about using a different AD-attribute. I would like to use the description field in AD and if the description matches “student” assign student licensing options, if the description matches “employee” assign employee licensing. I can use two separate scrips one for student, one for employee, but how do I make the script changes to check description field?

    In the current script what values do you have in the employeeType ad field?

    Thanks in advance.
    Jed

    Reply
  15. chrizwhit

    Hi Johan-

    First of all thanks! Your script was a big help in getting things going license wise. I was able to license a good chunk of my users with the Script. I went to use it again and now its throwing an error out.

    I edited the script to remove the other license types, so I have an AD attribute called l4s (license for students) tied to the STANDRDWOFFPACK_IW_STUDENT license type. When I run the script it immediately tries to run against users who already have a license and gives the error that “Unable to assign the license because it is invalid”

    These users have the same AD attribute (employee type) they have always had. That has not changed since I first used the script.

    I did just for kicks go look at a sampling of the users getting this message. They already have a license which jived with this document:

    https://technet.microsoft.com/en-us/library/dn771770.aspx

    What I don’t understand is why the script is suddenly thinking those users are unlicensed and going through them one by one.

    I did not change the master-on-premise parameter. (I left it default)

    So in summary the issue seems to be that its processing all the users, licensed or not in a random order, thus taking a long time to find the “unlicensed” ones in the haystack.

    Thanks in advance,

    Chris

    Reply
  16. Sam

    We have moved from scripts to a ready tool that can automatically assign licenses in O365. Even more than that, users can ask for licenses they need in a web interface and get them only after there is an approval from the manager. Super handy! http://www.adaxes.com/office-365_automation_management.htm

    The thing that makes it easier that a script, you can more easily let less skilled people look after it. I wouldn’t let junior admins to touch any scripts, whereas it’s fine with something that has a GUI and not much stuff to break.

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s