So, I've created myself a script for generating CSV files from an O365 tenant. It pulls various data sets such as users, groups etc. However when running this script on larger tenants with 10,000+ users it can take some time and I've been made aware I'm using a lot of Disk I/O. Does anyone have any suggestions on how to enhance the speed of this script?
# PS 7.2 Compatible
# Module Installations (required once)
Install-Module MicrosoftTeams
Install-Module ExchangeOnlineManagement
Install-Module Microsoft.Online.SharePoint.PowerShell
# Module Imports (required everytime a new terminal is ran)
Import-Module ExchangeOnlineManagement -UseWindowsPowerShell
Import-Module MicrosoftTeams
Import-Module Microsoft.Online.SharePoint.PowerShell -UseWindowsPowerShell
# Configurable Vars
$tenantName = "M365x51125269"
$OutputFolder = "C:\Users\admin\Documents\Scripts\O365 Reporting\Output"
# Connecting to Services
Connect-MgGraph -Scopes DeviceManagementApps.Read.All, DeviceManagementApps.ReadWrite.All, DeviceManagementManagedDevices.Read.All, DeviceManagementManagedDevices.ReadWrite.All, DeviceManagementServiceConfig.Read.All, DeviceManagementServiceConfig.ReadWrite.All, Directory.Read.All, Directory.ReadWrite.All, User.Read.All, User.ReadBasic.All, User.ReadWrite.All
Connect-ExchangeOnline
Connect-SPOService -Url https://$tenantName-admin.sharepoint.com
# Generate a list of all users with the given properties in a variable
$users = Get-MgUser -Property 'accountEnabled, userPrincipalName, displayName, givenName, surname, department,id' -All
$OneDriveUsage = Get-SPOSite -IncludePersonalSite $true -Limit all -Filter "Url -like '-my.sharepoint.com/personal/'"
$MailboxInfo = Get-EXOMailbox
# Iterate through the variable to pull individual details out
foreach ($user in $users) {
# License
$license = Get-MgUserLicenseDetail -UserId $user.Id
$CurrentOneDrive = $OneDriveUsage| Where-Object{$_.Owner -eq $user.UserPrincipalName}
$CurrentMailboxInfo = $MailboxInfo | Where-Object{$_.PrimarySmtpAddress -eq $user.UserPrincipalName}
$sortedArray = ($CurrentMailboxInfo.EmailAddresses | Select-String '(?<=smtp:)\S+(?!=\S)' -AllMatches).Matches.Value
$stringSMTP = $sortedArray -join ";"
[PSCustomObject]@{
'Enabled Credentials' = $user.AccountEnabled
'Unique ID' = $user.UserPrincipalName
'Display Name' = $user.DisplayName
'Assigned Products' = $license.SkuPartNumber -join " ; "
'First Name' = $user.GivenName
'Last Name' = $user.Surname
'Department' = $user.Department
'Proxy Addresses' = $stringSMTP
'OneDrive Storage Usage (MB)' = $CurrentOneDrive.StorageUsageCurrent
} | Export-Csv -path "$OutputFolder\UserInfo.csv" -Append -NoTypeInformation
}
# Generate list of all sites in a variable
$siteList = Get-SPOSite -Limit ALL
# Iterate through the variable to pull individual details out
foreach($site in $siteList){
# Checking the value result to determine a connection
if ($site.IsTeamsConnected){
$connectionWrite = 'TRUE'
}
else{
$connectionWrite = 'FALSE'
}
# Generate a custom object with our desired properties for export
[PSCustomObject]@{
'Unique ID' = $site.Url
'Display Name' = $site.Title
'Teams Connected' = $connectionWrite
'Sharepoint Storage (MB)' = $site.StorageUsageCurrent
'SharePoint Last Activity Date' = $site.LastContentModifiedDate
} | Export-Csv -path "$OutputFolder\Sites.csv" -Append -NoTypeInformation
}
$teamList = Get-Team
foreach($team in $teamlist){
$teamInfo = $team
$teamReport = Import-CSV "$InputFolder\TeamReport.csv"
$teamDetails = $teamReport | Where-Object{$_."Team Name" -eq $team.DisplayName}
$Sites = Get-SPOSite | Where-Object{$_.Title -eq $team.DisplayName}
[PSCustomObject]@{
'Display Name' = $teamInfo.DisplayName
'Teams Last Activity Date' = $teamDetails."Last Activity Date"
'Sharepoint Storage (MB)' = $Sites.StorageUsageCurrent
} | Export-Csv -path "$OutputFolder\TeamReport.csv" -Append -NoTypeInformation
}
$Groups = Get-MgGroup -Property "Members" -All
foreach($group in $Groups){
if ($group.SecurityEnabled -And -not $group.MailEnabled){
$members = Get-MgGroupMember -GroupId $group.Id
[PSCustomObject]@{
'Display Name' = $group.DisplayName
'Members' = $members.AdditionalProperties.displayName -join ";"
} | Export-Csv -path "$OutputFolder\SecurityGroups.csv" -Append -NoTypeInformation
}
continue
}
$Groups = Get-MgGroup -Property "Members" -All
foreach($group in $Groups){
if ($group.GroupTypes -eq 'Unified'){
$members = Get-MgGroupMember -GroupId $group.Id
[PSCustomObject]@{
'Display Name' = $group.DisplayName
'Members' = $members.AdditionalProperties.displayName -join ";"
} | Export-Csv -path "$OutputFolder\UnifiedGroups.csv" -Append -NoTypeInformation
}
continue
}
$Groups = Get-MgGroup -Property "Members" -All
foreach($group in $Groups){
if ( ( ($group.SecurityEnabled) -and ($group.MailEnabled) ) -and ('Unified' -notin $group.GroupTypes) ){
$members = Get-MgGroupMember -GroupId $group.Id
[PSCustomObject]@{
'Display Name' = $group.DisplayName
'Members' = $members.AdditionalProperties.displayName -join ";"
} | Export-Csv -path "$OutputFolder\MailEnabledSecurityGroups.csv" -Append -NoTypeInformation
}
continue
}
$Groups = Get-MgGroup -Property "Members" -All
foreach($group in $Groups){
if ( (($group.MailEnabled) -and (-not $group.SecurityEnabled)) -and ('Unified' -notin $group.GroupTypes))
{
$members = Get-MgGroupMember -GroupId $group.Id
[PSCustomObject]@{
'Display Name' = $group.DisplayName
'Members' = $members.AdditionalProperties.displayName -join ";"
} | Export-Csv -path "$OutputFolder\DistributionGroups.csv" -Append -NoTypeInformation
}
continue
}
$sharedmailList = Get-EXOMailbox
$filteredList = $sharedmailList | Where-Object {$_.RecipientTypeDetails -notcontains "UserMailbox" -and "DiscoveryMailbox"}
foreach($sharedmail in $filteredList){
if ($sharedmail.RecipientTypeDetails -eq 'DiscoveryMailBox'){
continue
}
else{
[PSCustomObject]@{
'Unique ID' = $sharedmail.PrimarySmtpAddress
'Display Name' = $sharedmail.DisplayName
} | Export-Csv -path "$OutputFolder\SharedMailbox.csv" -Append -NoTypeInformation
}
}
# Disconnect from services to avoid error before finalising script
Disconnect-Graph
Disconnect-ExchangeOnline
Disconnect-SPOService
People are welcome to take this for their own use by the way - I've found it useful for migrations when needing to merge multiple tenancies together but needing input from clients on what they wish to keep or exclude.