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.
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 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
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.
Assigning Licenses to Office 365 Users
The process of assigning licenses to your Office 365 users consists of two steps:
- Connecting to your Microsoft Online Service Tenants
- 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:
- Logon to a Windows 7 or Server 2008 R2 Machine as an Administrator.
- Create a folder called O365LicenseScripts.
- Create all files from the “PowerShell Script Code” section in this folder.
- Install the Microsoft Online Sign In Assistant.
- Install the Microsoft Online PowerShell Module.
- Run the SetupScript.ps1 script:
- When prompted type the user name of a tenant administrator (i.e. admin@contoso.edu).
- When prompted type the password of the same tenant administrator.
- 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.
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 |
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