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

Windows Azure AD 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.

 

 

Back to top

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.

 

Back to top

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 assignment, 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

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

 

Back to top

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:

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

 

Back to top

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

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

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

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.

Back to top

 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.

   
Q: What other options are there besides maintaining scripts?
A: For a non-scripted approach see Managing Office 365 Licenses with MIM2016 (or FIM2010) and AzMan - Part 1 and Part 2

 

Back to top

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

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