get-DLHierarchyFromExchangeOnline.ps1
############################################################################################# # DISCLAIMER: # # # # THE SAMPLE SCRIPTS ARE NOT SUPPORTED UNDER ANY MICROSOFT STANDARD SUPPORT # # PROGRAM OR SERVICE. THE SAMPLE SCRIPTS ARE PROVIDED AS IS WITHOUT WARRANTY # # OF ANY KIND. MICROSOFT FURTHER DISCLAIMS ALL IMPLIED WARRANTIES INCLUDING, WITHOUT # # LIMITATION, ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR OF FITNESS FOR A PARTICULAR # # PURPOSE. THE ENTIRE RISK ARISING OUT OF THE USE OR PERFORMANCE OF THE SAMPLE SCRIPTS # # AND DOCUMENTATION REMAINS WITH YOU. IN NO EVENT SHALL MICROSOFT, ITS AUTHORS, OR # # ANYONE ELSE INVOLVED IN THE CREATION, PRODUCTION, OR DELIVERY OF THE SCRIPTS BE LIABLE # # FOR ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS # # PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS) # # ARISING OUT OF THE USE OF OR INABILITY TO USE THE SAMPLE SCRIPTS OR DOCUMENTATION, # # EVEN IF MICROSOFT HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES # ############################################################################################# Function get-DLHierarchyFromExchangeOnline { <# .SYNOPSIS This function utilizes Microsoft Exchange Online to generate a Tree view of DL membership. .DESCRIPTION This function utilizes Microsoft Exchange Online to generate a Tree view of DL membership. .PARAMETER GROUPOBJECTID *REQUIRED* This is the group object ID from Entra ID. .PARAMETER LOGFOLDERPATH *REQUIRED* This is the logging directory for storing the migration log and all backup XML files. If running multiple SINGLE instance migrations use different logging directories. .PARAMETER EXCHANGEONLINEENVIRONMENTNAME *OPTIONAL* This specifies the Exchange Online instance to log into. Values include China, Global, USGov, and USGovDOD. .PARAMETER EXCHANGEONLINEORGANIZTIONNAME *OPTIONAL* This specifies the organization name (.onmicrosoft.com) for the Exchange ONline TEnant.. .PARAMETER EXCHANGEONLINECERTIFICATETHUMBPRINT *OPTIONAL* The certificate thumbprint assocaited with the app registration allowing non-interactive credentials. .PARAMETER EXCHANGEONLINEAPPID *OPTIONAL* This value specifies the application ID associated with the app registration allowing non-interactive credentials. .PARAMETER EXCHANGEONLINECREDENTIAL *OPTIONAL* Allows passing of a user name and password combination instead of certificate authentication. .PARAMETER ALLOWTELEMETRYCOLLECTION *OPTIONAL* Specifies if telemetry collection is allowed. .OUTPUTS Logs all activities and backs up all original data to the log folder directory. Outputs a text and HTML based view of the distribution group hierarchy. .NOTES The following blog posts maintain documentation regarding this module. https://timmcmic.wordpress.com. Refer to the first pinned blog post that is the table of contents. .EXAMPLE get-DLHierarchyFromExchangeOnline -groupObjectID XXXXX-XXX-XXXX-XXXXXXX -logFolderPath c:\temp -exchangeCredential $cred .EXAMPLE get-DLHierarchyFromExchangeOnline -groupObjectID XXXXX-XXX-XXXX-XXXXXXX -logFolderPath c:\temp -exchangeOrganizationName sometihng.onmicrosoft.com -exchangeCertificateThumbPrint ThumbPrint -ExchangeOnlineAppID APPID #> [cmdletbinding()] Param ( [Parameter(Mandatory = $true)] [string]$groupObjectID, #Exchange Online Parameters [Parameter(Mandatory = $false)] [pscredential]$exchangeOnlineCredential=$NULL, [Parameter(Mandatory = $false)] [string]$exchangeOnlineCertificateThumbPrint="", [Parameter(Mandatory = $false)] [string]$exchangeOnlineOrganizationName="", [Parameter(Mandatory = $false)] [ValidateSet("O365Default","O365GermanyCloud","O365China","O365USGovGCCHigh","O365USGovDoD")] [string]$exchangeOnlineEnvironmentName="O365Default", [Parameter(Mandatory = $false)] [string]$exchangeOnlineAppID="", #Define other mandatory parameters [Parameter(Mandatory = $true)] [string]$logFolderPath, [Parameter(Mandatory =$FALSE)] [boolean]$allowTelemetryCollection=$TRUE, #Define other non-mandatory parameters. [Parameter(Mandatory =$FALSE)] [boolean]$expandGroupMembership=$TRUE, [Parameter(Mandatory =$FALSE)] [boolean]$expandDynamicGroupMembership=$TRUE, [Parameter(Mandatory =$FALSE)] [boolean]$enableTextOutput=$TRUE, [Parameter(Mandatory =$FALSE)] [boolean]$enableHTMLOutput=$TRUE, [Parameter(Mandatory =$FALSE)] [boolean]$reverseHierarchy=$FALSE, [Parameter(Mandatory =$FALSE)] [boolean]$isHealthCheck=$FALSE ) #Define script based variables. #$logFileName = (Get-Date -Format FileDateTime) #Use random file date time for the log file name. $logFileName = $groupObjectID #Define the output file. [string]$global:outputFile="" #Initialize telemetry collection. $appInsightAPIKey = "63d673af-33f4-401c-931e-f0b64a218d89" $traceModuleName = "DLHierarchy" #Create telemetry values. $telemetryDLHierarchyVersion = $NULL $telemetryExchangeOnlineVersion = $NULL $telemetryOSVersion = (Get-CimInstance Win32_OperatingSystem).version $telemetryStartTime = get-universalDateTime $telemetryEndTime = $NULL [double]$telemetryElapsedSeconds = 0 $telemetryEventName = "get-DLHierarchyFromExchangeOnline" [boolean]$telemetryError=$FALSE #Specify stub object types. $exchangeOnlineGroupType = "Group" $exchangeOnlineType = "ExchangeOnline" [int]$defaultIndent = 0 $global:childCounter = 0 $global:exchangeObjects =@() $global:groupCounter = @() $global:mailUniversalSecurityGroupCounter = @() $global:mailUniversalDistributionGroupCounter = @() $global:userMailboxCounter = @() $global:mailUserCounter = @() $global:guestMailUserCounter = @() $global:mailContactCounter = @() $global:groupMailboxCounter = @() $global:groupMailboxDynamicCounter = @() $global:dynamicGroupCounter = @() $global:userCounter = @() $global:equipmentMailboxCounter = @() $global:sharedMailboxCounter = @() $global:roomMailboxCounter = @() $totalObjectsProcessed = 0 #Preare items for HTML Export. $global:HTMLSections = @() #Define windows title. $windowTitle = ("Get-DLHierarchyFromExchangeOnline "+$groupObjectID) $host.ui.RawUI.WindowTitle = $windowTitle #Define variables utilized in the core function that are not defined by parameters. $coreVariables = @{ exchangeOnlinePowershellModuleName = @{ "Value" = "ExchangeOnlineManagement" ; "Description" = "Static Exchange Online powershell module name" } DLHierarchy = @{ "Value" = "DLHierarchy" ; "Description" = "Static dlConversionv2 powershell module name" } } $processedGroupIds = New-Object System.Collections.Generic.HashSet[string] #Create the log file. if ($isHealthCheck -eq $FALSE) { new-logfile -logFileName $logFileName -logFolderPath $logFolderPath } out-logfile -string "***********************************************************" out-logfile -string "Starting get-DLHierarchyFromExchangeOnline" out-logfile -string "***********************************************************" if ($allowTelemetryCollection -eq $TRUE) { start-telemetryConfiguration -allowTelemetryCollection $allowTelemetryCollection -appInsightAPIKey $appInsightAPIKey -traceModuleName $traceModuleName } out-logfile -string "Testing for supported version of Powershell engine." test-powershellVersion out-logfile -string "********************************************************************************" out-logfile -string "NOCTICE" out-logfile -string "Telemetry collection is now enabled by default." out-logfile -string "For information regarding telemetry collection see https://timmcmic.wordpress.com/2022/11/14/4288/" out-logfile -string "Administrators may opt out of telemetry collection by using -allowTelemetryCollection value FALSE" out-logfile -string "Telemetry collection is appreciated as it allows further development and script enhacement." out-logfile -string "********************************************************************************" #Output all parameters bound or unbound and their associated values. Out-LogFile -string "********************************************************************************" Out-LogFile -string "PARAMETERS" Out-LogFile -string "********************************************************************************" write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) out-logfile -string "Ensure that all strings specified have no leading or trailing spaces." #Perform cleanup of any strings so that no spaces existin trailing or leading. $groupObjectID = remove-stringSpace -stringToFix $groupObjectID $logFolderPath = remove-stringSpace -stringToFix $logFolderPath $exchangeOnlineCertificateThumbPrint=remove-stringSpace -stringToFix $exchangeOnlineCertificateThumbPrint $exchangeOnlineEnvironmentName=remove-stringSpace -stringToFix $exchangeOnlineEnvironmentName $exchangeOnlineOrganizationName=remove-stringSpace -stringToFix $exchangeOnlineOrganizationName $exchangeOnlineAppID=remove-stringSpace -stringToFix $exchangeOnlineAppID out-logfile -string "Testing to ensure group ID passed is a GUID format." if (test-isGUID -stringGUID $groupObjectID) { out-logfile -string "Group is vaild string format." } else { Out-logfile -string "Identifier should be an acceptable GUID format. This incldues objectGUID, externalDirectoryObjectID, ExchangeObjectID" } Out-LogFile -string "Validating Exchange Online Credentials." start-parameterValidation -exchangeOnlineCredential $exchangeOnlineCredential -exchangeOnlineCertificateThumbprint $exchangeOnlineCertificateThumbprint #Validating that all portions for exchange certificate auth are present. out-logfile -string "Validating parameters for Exchange Online Certificate Authentication" start-parametervalidation -exchangeOnlineCertificateThumbPrint $exchangeOnlineCertificateThumbprint -exchangeOnlineOrganizationName $exchangeOnlineOrganizationName -exchangeOnlineAppID $exchangeOnlineAppID out-logfile -string "Calling Test-PowershellModule to validate the DL Conversion Module version installed." $telemetryDLHierarchyVersion = Test-PowershellModule -powershellModuleName $corevariables.DLHierarchy.value -powershellVersionTest:$TRUE Out-LogFile -string "Calling Test-PowerShellModule to validate the Exchange Module is installed." $telemetryExchangeOnlineVersion = Test-PowershellModule -powershellModuleName $corevariables.exchangeOnlinePowershellModuleName.value -powershellVersionTest:$TRUE Out-LogFile -string "Calling New-ExchangeOnlinePowershellSession to create session to office 365." if ($exchangeOnlineCertificateThumbPrint -eq "") { #User specified non-certifate authentication credentials. try { New-ExchangeOnlinePowershellSession -exchangeOnlineCredentials $exchangeOnlineCredential -exchangeOnlineEnvironmentName $exchangeOnlineEnvironmentName -debugLogPath $logFolderPath } catch { out-logfile -string "Unable to create the exchange online connection using credentials." out-logfile -string $_ -isError:$TRUE } } elseif ($exchangeOnlineCertificateThumbPrint -ne "") { #User specified thumbprint authentication. try { new-ExchangeOnlinePowershellSession -exchangeOnlineCertificateThumbPrint $exchangeOnlineCertificateThumbPrint -exchangeOnlineAppId $exchangeOnlineAppID -exchangeOnlineOrganizationName $exchangeOnlineOrganizationName -exchangeOnlineEnvironmentName $exchangeOnlineEnvironmentName -debugLogPath $logFolderPath } catch { out-logfile -string "Unable to create the exchange online connection using certificate." out-logfile -string $_ -isError:$TRUE } } out-logfile -string "Start building tree from group..." $tree = Get-GroupWithChildren -objectID $groupObjectID -processedGroupIds $processedGroupIds -objectType $exchangeOnlineGroupType -queryMethodExchangeOnline:$TRUE -expandGroupMembership $expandGroupMembership -expandDynamicGroupMembership $expandDynamicGroupMembership -reverseHierarchy $reverseHierarchy if ($enableTextOutput -eq $TRUE) { out-logfile -string "Set header in output file to group name." $global:outputFile += "Group Hierarchy for Group ID: "+$groupObjectID+"`n" out-logfile -string "Print hierarchy to log file." print-tree -node $tree -indent $defaultIndent -outputType $exchangeOnlineType -reverseHierarchy $reverseHierarchy out-logfile -string "Export hierarchy to file." out-HierarchyFile -outputFileName ("Hierarchy-"+$logFileName) -logFolderPath $global:logFolderPath } else { out-logfile -string "Text based output disabled." } out-logfile -string "Generate HTML File..." $global:GroupCounter = $global:GroupCounter | Sort-Object -unique $global:mailUniversalSecurityGroupCounter = $global:mailUniversalSecurityGroupCounter | Sort-Object -unique $global:mailUniversalDistributionGroupCounter = $global:mailUniversalDistributionGroupCounter | Sort-Object -unique $global:userMailboxCounter = $global:userMailboxCounter | Sort-Object -Unique $global:mailUserCounter = $global:mailUserCounter | Sort-Object -Unique $global:guestMailUserCounter = $global:guestMailUserCounter | Sort-Object -Unique $global:mailContactCounter = $global:mailContactCounter | Sort-Object -Unique $global:groupMailboxCounter = $global:groupMailboxCounter | Sort-Object -Unique $global:groupMailboxDynamicCounter = $global:groupMailboxDynamicCounter | Sort-Object -Unique $global:dynamicGroupCounter = $global:dynamicGroupCounter | Sort-Object -Unique $global:userCounter = $global:userCounter | Sort-Object -Unique $global:equipmentMailboxCounter = $global:equipmentMailboxCounter | Sort-Object -Unique $global:sharedMailboxCounter = $global:sharedMailboxCounter | Sort-Object -Unique $global:roomMailboxCounter = $global:roomMailboxCounter | Sort-Object -Unique if ($enableHTMLOutput -eq $TRUE) { start-HTMLOutput -node $tree -outputType $exchangeOnlineType -groupObjectID $groupObjectID -reverseHierarchy $reverseHierarchy -isHealthCheck $isHealthCheck } else { out-logfile -string "HTML output disabled." } out-logfile -string ("Total groups processed: "+$global:groupCounter.count) out-logfile -string ("Total mail security groups processed: "+$global:mailUniversalSecurityGroupCounter.count) out-logfile -string ("Total mail distribution groups processed: "+$global:mailUniversalDistributionGroupCounter.count) out-logfile -string ("Total user mailbox processed: "+$global:userMailboxCounter.count) out-logfile -string ("Total mail user processed: "+$global:mailUserCounter.count) out-logfile -string ("Total guest mail user processed: "+$global:guestMailUserCounter.count) out-logfile -string ("Total mail contact processed: "+$global:mailContactCounter.count) out-logfile -string ("Total group mailbox processed: "+$global:groupMailboxDynamicCounter.count) out-logfile -string ("Total group mailbox dynamic processed: ") out-logfile -string ("Total dynamic groups processed: "+$global:dynamicGroupCounter.count) out-logfile -string ("Total user processed: "+$global:userCounter.count) out-logfile -string ("Total equipment mailbox processed: "+$global:equipmentMailboxCounter.count) out-logfile -string ("Total shared mailbox processed: "+$global:sharedMailboxCounter.count) out-logfile -string ("Total room mailbox processed: "+$global:roomMailboxCounter.count) $totalObjectsProcessed = $global:groupMailboxDynamicCounter.count+$global:groupCounter.count+$global:mailUniversalSecurityGroupCounter.count+$global:mailUniversalDistributionGroupCounter.count+$global:userMailboxCounter.count+$global:mailUserCounter.count+$global:guestMailUserCounter.count+$global:mailContactCounter.count+$global:groupMailboxCounter.count+$global:dynamicGroupCounter.count+$global:userCounter.count+$global:equipmentMailboxCounter.count+$global:sharedMailboxCounter.count+$global:roomMailboxCounter.count out-logfile -string ("Total objects processed: "+$totalObjectsProcessed) $telemetryEndTime = get-universalDateTime $telemetryElapsedSeconds = get-elapsedTime -startTime $telemetryStartTime -endTime $telemetryEndTime $telemetryEventProperties = @{ DLConversionV2Command = $telemetryEventName DLHierarchyVersion = $telemetryDLHierarchyVersion ExchangeOnline = $telemetryExchangeOnlineVersion OSVersion = $telemetryOSVersion MigrationStartTimeUTC = $telemetryStartTime MigrationEndTimeUTC = $telemetryEndTime MigrationErrors = $telemetryError GroupsProcessed = $global:groupCounter.count MailSecurityGroupsProcessed = $global:mailUniversalSecurityGroupCounter.count MailDistributionGroupsProcessed = $global:mailUniversalDistributionGroupCounter.count UserMailboxProcessed = $global:userMailboxCounter.count MailUsersProcessed = $global:mailUserCounter.count GuestMailUserProcessed = $global:guestMailUserCounter.count MailContactProcessed = $global:mailContactCounter.count GroupMailboxProcessed = $global:groupMailboxCounter.count GroupMailboxProcessedDynamic = $global:groupMailboxDyanmicCounter.Count DynamicGroupsProcessed = $global:dynamicGroupCounter.count UsersProcessed = $global:userCounter.count RoomMailboxProcessed = $global:roomMailboxCounter.Count EquipmentMailboxProcessed = $global:equipmentMailboxCounter.Count SharedMailboxProcessed = $global:sharedMailboxCounter.Count TotalObjectsProcessed = $totalObjectsProcessed } $telemetryEventMetrics = @{ MigrationElapsedSeconds = $telemetryElapsedSeconds } if ($allowTelemetryCollection -eq $TRUE) { out-logfile -string "Telemetry1" out-logfile -string $traceModuleName out-logfile -string "Telemetry2" out-logfile -string $telemetryEventName out-logfile -string "Telemetry3" out-logfile -string $telemetryEventMetrics out-logfile -string "Telemetry4" out-logfile -string $telemetryEventProperties send-TelemetryEvent -traceModuleName $traceModuleName -eventName $telemetryEventName -eventMetrics $telemetryEventMetrics -eventProperties $telemetryEventProperties } disable-allPowerShellSessions } |