|
| 1 | +# A script to check the activity of Office 365 Groups and Teams and report the groups and teams that might be deleted because they're not used. |
| 2 | +# We check the group mailbox to see what the last time a conversation item was added to the Inbox folder. |
| 3 | +# Another check sees whether a low number of items exist in the mailbox, which would show that it's not being used. |
| 4 | +# We also check the group document library in SharePoint Online to see whether it exists or has been used in the last 90 days. |
| 5 | +# And we check Teams compliance items to figure out if any chatting is happening. |
| 6 | + |
| 7 | +# Created 29-July-2016 Tony Redmond |
| 8 | +# V2.0 5-Jan-2018 |
| 9 | +# V3.0 17-Dec-2018 |
| 10 | + |
| 11 | +# Check that we are connected to Exchange Online |
| 12 | +Write-Host "Checking that prerequisite PowerShell modules are loaded..." |
| 13 | +Try { $OrgName = (Get-OrganizationConfig).Name } |
| 14 | + Catch { |
| 15 | + Write-Host "Your PowerShell session is not connected to Exchange Online." |
| 16 | + Write-Host "Please connect to Exchange Online using an administrative account and retry." |
| 17 | + Break } |
| 18 | + |
| 19 | +# And check that we're connected to SharePoint Online as well |
| 20 | +Try { $SPOCheck = (Get-SPOTenant -ErrorAction SilentlyContinue ) } |
| 21 | + Catch { |
| 22 | + Write-Host "Your PowerShell session is not connected to SharePoint Online." |
| 23 | + Write-Host "Please connect to SharePoint Online using an administrative account and retry." |
| 24 | + Break } |
| 25 | + |
| 26 | +# And finally the Teams module |
| 27 | +Try { $TeamsCheck = (Get-Team) } |
| 28 | + Catch { |
| 29 | + Write-Host "Please connect to the Teams PowerShell module before proceeeding." |
| 30 | + Break } |
| 31 | + |
| 32 | +# OK, we seem to be fully connected to both Exchange Online and SharePoint Online... |
| 33 | +Write-Host "Checking for Obsolete Office 365 Groups in the tenant:" $OrgName |
| 34 | + |
| 35 | +# Setup some stuff we use |
| 36 | +$WarningDate = (Get-Date).AddDays(-90) |
| 37 | +$WarningEmailDate = (Get-Date).AddDays(-365) |
| 38 | +$Today = (Get-Date) |
| 39 | +$Date = $Today.ToShortDateString() |
| 40 | +$TeamsGroups = 0 |
| 41 | +$TeamsEnabled = $False |
| 42 | +$ObsoleteSPOGroups = 0 |
| 43 | +$ObsoleteEmailGroups = 0 |
| 44 | +$Report = @() |
| 45 | +$ReportFile = "c:\temp\GroupsActivityReport.html" |
| 46 | +$CSVFile = "c:\temp\GroupsActivityReport.csv" |
| 47 | +$htmlhead="<html> |
| 48 | + <style> |
| 49 | + BODY{font-family: Arial; font-size: 8pt;} |
| 50 | + H1{font-size: 22px; font-family: 'Segoe UI Light','Segoe UI','Lucida Grande',Verdana,Arial,Helvetica,sans-serif;} |
| 51 | + H2{font-size: 18px; font-family: 'Segoe UI Light','Segoe UI','Lucida Grande',Verdana,Arial,Helvetica,sans-serif;} |
| 52 | + H3{font-size: 16px; font-family: 'Segoe UI Light','Segoe UI','Lucida Grande',Verdana,Arial,Helvetica,sans-serif;} |
| 53 | + TABLE{border: 1px solid black; border-collapse: collapse; font-size: 8pt;} |
| 54 | + TH{border: 1px solid #969595; background: #dddddd; padding: 5px; color: #000000;} |
| 55 | + TD{border: 1px solid #969595; padding: 5px; } |
| 56 | + td.pass{background: #B7EB83;} |
| 57 | + td.warn{background: #FFF275;} |
| 58 | + td.fail{background: #FF2626; color: #ffffff;} |
| 59 | + td.info{background: #85D4FF;} |
| 60 | + </style> |
| 61 | + <body> |
| 62 | + <div align=center> |
| 63 | + <p><h1>Office 365 Groups and Teams Activity Report</h1></p> |
| 64 | + <p><h3>Generated: " + $date + "</h3></p></div>" |
| 65 | + |
| 66 | +# Get a list of all Office 365 Groups in the tenant |
| 67 | +Write-Host "Extracting list of Office 365 Groups for checking..." |
| 68 | +$Groups = Get-UnifiedGroup -ResultSize Unlimited | Sort-Object DisplayName |
| 69 | +# And create a hash table of Teams |
| 70 | +$TeamsList = @{} |
| 71 | +Get-Team | ForEach { $TeamsList.Add($_.GroupId, $_.DisplayName) } |
| 72 | + |
| 73 | +Write-Host "Processing" $Groups.Count "groups" |
| 74 | +# Progress bar |
| 75 | +$ProgDelta = 100/($Groups.count) |
| 76 | +$CheckCount = 0 |
| 77 | +$GroupNumber = 0 |
| 78 | + |
| 79 | +# Main loop |
| 80 | +ForEach ($G in $Groups) { |
| 81 | + $GroupNumber++ |
| 82 | + $GroupStatus = $G.DisplayName + " ["+ $GroupNumber +"/" + $Groups.Count + "]" |
| 83 | + Write-Progress -Activity "Checking group" -Status $GroupStatus -PercentComplete $CheckCount |
| 84 | + $CheckCount += $ProgDelta |
| 85 | + $ObsoleteReportLine = $G.DisplayName |
| 86 | + $SPOStatus = "Normal" |
| 87 | + $SPOActivity = "Document library in use" |
| 88 | + $NumberWarnings = 0 |
| 89 | + $NumberofChats = 0 |
| 90 | + $TeamChatData = $Null |
| 91 | + $TeamsEnabled = $False |
| 92 | + $LastItemAddedtoTeams = "No chats" |
| 93 | + $MailboxStatus = $Null |
| 94 | +# Check who manages the group |
| 95 | + $ManagedBy = $G.ManagedBy |
| 96 | + If ([string]::IsNullOrWhiteSpace($ManagedBy) -and [string]::IsNullOrEmpty($ManagedBy)) { |
| 97 | + $ManagedBy = "No owners" |
| 98 | + Write-Host $G.DisplayName "has no group owners!" -ForegroundColor Red} |
| 99 | + Else { |
| 100 | + $ManagedBy = (Get-Mailbox -Identity $G.ManagedBy[0]).DisplayName} |
| 101 | + |
| 102 | +# Fetch information about activity in the Inbox folder of the group mailbox |
| 103 | + $Data = (Get-MailboxFolderStatistics -Identity $G.Alias -IncludeOldestAndNewestITems -FolderScope Inbox) |
| 104 | + $LastConversation = $Data.NewestItemReceivedDate |
| 105 | + $NumberConversations = $Data.ItemsInFolder |
| 106 | + $MailboxStatus = "Normal" |
| 107 | + |
| 108 | + If ($Data.NewestItemReceivedDate -le $WarningEmailDate) { |
| 109 | + Write-Host "Last conversation item created in" $G.DisplayName "was" $Data.NewestItemReceivedDate "-> Obsolete?" |
| 110 | + $ObsoleteReportLine = $ObsoleteReportLine + " Last conversation dated: " + $Data.NewestItemReceivedDate + "." |
| 111 | + $MailboxStatus = "Group Inbox Not Recently Used" |
| 112 | + $ObsoleteEmailGroups++ |
| 113 | + $NumberWarnings++ } |
| 114 | + Else |
| 115 | + {# Some conversations exist - but if there are fewer than 20, we should flag this... |
| 116 | + If ($Data.ItemsInFolder -lt 20) { |
| 117 | + $ObsoleteReportLine = $ObsoleteReportLine + " Only " + $Data.ItemsInFolder + " conversation item(s) found." |
| 118 | + $MailboxStatus = "Low number of conversations" |
| 119 | + $NumberWarnings++} |
| 120 | + } |
| 121 | + |
| 122 | +# Loop to check SharePoint document library |
| 123 | + If ($G.SharePointDocumentsUrl -ne $Null) { |
| 124 | + $SPOSite = (Get-SPOSite -Identity $G.SharePointDocumentsUrl.replace("/Shared Documents", "")) |
| 125 | + $AuditCheck = $G.SharePointDocumentsUrl + "/*" |
| 126 | + $AuditRecs = 0 |
| 127 | + $AuditRecs = (Search-UnifiedAuditLog -RecordType SharePointFileOperation -StartDate $WarningDate -EndDate $Today -ObjectId $AuditCheck -SessionCommand ReturnNextPreviewPage) |
| 128 | + If ($AuditRecs -eq $null) { |
| 129 | + #Write-Host "No audit records found for" $SPOSite.Title "-> Potentially obsolete!" |
| 130 | + $ObsoleteSPOGroups++ |
| 131 | + $ObsoleteReportLine = $ObsoleteReportLine + " No SPO activity detected in the last 90 days." } |
| 132 | + } |
| 133 | + Else |
| 134 | + { |
| 135 | +# The SharePoint document library URL is blank, so the document library was never created for this group |
| 136 | + #Write-Host "SharePoint has never been used for the group" $G.DisplayName |
| 137 | + $ObsoleteSPOGroups++ |
| 138 | + $ObsoleteReportLine = $ObsoleteReportLine + " SPO document library never created." |
| 139 | + } |
| 140 | +# Report to the screen what we found - but only if something was found... |
| 141 | + If ($ObsoleteReportLine -ne $G.DisplayName) |
| 142 | + { |
| 143 | + Write-Host $ObsoleteReportLine |
| 144 | + } |
| 145 | +# Generate the number of warnings to decide how obsolete the group might be... |
| 146 | + If ($AuditRecs -eq $Null) { |
| 147 | + $SPOActivity = "No SPO activity detected in the last 90 days" |
| 148 | + $NumberWarnings++ } |
| 149 | + If ($G.SharePointDocumentsUrl -eq $Null) { |
| 150 | + $SPOStatus = "Document library never created" |
| 151 | + $NumberWarnings++ } |
| 152 | + |
| 153 | + $Status = "Pass" |
| 154 | + If ($NumberWarnings -eq 1) |
| 155 | + { |
| 156 | + $Status = "Warning" |
| 157 | + } |
| 158 | + If ($NumberWarnings -gt 1) |
| 159 | + { |
| 160 | + $Status = "Fail" |
| 161 | + } |
| 162 | + |
| 163 | +# If Team-Enabled, we can find the date of the last chat compliance record |
| 164 | +If ($TeamsList.ContainsKey($G.ExternalDirectoryObjectId) -eq $True) { |
| 165 | + $TeamsEnabled = $True |
| 166 | + $TeamChatData = (Get-MailboxFolderStatistics -Identity $G.Alias -IncludeOldestAndNewestItems -FolderScope ConversationHistory) |
| 167 | + If ($TeamChatData.ItemsInFolder[1] -ne 0) { |
| 168 | + $LastItemAddedtoTeams = $TeamChatData.NewestItemReceivedDate[1] |
| 169 | + $NumberofChats = $TeamChatData.ItemsInFolder[1] |
| 170 | + If ($TeamChatData.NewestItemReceivedDate -le $WarningEmailDate) { |
| 171 | + Write-Host "Team-enabled group" $G.DisplayName "has only" $TeamChatData.ItemsInFolder[1] "compliance record(s)" } |
| 172 | + } |
| 173 | + } |
| 174 | + |
| 175 | +# Generate a line for this group for our report |
| 176 | + $ReportLine = [PSCustomObject][Ordered]@{ |
| 177 | + GroupName = $G.DisplayName |
| 178 | + ManagedBy = $ManagedBy |
| 179 | + Members = $G.GroupMemberCount |
| 180 | + ExternalGuests = $G.GroupExternalMemberCount |
| 181 | + Description = $G.Notes |
| 182 | + MailboxStatus = $MailboxStatus |
| 183 | + TeamEnabled = $TeamsEnabled |
| 184 | + LastChat = $LastItemAddedtoTeams |
| 185 | + NumberChats = $NumberofChats |
| 186 | + LastConversation = $LastConversation |
| 187 | + NumberConversations = $NumberConversations |
| 188 | + SPOActivity = $SPOActivity |
| 189 | + SPOStatus = $SPOStatus |
| 190 | + NumberWarnings = $NumberWarnings |
| 191 | + Status = $Status} |
| 192 | +# And store the line in the report object |
| 193 | + $Report += $ReportLine |
| 194 | +#End of main loop |
| 195 | +} |
| 196 | +# Create the HTML report |
| 197 | +$PercentTeams = ($TeamsList.Count/$Groups.Count) |
| 198 | +$htmlbody = $Report | ConvertTo-Html -Fragment |
| 199 | +$htmltail = "<p>Report created for: " + $OrgName + " |
| 200 | + </p> |
| 201 | + <p>Number of groups scanned: " + $Groups.Count + "</p>" + |
| 202 | + "<p>Number of potentially obsolete groups (based on document library activity): " + $ObsoleteSPOGroups + "</p>" + |
| 203 | + "<p>Number of potentially obsolete groups (based on conversation activity): " + $ObsoleteEmailGroups + "<p>"+ |
| 204 | + "<p>Number of Teams-enabled groups : " + $TeamsList.Count + "</p>" + |
| 205 | + "<p>Percentage of Teams-enabled groups: " + ($PercentTeams).tostring("P") + "</body></html>" |
| 206 | +$htmlreport = $htmlhead + $htmlbody + $htmltail |
| 207 | +$htmlreport | Out-File $ReportFile -Encoding UTF8 |
| 208 | + |
| 209 | +# Summary note |
| 210 | +Write-Host $ObsoleteSPOGroups "obsolete group document libraries and" $ObsoleteEmailGroups "obsolete email groups found out of" $Groups.Count "checked" |
| 211 | +Write-Host "Summary report available in" $ReportFile "and CSV file saved in" $CSVFile |
| 212 | +$Report | Export-CSV -NoTypeInformation $CSVFile |
| 213 | + |
0 commit comments