diff --git a/Find Groups with Disabled Users/GroupsWithDisabledUsers.ps1 b/Find Groups with Disabled Users/GroupsWithDisabledUsers.ps1 new file mode 100644 index 0000000..0d5710b --- /dev/null +++ b/Find Groups with Disabled Users/GroupsWithDisabledUsers.ps1 @@ -0,0 +1,180 @@ +<# +============================================================================================= +Name: Find Groups with Disabled Users in Microsoft 365 using PowerShell +Version: 1.0 +website: o365reports.com + +~~~~~~~~~~~~~~~~~~ +Script Highlights: +~~~~~~~~~~~~~~~~~~ + +1. Exports disabled users based on group types, such as Microsoft 365, Security, Mail-enabled security, and Distribution list. +2. Provides counts for disabled members and disabled owners in each group. +3. Automatically installs the required Microsoft Graph PowerShell module with your confirmation. +4. The script can be executed with an MFA-enabled account too. +5. Supports Certificate-based Authentication too. +6. The script is scheduler friendly. +7. Exports report results into a CSV file. + +For detailed Script execution: https://o365reports.com/2025/01/21/find-groups-with-disabled-users-in-microsoft-365/ +============================================================================================ +#> +Param +( + [Parameter(Mandatory = $false)] + [switch]$M365GroupsOnly, + [switch]$SecurityGroupsOnly, + [switch]$MailEnabledSecurityGroupsOnly, + [switch]$DistributionListsOnly, + [string]$TenantId, + [string]$ClientId, + [string]$CertificateThumbprint +) + + +$CSVFilePath = "$(Get-Location)\GroupsWithDisabledUsers_$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm-ss` tt).ToString()).csv" + + +# Function to install and connect to Microsoft Graph +function Connect_ToMgGraph { + # Check if Microsoft Graph module is installed + $MsGraphModule = Get-Module Microsoft.Graph -ListAvailable + if ($MsGraphModule -eq $null) { + Write-Host "`nImportant: Microsoft Graph module is unavailable. It is mandatory to have this module installed in the system to run the script successfully." + $confirm = Read-Host "Are you sure you want to install Microsoft Graph module? [Y] Yes [N] No" + if ($confirm -match "[yY]") { + Write-Host "Installing Microsoft Graph module..." + Install-Module Microsoft.Graph -Scope CurrentUser -AllowClobber + Write-Host "Microsoft Graph module is installed in the machine successfully" + } else { + Write-Host "Exiting. `nNote: Microsoft Graph module must be available in your system to run the script" -ForegroundColor Red + Exit + } + } + + Write-Host "`nConnecting to Microsoft Graph..." + + if (($TenantId -ne "") -and ($ClientId -ne "") -and ($CertificateThumbprint -ne "")) { + # Use certificate-based authentication if TenantId, ClientId, and CertificateThumbprint are provided + Connect-MgGraph -TenantId $TenantId -AppId $ClientId -CertificateThumbprint $CertificateThumbprint -NoWelcome + } else { + # Use delegated permissions (Scopes) if credentials are not provided + Connect-MgGraph -Scopes "Group.Read.All", "GroupMember.Read.All" -NoWelcome + } + + # Verify connection + if ((Get-MgContext) -ne $null) { + Write-Host "Connected to Microsoft Graph PowerShell using account: $((Get-MgContext).Account)" + } else { + Write-Host "Failed to connect to Microsoft Graph." -ForegroundColor Red + Exit + } +} + + +# Export function to output data to CSV +function ExportResult { + $ExportResult = @{ + 'CreatedDateTime' = $CreatedDateTime + 'Group Name' = $GroupName + 'Group Email Address' = $GroupEmailAddress + 'Group Id' = $GroupId + 'Group Type' = $GroupType + 'Is Dynamic Group' = $IsDynamicGroup + 'Disabled Members' = $DisabledMembers -join(", ") + 'Disabled Members Count' = $DisabledMembersCount + 'Disabled Owners' = $DisabledOwners -join(", ") + 'Disabled Owners Count' = $DisabledOwnersCount + 'Total Members Count' = $GroupMembers.Count + 'Total Owners Count' = $GroupOwners.Count + } + $ExportResults = New-Object PSObject -Property $ExportResult + $ExportResults | Select-Object 'Group Name', 'Group Email Address', 'Group Type', 'Is Dynamic Group', 'Total Members Count', 'Total Owners Count', 'Disabled Members Count', 'Disabled Owners Count', 'Disabled Members', 'Disabled Owners', 'CreatedDateTime', 'Group Id' | Export-Csv -Path $CSVFilePath -NoTypeInformation -Append +} + + +# Connecting to the Microsoft Graph PowerShell Module +Connect_ToMgGraph + +$GroupsWithDisabledUsersCount = 0 +$ProcessedGroupsCount = 0 + +# Get all groups in Microsoft 365 +Get-MgGroup -All | ForEach-Object { + $CreatedDateTime = $_.CreatedDateTime + $GroupName = $_.DisplayName + $GroupEmailAddress = $_.Mail + $GroupId = $_.Id + $GroupTypes = $_.GroupTypes + $IsDynamicGroup = $false + + $ProcessedGroupsCount++ + Write-Progress -Activity "`n Processed group count: $ProcessedGroupsCount"`n" Checking members of: $GroupName" + + # Get the group type based on properties + if ($GroupTypes -contains "Unified") { + $GroupType = "Microsoft365" + } + elseif ($_.Mail -ne $null -and $_.SecurityEnabled -eq $false) { + $GroupType = "DistributionList" + } + elseif ($_.Mail -ne $null -and $_.SecurityEnabled -eq $true) { + $GroupType = "MailEnabledSecurity" + } + else { + $GroupType = "Security" + } + + # check whether it is a dynamic membership group + if ($GroupTypes -contains "DynamicMembership") { + $IsDynamicGroup = $true + } + + # If group email address is empty + if($GroupEmailAddress -eq $null) { + $GroupEmailAddress = "-" + } + + # Apply filters based on switches + if ($SecurityGroupsOnly -and $GroupType -ne "Security") { return } + if ($DistributionListsOnly -and $GroupType -ne "DistributionList") { return } + if ($MailEnabledSecurityGroupsOnly -and $GroupType -ne "MailEnabledSecurity") { return } + if ($M365GroupsOnly -and $GroupType -ne "Microsoft365") { return } + + + $GroupMembers = Get-MgGroupMemberAsUser -GroupId $GroupId -All -Property AccountEnabled, UserPrincipalName + $GroupOwners = Get-MgGroupOwnerAsUser -GroupId $GroupId -All -Property AccountEnabled, UserPrincipalName + + $DisabledMembers = $GroupMembers | Where-Object { $_.AccountEnabled -eq $false } | Select-Object -ExpandProperty UserPrincipalName + $DisabledOwners = $GroupOwners | Where-Object { $_.AccountEnabled -eq $false } | Select-Object -ExpandProperty UserPrincipalName + $DisabledMembersCount = $DisabledMembers.Count + $DisabledOwnersCount = $DisabledOwners.Count + + # Export results if there are disabled users + if (($DisabledMembersCount -gt 0) -or ($DisabledOwnersCount -gt 0)) { + $GroupsWithDisabledUsersCount++ + if ($DisabledMembers -eq $null) { $DisabledMembers = "-" } + if ($DisabledOwners -eq $null) { $DisabledOwners = "-" } + ExportResult + } +} + + +# Disconnect from Microsoft Graph +Disconnect-MgGraph | Out-Null + +Write-Host `n~~ Script prepared by AdminDroid Community ~~`n -ForegroundColor Green +Write-Host "~~ Check out " -NoNewline -ForegroundColor Green; Write-Host "admindroid.com" -ForegroundColor Yellow -NoNewline; Write-Host " to get access to 1900+ Microsoft 365 reports. ~~" -ForegroundColor Green `n + +if((Test-Path -Path $CSVFilePath) -eq "True") { + Write-Host " There are $GroupsWithDisabledUsersCount groups with disabled users." + Write-Host " The Output file is available in: " -NoNewline -ForegroundColor Yellow; Write-Host "$CSVFilePath" + $Prompt = New-Object -ComObject wscript.shell + $UserInput = $Prompt.popup("Do you want to open the Output file?",` 0,"Open Output File",4) + if ($UserInput -eq 6) { + Invoke-Item "$CSVFilePath" + } +} +else{ + Write-Host "No groups found with Disabled Users for the given criteria." +} \ No newline at end of file