DLHierarchy.psm1
############################################################################################# # 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-DLHierarchyFromGraph { <# .SYNOPSIS This function utilizes Microsoft Graph to generate a Tree view of DL membership. .DESCRIPTION This function utilizes Microsoft Graph 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 MSGRAPHENVIRONMENTNAME *OPTIONAL* This specifies the Entra ID instance to log into. Values include China, Global, USGov, and USGovDOD. .PARAMETER MSGRAPHTENANTID *MANDATORY* This specifies the tenant ID for the Entra ID instance. This is required as connect-MGGraph remembers the last connection and may result in wrong tenant collection. .PARAMETER MSGRAPHCERTIFICATETHUMBPRINT *OPTIONAL* The certificate thumbprint assocaited with the app registration allowing non-interactive credentials. .PARAMETER MSGRAPHAPPLICATIONID *OPTIONAL* This value specifies the application ID associated with the app registration allowing non-interactive credentials. .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-DLHierarchyFromGraph -groupObjectID XXXXX-XXX-XXXX-XXXXXXX -logFolderPath c:\temp -msGraphTenantID ID (triggers interactive auth.) .EXAMPLE get-DLHierarchyFromGraph -groupObjectID XXXXX-XXX-XXXX-XXXXXXX -logFolderPath c:\temp -msGraphTenantID ID -msGraphCertificateThumbprint Thumprinter -msGraphApplicationID AppID #> [cmdletbinding()] Param ( [Parameter(Mandatory = $true)] [string]$groupObjectID, #Define Microsoft Graph Parameters [Parameter(Mandatory = $false)] [ValidateSet("China","Global","USGov","USGovDod")] [string]$msGraphEnvironmentName="Global", [Parameter(Mandatory=$true)] [string]$msGraphTenantID="", [Parameter(Mandatory=$false)] [string]$msGraphCertificateThumbprint="", [Parameter(Mandatory=$false)] [string]$msGraphApplicationID="", #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=$false, [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 $msGraphScopesRequired = @("Directory.Read.All") #Define the grpah scopes required. #Define the output file. [string]$global:outputFile="" #Initialize telemetry collection. $appInsightAPIKey = "63d673af-33f4-401c-931e-f0b64a218d89" $traceModuleName = "DLHierarchy" #Create telemetry values. $telemetryDLHierarchyVersion = $NULL $telemetryMSGraphAuthentication = $NULL $telemetryMSGraphUsers = $NULL $telemetryMSGraphGroups = $NULL $telemetryMSGraphDirectory = $NULL $telemetryOSVersion = (Get-CimInstance Win32_OperatingSystem).version $telemetryStartTime = get-universalDateTime $telemetryEndTime = $NULL [double]$telemetryElapsedSeconds = 0 $telemetryEventName = "get-DLHierarchyFromGraph" [boolean]$telemetryError=$FALSE #Specify stub object types. $msGraphGroupType = "#microsoft.graph.group" $msGraphType = "MSGraph" [int]$defaultIndent = 0 $global:childCounter = 0 $global:msGraphObjects = @() $global:msGraphUserCount = @() $global:msGraphGroupCount = @() $global:msGraphGroupDynamicCount = @() $global:msGraphContactCount = @() $totalObjectsProcessed = 0 #Define windows title. $windowTitle = ("get-DLHierarchyFromGraph "+$groupSMTPAddress) $host.ui.RawUI.WindowTitle = $windowTitle #Define variables utilized in the core function that are not defined by parameters. $coreVariables = @{ msGraphAuthenticationPowershellModuleName = @{ "Value" = "Microsoft.Graph.Authentication" ; "Description" = "Static ms graph powershell name authentication" } msGraphUsersPowershellModuleName = @{ "Value" = "Microsoft.Graph.Users" ; "Description" = "Static ms graph powershell name users" } msGraphGroupsPowershellModuleName = @{ "Value" = "Microsoft.Graph.Groups" ; "Description" = "Static ms graph powershell name groups" } msGraphIdentityDirectoryManagement = @{ "Value" = "Microsoft.Graph.Identity.DirectoryManagement" ; "Description" = "Static ms graph powershell name groups" } 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-DLHierarchyFromGraph" 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 $msGraphTenantID = remove-stringSpace -stringToFix $msGraphTenantID $msGraphCertificateThumbprint = remove-stringSpace -stringToFix $msGraphCertificateThumbprint $msGraphApplicationID = remove-stringSpace -stringToFix $msGraphApplicationID 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" } if ($msGraphCertificateThumbprint -eq "") { out-logfile -string "Validation all components available for MSGraph Cert Auth" start-parameterValidation -msGraphCertificateThumbPrint $msGraphCertificateThumbprint -msGraphTenantID $msGraphTenantID -msGraphApplicationID $msGraphApplicationID } else { out-logfile -string "MS graph cert auth is not being utilized - assume interactive auth." } 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 Microsoft Graph Authentication versions installed." $telemetryMSGraphAuthentication = test-powershellModule -powershellmodulename $corevariables.msgraphauthenticationpowershellmodulename.value -powershellVersionTest:$TRUE out-logfile -string "Calling Test-PowershellModule to validate the Microsoft Graph Users versions installed." $telemetryMSGraphUsers = test-powershellModule -powershellmodulename $corevariables.msgraphuserspowershellmodulename.value -powershellVersionTest:$TRUE out-logfile -string "Calling Test-PowershellModule to validate the Microsoft Graph Users versions installed." $telemetryMSGraphGroups = test-powershellModule -powershellmodulename $corevariables.msgraphgroupspowershellmodulename.value -powershellVersionTest:$TRUE out-logfile -string "Calling Test-PowershellModule to validate the Microsoft Graph Director versions installed." $telemetryMSGraphDirectory = test-powershellModule -powershellmodulename $corevariables.msGraphIdentityDirectoryManagement.value -powershellVersionTest:$TRUE Out-LogFile -string "Calling nea-msGraphPowershellSession to create new connection to msGraph active directory." if ($msGraphCertificateThumbprint -ne "") { #User specified thumbprint authentication. try { new-msGraphPowershellSession -msGraphCertificateThumbprint $msGraphCertificateThumbprint -msGraphApplicationID $msGraphApplicationID -msGraphTenantID $msGraphTenantID -msGraphEnvironmentName $msGraphEnvironmentName -msGraphScopesRequired $msGraphScopesRequired } catch { out-logfile -string "Unable to create the msgraph connection using certificate." out-logfile -string $_ -isError:$TRUE } } elseif ($msGraphTenantID -ne "") { try { new-msGraphPowershellSession -msGraphTenantID $msGraphTenantID -msGraphEnvironmentName $msGraphEnvironmentName -msGraphScopesRequired $msGraphScopesRequired } catch { out-logfile -=string "Unable to create the msgraph connection using tenant ID and credentials." } } out-logfile -string "Start building tree from group..." $tree = Get-GroupWithChildren -objectID $groupObjectID -processedGroupIds $processedGroupIds -objectType $msGraphGroupType -queryMethodGraph:$TRUE -expandGroupMembership $expandGroupMembership -expandDynamicGroupMembership $expandDynamicGroupMembership -reverseHierarchy $reverseHierarchy out-logfile -string "Set header in output file to group name." $global:outputFile += "Group Hierarchy for Group ID: "+$groupObjectID+"`n" if ($enableTextOutput -eq $TRUE) { out-logfile -string "Print hierarchy to log file." out-logfile -string $global:outputFile print-tree -node $tree -indent $defaultIndent -outputType $msGraphType -reverseHierarchy $reverseHierarchy out-logfile -string "Export hierarchy to file." out-HierarchyFile -outputFileName ("Hierarchy-"+$logFileName) -logFolderPath $global:logFolderPath } else { out-logfile -string "Text output is disabled." } $global:msGraphGroupCount = $global:msGraphGroupCount | Sort-Object -Unique $global:msGraphContactCount = $global:msGraphContactCount | Sort-Object -Unique $global:msGraphGroupDynamicCount = $global:msGraphGroupDynamicCount | sort-object -Unique $global:msGraphUserCount = $global:msGraphUserCount | Sort-Object -Unique $totalObjectsProcessed = $global:msGraphGroupCount.count + $global:msGraphContactCount.count + $global:msGraphUserCount.count + $global:msGraphGroupDynamicCount.count if ($enableHTMLOutput -eq $TRUE) { out-logfile -string "Generate HTML File..." start-HTMLOutput -node $tree -outputType $msGraphType -groupObjectID $groupObjectID -reverseHierarchy $reverseHierarchy -isHealthCheck $isHealthCheck } else { out-logfile -string "HTML output is disabled." } out-logfile -string ("Graph group count: "+$global:msGraphGroupCount.count) out-logfile -string ("Graph contact count: "+$global:msGraphContactCount.count) out-logfile -string ("Graph user count: "+$global:msGraphUserCount.count) out-logfile -string ("Graph dynamic group count: "+$global:msGraphGroupDynamicCount.Count) out-logfile -string ("Total Objects Processed: "+$totalObjectsProcessed) $telemetryEndTime = get-universalDateTime $telemetryElapsedSeconds = get-elapsedTime -startTime $telemetryStartTime -endTime $telemetryEndTime $telemetryEventProperties = @{ DLConversionV2Command = $telemetryEventName DLHierarchyVersion = $telemetryDLHierarchyVersion MSGraphAuthentication = $telemetryMSGraphAuthentication MSGraphUsers = $telemetryMSGraphUsers MSGraphGroups = $telemetryMSGraphGroups MSGraphDirectory = $telemetryMSGraphDirectory OSVersion = $telemetryOSVersion MigrationStartTimeUTC = $telemetryStartTime MigrationEndTimeUTC = $telemetryEndTime MigrationErrors = $telemetryError GroupsProcessed = $global:msGraphGroupCount.Count DynamicGroupsProcessed = $global:msGraphGroupDynamicCount.Count ContactsProcessed = $global:msGraphContactCount.Count UsersProcessed = $global:msGraphUserCount.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 } |