Thanks for this. The principle is exactly what I'm looking for, but I'm not clear which elements I'm supposed to change for our tenant (if any), and what the complete script is (i.e. why there's some script in the code sample editor and some not). Please could you clarify as it would really help. Thanks.
Exchange online Powershell clean up distribution list
Hi,
I found the ps script below that helps determine with the distribution list can be cleaned up, but it seems like doesn't have the path where the report will be exported? Please advise, thank you!
$VerbosePreference = "Continue"
Get all DGs and historical searches
$DistributionGroups = Get-DistributionGroup -ResultSize Unlimited
$HistoricalSearches = Get-HistoricalSearch -ResultSize Unlimited | Sort-Object SubmitDate -Descending
For each DG, ensure a historical search is started
$SearchesToProcess = $DistributionGroups |
ForEach-Object {
Write-Verbose "Processing $($.PrimarySMTPAddress) ($($.Guid))"
$EmailAddresses = $.EmailAddresses | Where-Object {$ -like "smtp:*"} | ForEach-Object {$_ -replace "smtp:",""}
$ReportTitle = "Distribution group mapping - $($_.Guid)"
$HistoricalSearch = $HistoricalSearches | Where-Object ReportTitle -eq $ReportTitle | Select-Object -First 1
if($HistoricalSearch) {
Write-Verbose "Found existing historical search '$ReportTitle' with submit date $($HistoricalSearch.SubmitDate)"
if($HistoricalSearch.SubmitDate -lt (Get-Date).AddDays(-7)) {
Write-Verbose "Existing historical search '$ReportTitle' found, but it is more than 7 days old - starting again"
Start-HistoricalSearch -RecipientAddress $EmailAddresses -StartDate (Get-Date).AddDays(-90) -EndDate (Get-Date) -ReportType MessageTrace -ReportTitle $ReportTitle
} else {
$HistoricalSearch
}
} else {
Write-Verbose "No existing historical search '$ReportTitle' found, creating a new one"
Start-HistoricalSearch -RecipientAddress $EmailAddresses -StartDate (Get-Date).AddDays(-90) -EndDate (Get-Date) -ReportType MessageTrace -ReportTitle $ReportTitle
}
}
Wait for all searches to complete
$Percent = 0
while($SearchesToProcess.ReportStatusDescription -contains "Pending") {
Write-Verbose "Waiting for historical searches to complete... $Percent %"
Start-Sleep 60
# Refresh SearchesToProcess variable
$SearchesToProcess = $SearchesToProcess | Get-HistoricalSearch
$Percent = $SearchesToProcess |
ForEach-Object {
if($_.ReportStatusDescription -eq "Pending") {
0
} else {
100
}
} |
Measure-Object -Average |
Select-Object -ExpandProperty Average
$SearchesToProcess |
Select-Object ReportTitle, Status, ReportStatusDescription, JobProgress, @{Label="EstimatedCompletionTime";Expression={$_.EstimatedCompletionTime.ToLocalTime()}} |
Format-Table
}
Find any result that has more than 0 rows - these are for DGs that has received emails!
$DistributionGroupReport = $SearchesToProcess |
ForEach-Object {
if($.Status -ne "Done") {
Write-Host "Job '$($.JobId)' has status $($.Status)"
} else {
$Guid = $.ReportTitle -split " - " | Select-Object -Last 1
$DG = $DistributionGroups | Where-Object Guid -eq $Guid
[PSCustomObject] @{
DisplayName = $DG.DisplayName
GroupType = $DG.GroupType
PrimarySmtpAddress = $DG.PrimarySMTPAddress
Name = $DG.Name
Guid = $Guid
InUse = $_.Rows -gt 0
}
}
}
All distribution groups not in use
$DistributionGroupReport | Where-Object InUse -eq $false | Format-Table
Windows for business | Windows Server | User experience | PowerShell
4 answers
Sort by: Most helpful
-
-
Rich Matheisen 47,901 Reputation points
2021-12-04T03:28:35.48+00:00 Change the last line in the script:
#All distribution groups not in use $DistributionGroupReport | Where-Object InUse -eq $false | Export-CSV x:\DIR\FILE.csv -NoTypeInfo
When you post code please use the "Code Sample" editor. It's the 5th icon from the left on the Format Bar. The icon's graphic is "101 010".
When you post code using the normal editor it mangles the code. It also makes it hard to separate text from code.
-
Meghashree Vijaya 1 Reputation point
2022-10-12T08:52:00.057+00:00 What is the complete script to run to achive this
-
Rich Matheisen 47,901 Reputation points
2023-01-06T20:53:29.527+00:00 Your script would look like this:
$VerbosePreference = "Continue" # Get all DGs and historical searches $DistributionGroups = Get-DistributionGroup -ResultSize Unlimited $HistoricalSearches = Get-HistoricalSearch -ResultSize Unlimited | Sort-Object SubmitDate -Descending # For each DG, ensure a historical search is started $SearchesToProcess = $DistributionGroups | ForEach-Object { Write-Verbose "Processing $($_.PrimarySMTPAddress) ($($_.Guid))" $EmailAddresses = $_.EmailAddresses | Where-Object { $ -Like "smtp:*" } | ForEach-Object { $_ -replace "smtp:", "" } $ReportTitle = "Distribution group mapping - $($_.Guid)" $HistoricalSearch = $HistoricalSearches | Where-Object ReportTitle -EQ $ReportTitle | Select-Object -First 1 if ($HistoricalSearch) { Write-Verbose "Found existing historical search '$ReportTitle' with submit date $($HistoricalSearch.SubmitDate)" if ($HistoricalSearch.SubmitDate -lt (Get-Date).AddDays(-7)) { Write-Verbose "Existing historical search '$ReportTitle' found, but it is more than 7 days old - starting again" Start-HistoricalSearch -RecipientAddress $EmailAddresses -StartDate (Get-Date).AddDays(-90) -EndDate (Get-Date) -ReportType MessageTrace -ReportTitle $ReportTitle } else { $HistoricalSearch } } else { Write-Verbose "No existing historical search '$ReportTitle' found, creating a new one" Start-HistoricalSearch -RecipientAddress $EmailAddresses -StartDate (Get-Date).AddDays(-90) -EndDate (Get-Date) -ReportType MessageTrace -ReportTitle $ReportTitle } } # Wait for all searches to complete $Percent = 0 while ($SearchesToProcess.ReportStatusDescription -contains "Pending") { Write-Verbose "Waiting for historical searches to complete... $Percent %" Start-Sleep 60 # Refresh SearchesToProcess variable $SearchesToProcess = $SearchesToProcess | Get-HistoricalSearch $Percent = $SearchesToProcess | ForEach-Object { if ($_.ReportStatusDescription -eq "Pending") { 0 } else { 100 } } | Measure-Object -Average | Select-Object -ExpandProperty Average $SearchesToProcess | Select-Object ReportTitle, Status, ReportStatusDescription, JobProgress, @{Label = "EstimatedCompletionTime"; Expression = { $_.EstimatedCompletionTime.ToLocalTime() } } | Format-Table } # Find any result that has more than 0 rows - these are for DGs that has received emails! $DistributionGroupReport = $SearchesToProcess | ForEach-Object { if ($_.Status -ne "Done") { Write-Host "Job '$($_.JobId)' has status $($_.Status)" } else { $Guid = $_.ReportTitle -split " - " | Select-Object -Last 1 $DG = $DistributionGroups | Where-Object Guid -EQ $Guid [PSCustomObject] @{ DisplayName = $DG.DisplayName GroupType = $DG.GroupType PrimarySmtpAddress = $DG.PrimarySMTPAddress Name = $DG.Name Guid = $Guid InUse = $_.Rows -gt 0 } } } # All distribution groups not in use $DistributionGroupReport | Where-Object InUse -eq $false | Export-CSV x:\DIR\FILE.csv -NoTypeInfo
To answer your question "why there's some script in the code sample editor and some not", it's because when you posted your question you didn't use the Code Sample editor. The "normal" editor recognized some of what you posted a code. It also mangled many of the "<dollarsign><period><underbar>" character sequences by removing the "<underbar>" character, and it also turned lies that begin with "#" into "headers" using different font characteristics, and italicized some of the text.