How to Use PowerShell to Automatically Assign Licenses to Your Office 365 Users

How to Use PowerShell to Automatically Assign Licenses to Your Office 365 Users

AAD ScriptBox Item

The objective of this article is to introduce you to a process to assign licenses to your Office 365 users automatically.
This process is based on a set of PowerShell scripts.
You can find the required PowerShell script code at the end of this article.

The following table provides an overview of the related code files:

File Description
Get-LicensingInputFromAD.ps1 Creates a source data file based on information from your Active Directory Domain Service.
SetupScript.ps1 Configures your environment for running the Licensing scripts
AssignLicense.tmp Assigns licenses based on a source data file. The content of this template file is used by SetupScript.ps1 to create the actual PowerShell script file.
Get-MSOLUserLicensingReport.tmp Creates a report of all licensed users in Office 365. The content of this template file is used by SetupScript.ps1 to create the actual PowerShell script file.

 

The description in this article assumes that these files exist in a folder called O365LicenseScripts on the hard drive of your computer.

The following diagram outlines all possible steps that part of a license assignment process:


Understanding the Source Data Structure

To assign licenses, you need to provide the related source data that consists of the following components:

  • An object identifier
  • An operational attribute that describes the SKU you want to assign and the service plans you want to disable
  • For group based licencense assignment, you need to provide an additional attribute on the group object to store the license information for the user account.
  • For user based license assignement, you need to provide the following attributes:
    • UserPrincipalName
    • SearchFilter (e.g.: "@contoso.com")
    • an attribute that stores the licence information 

In the operational attribute, the SKU you intend to assign is expressed in form of the SKU name.
For example, the value of this attribute is “DESKLESSPACK” if you want to assign this SKU to a user.

The SKU you want to assign and the service plans you want to disable are separated by using the pipe symbol (“|”).
For example, if a user should be assigned to the E3 suite but not to SharePoint Online, the value of your operational attribute is:

ENTERPRISEPACK|SHAREPOINTENTERPRISE|SHAREPOINTWAC

 

note Note
Any non-licensing data in your employeeType attribute will result in no license set on that user, but it won’t replace any existing license assignments.

 

Your source data is stored in a delimited text file that must have a specific header row, which is followed by the data rows.br> In this file, a semicolon is used as separator between the object identifier and the operational attribute.
The following shows an example for the content of a source data file:

  • userPrincipalName;O365LicenseType
  • user1@contoso.com;ENTERPRISEPACK
  • user2@contoso.com;DESKLESSPACK
  • user3@contoso.com;ENTERPRISEPACK|MCOSTANDARD|SHAREPOINTWAC

 


Creating the Source Data File

The process outlined in this article requires you to create a source data file.
You can either create your source data file manually using the structure outlined in the previous section or you can use a script to retrieve data from your Active Directory Domain Service (ADDS).

Creating the Source Data File Using Your Active Directory Domain Service

If you want to use your on premise Active Directory Domain Service to create your source data file, you need to first populate the data about the SKU you want to assign and the service plans you want to disable.
As soon as you have populated the required data, you can create your source data file by using a script.

Preparing Your Active Directory Domain Service

In the section called “Understanding the Source Data Structure”, you have been introduced to the required data format for the attribute that is used to store the information about the SKU you want to assign and the service plans you want to disable.
If you want to use your Active Directory Domain Service to store the related data, you need to populate an Active Directory attribute of the affected users with the required values.

The solution outlined in this article is based on a series of AD attributes that are used to locate affected objects and to store licence information.
Depending on your approach to assign license information, you might be required to set certain attributes in Active Directory prior to running the related script.

If you choose to create the licensing input file in some other manner, you can skip the following step:

Option 1: User based license assignment example

  • Search Attribute ldapDisplayName i.e. userPrincipalName
  • Search Filter i.e. *@contoso.com
  • LicenseInformation Attribute ldapDisplayName i.e. extensionAttribute14

This retrieves all users in the specified domain that have the userprincipalName attribute set and where the attribute ends with "@contoso.com".
When the output file is created, the license information is added using the value from the extensionAttribute14 attribute.
The following shows an example for a related output file:

  • userPrincipalName;LicenseType
  • user1@contoso.com;ENTERPRISEPACK
  • user2@contoso.com;DESKLESSPACK
  • user3@contoso.com;ENTERPRISEPACK|MCOSTANDARD|SHAREPOINTWAC

 

Option 2: Group based license assignment

  • Search Attribute ldapDisplayName memberOf
  • Search Filter i.e. CN=GroupName,CN=Users,DC=Contoso,DC=com
  • LicenseInformation Attribute ldapDisplayName i.e. extensionAttribute14

This retrives all users in the specified domain that are DIRECT members of the specified group ().
When the output file is created, the license information is added using the value from the extensionAttribute14 attribute.
The folowing shows an example for a related output file:

  • userPrincipalName;LicenseType
  • user1@contoso.com;ENTERPRISEPACK
  • user2@contoso.com;ENTERPRISEPACK
  • user3@contoso.com;ENTERPRISEPACK

 

Creating the Source Data File Using a PowerShell Script

To create the source data file using a PowerShell script, you need to run the script called Get-LicensingInputFromAD.ps1.
You can find the code for this script at the end of this article in the section called “PowerShell Script Code”.
The script stores the created data file in a folder called queuedLicense.


Setup of the scripted solution

If you plan on using Active Directory as your source, you can use the included SetupScript.ps1 script to configure the folders and scripts for use.
You can re-run the Setup script at any point to change the configuration.
If you want to run multiply copies of the script i.e. for group based assignment you can create multiple copies of the scripts in different folders and configure each script instance separately.


Assigning Licenses to Office 365 Users

The process of assigning licenses to your Office 365 users consists of two steps:

  1. Connecting to your Microsoft Online Service Tenants
  2. Assigning the licenses

Connecting to your Microsoft Online Service Tenant

Before you can run the AssignLicense.ps1 script, you need to configure the scripts to allow them to connect to your Microsoft Online Service tenant.
To make the configuration you run the script called SetupScript.ps1.
This script does also create the required folder structure and it turns the two template script files (AssignLicense.tmp, Get-MSOLUserLicensingReport.tmp) into actual PowerShell scripts.

You can find the code for this script at the end of this article in the section called “PowerShell Script Code”.

 

note Note
You only need to run this script once per machine the scripts are intended to be run on. Run the SetupScript.ps1 script again if the password of the tenant admin account changes.

 

To connect you to your Microsoft Online Service tenant:

  1. Logon to a Windows 7 or Server 2008 R2 Machine as an Administrator.
  2. Create a folder called O365LicenseScripts.
  3. Create all files from the “PowerShell Script Code” section in this folder.
  4. Install the Microsoft Online Sign In Assistant.
  5. Install the Microsoft Online PowerShell Module.
  6. Run the SetupScript.ps1 script:
    1. When prompted type the user name of a tenant administrator (i.e. admin@contoso.edu).
    2. When prompted type the password of the same tenant administrator.
  7. Close PowerShell.

 

note Note
If you have already created your source data file, you should copy it to the queuedLicense folder.

 

Assigning the licenses

To assign the licenses, you need to run the AssignLicense.ps1 script.
The script moves the processed source data file into the completedImportFiles folder.

In addition to this, it creates a logfile in the logs folder and logs events in the Application EventLog.

 

note Note
Not all users may be able to use all service plans in a SKU.
Please refer to our geography-specific restrictions here: http://www.microsoft.com/en-us/office365/licensing-restrictions.aspx#fbid=vztrPYJ44LA.

 


FAQ

Q: What if I would like to change the service plans within a SKU I already assigned to a user?
A: The licensing script does not support this scenario.
For example: Your user has the A3 Suite and you would like to assign the user the A3 Suite without Exchange Online.
We will not allow this reassignment.
You must manually remove the Exchange Online service plan from your user.
   
Q: What if I’d like to change between two SKUs?
A: The licensing script supports this scenario.
   
Q: What if I would like to assign more than one SKU at a time to the same user?
A: You will need to store your second SKU in another user attribute, and create a copy of these licensing scripts in a new folder.
   
Q: What if I try to license a user with a SKU I don’t have?
What if there’s junk data in my AD attribute that I use for user license assignment?
A: This script will be unable to assign a nonexistent license to your user.
It will not remove any existing license on your user in an attempt to replace it with a nonexistent license.

 


PowerShell Script Code

The objective of this section is to provide the script code you need to complete the process outlined in this article.

Get-LicensingInputFromAD.ps1

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
# Script to read the Licensing Information from Active Directory
# Copyright Microsoft @ 2012
# DISCLAIMER
# The sample scripts are not supported under any Microsoft standard support program or service. 
# The sample scripts are provided AS IS without warranty of any kind. 
# Microsoft further disclaims all implied warranties including, without limitation, 
# any implied warranties of merchantability or of fitness for a particular purpose. 
# The entire risk arising out of the use or performance of the sample scripts and documentation remains with you. 
# In no event shall Microsoft, its authors, or anyone else involved in the creation, production, 
# or delivery of the scripts be liable for any damages whatsoever (including, without limitation, 
# damages for loss of business profits, business interruption, loss of business information, 
# or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation, 
# even if Microsoft has been advised of the possibility of such damages.
#-----------------------------------------------------------------------------------------------------------------
# define the attribute containing the license information
# use the ldapDisplayname of the attribute, default is EmployeeType
$licenseProperty="AttribNotSet"
$userLicenseProperty = "userLicenseAttribute"
$filterValue="FilterNotSet"
$groupLicenseInfoAttribute="GroupInfoNotSet"
$useGroupLicense = $false
$groupLicenseInformation=""

# Get the RootDSE
$rootDSE=[ADSI]"LDAP://RootDSE"

# Get the defaultNamingContext
$Ldap="LDAP://"+$rootDSE.defaultNamingContext

# Create a LicensesInput File
$outFile=".\queuedLicense\LicenseInput_{0:yyyyMMdd-HHmm}.csv" -f (Get-Date)

# Get all users with the EmployeeType Set. If you use another attribute to store the license information change the filter below.
$filter="(&(ObjectClass=user)(ObjectCategory=person)($licenseProperty=$filterValue))"

# create the Header for the Output File
$header="userPrincipalName;O365LicenseType"
$timeStamp=

# Check if the file exists and if it does with the same timestamp remove it
if(Test-Path $outFile)
{
Remove-Item $outFile
}
# Check if we are using Group Membership and pick up the license Info from the group
if($licenseProperty.ToLower() -eq "memberof")
{
if($groupLicenseInfoAttribute -ne "GroupNotSet")
{
Write-Host "Retrieving groupLicenseSetting"
$useGroupLicense=$true
$grp=New-Object System.DirectoryServices.DirectoryEntry("LDAP://$filterValue")
$groupLicenseInformation =([string]$grp.Properties.Item($groupLicenseInfoAttribute))
}
}
# create the output file and write the header
Out-File -InputObject $header -FilePath $outFile

# main routine
function GetLicenseInformation()
{
    # create a adsisearcher with the filter
$searcher=[adsisearcher]$Filter

# setup the searcher properties
$Ldap = $Ldap.replace("LDAP://","")
$searcher.SearchRoot="LDAP://$Ldap"
$searcher.propertiesToLoad.Add($userLicenseProperty)
$searcher.propertiesToLoad.Add("userPrincipalName")
$searcher.pageSize=1000

# find all objects matching the filter
$results=$searcher.FindAll()

# create an empty array
$ADObjects = @()
foreach($result in $results)
{
# work through the array and build a custom PS Object
[Array]$propertiesList = $result.Properties.PropertyNames
$obj = New-Object PSObject
if($useGroupLicense -eq $false)
{
$obj | add-member -membertype noteproperty -name "userPrincipalName" -value ([string]$result.Properties.Item("userPrincipalName"))
$obj | add-member -membertype noteproperty -name "licenseInfo" -value ([string]$result.Properties.Item($userLicenseProperty))
}
else
{
$obj | add-member -membertype noteproperty -name "userPrincipalName" -value ([string]$result.Properties.Item("userPrincipalName"))
$obj | add-member -membertype noteproperty -name "licenseInfo" -value $groupLicenseInformation
}
# add the object to the array
$ADObjects += $obj

# build the output line
$lineOut=$obj.UserPrincipalName +";"+ $obj.licenseInfo

# Write the line to the output file
Out-File -Append -InputObject $lineOut -FilePath $outFile
}

Return $ADObjects
}

# main routine
GetLicenseInformation 

 

 

SetupScript.ps1

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# Script to Setup a new password encrypted string to put it into a script file
#Copyright Microsoft @ 2012
#DISCLAIMER
#The sample scripts are not supported under any Microsoft standard support program or service. 
#The sample scripts are provided AS IS without warranty of any kind. 
#Microsoft further disclaims all implied warranties including, without limitation, 
#any implied warranties of merchantability or of fitness for a particular purpose. 
#The entire risk arising out of the use or performance of the sample scripts and documentation remains with you. 
#In no event shall Microsoft, its authors, or anyone else involved in the creation, production, 
#or delivery of the scripts be liable for any damages whatsoever (including, without limitation, 
#damages for loss of business profits, business interruption, loss of business information, 
#or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation, 
#even if Microsoft has been advised of the possibility of such damages.
Clear
$AssignmentScriptTemplate = "AssignLicense.tmp"
$AssignmentScriptName="AssignLicense.ps1"
$ReportScriptTemplate = "Get-MSOLUserLicensingReport.tmp"
$ReportScriptName="Get-MSOLUserLicensingReport.ps1"
$ADScriptTemplate="Get-LicensingInputFromAD.tmp"
$ADScriptName="Get-LicensingInputFromAD.ps1"
$script:userName=""
$script:pass=""
$host.ui.RawUI.ForegroundColor = "DarkYellow"
$host.ui.RawUI.BackgroundColor = "Black"
$FilterDefault="*"
$existingSKUInfo=$null
$userLicenseInfo="employeeType"

function getCredentials{
write-host 'Please enter your Office365 tenant admin username.'
$script:userName=Read-Host -Prompt "UserName:"
write-host 'Please enter your Office365 tenant admin password to secure it for use in the license assignment script.'
$sec=Read-Host -Prompt "Enter the Password:" -AsSecureString
$script:pass=ConvertFrom-SecureString $sec
# create a credential object and try to connect to Office 365
$p=ConvertTo-SecureString $script:pass
$cred = New-Object System.Management.Automation.PSCredential $script:userName,$p
verifyCredentials $cred
}

function verifyCredentials($cred){

Write-Host "Verifying Credentials. Please wait."
$bModuleLoaded=$false
$bConnectedToService=$false

Get-Module|%{if($_.Name -eq "MsOnline"){$bModuleLoaded = $true}}
if($bModuleLoaded -eq $true)
{
#Module is loaded proceed checking if we are logged in.
Write-Host -ForeGroundColor yellow "MsOnline Module is loaded."
}
else
{
# Module is not loaded. Load the module and connect.
try
{
Import-Module MsOnline -ErrorAction Stop
}
catch
{
Write-Host -ForegroundColor Red "Could not load MsOnline Module. Ensure it is installed."
exit
}
}
try
{
Connect-MsolService -Credential $cred -ErrorAction Stop
Write-Host -ForegroundColor Green "Your Credentials have been successfully Verified."
Write-Host "Retrieving SKU Information from Tenant"
$existingSKUInfo=Get-MsolAccountSku
$existingSKUInfo
}
catch
{
Write-Host -ForegroundColor Red "Could not connect to the service. Ensure the credentials are correct and then try again."
exit
}
}

function SetADAttributeName{
If((Test-Path $ADScriptName-eq $true)
{
Write-Host -ForegroundColor Yellow "The script $ADScriptName has already been configured. Do you want to reset the configuration (Y/N)?"
$res = Read-Host
if($res.ToLower() -eq "y")
{
Remove-Item $ADScriptName -Force
}
else
{
Write-Host -ForegroundColor Red "Backup the " $ADScriptName " File and run the SetupScript again."
exit
}
}

Write-Host "Configuring Scripts"
Write-Host -ForegroundColor Green "Please enter the ldapDisplayName of the Attribute you will be using to search for users in AD."
$ldapName=Read-Host -Prompt "ldapdisplayName: "
Write-Host -ForegroundColor Green "Please enter the Filter Value for the attribute you entered above."
Write-Host "The default value is * so all objects will be returned having the attribute set regardless of the value in the attribute."
Write-Host "to limit the objects returned you can use any valid LDAP Filter syntax."
$FilterDefault=Read-Host -Prompt "Attribute Filter"

if($ldapName.ToLower() -eq "memberof")
{
Write-Host -ForegroundColor Yellow "You have chosen to filter by group membership. This requires some additional information."
Write-Host "Please specify the ldapDisplayName of the attribute of the group object $FilterDefault which will contain the license information."
$grpLicenseInfoAttrib = Read-Host -Prompt "ldapDisplayName"
}
elseif($ldapName.ToLower() -ne "employeetype")
{
Write-Host -ForegroundColor Green "You have selected an attribute different then employeeType"
Write-Host "Please specify the ldapDisplayName of the attribute of the user object containing the license information."
$userLicenseInfo=Read-Host -Prompt "ldapDisplayName"
}

if ($FilterDefault -eq $null)
{
$FilterDefault="*"
}
$ldapName=$ldapName.ToLower()
if(($ldapName -eq $null-or ($ldapName -eq "employeetype"))
{
Write-Host -ForegroundColor Green "Default Value accepted and set."
$ldapName="employeeType"
}

$rootDSE=[ADSI]"LDAP://RootDSE"
$Ldap="LDAP://"+$rootDSE.schemaNamingContext
$Ldap=$Ldap.Replace("LDAP://","")
$filter="(ldapdisplayName=$ldapName)"
$searcher=[adsisearcher]$Filter
$searcher.SearchRoot="LDAP://$Ldap"
$searcher.propertiesToLoad.Add("ldapDisplayName")
$results=$searcher.FindAll()
if($results.count -ne $null)
{
$input = Get-Content $ADScriptTemplate

foreach($l in $input)
{
$l=$l.replace("AttribNotSet",$ldapName)
$l=$l.replace("FilterNotSet",$FilterDefault)
$l=$l.replace("GroupInfoNotSet",$grpLicenseInfoAttrib)
$l=$l.replace("userLicenseAttribute",$userLicenseInfo)
Out-File -FilePath $ADScriptName -InputObject $l -Append
}

}
else
{
Write-Host -ForegroundColor Red "The specified Attribute $ldapName was not found. Please verify the Atribute Name and try again."
Write-Host "The configuration will be aborted!"
exit
}
}

function configureScript{
If((Test-Path $AssignmentScriptName-eq $true)
{
Write-Host -ForegroundColor Yellow "The script $AssignmentScriptName has already been configured. Do you want to reset the configuration (Y/N)?"
$res = Read-Host
if($res.ToLower() -eq "y")
{
Remove-Item $AssignmentScriptName -Force
}
else
{
Write-Host -ForegroundColor Red "Backup the " $AssignmentScriptName " File and run the SetupScript again."
exit
}
}
Write-Host "Configuring Scripts"
$Content=Get-Content $AssignmentScriptTemplate
Foreach($line in $Content)
{
$line = $line.Replace("usernotset",$script:userName)
$line=$line.Replace("passnotset",$script:pass)

Out-File -FilePath $AssignmentScriptName -InputObject $line -Append
}

$Content=Get-Content $ReportScriptTemplate 

Foreach($line in $Content)
{
$line = $line.Replace("usernotset",$script:userName)
$line=$line.Replace("passnotset",$script:pass)

Out-File -FilePath $ReportScriptName -InputObject $line -Append
}
}

function configureFolders{
Write-Host "Configuring Folders"
if((Test-Path ".\Logs"-eq $false){md ".\Logs"}
if((Test-Path ".\queuedLicense"-eq $false){md ".\queuedLicense"}
if((Test-Path ".\completedImportFiles"-eq $false){md ".\completedImportFiles"}
}

# Get the input from the user
getCredentials

# configure the Script with the credentials
configureScript

# Set the LDAP Property
SetADAttributeName

# configure the required folders
configureFolders

# show the valid license packages
$licMsg ="To configure the correct License Information in your on premises Active Directory use the following information"
Write-Host $licMsg
Out-File -FilePath licenseInformation.txt -InputObject $licMsg
if($existingSKUInfo -eq $null){$existingSKUInfo = Get-MsolAccountSku}
foreach($sku in $existingSKUInfo)
{
if($sku.ServiceStatus.Count -gt 1)
{
foreach($o in ($sku.ServiceStatus))
{
if($options -eq "")
{
$options= $o.ServicePlan.ServiceName
}
else
{
$options = $options +", " + $o.ServicePlan.ServiceName
}
}
}
else
{
 $options = "No options available."
}
$skuID=$sku.AccountSkuID.Split(":")[1]
Write-Host "$skuID has the following options: $options"
$outLine="$skuID;$options"
Out-File -Append -FilePath .\licenseinformation.txt -InputObject $outLine
$options =""
$skuID=""
$outLine=""
}
Write-Host "License Information has been stored in file licenseinformation.txt for your reference."
Write-Host "Setup Complete."

 

 

AssignLicense.tmp

 

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# script to assign or swap the licenses of a user
#Copyright Microsoft @ 2012
#DISCLAIMER
#The sample scripts are not supported under any Microsoft standard support program or service.
#The sample scripts are provided AS IS without warranty of any kind.
#Microsoft further disclaims all implied warranties including, without limitation,
#any implied warranties of merchantability or of fitness for a particular purpose.
#The entire risk arising out of the use or performance of the sample scripts and documentation remains with you.
#In no event shall Microsoft, its authors, or anyone else involved in the creation, production,
#or delivery of the scripts be liable for any damages whatsoever (including, without limitation,
#damages for loss of business profits, business interruption, loss of business information,
#or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation,
#even if Microsoft has been advised of the possibility of such damages.
# Setup the UI Colors
$host.ui.RawUI.ForegroundColor = "White"
$host.ui.RawUI.BackgroundColor = "Black"

# change the username to a admin account in your tenant i.e. admin@contos.onmicrosoft.com
$Username="usernotset"
# to set a new password delete this script and run SetupScript.ps1 again.
$pass="passnotset"

# check if the password and user are set to something
if(($pass -contains "notset")-or($Username -contains "notset"))
{
"You need to set your username and/or create an encrypted password for the admin account specified in this script before continuing."
Exit 2
}
# Global Variables
<#---------Logfile Info----------#>
# setup the logfile
$script:logfile = ".\Logs\AssignLicense-$(get-date -format MMddyyHHmmss).log"
            
$script:Seperator = @"
 $("-" * 25)
 "@
$script:loginitialized = $false
$script:FileHeader = @"
 $seperator
 ***Application Information***
 "@

# Global Functions
function write-log([string]$info)
{
    # verify the Log is setup and if not create the file
if($script:loginitialized -eq $false)
{
        $FileHeader > $logfile
        $script:loginitialized = $True
    }
$info = $(get-date).ToString()+": "+$info
    $info >> $script:logfile
}

# setup the eventlog source if it does not exist
New-EventLog -LogName Application -Source O365LicenseUpdate -ErrorAction SilentlyContinue > Out-Null

# write the start event to the eventlog
write-Eventlog -logname Application -Entrytype information -EventId 0 -source O365LicenseUpdate -message 'The O365 License Update AssignLicense has been started'

# load the MSOnline PowerShell Module
# verify that the MSOnline module is installed and import into current powershell session
If (!([System.IO.File]::Exists(("{0}\modules\msonline\Microsoft.Online.Administration.Automation.PSModule.dll" -f $pshome))))
{
Write-EventLog -LogName Application -EntryType Error -EventId 99 -Source O365LicenseUpdate -Message "The Microsoft Online Services Module for PowerShell is not installed. The Script cannot continue."
write-log "Please download and install the Microsoft Online Services Module."
Exit 99
}
$getModuleResults = Get-Module
If (!$getModuleResults) {Import-Module MSOnline -ErrorAction SilentlyContinue}
Else {$getModuleResults | ForEach-Object {If (!($_.Name -eq "MSOnline")){Import-Module MSOnline -ErrorAction SilentlyContinue}}}
write-Eventlog -logname Application -Entrytype information -EventId 0 -source O365LicenseUpdate -message 'MSOnline module imported'

# create the password from the encrypted string and setup the credential object
$password = ConvertTo-SecureString $pass
$cred = New-Object -typename System.Management.Automation.PSCredential -argumentlist $Username,$password
$er = ""

# Connect to Microsoft Online Service
Connect-MsolService -Credential $cred -errorAction silentlyContinue -errorvariable $er

if ($er -ne ""){
# handle any logon errors
        $message6 = 'Could not log on O365 with ' + $($Username) + ' to update licenses. ' + $er
        write-Eventlog -logname Application -Entrytype error -EventId 6 -source O365LicenseUpdate -message $message6
        exit
}
else {
write-Eventlog -logname Application -Entrytype information -EventId 0 -source O365LicenseUpdate -message 'Logged In'
}

# setup the user info
$script:UseInfo = $($(get-date -format HH:mm:ss) + "`t" + $env:username + "`t")

# get the company prefix for the license packages
$licenseList=Get-MsolAccountSku
if(($licenseList.GetType().Name) -eq "AccountSkuDetails")
{
$licensePrefix =$licenseList.AccountSkuId.Split(":")[0]
}
else
{
$licensePrefix =$licenseList[0].AccountSkuId.Split(":")[0]
}

Trap [Exception] {
  # Something bad happened let's dump it into the log file
  write-log $("$UseInfo`t$_. - Line:(" + $($_.InvocationInfo.ScriptLineNUmber)+":"+$($_.InvocationInfo.OffsetInLine)+ ") " + $($_.InvocationInfo.Line))
  continue
}
                      
# Main Loop starts here
$csvfile = ''
# Get the list of all CSV Files
$filecol = dir .\queuedLicense\*.csv

if($filecol -ne $null)
{
# iterate through the list of files and execute on every user in each file
$filecol|foreach-object{
    $csvfile = $_.FullName
    Write-host "Processing "$csvfile

    $Users = import-csv $csvfile -Delimiter ";"
    
    $Message7 = 'Start processing license file ' + $csvfile
    write-Eventlog -logname Application -Entrytype information -EventId 0 -source O365LicenseUpdate -message $Message7
write-log $Message7

    $UPN = ''

# iterate through the users in the file
    $users|foreach-object {
   # make sure the user has a license and a UPN in the row. If not skip the user
   if (( $_.O365LicenseType -ne "") -and ($_.userPrincipalName -ne "")){
    $er = ""
            $NewLicenseExc = ""
            $OldLicenseExc = ""
            $NewLicenseLync = ""
            $OldLicenseLync = ""
            $LyncLicense = ""
            $ExchLicense = ""
            $Encrypt = ""
            $O365LicenseType = $licensePrefix + ":" + $_.O365LicenseType
    $UPN = $_.userPrincipalName
    $message1 = 'Update user license for user: ' + $UPN + ', this can take 15 minutes to become effective'
            $message2 = 'license for user ' + $UPN + ' is added in O365'
            $message3 = 'Wrong Licence type for user: ' + $UPN + ' in AD'
            $message4 = 'License stays the same for user: ' + $UPN
            $message5 = 'Error file ' + $csvfile +' empty or user not found in O365: ' + $UPN
$message6 = 'User or License Record were empty. Skipped.'
          $setLicenseOptions = $false
$skipUser =$false
            write-host $UPN , $O365LicenseType
                    
# Apply Licenses as needed
# Check if we need to create LicenseOptions
# Using the pipe (|) symbol as a delimter because some service plans do contain an underscore (_) character
if($O365LicenseType.Contains("|"))
{
  # Split the Options into an Array
  $licenseOptions = $O365LicenseType.Split("|")
  # Pick the first Option as the AccountSkuID
  $O365LicenseType = $licenseOptions[0]
  # Create an empty Array for the disabledPlans Object
  $lo=@()
  for($i=1;$i -le $licenseOptions.Count;$i+=1)
  {
# Make sure we only add non-null disabledPlans to the new Array
if($licenseOptions[$i] -ne $null)
{
$lo+=$licenseOptions[$i]
}
  }
  # Create the LicenseOptions Object
  $licenseOptionObject = New-MsolLicenseOptions -AccountSkuId $O365LicenseType -DisabledPlans $lo
  $setLicenseOptions = $true
}
                # Check if user exists in Office 365
                Try{
           $userObject = Get-MsolUser -UserPrincipalName $UPN -erroraction stop
        if ($userObject.islicensed)
{
                       $OldLicenseExc=$userObject.Licenses[0].AccountSkuID
  foreach($license in $userObject.Licenses)
  {
    # check if the user has already the same license package set
if($license.AccountSkuID -eq $O365LicenseType)
{
# The user has the same License Package. To avoid Dataloss we will Skip this user
$skipUser=$true
}
  }
                    }
                }
                Catch{
                     # Something went wrong log it
 write-Eventlog -logname Application -Entrytype warning -EventId 5 -source O365LicenseUpdate -message $message5
                     write-host -foregroundcolor red $message5 ' ' $UPN
         write-log $message5 $UPN
         }

         # if the user has a license that is not equal to the new license switch it out
                if (!$skipUser)
{
$NewLicenseExc = $O365LicenseType
                    Try{
                        $userObject=Get-MsolUser -UserPrincipalName $UPN -erroraction stop
          if (!$userObject.isLicensed){
if($setLicenseOptions -eq $true)
{
                              Set-MsolUserLicense -UserPrincipalName $UPN -AddLicenses $NewLicenseExc -LicenseOptions $licenseOptionObject -ErrorVariable $er
}
else
{
Set-MsolUserLicense -UserPrincipalName $UPN -AddLicenses $NewLicenseExc -ErrorVariable $er
}
                            # we added a new license for the user
write-Eventlog -logname Application -Entrytype information -EventId 2 -source O365LicenseUpdate -message $message2
write-log $message2
                            write-host 'A new license is set for user ' $_.UserPrincipalName
                            }
                            elseIf (!$skipUser)
{
if(!($NewLicenseExc -eq $OldLicenseExc))
{
if($setLicenseOptions -eq $true)
{
Set-MsolUserLicense -UserPrincipalName $UPN -RemoveLicenses $OldLicenseExc -AddLicenses $NewLicenseExc -LicenseOptions $licenseOptionObject -ErrorVariable $er
$setLicenseOptions=$false
}
else
{
Set-MsolUserLicense -UserPrincipalName $UPN -RemoveLicenses $OldLicenseExc -AddLicenses $NewLicenseExc -ErrorVariable $er
}
}
# We have updated the license of the user
                            write-Eventlog -logname Application -Entrytype warning -EventId 1 -source O365LicenseUpdate -message $message1
Write-Host $message1
write-log $message1
                            }
                        else
{
                          # the license was the same before and after, nothing has changed
write-Eventlog -logname Application -Entrytype information -EventId 4 -source O365LicenseUpdate -message $message4
Write-Host $message4
write-log $message4
                        }
$setLicenseOptions=$false
$skipUser=$false
                     }

                     catch{
    # Something went wrong log it
                          write-Eventlog -logname Application -Entrytype warning -EventId 5 -source O365LicenseUpdate -message $message5 + " " + $er
                          write-host -foregroundcolor red 'File is empty or could not find user ' $er
              write-log $message5 ' ' $er
  $setLicenseOptions=$false
  $skipUser=$false
                          }
     }
                else{
# the license was the same before and after, nothing has changed
                    write-Eventlog -logname Application -Entrytype warning -EventId 4 -source O365LicenseUpdate -message $message4
                    write-host -foregroundcolor red $message4 + " " + $UPN
        write-log $message4 ' ' $UPN
$setLicenseOptions=$false
                    }
          }
            else{
# One of the fields in the CSV file was not valid for setting up a license for the user
                write-Eventlog -logname Application -Entrytype warning -EventId 6 -source O365LicenseUpdate -message $message6
                write-host -foregroundcolor red $message6 ' ' $UPN
    write-log $message6 ' ' $UPN
$setLicenseOptions=$false
                }
         }
   

   # Belongs to foreach *.csv

   Try{
       # move the file to the completedImportFiles Folder
   move-item -path $csvfile -destination .\completedImportFiles -ErrorVariable $er
       }
   Catch{
       # Something went wrong log it
       write-Eventlog -logname Application -Entrytype warning -EventId 5 -source O365LicenseUpdate -message 'Could not move the file '+$er
   write-log "Could not move the file" $er
       }
}
}
else
{
# We have no file to process
write-Eventlog -logname Application -Entrytype information -EventId 0 -source O365LicenseUpdate -message 'No queued license import file found.'
write-log "No queued license import file found."

}
# Log All Done Message
write-Eventlog -logname Application -Entrytype information -EventId 0 -source O365LicenseUpdate -message 'The O365 License Update AssignLicense has been ended'
write-log "License Update completed."
write-log "==========================================================================="
Write-Host "License Update completed."

 

Get-MSOLUserLicensingReport.tmp

 

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
# Script to retrieve a licensing report from Office 365 and output it to CSV
# Copyright Microsoft @ 2012
# DISCLAIMER
# The sample scripts are not supported under any Microsoft standard support program or service.
# The sample scripts are provided AS IS without warranty of any kind.
# Microsoft further disclaims all implied warranties including, without limitation,
# any implied warranties of merchantability or of fitness for a particular purpose.
# The entire risk arising out of the use or performance of the sample scripts and documentation remains with you.
# In no event shall Microsoft, its authors, or anyone else involved in the creation, production,
# or delivery of the scripts be liable for any damages whatsoever (including, without limitation,
# damages for loss of business profits, business interruption, loss of business information,
# or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation,
# even if Microsoft has been advised of the possibility of such damages.

# change the username to a admin account in your tenant i.e. admin@contos.onmicrosoft.com
$Username="usernotset"
# to set a new password delete this script and run SetupScript.ps1 again.
$pass="passnotset"

# check if the password and user are set to something
if(($pass -contains "notset")-or($Username -contains "notset"))
{
"You need to set your username and/or create an encrypted password for the admin account specified in this script before continuing."
Exit 2
}

# load the MSOnline PowerShell Module
# verify that the MSOnline module is installed and import into current powershell session
If (!([System.IO.File]::Exists(("{0}\modules\msonline\Microsoft.Online.Administration.Automation.PSModule.dll" -f $pshome))))
{
Write-EventLog -LogName Application -EntryType Error -EventId 99 -Source O365LicenseUpdate -Message "The Microsoft Online Services Module for PowerShell is not installed. The Script cannot continue."
write-log "Please download and install the Microsoft Online Services Module."
Exit 99
}
$getModuleResults = Get-Module
If (!$getModuleResults) {Import-Module MSOnline -ErrorAction SilentlyContinue}
Else {$getModuleResults | ForEach-Object {If (!($_.Name -eq "MSOnline")){Import-Module MSOnline -ErrorAction SilentlyContinue}}}
write-Eventlog -logname Application -Entrytype information -EventId 0 -source O365LicenseUpdate -message 'MSOnline module imported'

# create the password from the encrypted string and setup the credential object
$password = ConvertTo-SecureString $pass
$cred = New-Object -typename System.Management.Automation.PSCredential -argumentlist $Username,$password                
$er = "" 

# Connect to Microsoft Online Service
Connect-MsolService -Credential $cred -errorAction silentlyContinue -errorvariable $er

if ($er -ne ""){
# handle any logon errors
        $message6 = 'Could not log on O365 with ' + $($Username) + ' to create your licensing report ' + $er
        write-Eventlog -logname Application -Entrytype error -EventId 6 -source O365LicenseUpdate -message $message6
        exit
}
else {
write-Eventlog -logname Application -Entrytype information -EventId 0 -source O365LicenseUpdate -message 'Logged In'
}

$outFile="licenses_{0:yyyyMMdd-HHmm}.csv" -f (Get-Date)
$users = Get-MsolUser -all 
$header = "userPrincipaName,usageLocation,isLicensed,accountSKUid,servicePlan1,provisioningStatus1,servicePlan2,provisioningStatus2,servicePlan3,provisioningStatus3,servicePlan4,provisioningStatus4,servicePlan5,provisioningStatus5"
Out-File -FilePath $outfile -InputObject $header

foreach($usr in $users)
{

$lineOut=$usr.UserPrincipalName + "," + $usr.usageLocation + "," + $usr.isLicensed + ","
foreach($lic in $usr.Licenses)
{
    $lineOut = $lineOut + $lic.AccountSkuID
foreach($s in $lic.ServiceStatus)
{
$lineout = $lineout + $s.ServicePlan.ServiceName + "," + $s.ProvisioningStatus +","
}
}
Out-File -FilePath $outfile -Append -NoClobber -InputObject $lineOut
$lineOut = $null
}

Write-Host -ForeGroundColor yellow "Please review your output file at " $outFile

 

 

note Note
To provide feedback about this article, create a post on the AAD TechNet Forum.
For more FIM related Windows PowerShell scripts, see the AAD ScriptBox

 

Sort by: Published Date | Most Recent | Most Useful
Comments
  • Revision: edited tags

  • First of all, @Payman.... this script collection is pretty amazing.  We are working from a large Seminary where student accounts area created every day automatically using a PERL script that talks to our Student Database program and creates new AD users, Exchange mailboxes, etc.  Since I have certain AD containers setup to sync new users automatically, this script is a HUGE answer to what I was looking for to automatically assign licences to all of our Students.  It looks very promising.

    BUT... Everything seems to work fine up to the point of running the AssignLicenses.ps1 script.  

    I would like help with this error:

    -----------------------------------------------------

    The string starting:

    At C:\users\me\documents\O365LicenseScripts\AssignLicense.ps1:34 char:21

    + $script:Seperator =  <<<< @"

    is missing the terminator: "@.

    At C:\users\me\documents\O365LicenseScripts\AssignLicense.ps1:306 char:1

    +  <<<<

       + CategoryInfo          : ParserError: ( $("-" * 25)

    ... completed.`"

    :String) [], ParseException

       + FullyQualifiedErrorId : TerminatorExpectedAtEndOfString

    -----------------------------------------------------

    >> I am not a PowerShell whiz, so I tried following it's suggestion by adding [ @. ] at the end of the file, which is at line 306, Char:1 but this only caused the script to be accepted in PS but apparently not do anything at all.  No log files, not errors, nothing.

    >> The AssignLicenses.ps1 was created by running the SetupScript.ps1, which referenced an AssignLicences.tmp as a template.

    >> BTW, the instructions above need to be updated to reflect the need to save the AssignLicences.tmp first, not AssignLicenses.ps1

    Thanks,

    Bryan

  • Ok, first problem resolved:

    Updated AssignLicense.tmp script:  I had to remove the leading spaces on lines 36 and 41 where the [ "@ ] script separator began.  When you close a script separator it has to be in position Char 1 of the line.

    -------------------------------

    New problem:

    Running the AssignLicense.ps1 script, I am seeing this error dumped into the log file:

    Cannot validate argument on parameter 'Message'. The argument is null or empty. Supply an argument that is not null or empty and then try the command again.. - Line:(134:26)     $users|foreach-object {

    5/24/2013 3:00:15 PM: License Update completed.

    I cannot figure out what this error means!  Again, I am not a PowerShell expert so this is a bit discouraging for me.  It appears to be a syntax error but I am just not sure.  Any help you could provide would be most appreciated.

  • Thank you very much for sharing this script. I wish this information was alot easier and built into the portal because we also need to assign different licenses to staff and students.

    Bryan, were you able to figure out the parameter issue inn the script because i see the same thing. I am not sure what needs to be done to correct this issue and it seems to be the same error that your getting.

    My error is:

             A positional parameter cannot be found that accepts argument '+'.. - Line:(134:27)      $users|foreach-object {

    Thanks,

    Mark

  • This is great. However, I really think that much of this functionality should be built into the Office 365 Admin portal as well. The filter options that you can use to list users and bulk assign licenses in the portal today could be far more extended and advanced.

  • I love the scripts but there are some major caveats about it.

    1. Usage Location needs to be set before you can Add a license

    2. For some reason, $er in the AssignLicenses make alot of cmdlets fail

    3. The scripts should be easy downloadable

    4. @ and whitespaces.

    5. Extend script for multiple licenses, it's not only office 365 licenses anymore. Project Online, CRM Online, ...

    I'm rebuilding them for myself now, if you want I can send them to you ..

  • Just one minor note - "automatically" means "without user intervention." You really can't say "automatically provision with a script" because then it's not automatic. Microsoft needs to provide a way to simply configure in Office 365 "provision any user in [AD Group] with [O365 License]" - *that* will be automatic.

    It's really sad that MSFT is pitching this as an enterprise solution when there are so many "mom & pop" limitations.

  • I have been trying to implement this and at the moment continually failing.

    It looks as though the AssignLicense finishes but the license isn't getting set in the tenancy.

    The Get-MSOLUserLicensingReport has errors but seems to come back with data from the tenancy and the licensing information.

    Jethro - I hope you don't mind me asking but did you get your scripts working especially around multiple SKU's as this would be something I would be interested in.

    Darren