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!
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.
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
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.
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-O365OrgPlanner -Verbose | Format-Table Set-O365OrgPlanner -AllowCalendarSharing $true -Verbose -WhatIf
Get-O365OrgReports -Verbose | Format-Table Set-O365OrgReports -Verbose -PowerBiEnabled $true -PrivacyEnabled $true -WhatIf
Get-O365OrgWhiteboard -Verbose | Format-List Set-O365OrgWhiteboard -DiagnosticData Neither -OptionalConnectedExperiences $true -BoardSharingEnabled $true -OneDriveStorageEnabled $false -WhiteboardEnabled $true -Verbose -WhatIf
Get-O365OrgProject -Verbose Set-O365OrgProject -RoadmapEnabled $true -ProjectForTheWebEnabled $true -Verbose -WhatIf
Get-O365OrgMyAnalytics -Verbose | Format-Table Set-O365OrgMyAnalytics -EnableInsightsDashboard $true -EnableWeeklyDigest $true -EnableInsightsOutlookAddIn $true -Verbose -WhatIf
Get-O365OrgToDo | Format-Table Set-O365OrgTodo -Verbose -PushNotificationEnabled $true -ExternalJoinEnabled $true -ExternalShareEnabled $true -WhatIf
Get-O365OrgSway -Verbose Set-O365OrgSway -FlickrEnabled $true -YouTubeEnabled $true -Verbose -WhatIf
Get-O365OrgForms | Format-List Set-O365OrgForms -Verbose -BingImageSearchEnabled $false -WhatIf
Get-O365OrgBookings | Format-Table Set-O365OrgBookings -Verbose -Enabled $true -SocialSharingRestricted $false -WhatIf
Get-O365OrgBingDataCollection -Verbose | Format-Table Set-O365OrgBingDataCollection -IsBingDataCollectionConsented $true -Verbose
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.
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.
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.
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.
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
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!
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