Managing Office 365 Licenses in Azure AD with the new Azure AD V2 PowerShell Module

Microsoft recently released a new version of the PowerShell module for administering Azure Active Directory to General Availability.

The previous module used MSOL (Microsoft Online) cmdlets to perform tasks (i.e. Get-MSOLUser).  The new cmdlets use the AzureAD cmdlets (i.e. Get-AzureADUser) which leverage the Graph API.

Because of this, you’ll want to make sure you download the latest version of the modules and update your existing scripts accordingly.

Assigning Office 365 licenses with these new cmdlets can be a bit tricky and confusing at first.  So, I’ll try to explain the process step-by-step so you gain an understanding of what’s going on.

Understanding licenses in Office 365:

Each license in Office 365 has an associated SkuID and SkuPartNumber and a list of one or more associated ServicePlans.

For instance, the E3 license has a SkuID of 6fd2c87f-b296-42f0-b197-1e91e994b900, a SkuPartNumber of ‘ENTERPRISEPACK’, and is comprised of the following Service Plans:

Service plan Description
SWAY Sway
INTUNE_O365 Mobile Device Management for Office 365
YAMMER_ENTERPRISE Yammer
RMS_S_ENTERPRISE Azure Rights Management (RMS)
OFFICESUBSCRIPTION Office Professional Plus
MCOSTANDARD Skype for Business Online
SHAREPOINTWAC Office Online
SHAREPOINTENTERPRISE SharePoint Online
EXCHANGE_S_ENTERPRISE Exchange Online Plan 2

You can get a listing of the friendlier Descriptions for each of the SkuPartNumbers from TechNet here.

When you assign an E3 license to an individual user, you can choose to exclude one or more Service Plans so they don’t get access to those services.

Assigning Licenses in PowerShell

Each Office 365 tenant has a unique TenantID that looks similar to the SkuID or any other GUID.  In our example below, the TenantID is 85b5ff1e-0402-400c-9e3c-0f9e965325d1.

To get a list of the SkuIDs you are subscribed to in your Office 365 tenant, connect to Azure AD using the Connect-AzureAD cmdlet.  Then, run:

C:\> Get-AzureADSubscribedSku

You’ll get returned a list of ObjectIDs, SkuPartNumbersPrepaidUnits and ConsumedUnits, showing how many licenses from each Sku have already been assigned (see example below from the online documentation for Get-AzureADSubscribedSku).  The ObjectID is made up of the TenantID, an underscore, and the SkuID for each subscription you have purchased:

ObjectId                                                                  SkuPartNumber         PrepaidUnits                  ConsumedUnits

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

85b5ff1e-0402-400c-9e3c-0f9e965325d1_078d2b04-f1bd-4111-bbd4-b4b1b354cef4 AAD_PREMIUM           class LicenseUnitsDetail {... 6

85b5ff1e-0402-400c-9e3c-0f9e965325d1_f245ecc8-75af-4f8e-b61f-27d8114de5f3 O365_BUSINESS_PREMIUM class LicenseUnitsDetail {... 24

85b5ff1e-0402-400c-9e3c-0f9e965325d1_6fd2c87f-b296-42f0-b197-1e91e994b900 ENTERPRISEPACK                                      24

Once you know the SkuPartNumber of the license you want to assign, you’ll need to know the SkuID for it.  It’s the second half of the ObjectID for the Subscribed Sku after the underscore (_).

C:\> (Get-AzureADSubscribedSku | ?{$_.SkuPartNumber -eq "ENTERPRISEPACK"}).SkuId

6fd2c87f-b296-42f0-b197-1e91e994b900

If you want to assign an E3 license to a user but exclude, for instance, the SharePoint component, you’ll need to know the ServicePlans assigned to your Sku.

C:\> (Get-AzureADSubscribedSku | ?{$_.SkuPartNumber -eq "ENTERPRISEPACK"}).ServicePlans

 

AppliesTo ProvisioningStatus ServicePlanId                        ServicePlanName

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

User      Success            76846ad7-7776-4c40-a281-a386362dd1b9 FLOW_O365_P2

User      Success            c68f8d98-5534-41c8-bf36-22fa496fa792 POWERAPPS_O365_P2

User      Success            57ff2da0-773e-42df-b2af-ffb7a2317929 TEAMS1

User      Success            b737dad2-2f6c-4c65-90e3-ca563267e8b9 PROJECTWORKMANAGEMENT

User      Success            a23b959c-7ce8-4e57-9140-b90eb88a9e97 SWAY

Company   PendingActivation  882e1d05-acd1-4ccb-8708-6ee03664b117 INTUNE_O365

User      Success            7547a3fe-08ee-4ccb-b430-5077c5041653 YAMMER_ENTERPRISE

User      Success            bea4c11e-220a-4e6d-8eb8-8ea15d019f90 RMS_S_ENTERPRISE

User      Success            43de0ff5-c92c-492b-9116-175376d08c38 OFFICESUBSCRIPTION

User      Success            0feaeb32-d00e-4d66-bd5a-43b5b83db82c MCOSTANDARD

User      Success            e95bec33-7c88-4a70-8e19-b10bd9d0c014 SHAREPOINTWAC

User      Success            5dbe027f-2339-4123-9542-606e4d348a72 SHAREPOINTENTERPRISE

User      Success            efb87545-963c-4e0d-99df-69c6916d9eb0 EXCHANGE_S_ENTERPRISE

 

Now that we have the SkuID and Service Plans, we are ready to begin preparing to assign these licenses.

To assign the license, we must first create two new objects in our PowerShell session (!!!Yes, notice below that one of them is plural and one of them isn’t – License vs. Licenses!!!).

The Microsoft.Open.AzureAD.Model.AssignedLicense object contains two properties:

  • SkuID – a string of the SkuID to be assigned
  • DisabledPlans – String list of ServicePlanIds to be excluded/disabled

The Microsoft.Open.AzureAD.Model.AssignedLicenses object contains two properties as well:

  • AddLicenses – a list of one or more AssignedLicense objects above
  • RemoveLicenses – String list of one or more SkuIDs to be removed (optional)

Suppose I want to assign an E3 license to a user but exclude the Yammer and Skype for Business components.

First, I’d create the empty AssignedLicense object called $E3.

C:\> $E3 = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicense

Then, since the SkuPartNumber for E3 is ENTERPRISEPACK, I isolate the AzureADSubscribedSku for ENTERPRISEPACK into a new variable called $Sku, then assign its SkuID value to the SkuID property of $E3:

C:\> $Sku = Get-AzureADSubscribedSku | ?{$_.SkuPartNumber -eq "ENTERPRISEPACK"}

C:\> $E3.SkuId = $Sku.SkuId

Next, I add ServicePlanIDs for each of the components I want to exclude (MCOSTANDARD for Skype for Business and YAMMER_ENTERPRISE) into the DisabledPlans property of $E3:

C:\> $E3.DisabledPlans += ($sku.ServicePlans | ?{$_.ServicePlanName -eq "MCOSTANDARD"}).ServicePlanID

C:\> $E3.DisabledPlans += ($sku.ServicePlans | ?{$_.ServicePlanName -eq "YAMMER_ENTERPRISE"}).ServicePlanID

At this point, my $E3 AssignedLicense object looks like this.  A value for SkuID corresponding to E3, and 2 values for DisabledPlans corresponding to Skype for Business and Yammer:

C:\> $E3

DisabledPlans                                                                SkuId

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

{0feaeb32-d00e-4d66-bd5a-43b5b83db82c, 7547a3fe-08ee-4ccb-b430-5077c5041653 } 6fd2c87f-b296-42f0-b197-1e91e994b900

We’re not quite ready to begin assigning this license yet, though.  We next create the empty AssignedLicenses (note the extra ‘s’ here) object $AssignedLicenses.

C:\> $AssignedLicenses = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicenses

Then, we add our $E3 object to the AddLicenses property of $AssingedLicenses:

C:\> $AssignedLicenses.AddLicenses += $E3

We must also define the value for RemoveLicenses.  (We’re not removing any licenses, and this property cannot be empty or null.)

C:\> $AssignedLicenses.RemoveLicenses = @()

Now we have an AssignedLicenses object with the following values:

C:\> $AssignedLicenses | FL

AddLicenses    : {class AssignedLicense {

DisabledPlans: System.Collections.Generic.List`1[System.String]

SkuId: 6fd2c87f-b296-42f0-b197-1e91e994b900

}

}

RemoveLicenses :

If we also wanted to assign, for instance, EMS licenses to the user in addition to the E3 license, we’d repeat the process above and create a second AssignedLicense object and add it to the AddLicenses property of $AssingedLicenses.  I’ve done this below for brevity:

C:\> $EMS = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicense

C:\> $Sku = Get-AzureADSubscribedSku | ?{$_.SkuPartNumber -eq "EMS"}

C:\> $EMS.SkuId = $Sku.SkuId

C:\> $EMS.DisabledPlans += ($sku.ServicePlans | ?{$_.ServicePlanName -eq "RMS_S_ENTERPRISE"}).ServicePlanID

C:\> $AssignedLicenses.AddLicenses += $EMS

Now our $AssignedLicenses variable looks like this (note the two SkuIDs under AddLicenses now):

C:\> $AssignedLicenses | FL

AddLicenses    : {class AssignedLicense {

DisabledPlans: System.Collections.Generic.List`1[System.String]

SkuId: 6fd2c87f-b296-42f0-b197-1e91e994b900

}

, class AssignedLicense {

DisabledPlans: System.Collections.Generic.List`1[System.String]

SkuId: efccb6f7-5641-4e0e-bd10-b4976e1bf68e

}

}

RemoveLicenses :

Now that we’ve got an object that contains a list of all the licenses and excluded service plans, we’re ready to actually assign these licenses to your user(s).  To assign the license, simply run the Set-AzureADUserLicense cmdliet, providing the $AssignedLicenses variable:

C:\>Set-AzureADUserLicense -ObjectId "user@domain.com" -AssignedLicenses $AssignedLicenses

 

Simple, huh?

 

Disable Audio/Video functionality for Skype Users via PowerShell

Like my previous post on Exchange Mailbox Protocols, many companies limit the ability of some of their users (but not all of them) to use the audio/video functionality built into Skype for Business Online.  Normally, this occurs in office environments that don’t have the internet bandwidth to support all that A/V traffic for a large number of users, so they limit that use to those who need it or executives, VIPs.

Unfortunately, it’s not possible to create a SIP profile that enables/disables the protocols for any user to which its assigned. Therefore, administrators must disable this functionality for each individual user.  This is easily done for one or two users via the administrative console web page, but doing this in bulk requires PowerShell.

To help alleviate this, I’ve created a script that leverages security groups in Azure AD (and on-premises AD if they are synchronized via DirSync) as a way to indicate which users should be allowed the use of Audio/Video functionality in Skype for Business Online.

By default, the script will assume your group is named Office365-AllowSkypeAV, but you could use any group name you want and feed that to the script via a command-line parameter.

When run, the script will disable AudioVideo functionality for ANY user who is NOT a member of the above referenced groups.

This script also leverages my WriteTo-Log function so that a running log can be generated keeping track of each change made to each user for auditing purposes.

Finally, there are optional command-line parameters (-From, -To, -SMTPServer) that can be used to ensure the log is emailed to an address of your choice after completing.

You can download the script here.

Disable Exchange Mailbox Protocols via PowerShell Script

Many companies limit the ability of some of their users (but not all of them) to leverage all of the default protocols enabled for accessing a mailbox in Office 365/Exchange Online while still allow them to connect with Outlook via MAPI.

Unfortunately, it’s not possible to create an OWA profile or a POP profile, for example, that enables/disables the protocols for any user to which its assigned. Therefore, administrators must disable these protocols for each individual user at the CASMailbox-level.

To help alleviate this, I’ve created a script that leverages security groups in Azure AD (and on-premises AD if they are synchronized via DirSync) as a way to indicate which users should be allowed the use of a certain protocol.

By default, the script will assume your groups are named as listed below, but you could use any group name you want and feed that to the script via a command-line parameter.

  • Office365-AllowActiveSync
  • Office365-AllowOWA-Device
  • Office365-AllowIMAP
  • Office365-AllowPOP

When run, the script will disable the protocols for ANY user who is NOT a member of the above referenced groups.

This script also leverages my WriteTo-Log function so that a running log can be generated keeping track of each change made to each user’s mailbox for auditing purposes.

Finally, there are optional command-line parameters (-From, -To, -SMTPServer) that can be used to ensure the log is emailed to an address of your choice after completing.

You can download the script here.

PowerShell WriteTo-Log Function

*** UPDATED! ***

I’ve discovered that if I used this function as I’d designed it originally, I couldn’t use it in a script that ran as a scheduled task since there’s no History in the $MyInvocation variable to draw from to name the log file.  So I re-wrote the function to accomodate this in the following way:

  1. If NO -Logfile parameter is passed to the function within a script, it will automatically name the resulting log file as originally described below
  2. If the -Logfile parameter IS passed to the function within a script, it will use the string value passed to it as the log file name to write to.
  3. If a $Script:Logfile variable is defined in the body of the script, and the -Logfile parameter is NOT passed in the script, the function will always use that string value defined.

This allows me to write a script and configure it to run as a scheduled task.  Then I just define a $Script:Logfile variable once at the beginning of the script like this:

$Script:Logfile = ‘.\’+(Get-Date).ToString(‘yyyyMMdd-HHmm’)+”-MyLogFileName.log”

This insures my script always uses the same log file name while it’s running and doesn’t have to rely on HistoryID.


I write a LOT of PowerShell scripts for my clients on almost every engagement I’m on.  It’s not unusual for one of those scripts to execute a command or lots of commands against a lot of user accounts.

In order to ensure I can keep track of what changes are being made to which users, and when the change was made, it’s helpful to have a log file for each execution of a script.

That’s why I wrote my own PowerShell function called WriteTo-Log.  This function creates a log file named with the date & time of the script execution, then the file name of the script (minus the extension) and adds a .log extension and places that log file in the same directory from which the script was executed. (note – not the same directory the script resides, but the directory from which the script was executed)

Example file name: .\2016-01-12-03.22-test-script.log

If you prefer the script use a different file name, you can use the -FileName parameter and pass your own file name to the script each time you invoke the function.

Each time the function is called, a new line is added to the log file with a date/time stamp and whatever string you pass to the function.  You can also add Foreground and Background color formatting and use the optional -OutputToScreen switch parameter to also echo the line out to the screen for easy review.

Example log entry: 2016-01-12 03.22.32 : Found a file!

The function can be easily copied/pasted at the beginning of every script I write.  Then whenever the script takes an action on a user account, I can execute my WriteTo-Log function and pass to it the details of the user and any success/fail results.

I’ve found that running this function in the ISE can cause problems because the ISE doesn’t like or support the Foreground & Background parameters the way I have implemented them.  For best results, save your script to a .ps1 and run it in a separate PowerShell window.

You can find the script an an example use below.  You can also find the script in the TechNet Gallery

Sample usage:


$processes = Get-Process
WriteTo-Log ("Found "+$processes.Count+" processes running") -OutputToScreen -ForegroundColor Green
foreach ($process in $processes) {
WriteTo-Log $process.processname -OutputToScreen -ForegroundColor Yellow
}

You can get the script here.

Using Remote PowerShell to Bulk-Add and Verify Vanity Domains to Office 365 [Updated]

Rare is the customer who uses the built-in Office 365 tenant domains for doing business. After all, what company wants to be known as “Widgets.OnMicrosoft.com”? You’re vane and want to have your own name, right? Sometimes, you want to go by LOTS of names, especially if your company is involved in different areas of business or has lots of different subsidiaries that go by different names.

Adding individual vanity domains to Office 365 is very easily done through the web portal and is well-documented in the Office 365 contextual help located here. But the purpose of this post is to show you how to bulk-add lots of domains to your tenant via remote PowerShell.

Step 1 is to collect a list of all the domains you need to add and save them in a TXT file like the one below. All you need is a list of domains – one per line.

fabrikam1.com
fabrikam2.com
fabrikam3.com
fabrikam4.com

Save this file with a name like VanityDomains.txt or something like that.

Then, open a remote PowerShell session to your Office 365 tenant, making sure to load the MSOnline module. I recommend using my script that I wrote a while back – it loads the modules for you. You can find it here.

Once connected, run the following command:

$VanityDomains = Get-Content .VanityDomains.txt
foreach ($domain in $VanityDomains) {New-MsolDomain -Name $domain}

You’ll get results similar to below:

Name                Status                Authentication
—-                ——                ————–
fabrikam1.com        Unverified                Managed
fabrikam2.com        Unverified                Managed
fabrikam3.com        Unverified                Managed
fabrikam4.com        Unverified                Managed

Now that you’ve added the domains to the tenant, you need to setup the DNS TXT records so that you can validate that you own the domains. To do that, you can run the following PowerShell command next to get a list of what TXT records you need to add:

foreach ($domain in $VanityDomains) {Get-MsolDomainVerificationDns -DomainName $domain -Mode DnsTxtRecord}

You’ll get results similar to below:

Label : contoso1.com
Text : MS=ms34591742
Ttl : 3600

Label : contoso2.com
Text : MS=ms16484203
Ttl : 3600

Label : contoso3.com
Text : MS=ms17807888
Ttl : 3600

Label : contoso4.com
Text : MS=ms22135974
Ttl : 3600

Now, log onto your external DNS provider’s website or provide this list to your DNS administrator to have the TXT records created in the public DNS for each zone. Once that’s done, you can validate the domains with the following command:

foreach ($domain in $VanityDomains) {Confirm-MsolDomain -DomainName $domain}

Office 365 will check for the existence of those DNS TXT records for each domain and if so, change the Status for each domain from Unverified to Verified.

The last thing you’ll need to do for each domain is to assign intent. (is this domain meant for Email or SharePoint or Lync?). Unfortunately, as of this writing there is no way to script these settings via PowerShell. This isn’t such a bad thing if you have a handful of domains to work through. But if you have A LOT (and why would you be looking to do all this through PowerShell if you didn’t) you are looking at a grueling experience clicking through the portal interface for each domain to assign intent.

If this changes in the future I’ll come back and update this post to reflect that. Until then, happy clicking.

Grant Full Access rights to a mailbox in Office 365

Not all Office 365 customers have a hybrid scenario deployed in their environment. The hybrid server provides the ability to perform administrative tasks in Office 365 via the familiar EMC GUI (Exchange Management Console).

But tenant administrators who don’t have a Hybrid server (and thus no EMC) must rely on PowerShell to perform some basic administrative tasks. This can be confusing to someone not used to PowerShell or command-line-based administration.

One of the most frequently asked questions is:

“How do I give <user 1> access to <mailbox a>?”

The first step is making sure you have the prerequisites installed on your workstation.

Second, connect PowerShell to the service using your tenant credentials. I’ve previously posted a useful script to help simplify that process.

Next, run the following command:

Add-MailboxPermission -Identity <Mailbox A> -User <User 1> -AccessRights FullAccess -InheritanceType All

  • <Mailbox A> is the alias of the mailbox to which you want to grant someone access rights
  • <User 1> is the alias of the mailbox for the user to whom you want to grant these rights

Exchange supports AutoMapping within the Outlook and OWA clients. AutoMapping is enabled by default. Therefore, when User A has FullAccess rights to Mailbox B, Mailbox B will show up in User A’s Outlook client or OWA automatically, without having to manually add it to their profile as was once previously required.

If User A only has FullAccess rights to a couple of other mailboxes this is a great feature. However, since AutoMapping utilizes Outlook’s cached mode, this can become cumbersome for users who have FullAccess rights to many mailboxes (like administrators).

To prevent AutoMapping, you have two options:

  1. Tack on the
    –AutoMapping:$False
    parameter at the end of the
    Add-MailboxPermission
    commandlet to prevent Outlook from using the AutoMapping feature.
  2. Instead of granting FullAccess rights to an individual user, grant it to a new security group and add that user to the security group. AutoMapping is not supported when FullAccess is granted through security groups.

Daily Sent Items Report with PowerShell

elow is a PowerShell script I wrote recently for a customer that wanted daily emailed reports of all messages sent from a specified email address.

The script is called Generate-DailySendReports.ps1 and takes command line parameters as input.

The script uses command-line parameters as input:

  • SenderSMTP (Required) – This is the sender smtp address you want to search the logs for.
  • Days (optional) – This is how many days back in time you want to search. The default is 1 day which will cause the script to search back n-1 days.
  • SMTPServer (optional) – This is the SMTP server you want to use to send the resulting email. By default, it will use the local host the script is running against. So if you’re not running this script from an Exchange server, you’ll want to supply this value or the script will fail to send the report.
  • ReportRecipient (Required) – What email address(es) do you want to send the report to. Separate addresses by a comma without spaces (i.e. user1@contoso.com,user2@fabrikam.com)
  • ReportSender (optional) – What email address do you want the report sent from? By default it will send from Reports@<logged-in-user-domain>
  • TransportServer (optional) – What transport server(s) do you want to search through? By default, it will only search the local server the script is running on. So, if you’re not running this script from an Exchange server, you’ll want to specify which server’s logs to search.

When the script runs, if it finds results it generates a CSV file of selected fields from the transport logs; specifically, it includes ClientIP, ServerIP, ConnectorID, MessageID, Recipients (expands this list if multiple) and MessageSubject. It then attaches this CSV file to a new email message that it sends to the email addresses you provide in the ReportRecipient parameter.

Copy the script below and save to a file named Generate-DailySendReports.ps1

# ===================================================

#

# This PowerShell script does the following:

# 1. Generates a CSV report for Messages Sent

# from a specified email address going back

# a specified number of days.

# 3. Sends an email with the resulting CSV file

# as an attachment

#

# Written by: David Smith

# Last modified: 11/21/2012

# Notes:

#

# ===================================================

 

#——-Establish variables

[CmdletBinding()]

Param(

[Parameter(Mandatory=$True)]

[string]$SenderSMTP,

 

[Parameter(Mandatory=$false)]

[int]$Days=1,

 

[Parameter(Mandatory=$false)]

[string]$SMTPServer=$env:COMPUTERNAME,

 

[Parameter(Mandatory=$true)]

[array]$ReportRecipient,

 

[Parameter(Mandatory=$false)]

[string]$ReportSender=(“Reports@”
+ $env:USERDNSDOMAIN
),


 

[Parameter(Mandatory=$false)]

[string]$TransportServer=$env:COMPUTERNAME

)

 

 

Add-PSSnapin
Microsoft.Exchange.Management.PowerShell.E2010
-ErrorAction
SilentlyContinue

$Date
= (get-date).AddDays($Days)

$ReportSubject
= ($SenderSMTP Sent Items Report – “
+ $date.ToShortDateString() +
” to “
+
[string](Get-Date).ToShortDateString())

 

#——-Generage the report results

$SentMessages
=
Get-MessageTrackingLog
-Start
$date
-Sender
$SenderSMTP
-EventId
SEND
-ResultSize
Unlimited
-Server
$TransportServer

$CountSentMessages
= ($SentMessages
|
Measure-Object).Count

 

#——-Determine the Body of the message and attachment

if ($SentMessages
-eq
$null)

{$ReportMessage
=
“0 Messages sent from $SenderToCheck since $date

Send-MailMessage
-From
$ReportSender
-To
$ReportRecipient
-Subject
$ReportSubject
-Body
$ReportMessage
-SmtpServer
$SMTPServer}

Else

{$ReportMessage
=
$CountSentMessages Messages Sent from $SenderSMTP since $date

$SentMessages
|
Select
TimeStamp, ClientIP, ServerIP, ConnectorID, MessageID, {$_.Recipients},
MessageSubject
|
Export-Csv
-NoTypeInformation
.SentItemsReport.csv

Send-MailMessage
-From
$ReportSender
-To
$ReportRecipient
-Subject
$ReportSubject
-Body
$ReportMessage
-Attachment
.SentItemsReport.csv
-SmtpServer
$SMTPServer}