The last post in this series, covered how many years we’ve been around gobbledygoop, and how ‘expenses’ over those ‘years’, force some of us to do more gobbledygoop. Well, I wont bore you with any of that until next post. Rest assured, let’s just get on with why we are here. Here (I said it again) we explore how to get configuration data from Exchange Online and Azure Active Directory. This will cover tenant configurations and not delve too deep into policies and the policy engine as they deserve their own space.
Pull Exchange Online Configuration using Powershell
This Exchange online configuration does the same set of work as its On-Prem counterpart using Online verbs. It attempts to grab all policies but will error and skip(currently) if none exist. We then use the same motion to get all RBAC and their memberships
$O365Configs = @{}
Write-Host " "
Write-Host " "
Write-Host "Gather O365 configurations..."
$O365Configs.OrgConfig = $(Get-OrganizationConfig)
$O365Configs.OrgRelations = $(Get-OrganizationRelationship)
$O365Configs.FedID = $(Get-FederatedOrganizationIdentifier)
$O365Configs.FedInfo = $(ForEach ($Dom in $O365Configs.FedID.domains) {Get-FederationInformation $Dom})
$O365Configs.FedTrust = $(Get-FederationTrust)
$O365Configs.IRM = $(Get-IRMConfiguration)
$O365Configs.MsgEncryptConfig = $(Get-OMEConfiguration)
$O365Configs.AcceptDom = $(Get-AcceptedDomain)
$O365Configs.InboundConn = $(Get-InboundConnector)
$O365Configs.OutboundConn = $(Get-OutboundConnector)
$O365Configs.Transport = $(Get-TransportConfig)
$O365Configs.AdministrativeUnit = $(Get-AdministrativeUnit)
$O365Configs.HybridMailFlow = $(Get-HybridMailflow)
$O365Configs.MigrationEndpoint = $(Get-MigrationEndpoint)
$O365Configs.RetentionPolicy = $(Get-RetentionPolicy)
$O365Configs.SharingPolicy = $(Get-SharingPolicy)
Write-Host " "
Write-Host " "
Write-Host "Retrieving Admin Roles..."
$O365Configs.Roles = $(pull-MsolRoles)
$O365Info.HIPAAStuff = $(Get-DataClassificationConfig)
$O365Configs.DLP = $(Get-DLPPolicy)
$O365Configs | Export-Clixml .\O365ConfigReportXML-$($expoDate).xml -Encoding UTF8
We export all configs to XML (due to limitations in the CSV package) so that we can easily query the complete dataset again later. For example:
We take this one step further:
It looks like we are querying AD directly doesnt it?
Get Mailbox Folder Permissions
This next function may look familiar to those of you who’ve read the On-Premise version of this post. It iterates through all permissions assigned to all ‘folders‘ in every mailbox ignoring several default permissions.
Function Pull-MBXFolderPerms {
Param ([string]$MBXName)
Start-Sleep -Milliseconds 600
$Mailbox = '' + $MBXName
$Folders = Get-MailboxFolderStatistics $Mailbox | % {$_.folderpath} | % {$_.replace("/","\")}
Write-host "Processing folders on mailbox $Mailbox"
ForEach ($Fold in $Folders) {
$FolderMosh = $Mailbox + ":" + $Fold
$Permissions = Get-MailboxFolderPermission -identity $FolderMosh -ErrorAction silentlycontinue
$FolderPerms += $Permissions | ? {$_.User -notlike "Default" -and $_.User -notlike "Anonymous" -and $_.AccessRights -notlike "None" -and $_.AccessRights -notlike "Owner" } | Select-Object @{name='Mailbox';expression={$MBXName}},FolderName,@{name='UserWithAccess';expression={$_.User -join ','}},@{name='AccessRights';expression={$_.AccessRights -join ','}}
}
Return $FolderPerms
}
This next function connects to the MsolService and gets the elevated group memberships from the tenant. I use a slightly different method here to build an array (we may do a post on arrays and datasets later).
Get O365 Roles and Membership using Powershell
Function pull-MsolRoles {
Connect-MsolService -Credential $O365Cred
$UserRoles = @()
$Roles = Get-MsolRole | ? Name -Like "*"
foreach($Role in $Roles) {
$RoleName = $Role.Name
$Members = Get-MsolRoleMember -RoleObjectId $Role.ObjectId
if($Members) {
foreach($Member in $Members) {
$Role = New-Object PSObject
Add-Member -input $Role noteproperty 'RoleName' $RoleName
Add-Member -input $Role noteproperty 'RoleMemberType' $Member.RoleMemberType
Add-Member -input $Role noteproperty 'EmailAddress' $Member.EmailAddress
Add-Member -input $Role noteproperty 'DisplayName' $Member.DisplayName
Add-Member -input $Role noteproperty 'isLicensed' $Member.isLicensed
$UserRoles += $Role
}
}
}
Return $UserRoles
}
Pull Exchange Online User, Device, Mailbox and Folder Permissions using Powershell
In this function we are pulling all user, guest, device and mailbox and folder permission data in the online mailbox store. This section tries to get all mailbox and folder permissions. When i say “tries“, It’s because this process can take many days and is likely to break often due to the unreliability of the internet . When using this method, I so very often find myself doing Exchange Online permissions in smaller chunks. When we revisit this work in its own post we will explore methods to work around this problem, but for now this is what it does:
$O365Data = @{}
Write-Host " "
Write-Host " "
Write-Host "Gather O365 data..."
Write-Host " "
Write-Host "Retrieving MobileDevice Data..."
$O365Data.MobileDevices = $(Get-Mobiledevice)
$O365Data.Clutter = $(Get-clutter | ? {$_.IsEnabled -eq $TRUE})
Write-Host "Retrieving Contacts..."
$O365Data.Contacts = $(Get-Contact)
Write-Host "Retrieving O365 Groups..."
$O365Data.UGroups = $(Get-UnifiedGroup)
Write-Host "Retrieving Mailbox Data..."
$O365Data.MBXs = $(Get-Mailbox -resultsize unlimited)
Write-Host "Retrieving Mailbox Permissions..."
$O365Data.MBXPerms = $(ForEach ($Box in $O365Data.MBXs) {Get-MailboxPermission $Box.Identity | ? {$_.user.tostring() -ne "NT AUTHORITY\SELF" -and $_.user.tostring() -notlike "S-1-5-21*" -and $_.user.tostring() -notlike "*admin*" -and $_.IsInherited -eq $false}} )
Write-Host "Retrieving Mailbox Folder Permissions..."
$O365Data.MBXFolderPerms = $(ForEach ($MBox in $O365Data.MBXs) {Pull-MBXFolderPerms($MBox.Name)})
Write-Host "Write all data to File..."
$O365Data | Export-Clixml .\O365DataReportXML-$($expoDate).xml -Encoding UTF8
As you can see it uses the code from the ‘Pull-MBXFolderPerms’ function listed earlier to perform the folder permissions query.
The whole script looks like this when its all done:
[cmdletbinding()]
Param (
[switch]$O365,
[switch]$AzureAD,
[switch]$GetData
)
# Grab all Active Directory Configuration Data
Function Pull-AzureADconfigs {
$AZADConfigs = @{}
Write-Host " "
Write-Host " "
Write-Host "Gather Azure Active Directory configurations..."
$AZADConfigs.AdminUnits = $(Get-AzureADAdministrativeUnit -all $true)
$AZADConfigs.Applications = $(Get-AzureADApplication -all $true)
$AZADConfigs.AZADSetting = $(Get-AzureADDirectorySetting -all $true)
$AZADConfigs.AZADDomain = $(Get-AzureADDomain)
$AZADConfigs.AZADExtProp = $(Get-AzureADExtensionProperty)
$AZADConfigs.ExternalDomFed = $(Get-AzureADExternalDomainFederation)
$AZADConfigs.AZADPolicy = $(Get-AzureADPolicy)
$AZADConfigs.AuthPolicy = $(Get-AzureADMSAuthorizationPolicy)
$AZADConfigs.CAP = $(Get-AzureADMSConditionalAccessPolicy)
$AZADConfigs.IDProvider = $(Get-AzureADMSIdentityProvider)
# $AZADConfigs.LifecycPolicy = $(Get-AzureADMSLifecyclePolicyGroup)
$AZADConfigs.NamedLocPolicy = $(Get-AzureADMSNamedLocationPolicy)
$AZADConfigs.SubscibedSkus = $(Get-AzureADSubscribedSku)
$AZADConfigs.TenantDetail = $(Get-AzureADTenantDetail)
$AZADConfigs.TrustedCertAuth = $(Get-AzureADTrustedCertificateAuthority)
IF ($getdata){
Pull-AZADObjectData
}
$AZADConfigs | Export-Clixml .\AzureADConfigReport-$($expoDate).xml -Encoding UTF8
}
Function Pull-AZADObjectData {
$AZADObjects = @{}
Write-Host " "
Write-Host " "
Write-Host "Gather Azure Active Directory Object Data..."
$AZADObjects.Contacts = $(Get-AzureADContact -all $true)
$AZADObjects.Devices = $(Get-AzureADDevice -all $true)
$AZADObjects.ADRoles = $(Get-AzureADDirectoryRole)
$AZADObjects.ADRoleMembers = $(ForEach ($mem in $AZADObjects.ADRoles) {Get-AzureADDirectoryRoleMember -ObjectId $mem.ObjectId})
$AZADObjects.ADGroups = $(Get-AzureADGroup -all $true)
$AZADObjects.ADUsers = $(Get-AzureADUser -all $true)
$AZADObjects | Export-Clixml .\AzureADDataReport-$($expoDate).xml -Encoding UTF8
}
Function Pull-MBXFolderPerms {
Param ([string]$MBXName)
Start-Sleep -Milliseconds 600
$Mailbox = '' + $MBXName
$Folders = Get-MailboxFolderStatistics $Mailbox | % {$_.folderpath} | % {$_.replace("/","\")}
Write-host "Processing folders on mailbox $Mailbox"
ForEach ($Fold in $Folders) {
$FolderKey = $Mailbox + ":" + $Fold
$Permissions = Get-MailboxFolderPermission -identity $FolderKey -ErrorAction silentlycontinue
$FolderPerms += $Permissions | ? {$_.User -notlike "Default" -and $_.User -notlike "Anonymous" -and $_.AccessRights -notlike "None" -and $_.AccessRights -notlike "Owner" } | Select-Object @{name='Mailbox';expression={$MBXName}},FolderName,@{name='UserWithAccess';expression={$_.User -join ','}},@{name='AccessRights';expression={$_.AccessRights -join ','}}
}
Return $FolderPerms
}
Function O365-Conn {
$msoExchangeURL = "https://outlook.office365.com/powershell-liveid/"
$Global:session1 = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri $msoExchangeURL -Credential $O365Cred -Authentication Basic -AllowRedirection
Import-PSSession $session1 -DisableNameChecking -AllowClobber
Pull-O365Configs
IF ($GetData) {
Pull-O365Data
}
Remove-PSSession $Session1
}
#Grab all ExchangeOnline Configs
Function pull-O365Configs {
$O365Configs = @{}
Write-Host " "
Write-Host " "
Write-Host "Gather O365 configurations..."
$O365Configs.OrgConfig = $(Get-OrganizationConfig)
$O365Configs.OrgRelations = $(Get-OrganizationRelationship)
$O365Configs.FedID = $(Get-FederatedOrganizationIdentifier)
$O365Configs.FedInfo = $(ForEach ($Dom in $O365Configs.FedID.domains) {Get-FederationInformation $Dom})
$O365Configs.FedTrust = $(Get-FederationTrust)
$O365Configs.IRM = $(Get-IRMConfiguration)
$O365Configs.MsgEncryptConfig = $(Get-OMEConfiguration)
$O365Configs.AcceptDom = $(Get-AcceptedDomain)
$O365Configs.InboundConn = $(Get-InboundConnector)
$O365Configs.OutboundConn = $(Get-OutboundConnector)
$O365Configs.Transport = $(Get-TransportConfig)
$O365Configs.AdministrativeUnit = $(Get-AdministrativeUnit)
$O365Configs.HybridMailFlow = $(Get-HybridMailflow)
$O365Configs.MigrationEndpoint = $(Get-MigrationEndpoint)
$O365Configs.RetentionPolicy = $(Get-RetentionPolicy)
$O365Configs.SharingPolicy = $(Get-SharingPolicy)
Write-Host " "
Write-Host " "
Write-Host "Retrieving Admin Roles..."
$O365Configs.Roles = $(pull-MsolRoles)
$O365Info.HIPAAStuff = $(Get-DataClassificationConfig)
$O365Configs.DLP = $(Get-DLPPolicy)
$O365Configs | Export-Clixml .\O365ConfigReportXML-$($expoDate).xml -Encoding UTF8
}
#Grab Useful ExchangeOnline Info
Function pull-O365Data {
$O365Data = @{}
Write-Host " "
Write-Host " "
Write-Host "Gather O365 data..."
Write-Host " "
Write-Host "Retrieving MobileDevice Data..."
$O365Data.MobileDevices = $(Get-Mobiledevice)
$O365Data.Clutter = $(Get-clutter | ? {$_.IsEnabled -eq $TRUE})
Write-Host "Retrieving Contacts..."
$O365Data.Contacts = $(Get-Contact)
Write-Host "Retrieving O365 Groups..."
$O365Data.UGroups = $(Get-UnifiedGroup)
Write-Host "Retrieving Mailbox Data..."
$O365Data.MBXs = $(Get-Mailbox -resultsize unlimited)
Write-Host "Retrieving Mailbox Permissions..."
$O365Data.MBXPerms = $(ForEach ($Box in $O365Data.MBXs) {Get-MailboxPermission $Box.Identity | ? {$_.user.tostring() -ne "NT AUTHORITY\SELF" -and $_.user.tostring() -notlike "S-1-5-21*" -and $_.user.tostring() -notlike "*admin*" -and $_.IsInherited -eq $false}} )
Write-Host "Retrieving Mailbox Folder Permissions..."
$O365Data.MBXFolderPerms = $(ForEach ($MBox in $O365Data.MBXs) {Pull-MBXFolderPerms($MBox.Name)})
Write-Host "Write all data to File..."
$O365Data | Export-Clixml .\O365DataReportXML-$($expoDate).xml -Encoding UTF8
}
Function pull-MsolRoles {
Connect-MsolService -Credential $O365Cred
$UserRoles = @()
$Roles = Get-MsolRole | ? Name -Like "*"
foreach($Role in $Roles) {
$RoleName = $Role.Name
$Members = Get-MsolRoleMember -RoleObjectId $Role.ObjectId
if($Members) {
foreach($Member in $Members) {
$Role = New-Object PSObject
Add-Member -input $Role noteproperty 'RoleName' $RoleName
Add-Member -input $Role noteproperty 'RoleMemberType' $Member.RoleMemberType
Add-Member -input $Role noteproperty 'EmailAddress' $Member.EmailAddress
Add-Member -input $Role noteproperty 'DisplayName' $Member.DisplayName
Add-Member -input $Role noteproperty 'isLicensed' $Member.isLicensed
$UserRoles += $Role
}
}
}
Return $UserRoles
}
# Start Timer
$StopWatch = new-object system.diagnostics.stopwatch
$StopWatch.Start()
# Begin work
#####################################
$expoDate = Get-Date -Format ddMMyyyy
$O365Cred = get-credential
IF ($AzureAD) {
Write-Host "Please Enter Azure Tenant credentials"
Connect-AzureAD -credential $o365cred
Pull-AzureADconfigs
}
IF ($O365) {
Write-Host "Please Enter O365 credentials"
# $O365Cred = get-credential
O365-Conn
}
# Stop the timer
$StopWatch.Stop()
# Done
Write $Stopwatch.Elapsed | Select days,hours,minutes,seconds
I like to throw in a stopwatch to track the amount of time the scripts take to run. Name the script file anything you want then execute it. The final script comes with three switches.
-AzureAD
-O365
-GetData
The first two will pull one or both the configs from AzureAD or O365.
Using -GetData will pull all user and device data available for the service being queried. Use caution with -GetData as it can be unreliable on getting mailbox permission data on large organizations. I am still in the process of building a method to get past this but for now you may want to edit the ‘scope’ of mailboxes that we pull permissions from to chunks of 500 or so mailboxes. If you are connected via ExpressRoute this problem goes away.
Bottom Line
With this script you should be able to get a configuration snapshot of your AzureAD and Exchange Online environments. I will be posting a method for reporting and wielding this data using the Pscribo powershell module and lots of patience. Look for that coming soon!