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
SetupScript.ps1
Configures your environment for running the Licensing scripts. This script will prompt you for various parameters and will take use the three template files below to create customized scripts for your environment.

Get-LicensingInputFromAD.tmp
Creates a source data file based on information from your Active Directory Domain Service.
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 subfolder of your choice on the hard drive of your computer.

 


Setup of the scripted solution

In order to use this solution you should start by creating a subdirectory and place the four scripts below in a subdirectory of your choice.  The SetupScript.ps1 will create the required subdirectory strucuture, prompt for O365 admin credentials and will generate the Powershell scripts AssignLicense.ps1,  Get-LicensingInputFromAD.ps1 and Get-MSOLUserLicensingReport.ps1. 

It will also prompt your for a DefaultUsageLocation, which is required in Office 365 for a license assignment. The script will use the country configured of a user in your AD by default. If this attribute is not configured - the DefaultUsageLocation you specify will be applied to the user. Please use the 2 letter country code from ISO-3166-1 alpha-2.




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.


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.

Information: Currently this solution does not handle the assignment of product combinations like Office 365 and CRM Online, Project Online and Visio Online. The command set-msoluserlicense needs to be called per product (SKU) and the Assignlicense.tmp still needs to be modified between line 165 and 200 in order to handle this.

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:




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;UsageLocation;LicenseType
  • user1@contoso.com;GR;ENTERPRISEPACK
  • user2@contoso.com;DE;DESKLESSPACK
  • user3@contoso.com;US;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:



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.


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.

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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# 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"
###added 20140515-timbos
$DefaultUsageLocation = "GR"
###

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 -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
}
}
###added 20140515-timbos
Write-Host "Configuring Scripts"
Write-Host -ForegroundColor Green "Please enter the default UsageLocation for your users if the Country attribute 'c' is not set for the user in your AD"
Write-Host -ForegroundColor Green "Use ISO-3166-1 alpha-2 notation - for more information see http://en.wikipedia.org/wiki/ISO_two-letter_country_codes if the code is unknwon to you"
Write-host -ForegroundColor Green "If you just press enter - then it is all greek to the script :)"
$script:defaultusagelocation=Read-Host -Prompt "DefaultUsageLocation: "
if (!$script:defaultusagelocation) {$script:defaultusagelocation = $DefaultUsageLocation}
       

Write-Host "Configuring Scripts"
$Content=Get-Content $AssignmentScriptTemplate
Foreach($line in $Content)
{
$line = $line.Replace("usernotset",$script:userName)
$line=$line.Replace("passnotset",$script:pass)
###added 20140515-timbos
$line=$line.Replace("usagelocationnotset",$script:defaultusagelocation)
###

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."  

 

 

Get-LicensingInputFromAD.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
# 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
###modified 20140515-timbos
$header="userPrincipalName;UsageLocation;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")
###added 20140515-timbos
$searcher.propertiesToLoad.Add("c")
###
$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"))
###added 20140515-timbos
$obj | add-member -membertype noteproperty -name "UsageLocation" -value ([string]$result.Properties.Item("c"))
###
$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"))
###added 20140515-timbos
$obj | add-member -membertype noteproperty -name "UsageLocation" -value ([string]$result.Properties.Item("c"))
###
$obj | add-member -membertype noteproperty -name "licenseInfo" -value $groupLicenseInformation
}
# add the object to the array
$ADObjects += $obj

# build the output line
###modified 20140515-timbos
$lineOut=$obj.UserPrincipalName + ";" + $obj.UsageLocation + ";" + $obj.licenseInfo
###

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

Return $ADObjects
}

# main routine
GetLicenseInformation


 

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"

### added 20140515-timbos
#In order to assign a license a user needs to have a UsageLocation defined in Office 365
# By default this script will set the UsageLocation to the country attribute ("c" in AD) defined for the user
# if this attribute is not configured the following default UsageLocation will be assumed
# to set a new default Usagelocation change the folling
$DefaultUsageLocation ="usagelocationnotset"
###

# 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 = "***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
$ErrHandle = ""

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

if ($ErrHandle -ne ""){
# handle any logon errors
$message6 = 'Could not log on O365 with ' + $($Username) + ' to update licenses. ' + $ErrHandle
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
###changed 20140515
$filecol = Get-childitem -path .\queuedLicense | Where-Object {$_.Extension -eq '.csv'}


if($filecol -ne $null){
# iterate through the list of files and execute on every user in each file
foreach ($file in $filecol) {
$csvfile = $file.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
foreach ($user in $users) {
# make sure the user has a license and a UPN in the row. If not skip the user
if (( $user.O365LicenseType -ne "") -and ($user.userPrincipalName -ne "")){
$ErrHandle = ""
$NewLicenseExc = ""
$OldLicenseExc = ""
$O365LicenseType = $licensePrefix + ":" + $user.O365LicenseType.trim()
$UPN = $user.userPrincipalName.Trim()


#added 20140515-timbos
if ($user.UsageLocation -ne "") {
$UsageLocation = $user.UsageLocation
}
else {
$UsageLocation = $DefaultUsageLocation
}
###
$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

###added 20140515-timbos
if (!$userObject.UsageLocation) {
Set-MsolUser -UserPrincipalName $UPN -UsageLocation $UsageLocation
}
###
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
$ErrHandle = ""
Try {
$userObject=Get-MsolUser -UserPrincipalName $UPN -erroraction stop
if (!$userObject.isLicensed) {
if($setLicenseOptions -eq $true) {
Set-MsolUserLicense -UserPrincipalName $UPN -AddLicenses $NewLicenseExc -LicenseOptions $licenseOptionObject -ErrorVariable $ErrHandle
}
else {
Set-MsolUserLicense -UserPrincipalName $UPN -AddLicenses $NewLicenseExc #-ErrorVariable $ErrHandle
}
# 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 ' $UPN
}
elseIf (!$skipUser) {
if(!($NewLicenseExc -eq $OldLicenseExc)) {
if($setLicenseOptions -eq $true) {
Set-MsolUserLicense -UserPrincipalName $UPN -RemoveLicenses $OldLicenseExc -AddLicenses $NewLicenseExc -LicenseOptions $licenseOptionObject -ErrorVariable $ErrHandle
$setLicenseOptions=$false
}
else {
Set-MsolUserLicense -UserPrincipalName $UPN -RemoveLicenses $OldLicenseExc -AddLicenses $NewLicenseExc -ErrorVariable $ErrHandle
}
}
# 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" "$ErrHandle
write-host -foregroundcolor red 'File is empty or could not find user ' $ErrHandle
write-log $message5 ' ' $ErrHandle
$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 $ErrMsg
}
Catch {
# Something went wrong log it
write-Eventlog -logname Application -Entrytype warning -EventId 5 -source O365LicenseUpdate -message 'Could not move the file '$ErrMSG
write-log "Could not move the file" $ErrMsg
}
}
}
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