Office 365

Configuring Office 365 settings using PowerShell – The non-supported way

Office 365 is a huge beast. It has so many services that it's hard to track all of them. It's even harder if you want to manage Office 365 using PowerShell. Microsoft makes many different PowerShell modules available for you, such as AzureAD, AzureADPreview, ExchangeOnline, MicrosoftTeams, and recently, Microsoft.Graph. But even with so many different modules, there are still tasks that Microsoft won't let you do from PowerShell. But it doesn't mean that it's not possible to do it. I've spent some time tracking how Microsoft does things while you click thru the interface and created a PowerShell module that can do it in an automated way. But before I go and introduce the O365Essentials module to you, I wanted to thank Jannick Oeben and Jos Lieben, as, without their help, this module wouldn't happen. Please give them a follow on Twitter!

Unsupported API to read and configure using PowerShell

One of the sections that are almost completely impossible to do with PowerShell (using official Microsoft modules) is configuring Microsoft 365 Admin Center Org Settings. While it may seem that all those names and settings are buried somewhere in the modules, those aren't available in 90% of cases. For a guy like me who likes to create automated documentation of Active Directory or Office 365, it's not fun. It's even more problematic if you're managing Office 365 and have to make sure nobody changes those settings to something less secure.

Of course, most of the time – those settings are set up once and forget, but if you're dealing with 5-10-50-100 Office 365 tenants, you don't really want to go thru all of them and check for compliance. This is why I present to you the O365Essentials PowerShell module. It's a work in progress module whose main goal is to expose GET and SET commands for most (if not all) settings in Admin Center. It also tries to expose some things you can see in the interface, but there's no way to read it via standard queries. Curious about what you get? Before I dive into the details, I just wanted to say that most of the commands use an unsupported API. This means Microsoft may at any moment change it, modify it, and things will stop working. When some command gets official Graph API, I'll replace it with a properly supported command. Until then, it is what it is.

Displaying Directory Sync Status and Errors using PowerShell

Have you ever wanted to see the status of the Directory Sync that Microsoft displays in their portal using PowerShell?

Get-O365DirectorySync
Get-O365DirectorySyncManagement

You can also see the Directory Sync Errors – but I don't have anything to show 🙂

Get-O365DirectorySyncErrors | ft

Getting Office 365 Domain Information using PowerShell

Would you like to find which Domains you have added to the tenant and what Microsoft knows about them?

Get-O365Domain

Would you like to check Office 365 Domain Records?

Get-O365DomainRecords

Or maybe you want to see Office 365 Domain Dependencies? You know the ones when you try to remove a domain, and Microsoft tells you some object is still holding to it, and you first need to remove it? Now you can ask PowerShell to give you all the objects and probably automate around it.

Get-O365DomainDependencies -DomainName 'evotec.pl' | ft

Office 365 Domain Health Check with PowerShell

You're now able to display Domain Health Check that shows problems with domain records for a domain.

$Tenant = Get-O365DomainHealth -DomainName 'evotec.pl'
$Tenant
$Tenant.HealthResultList

Azure AD Connect Information from Office 365 using PowerShell

Additionally, I've exposed Azure AD Connect settings, so now it's possible to query Office 365 and get information about current Azure AD Connect settings, Pass-Thru Agents, and their status or SSO configuration.

Get-O365AzureADConnect
Get-O365AzureADConnectPTA
Get-O365AzureADConnectSSO

This means that auditing Office 365 Tenants can be much easier with those new commands. While it shows above that there is an error when processing PTA agents, it does work properly for the tenant that has it set up.

Reading and changing Admin Center settings using PowerShell

As I mentioned initially, it's now possible to list and change settings hidden inside the Org Settings. That means you can enable/disable Azure Speech Services, Booking, Calendar, Cortana, Dynamics 365 Sales Insights, Microsoft Forms, Microsoft Planner, Microsoft To-Do, Modern Authentication, My Analytics, Office Scripts, and more using PowerShell! But all the above is just a small set of what the module exposes to you. The module exposes over 100 commands doing both GET and SET actions. To give you some ideas on how this works, here are few examples (more can be found on GitHub in the Examples folder).

Get or Set configuration for Microsoft Planner
Get-O365OrgPlanner -Verbose | Format-Table
Set-O365OrgPlanner -AllowCalendarSharing $true -Verbose -WhatIf
Get or Set configuration for Reports
Get-O365OrgReports -Verbose | Format-Table
Set-O365OrgReports -Verbose -PowerBiEnabled $true -PrivacyEnabled $true -WhatIf
Get or Set configuration for Whiteboard
Get-O365OrgWhiteboard -Verbose | Format-List
Set-O365OrgWhiteboard -DiagnosticData Neither -OptionalConnectedExperiences $true -BoardSharingEnabled $true -OneDriveStorageEnabled $false -WhiteboardEnabled $true -Verbose -WhatIf
Get or Set configuration for Microsoft Project
Get-O365OrgProject -Verbose
Set-O365OrgProject -RoadmapEnabled $true -ProjectForTheWebEnabled $true -Verbose -WhatIf
Get or Set configuration for Microsoft Analytics
Get-O365OrgMyAnalytics -Verbose | Format-Table
Set-O365OrgMyAnalytics -EnableInsightsDashboard $true -EnableWeeklyDigest $true -EnableInsightsOutlookAddIn $true -Verbose -WhatIf
Get or Set configuration for Microsoft ToDo
Get-O365OrgToDo | Format-Table
Set-O365OrgTodo -Verbose -PushNotificationEnabled $true -ExternalJoinEnabled $true -ExternalShareEnabled $true -WhatIf
Get or Set configuration for Microsoft Sway
Get-O365OrgSway -Verbose
Set-O365OrgSway -FlickrEnabled $true -YouTubeEnabled $true -Verbose -WhatIf
Get or Set configuration for Microsoft Forms
Get-O365OrgForms | Format-List
Set-O365OrgForms -Verbose -BingImageSearchEnabled $false -WhatIf
Get or Set configuration for Microsoft Bookings
Get-O365OrgBookings | Format-Table
Set-O365OrgBookings -Verbose -Enabled $true -SocialSharingRestricted $false -WhatIf
Get or Set configuration for Bing Data
Get-O365OrgBingDataCollection -Verbose | Format-Table
Set-O365OrgBingDataCollection -IsBingDataCollectionConsented $true -Verbose
Get or Set configuration for Graph Data Connect
Get-O365OrgGraphDataConnect | Format-Table

# THE EMAIL ADDRESS for GROUP MUST EXISTS - if not you will break the API (which you can fix with force)
Set-O365OrgGraphDataConnect -ServiceEnabled $true -WhatIf
Set-O365OrgGraphDataConnect -ServiceEnabled $false -TenantLockBoxApproverGroup 'graph@evotec.pl' -Verbose -Force -WhatIf
Set-O365OrgGraphDataConnect -ServiceEnabled $true -TenantLockBoxApproverGroup 'test@evotec.pl' -Verbose -Force -WhatIf
Set-O365OrgGraphDataConnect -ServiceEnabled $false -WhatIf

And this is just the tip of the iceberg. Over 150 commands for reading and configuring Office 365 settings are available. The current list is available at the end of the blog post, or you can check GitHub directly, which I'll try to update when things change.

Additional commands over Graph API

While this module at the beginning of its existence focuses on things you're not able to do using official PowerShell modules, I will add more and more commands that use supported Graph API calls to manage Office 365. That's why I'm going to write some commands to behave more human-friendly, starting with Azure AD Roles Members. You can now ask for specific roles or all roles and get everything back with just one query. No need to convert ID roles to names, no need to query Office 365 multiple times. One query – done.

Get-O365AzureADRolesMember -RoleName 'Global Administrator', 'Groups Administrator' | ft
Get-O365AzureADRolesMember -All | ft

Microsoft introduced Graph API, which is the new way to deal with Office 365 and Azure. While the Graph API idea is great, it makes you work for even simple data you want to query. The same goes for other queries for users, guests, groups. I hate querying things by ID.

Get-O365User -UserPrincipalName 'przemyslaw.klys@domain.pl'
Get-O365User -GuestsOnly
Get-O365User -EmailAddress 'przemyslaw.klys@domain.pl'

Most of those Graph API calls are there for a reason. I use them internally, so when there's a need to provide a user or a group to functions I'm using over unsupported API, I try to make it easy for a user and provide group via Display Name, rather than remembering the ID of a group.

Office 365 Licenses with Service Plans information

But that's not all. What I don't like about Microsoft's way of displaying data to users in their official modules or Graph API calls is that they return ID in many cases. ID for everything, and it's up to you to find out what that ID means. This is something that, for most people, in the beginning, is very hard to understand and do their own logic for that. For years, dealing with Office 365 licenses, you had to provide SKUID or ServiceName rather than what you see in the Office 365 portal. I understand there may be a reason for this, but it makes it a real pain for normal users. What does ADALLOM_S_STANDALONE mean? What is DYN365_CDS_VIRAL for? That's why if possible, for most commands that output data or even set some data, I try to allow the ability to use either ID, GroupName, Name, or DisplayName. One of those commands that may be useful for day-to-day is extracting license names and service plans for them with a full display name.

$Licenses = Get-O365AzureLicenses -Verbose
$Licenses | Format-Table

$LicensesServicePlans = Get-O365AzureLicenses -ServicePlans -IncludeLicenseDetails -Verbose
$LicensesServicePlans | Format-Table

As you can see, using Get-O365AzureLicenses, you're now able to see what each license SKUID resolves to, what each service name means. Microsoft has hidden in the GUI and never exposed it – you can now get it directly with a single command.

Setting up Group Based Licensing using PowerShell

I've added the commands above for licensing for a simple reason – to make it more user-friendly when using other commands. Let's take this another example of using Group-Based Licensing. You can do it from the GUI, of course, but it's not so easy when using PowerShell. Using GroupID, LicenseSKUID, and Disabled Service Name (instead of what makes more sense – Enabled).

Set-O365GroupLicenses -GroupDisplayName 'Test-Group-TestEVOTECPL' -Licenses @(
    New-O365License -LicenseName 'Office 365 E3' -Verbose
    New-O365License -LicenseName 'Enterprise Mobility + Security E5' -Verbose
) -Verbose -WhatIf

As you can see above or below, I'm allowing users to define Disabled Service Display Names or Enabled Service Display Names, full License Name instead of what Microsoft expects in their backend.

Set-O365GroupLicenses -GroupDisplayName 'Test-Group-TestEVOTECPL' -Licenses @(
    New-O365License -LicenseName 'Office 365 E3' -Verbose -DisabledServicesDisplayName 'Microsoft Kaizala Pro', 'Whiteboard (Plan 2)'
    New-O365License -LicenseName 'Enterprise Mobility + Security E5' -Verbose -EnabledServicesDisplayName 'Azure Information Protection Premium P2', 'Microsoft Defender for Identity'
) -Verbose -WhatIf

Of course, all SET commands have WhatIf implemented to see what will happen when you execute it. It may not be that obvious because WhatIf will display a URL being used along with JSON configuration.

Set-O365GroupLicenses -GroupDisplayName 'Test-Group-TestEVOTECPL' -Licenses @(
    New-O365License -LicenseName 'Office 365 E3' -Verbose -EnabledServicesDisplayName 'Microsoft Kaizala Pro', 'Whiteboard (Plan 2)', 'Microsoft Forms (Plan E3)'
    New-O365License -LicenseSKUID 'evotecpoland:EMSPREMIUM' -Verbose -EnabledServicesDisplayName 'Azure Information Protection Premium P2', 'Azure Rights Managemen', 'Azure Active Directory Premium P2'
) -WhatIf -Verbose

I do hope, someday Microsoft PowerShell Developers will start working on more user-friendly solutions. If it's possible to do it from the front-end, it must be possible from the back-end.

Connecting to Office 365 using O365Essentials

If you want to connect to O365 using simple credentials without MFA that can be further automated, you can use the following way to connect.

if (-not $Credentials) {
    $Credentials = Get-Credential
}
# This makes a connection to Office 365 tenant
# since we don't want to save the data we null it out
# keep in mind that if there's an MFA you would be better left without Credentials and just let it prompt you
$null = Connect-O365Admin -Verbose -Credential $Credentials

If you want to connect to Office 365 using an MFA account you have to skip all parameters and just let Connect-O365Admin prompt you for them.

# This makes a connection to Office 365 tenant
$null = Connect-O365Admin -Verbose 
How to use/install O365Essentials

To run it, just install it from PowerShellGallery, and you're good. If you are not an administrator, you can use this module within the scope of the current user.

Install-Module O365Essentials -Force -Scope CurrentUser

If, however, you would like to make sure the module is available machine-wide, you can do this without providing scope.

Install-Module O365Essentials -Force

All source codes are available on GitHub. If you have an issue, feature request, problem, please use GitHub as a way to reach for support. As I have limited time, reaching out via email doesn't bring many results. As with many of my other PowerShell modules, it's always a work in progress, and not everything is 100% finished. It's highly recommended to run this module using PowerShell 7+ as it has better error handling. There are multiple open issues on GitHub that still are unsolved. As most of the code is based on reverse engineering, it's not straightforward how things are working in the backend and what will work and what won't work. If you know-how, want to contribute – please do so!

Commands reference

Few of those commands are not working and have open issues. It's mostly related to a problem with scopes that my module doesn't know how to deal with. If you know how to fix it without explicitly defining ClientID/bit's going to solve that problem for us.

Get Set Status
Get-O365AzureADConnect
Get-O365AzureADConnectPTA
Get-O365AzureADConnectSSO
Get-O365AzureADRoles
Get-O365AzureADRolesMember
Get-O365AzureConditionalAccess
Get-O365AzureConditionalAccessClassic
Get-O365AzureConditionalAccessLocation Missing scopes in Graph API calls
Get-O365AzureConditionalAccessPolicy
Get-O365AzureConditionalAccessTerms
Get-O365AzureConditionalAccessVPN
Get-O365AzureEnterpriseAppsGroupConsent Set-O365AzureEnterpriseAppsGroupConsent
Get-O365AzureEnterpriseAppsUserConsent Set-O365AzureEnterpriseAppsUserConsent
Get-O365AzureEnterpriseAppsUserSettings Set-O365AzureEnterpriseAppsUserSettings
Get-O365AzureEnterpriseAppsUserSettingsAdmin Set-O365AzureEnterpriseAppsUserSettingsAdmin Set cmd not working
Get-O365AzureEnterpriseAppsUserSettingsPromoted
Get-O365AzureExternalCollaborationFlows Not working
Get-O365AzureExternalCollaborationSettings Set-O365AzureExternalCollaborationSettings Not working
Get-O365AzureExternalIdentitiesEmail Missing scopes in Graph API calls
Get-O365AzureExternalIdentitiesPolicies
Get-O365AzureFeatureConfiguration
Get-O365AzureFeaturePortal
Get-O365AzureGroupExpiration Set-O365AzureGroupExpiration
Get-O365AzureGroupGeneral
Get-O365AzureGroupM365 Set-O365AzureGroupM365
Get-O365AzureGroupNamingPolicy Set-O365AzureGroupNamingPolicy
Get-O365AzureGroupSecurity Set-O365AzureGroupSecurity
Get-O365AzureGroupSelfService Set-O365AzureGroupSelfService
Get-O365AzureLicenses
Get-O365AzureMultiFactorAuthentication Set-O365AzureMultiFactorAuthentication Set cmd not working
Get-O365AzureTenantSKU
Get-O365AzureUserSettings Set-O365AzureUserSettings
Get-O365BillingAccounts Doesn't work, probably missing parameters such as accountid
Get-O365BillingInvoices
Get-O365BillingLicenseAutoClaim Set-O365BillingLicenseAutoClaim
Get-O365BillingLicenseRequests
Get-O365BillingNotifications Set-O365BillingNotifications
Get-O365BillingNotificationsList
Get-O365BillingPaymentMethods
Get-O365BillingProfile Doesn't work, wrong URL, no data to test
Get-O365BillingSubscriptions
Get-O365ConsiergeAll
Get-O365DirectorySync
Get-O365DirectorySyncErrors
Get-O365DirectorySyncManagement
Get-O365Domain
Get-O365DomainDependencies
Get-O365DomainHealth
Get-O365DomainRecords
Get-O365DomainTroubleshooting
Get-O365Group
Get-O365GroupAdministrativeUnit
Get-O365GroupLicenses Set-O365GroupLicenses
Get-O365GroupMember
Get-O365OrgAzureSpeechServices Set-O365OrgAzureSpeechServices
Get-O365OrgBingDataCollection Set-O365OrgBingDataCollection
Get-O365OrgBookings Set-O365OrgBookings
Get-O365OrgBriefingEmail Set-O365OrgBriefingEmail
Get-O365OrgCalendarSharing Set-O365OrgCalendarSharing
Get-O365OrgCommunicationToUsers Set-O365OrgCommunicationToUsers
Get-O365OrgCortana Set-O365OrgCortana
Get-O365OrgCustomerLockbox Set-O365OrgCustomerLockbox
Get-O365OrgCustomThemes
Get-O365OrgDataLocation
Get-O365OrgDynamics365ConnectionGraph Set-O365OrgDynamics365ConnectionGraph
Get-O365OrgDynamics365CustomerVoice Set-O365OrgDynamics365CustomerVoice
Get-O365OrgDynamics365SalesInsights Set-O365OrgDynamics365SalesInsights
Get-O365OrgForms Set-O365OrgForms
Get-O365OrgGraphDataConnect Set-O365OrgGraphDataConnect
Get-O365OrgHelpdeskInformation Set-O365OrgHelpdeskInformation
Get-O365OrgInstallationOptions Set-O365OrgInstallationOptions
Get-O365OrgM365Groups Set-O365OrgM365Groups
Get-O365OrgMicrosoftTeams Set-O365OrgMicrosoftTeams Set command not working – 100-500 nested properties
Get-O365OrgModernAuthentication Set-O365OrgModernAuthentication
Get-O365OrgMyAnalytics Set-O365OrgMyAnalytics
Get-O365OrgNews Set-O365OrgNews
Get-O365OrgOfficeOnTheWeb Set-O365OrgOfficeOnTheWeb
Get-O365OrgOfficeProductivity Set-O365OrgOfficeProductivity
Get-O365OrgOrganizationInformation Set-O365OrgOrganizationInformation
Get-O365OrgPasswordExpirationPolicy Set-O365OrgPasswordExpirationPolicy
Get-O365OrgPlanner Set-O365OrgPlanner
Get-O365OrgPrivacyProfile Set-O365OrgPrivacyProfile
Get-O365OrgPrivilegedAccess Set-O365OrgPrivilegedAccess Requires more testing on SET cmd
Get-O365OrgProject Set-O365OrgProject
Get-O365OrgReleasePreferences Set-O365OrgReleasePreferences
Get-O365OrgReports Set-O365OrgReports
Get-O365OrgScripts Set-O365OrgScripts
Get-O365OrgSharePoint Set-O365OrgSharePoint
Get-O365OrgSharing Set-O365OrgSharing
Get-O365OrgSway Set-O365OrgSway
Get-O365OrgToDo Set-O365OrgTodo
Get-O365OrgUserConsentApps Set-O365OrgUserConsentApps
Get-O365OrgUserOwnedApps Set-O365OrgUserOwnedApps
Get-O365OrgWhiteboard Set-O365OrgWhiteboard
Get-O365PartnerRelationship
Get-O365PasswordReset Set-O365PasswordReset
Get-O365PasswordResetIntegration Set-O365PasswordResetIntegration
Get-O365SearchIntelligenceBingConfigurations Set-O365SearchIntelligenceBingConfigurations
Get-O365SearchIntelligenceBingExtension Set-O365SearchIntelligenceBingExtension
Get-O365SearchIntelligenceItemInsights Set-O365SearchIntelligenceItemInsights
Get-O365SearchIntelligenceMeetingInsights Set-O365SearchIntelligenceMeetingInsights
Get-O365ServicePrincipal
Get-O365TenantID
Get-O365User

Please remember to use any of those commands; you first need to connect to the Office 365 tenant using an account with permissions. For most read-only tasks Global Reader will be enough, but if you want to use set commands, you may need to use Global Admin for that. Feel free to review source code on GitHub/PowerShellGallery. There's nothing complicated in there.

# This makes a connection to Office 365 tenant
$null = Connect-O365Admin -Verbose 

Przemyslaw Klys

System Architect with over 14 years of experience in the IT field. Skilled, among others, in Active Directory, Microsoft Exchange and Office 365. Profoundly interested in PowerShell. Software geek.

Share
Published by
Przemyslaw Klys

Recent Posts

Syncing Global Address List (GAL) to personal contacts and between Office 365 tenants with PowerShell

Hey there! Today, I wanted to introduce you to one of the small but excellent…

4 months ago

Active Directory Health Check using Microsoft Entra Connect Health Service

Active Directory (AD) is crucial in managing identities and resources within an organization. Ensuring its…

6 months ago

Seamless HTML Report Creation: Harness the Power of Markdown with PSWriteHTML PowerShell Module

In today's digital age, the ability to create compelling and informative HTML reports and documents…

7 months ago

How to Efficiently Remove Comments from Your PowerShell Script

As part of my daily development, I create lots of code that I subsequently comment…

7 months ago

Unlocking PowerShell Magic: Different Approach to Creating ‘Empty’ PSCustomObjects

Today I saw an article from Christian Ritter, "PowerShell: Creating an "empty" PSCustomObject" on X…

8 months ago

Report Active Directory Accounts that are Synchronized with Azure AD

I was scrolling X (aka Twitter) today and saw this blog post, "PowerShell: Report On-Premises…

8 months ago