Managing Office 365 Tenants via PowerShell; Connect-O365

When you work at a Managed Services Provider that handles a large number of customers in Office 365, it's an envitability that you will spend a decent amount of time working within PowerShell. This means that you'll be constantly connecting and disconnecting PSSessions between customers. Wouldn't it be great if there were a better way? This is where I started.

Microsoft thought of this and has a solution! Well... sorta, but I'll get to that in a minute. According to TechNet, you can append the parameter -TenantID with the customers Tenant ID to any cmdlet to target that account. For instance, when connected to your Office 365 PowerShell environment, you can use the cmdlet Get-MsolUser to display all users under your organization's account. To perform the same operation on a tenant account under your organization, you'd use Get-MsolUser -TenantID a4cd11c3-9d92-3b6f-3df4-61c1411d60c8 to get a list of all their O365 users. This is assuming that your organization has been granted Delegated Access Permissions or has been made the Partner of Record for the tenant (which to be considered a tenant, one of these statements must be true).

This is awesome! Now you can issue commands to your customers from your own O365 account.

...But now Microsoft expects you to use the TenantID parameter for every command destined for your customer. Sure you can create a variable containing the Tenant ID string, but you better make sure that you remember that extra parameter and variable for every command that you enter otherwise there might be some not-so-great changes to your organization. I, however, do not have that much faith in myself.

I did what any administrator would do when presented with a solution that rustled my jimmies... I turned to PowerShell ISE!

You can see it in it's entirety at the bottom of my post, otherwise keep reading to see how it works. I've commented it fairly well so you should be able to figure it out for yourself if time is a factor.

An important thing to keep in mind is this script uses an administrative account under each customer to perform the administration. If this account doesn't exist, Connect-O365 (original, right?) will create it for you. To specify the account that your organization will use to administer the customers (let's keep it PG people; this account will be visible within the O365 Admin Portal), update these variables at the top of the script:

 

###SET THESE VARIABLES
$msp_admin = "mspadmin"
$msp_name = "MSP"
###SET THESE VARIABLES

 

This section is all about making sure that your workstation is setup to connect properly. It checks to ensure all the required modules are installed (MSOnline, SharePoint, and Skype for Business Online) and checks to see if your PowerShell console is running as Administrator. If it finds that any of these items are not installed, it warns you then redirects you to the proper page within the Microsoft Download Center. Helpful, right?! You betcha! 

 

#Variables for PowerShell Modules
$officeMod = Get-Module -ListAvailable | Where-Object {$_.Name -eq "MSOnline"}
$sharepointMod = Get-Module -ListAvailable | Where-Object {$_.Name -eq "Microsoft.Online.SharePoint.Powershell"}
$skypeMod = Get-Module -ListAvailable | Where-Object {$_.Name -eq "LyncOnlineConnector"}

#Check Shell Dependecies
$adminCheck = Check-Administrator
if (-not ($adminCheck))
    {
    Write-Host "Run As Administrator, dork." -ForegroundColor Red
    Start-Sleep 2
    break
    }
elseif ($officeMod -eq $null)
    {
    Read-Host "Office 365 Module is missing. Press Enter to be forwarded to the needed downloads. There are two downloads required: Microsoft Online Server Sign-in Assistant and the Azure Active Directory Module"
    Start-Sleep -s 3
    Start-Process "https://www.microsoft.com/en-us/download/details.aspx?id=41950"
    STart-Sleep -s 3
    Start-Process "http://go.microsoft.com/fwlink/p/?linkid=236297"
    break
    }
elseif ($sharepointMod -eq $null)
    {
    Read-Host "Sharepoint Online Module is missing. Press Enter to be forwarded to the Microsoft Download Center."
    Start-Sleep -s 3
    Start-Process "https://www.microsoft.com/en-us/download/details.aspx?id=35588"
    break
    }
elseif ($skypeMod -eq $null)
    {
    Read-Host "Skype Online Module is missing. Press Enter to be forwarded to the Microsoft Download Center."
    Start-Sleep -s 3
    Start-Process "https://www.microsoft.com/en-us/download/details.aspx?id=39366"
    break
    }

 

You're likely familiar with this part, connecting to Office 365 requires these two lines no matter how you write your script. It imports the Microsoft Online module and asks for your O365 credentials.

 

#Gather Credentials for Office 365 Partner
Import-Module MsOnline
$credential = Get-Credential

 

The rest of the script has been built using functions to keep things organized, and let me tell you.. if you don't use functions in your PowerShell scripts, you need to start!

This function is what I used in an early segment to check if the script was running in the context of an Administrator.

 

#Checks to see if you're running as an Administrator
function Check-Administrator
{  
    $user = [Security.Principal.WindowsIdentity]::GetCurrent();
    (New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)  
}

 

The comment for this function explains exactly what it does.. builds a selectable menu of tenant accounts tied to your organization. Simply enter the number assigned to the customer you'd like to connect to and it passes the selected tenant to the next function Connect-O365.

 

#Builds Menu from Tenant Accounts
function Select-ItemFromList 
{ 
[CmdletBinding()] 
PARAM  
( 
    [Parameter(Mandatory=$true)] 
    $options, 
    [string]$displayProperty, 
    [string]$title = "Select Office 365 Tenant to manage:", 
    [ValidateSet("Menu")] 
    [string]$mode = "Menu", 
    [System.Windows.Forms.SelectionMode]$selectionMode = [System.Windows.Forms.SelectionMode]::One 
) 
    $script:selectedItem = $null 
 
    function BuildMenu 
    { 
    PARAM  
    ( 
        [Parameter(Mandatory=$true)] 
        $options, 
        [string]$displayProperty, 
        [string]$title = "Select Item" 
    ) 
        [int]$optionPrefix = 1 
        [System.Text.StringBuilder]$sb = New-Object System.Text.StringBuilder 
        $sb.Append([Environment]::NewLine + $title + [Environment]::NewLine + [Environment]::NewLine) | Out-Null 
         
        foreach ($option in $options) 
        { 
            $sb.Append(("{0,3}: {1}" -f $optionPrefix,$option.$displayProperty) + [Environment]::NewLine) | Out-Null 
            $optionPrefix++ 
        } 
        $sb.Append([Environment]::NewLine) | Out-Null 
        return $sb.ToString() 
    } 
         
    switch($mode.ToLower())  
    { 
        "menu" 
            { 
                [string]$menuText = BuildMenu -options $options -DisplayProperty $displayProperty -title $title 
                Write-Host $menuText 
                [string]$responseString = Read-Host "Enter Selection" 
                $index = [int]$responseString 
                $script:selectedItem = @($options[$index-1]) 
            } 
    } 
    Write-Output $script:selectedItem 
} 

 

Here's the meat of the script that really gets the job done. When the function Connect-O365 is called, it attempts to use your credentials to connect to your organizations O365. If it fails, you'll know ;). The next step is to gather a list of all tenant accounts tied to your organization then pass that information to the Select-ItemFromList function to build the selectable menu. Once you've chosen the tenant account you want to administer, Connect-O365 checks to see is the Administrative Account exists (if you didn't modify the variables at the top, the default will be msp@onmicrosoft.<tenantdomain>.com). If it doesn't exist, the script creates it for you and makes it a Global Administrator for the tenant. Wondering what the password for this account is? You get to choose that too!

Once the account has been found (or created), you'll be prompted for the Administrative Account's credentials. The credentials are not saved in the script so you'll need to enter this each time you establish a connection to a tenant. Once the credentials are entered, you'll then be connected to all the Office 365 services including the Office 365 Admin Center, SharePoint Online, and Skype for Business Online. On the first run after your Administrative account is created, you may not be able to connect to Skype for Business Online. This script runs much faster than Microsoft is able to provision the account so patience is a virtue. It may take a couple hours before it's provisioned, but no worries! You'll be connected to the other services without issue.

While we're talking about provisioning accounts for your tenants, let's be clear about something: This user is not licensed. Your tenants will not be billed for the Administrative Account that this creates and uses.

 

function Connect-O365
    {
    try
        {
        #Connect to Microsoft Online
        Connect-MsolService -Credential $credential -ErrorAction Stop
        }
    catch [Microsoft.Online.Administration.Automation.MicrosoftOnlineException]
        {
        Write-Host "Your credentials are bogus, dude. No bueno." -ForegroundColor Red
        Start-Sleep 4
        Connect-O365
        }

    #Select Tenant
    $o365tenants = Get-MsolPartnerContract | Select *
    $OrgName = Get-MsolCompanyInformation | select -exp DisplayName
    $host.ui.RawUI.WindowTitle = "No Active Connections. Viewing the Tenant list now."
    $selectedTenant = Select-ItemFromList -Options $o365tenants -displayProperty Name 
    $defaultDomain = $selectedTenant.DefaultDomainName

    #Gather Tenant Credentials
    $roleIdEntry = Get-MsolRole -RoleName "Company Administrator" -TenantId $selectedTenant.tenantID
    $o365admin = Get-MsolRoleMember -RoleObjectId $roleIdEntry.ObjectId -TenantId $selectedTenant.tenantID | where {$_.EmailAddress -like ("intrustadmin*" + $defaultDomain)}

    #Check for o365admin account. If it does not exist, create it
    if ($o365admin -eq $null)
        {
        $tenantPassword = Read-Host "$msp_name Office 365 Administrator account not found! I'll generate one for you. What password should I use? (8-16 Complex Characters)"
        New-MsolUser -UserPrincipalName $msp_admin@$defaultDomain -DisplayName “$msp_name Office 365 Administrator” -TenantId $selectedTenant.tenantId
        Add-MsolRoleMember -RoleName “Company Administrator” –RoleMemberEmailAddress  $msp_admin@$defaultDomain -TenantId $selectedTenant.tenantId
        Set-MsolUserPassword -userPrincipalName $msp_admin@$defaultDomain -NewPassword $tenantPassword -ForceChangePassword $false -TenantId $selectedTenant.tenantId
        Start-Sleep 2
        $o365admin = Get-MsolRoleMember -RoleObjectId $roleIdEntry.ObjectId -TenantId $selectedTenant.tenantID | where {$_.EmailAddress -like "$msp_admin*"}
        $tenantUsername = $o365admin.EmailAddress
        $tenantSPassword = ConvertTo-SecureString "$tenantPassword" -AsPlainText -Force
        $tenantCredentials = New-Object -typename System.Management.Automation.PSCredential -argumentlist ($tenantUsername, $tenantSPassword)
        }

    #Gather existing account's password
    else
        {
        $tenantUsername = $o365admin.EmailAddress
        $tenantSPassword = Read-Host "Enter the password for $tenantUsername" | ConvertTo-SecureString -AsPlainText -Force
        $tenantCredentials = New-Object -typename System.Management.Automation.PSCredential -argumentlist ($tenantUsername, $tenantSPassword)
        }

    #Connect to Office 365 > Exchange Online > Skype Online > Compliance Center
    Connect-MsolService -Credential $tenantCredentials
    try
        {
        $exchangeSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://outlook.office365.com/powershell-liveid/" -Credential $tenantCredentials -Authentication "Basic" -AllowRedirection -ErrorAction Stop
        }
    catch [System.Management.Automation.Remoting.PSRemotingTransportException]
        {
        Write-Host "Received a 403: Unauthorized Access. I may be working too quickly. Waiting 60 seconds before trying that again.." -ForegroundColor Yellow
        Start-Sleep 61
        $exchangeSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://outlook.office365.com/powershell-liveid/" -Credential $tenantCredentials -Authentication "Basic" -AllowRedirection -ErrorAction Stop
        }
    if ($exchangeSession)
        {
        Import-PSSession $exchangeSession -DisableNameChecking
        }

    try
        {
        $skypesession = New-CsOnlineSession -Credential $tenantCredentials -Verbose -OverrideAdminDomain $selectedTenant.DefaultDomainName
        }
    catch [Microsoft.Rtc.Admin.Authentication.CommonAuthException]
        {
        Write-Host "Microsoft has not completed the provisioning of this account within Skype for Business Online. This typically takes a few hours. I am not connecting you to Skype Online." -ForegroundColor Yellow
        }
    if ($skypesession)
        {
        Import-PSSession $skypesession -AllowClobber 
        }

    #Confirmation of connection
    $OrgName = Get-MsolCompanyInformation | select -exp DisplayName
    $host.ui.RawUI.WindowTitle = "You are connected to: " + $OrgName
    Write-Host "You are connected to:" $OrgName -ForegroundColor Green
    }

 

The last piece of this script is a short function that disconnects any active PSSessions to tenants and drops you back out to your organization's tenant list. Once finished with a tenant, call the function Cheese-It (inspired by A Goofy Movie).

 

#Drop to Tenant List
function Cheese-It
{  
    Get-PSSession | Remove-PSSession
    $host.ui.RawUI.WindowTitle = "No Active Connections. Viewing the Tenant list now."
    Connect-O365
}

 

That's it! You are now one proficient machine at administering Office 365 tenants! Just below is Connect-O365 in it's entirety.

 

Connect-O365.ps1

<# 

                O365 Tenant Management
    Date created:                      11/23/15
    Date modified:                     11/25/15
    Author:                            Eric Martinez

    Goal:

    Designed for Managed Services Providers to easily move
    between tenant accounts within the Office 365 service
    offering. Provides access to all O365 modules including
    Exhange Online, SharePoint Online, Admin Center, 
    Compliance Center, and Skype for Business Online.

    Variables:
    $msp_admin = Company Administrator accounts to be created
                 in tenants. This will be the name of your 
                 Company Administrator account that you'll 
                 use to manage your tenants accounts. This
                 account is specific to each tenant.

    $msp_name  = The name of your organization.

    Usage:

    1) Set variables in the SET THESE VARIABLES section
    2) Execute script
    3) Once connected to a tenant, use the command Cheese-It
       to drop back to the tenant selection menu. This also
       disconnects your sessions to the previous tenant.

#> 

###SET THESE VARIABLES
$msp_admin = "mspadmin"
$msp_name = "MSP"
###SET THESE VARIABLES

#Drop to Tenant List
function Cheese-It
{  
    Get-PSSession | Remove-PSSession
    $host.ui.RawUI.WindowTitle = "No Active Connections. Viewing the Tenant list now."
    Connect-O365
}

#Checks to see if you're running as an Administrator
function Check-Administrator
{  
    $user = [Security.Principal.WindowsIdentity]::GetCurrent();
    (New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)  
}

#Builds Menu from Tenant Accounts
function Select-ItemFromList 
{ 
[CmdletBinding()] 
PARAM  
( 
    [Parameter(Mandatory=$true)] 
    $options, 
    [string]$displayProperty, 
    [string]$title = "Select Office 365 Tenant to manage:", 
    [ValidateSet("Menu")] 
    [string]$mode = "Menu", 
    [System.Windows.Forms.SelectionMode]$selectionMode = [System.Windows.Forms.SelectionMode]::One 
) 
    $script:selectedItem = $null 
 
    function BuildMenu 
    { 
    PARAM  
    ( 
        [Parameter(Mandatory=$true)] 
        $options, 
        [string]$displayProperty, 
        [string]$title = "Select Item" 
    ) 
        [int]$optionPrefix = 1 
        [System.Text.StringBuilder]$sb = New-Object System.Text.StringBuilder 
        $sb.Append([Environment]::NewLine + $title + [Environment]::NewLine + [Environment]::NewLine) | Out-Null 
         
        foreach ($option in $options) 
        { 
            $sb.Append(("{0,3}: {1}" -f $optionPrefix,$option.$displayProperty) + [Environment]::NewLine) | Out-Null 
            $optionPrefix++ 
        } 
        $sb.Append([Environment]::NewLine) | Out-Null 
        return $sb.ToString() 
    } 
         
    switch($mode.ToLower())  
    { 
        "menu" 
            { 
                [string]$menuText = BuildMenu -options $options -DisplayProperty $displayProperty -title $title 
                Write-Host $menuText 
                [string]$responseString = Read-Host "Enter Selection" 
                $index = [int]$responseString 
                $script:selectedItem = @($options[$index-1]) 
            } 
    } 
    Write-Output $script:selectedItem 
} 

function Connect-O365
    {
    try
        {
        #Connect to Microsoft Online
        Connect-MsolService -Credential $credential -ErrorAction Stop
        }
    catch [Microsoft.Online.Administration.Automation.MicrosoftOnlineException]
        {
        Write-Host "Your credentials are bogus, dude. No bueno." -ForegroundColor Red
        Start-Sleep 4
        Connect-O365
        }

    #Select Tenant
    $o365tenants = Get-MsolPartnerContract | Select *
    $OrgName = Get-MsolCompanyInformation | select -exp DisplayName
    $host.ui.RawUI.WindowTitle = "No Active Connections. Viewing the Tenant list now."
    $selectedTenant = Select-ItemFromList -Options $o365tenants -displayProperty Name 
    $defaultDomain = $selectedTenant.DefaultDomainName

    #Gather Tenant Credentials
    $roleIdEntry = Get-MsolRole -RoleName "Company Administrator" -TenantId $selectedTenant.tenantID
    $o365admin = Get-MsolRoleMember -RoleObjectId $roleIdEntry.ObjectId -TenantId $selectedTenant.tenantID | where {$_.EmailAddress -like ("intrustadmin*" + $defaultDomain)}

    #Check for o365admin account. If it does not exist, create it
    if ($o365admin -eq $null)
        {
        $tenantPassword = Read-Host "$msp_name Office 365 Administrator account not found! I'll generate one for you. What password should I use? (8-16 Complex Characters)"
        New-MsolUser -UserPrincipalName $msp_admin@$defaultDomain -DisplayName “$msp_name Office 365 Administrator” -TenantId $selectedTenant.tenantId
        Add-MsolRoleMember -RoleName “Company Administrator” –RoleMemberEmailAddress  $msp_admin@$defaultDomain -TenantId $selectedTenant.tenantId
        Set-MsolUserPassword -userPrincipalName $msp_admin@$defaultDomain -NewPassword $tenantPassword -ForceChangePassword $false -TenantId $selectedTenant.tenantId
        Start-Sleep 2
        $o365admin = Get-MsolRoleMember -RoleObjectId $roleIdEntry.ObjectId -TenantId $selectedTenant.tenantID | where {$_.EmailAddress -like "$msp_admin*"}
        $tenantUsername = $o365admin.EmailAddress
        $tenantSPassword = ConvertTo-SecureString "$tenantPassword" -AsPlainText -Force
        $tenantCredentials = New-Object -typename System.Management.Automation.PSCredential -argumentlist ($tenantUsername, $tenantSPassword)
        }

    #Gather existing account's password
    else
        {
        $tenantUsername = $o365admin.EmailAddress
        $tenantSPassword = Read-Host "Enter the password for $tenantUsername" | ConvertTo-SecureString -AsPlainText -Force
        $tenantCredentials = New-Object -typename System.Management.Automation.PSCredential -argumentlist ($tenantUsername, $tenantSPassword)
        }

    #Connect to Office 365 > Exchange Online > Skype Online > Compliance Center
    Connect-MsolService -Credential $tenantCredentials
    try
        {
        $exchangeSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://outlook.office365.com/powershell-liveid/" -Credential $tenantCredentials -Authentication "Basic" -AllowRedirection -ErrorAction Stop
        }
    catch [System.Management.Automation.Remoting.PSRemotingTransportException]
        {
        Write-Host "Received a 403: Unauthorized Access. I may be working too quickly. Waiting 60 seconds before trying that again.." -ForegroundColor Yellow
        Start-Sleep 61
        $exchangeSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://outlook.office365.com/powershell-liveid/" -Credential $tenantCredentials -Authentication "Basic" -AllowRedirection -ErrorAction Stop
        }
    if ($exchangeSession)
        {
        Import-PSSession $exchangeSession -DisableNameChecking
        }

    try
        {
        $skypesession = New-CsOnlineSession -Credential $tenantCredentials -Verbose -OverrideAdminDomain $selectedTenant.DefaultDomainName
        }
    catch [Microsoft.Rtc.Admin.Authentication.CommonAuthException]
        {
        Write-Host "Microsoft has not completed the provisioning of this account within Skype for Business Online. This typically takes a few hours. I am not connecting you to Skype Online." -ForegroundColor Yellow
        }
    if ($skypesession)
        {
        Import-PSSession $skypesession -AllowClobber 
        }

    #Confirmation of connection
    $OrgName = Get-MsolCompanyInformation | select -exp DisplayName
    $host.ui.RawUI.WindowTitle = "You are connected to: " + $OrgName
    Write-Host "You are connected to:" $OrgName -ForegroundColor Green
    }

#Variables for PowerShell Modules
$officeMod = Get-Module -ListAvailable | Where-Object {$_.Name -eq "MSOnline"}
$sharepointMod = Get-Module -ListAvailable | Where-Object {$_.Name -eq "Microsoft.Online.SharePoint.Powershell"}
$skypeMod = Get-Module -ListAvailable | Where-Object {$_.Name -eq "LyncOnlineConnector"}

#Check Shell Dependecies
$adminCheck = Check-Administrator
if (-not ($adminCheck))
    {
    Write-Host "Run As Administrator, dork." -ForegroundColor Red
    Start-Sleep 2
    break
    }
elseif ($officeMod -eq $null)
    {
    Read-Host "Office 365 Module is missing. Press Enter to be forwarded to the needed downloads. There are two downloads required: Microsoft Online Server Sign-in Assistant and the Azure Active Directory Module"
    Start-Sleep -s 3
    Start-Process "https://www.microsoft.com/en-us/download/details.aspx?id=41950"
    STart-Sleep -s 3
    Start-Process "http://go.microsoft.com/fwlink/p/?linkid=236297"
    break
    }
elseif ($sharepointMod -eq $null)
    {
    Read-Host "Sharepoint Online Module is missing. Press Enter to be forwarded to the Microsoft Download Center."
    Start-Sleep -s 3
    Start-Process "https://www.microsoft.com/en-us/download/details.aspx?id=35588"
    break
    }
elseif ($skypeMod -eq $null)
    {
    Read-Host "Skype Online Module is missing. Press Enter to be forwarded to the Microsoft Download Center."
    Start-Sleep -s 3
    Start-Process "https://www.microsoft.com/en-us/download/details.aspx?id=39366"
    break
    }

#Gather Credentials for Office 365 Partner
Import-Module MsOnline
$credential = Get-Credential

#Make it rain $$$
Connect-O365