-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Phase 6 - M365 Admin Scripts #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
PowerShell scripts for Microsoft 365 administration: **Connection Module:** - Connect-M365.ps1: Graph API connection management with OAuth2 - Connection testing and verification - Scope-based authentication **User Management:** - Get-M365Users.ps1: List all users with details, filtering, CSV export - User statistics (active/inactive) - Sign-in activity tracking **Group Management:** - Get-M365Groups.ps1: List groups with member counts - M365/Security group type detection - Optional member enumeration **License Management:** - Get-M365Licenses.ps1: License usage report - Availability warnings - Usage percentage tracking - Product name mapping **Reports:** - Get-M365InactiveUsers.ps1: Inactive user detection - Configurable inactivity threshold - Never-signed-in user identification - Recommendations for account cleanup All scripts include bilingual documentation (English/Hungarian). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Reviewer's GuideAdds a modular set of PowerShell scripts for Microsoft 365 administration built on Microsoft Graph, including a shared connection module and specialized scripts for users, groups, licenses, and inactive user reporting, all documented in a bilingual README. Sequence diagram for running Get-M365InactiveUsers reportsequenceDiagram
actor Admin
participant Shell as PowerShell
participant InactiveScript as Get-M365InactiveUsers.ps1
participant ConnectModule as Connect-M365.ps1
participant MgGraph as Microsoft Graph
participant Dir as Directory service
participant Reports as Sign-in reports
Admin->>Shell: Run .\reports\Get-M365InactiveUsers.ps1 -DaysInactive 90 -ExportPath path
Shell->>InactiveScript: Invoke script with DaysInactive, ExportPath
InactiveScript->>ConnectModule: Import connection module (dot source)
InactiveScript->>ConnectModule: Test-M365Connection()
alt No active connection
ConnectModule-->>InactiveScript: false
InactiveScript->>ConnectModule: Connect-M365Services(Scopes=[User.Read.All, AuditLog.Read.All])
ConnectModule->>MgGraph: Connect-MgGraph(Scopes)
MgGraph-->>ConnectModule: OAuth2 token and context
ConnectModule-->>InactiveScript: true
else Active connection
ConnectModule-->>InactiveScript: true
end
InactiveScript->>Shell: Compute threshold date (Now - DaysInactive)
InactiveScript->>MgGraph: Get-MgUser(All, Properties including SignInActivity)
MgGraph->>Dir: Query users with sign-in activity
Dir-->>MgGraph: User list with SignInActivity
MgGraph-->>InactiveScript: Users collection
InactiveScript->>InactiveScript: Filter users with LastSignInDateTime missing or < threshold
InactiveScript->>InactiveScript: Build PSCustomObject results and statistics
alt ExportPath provided
InactiveScript->>Shell: Export-Csv(results, ExportPath)
end
InactiveScript->>Shell: Write statistics and recommendations
InactiveScript-->>Admin: Return results collection
Sequence diagram for running Get-M365Licenses reportsequenceDiagram
actor Admin
participant Shell as PowerShell
participant LicenseScript as Get-M365Licenses.ps1
participant ConnectModule as Connect-M365.ps1
participant MgGraph as Microsoft Graph
participant LicService as Licensing service
Admin->>Shell: Run .\licenses\Get-M365Licenses.ps1 -ExportPath path
Shell->>LicenseScript: Invoke script with ExportPath
LicenseScript->>ConnectModule: Import connection module (dot source)
LicenseScript->>ConnectModule: Test-M365Connection()
alt No active connection
ConnectModule-->>LicenseScript: false
LicenseScript->>ConnectModule: Connect-M365Services(Scopes=[Organization.Read.All, Directory.Read.All])
ConnectModule->>MgGraph: Connect-MgGraph(Scopes)
MgGraph-->>ConnectModule: OAuth2 token and context
ConnectModule-->>LicenseScript: true
else Active connection
ConnectModule-->>LicenseScript: true
end
LicenseScript->>MgGraph: Get-MgSubscribedSku(All)
MgGraph->>LicService: Query subscribed SKUs
LicService-->>MgGraph: SKU list with PrepaidUnits and ConsumedUnits
MgGraph-->>LicenseScript: SKUs collection
LicenseScript->>LicenseScript: Map SkuPartNumber to friendly names
LicenseScript->>LicenseScript: Compute totals, availability, UsagePercent
alt ExportPath provided
LicenseScript->>Shell: Export-Csv(results, ExportPath)
end
LicenseScript->>Shell: Write overall statistics and low-availability warnings
LicenseScript-->>Admin: Return results collection
Class diagram for M365 admin script modules and scriptsclassDiagram
class ConnectM365Module {
<<PowerShellModule>>
+bool Connect-M365Services(string TenantId, string[] Scopes)
+void Disconnect-M365Services()
+bool Test-M365Connection()
}
class GetM365UsersScript {
<<Script>>
+string Filter
+string ExportPath
+Collection Run(string Filter, string ExportPath)
}
class GetM365GroupsScript {
<<Script>>
+string GroupName
+bool IncludeMembers
+Collection Run(string GroupName, bool IncludeMembers)
}
class GetM365LicensesScript {
<<Script>>
+string ExportPath
+Collection Run(string ExportPath)
}
class GetM365InactiveUsersScript {
<<Script>>
+int DaysInactive
+string ExportPath
+Collection Run(int DaysInactive, string ExportPath)
}
ConnectM365Module <.. GetM365UsersScript : uses
ConnectM365Module <.. GetM365GroupsScript : uses
ConnectM365Module <.. GetM365LicensesScript : uses
ConnectM365Module <.. GetM365InactiveUsersScript : uses
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Caution Review failedThe pull request is closed. WalkthroughA new M365 admin scripts module is introduced with core connection functionality and four reporting scripts. The module includes helper functions for Graph API authentication and query scripts for retrieving user, group, license, and inactive user data from Microsoft 365, with optional CSV export capabilities and bilingual console messaging. Changes
Sequence DiagramsequenceDiagram
actor Admin
participant Script as M365 Admin Script
participant Graph as Microsoft Graph API
participant M365 as Microsoft 365 Backend
Admin->>Script: Execute script (with optional parameters)
Script->>Script: Import Connect-M365.ps1
Script->>Script: Test-M365Connection
alt No Active Context
Script->>Graph: Connect-MgGraph (with scopes)
Graph->>M365: Authenticate & validate
M365-->>Graph: Auth token
Graph-->>Script: Connection established
end
Script->>Graph: Query data (Get-MgUser/Group/SubscribedSku)
Graph->>M365: Fetch data
M365-->>Graph: Return dataset
Graph-->>Script: Data results
Script->>Script: Process & format results
Script->>Script: Calculate metrics/statistics
alt Export Requested
Script->>Script: Format as CSV
Script->>Admin: Export to file
end
Script->>Admin: Display formatted table & summary
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Poem
✨ Finishing touches🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (6)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there - I've reviewed your changes and found some issues that need to be addressed.
- Connect-M365.ps1 is dot-sourced as a plain script, so using
Export-ModuleMemberat the end will fail because it only works in module (.psm1) context; consider removing it or converting this into a proper module. - In Get-M365Groups.ps1, when
-IncludeMembersis used you callGet-MgUserfor eachGet-MgGroupMemberresult, which will be slow for large groups and can fail for non-user members (e.g., groups, service principals); consider either selecting properties directly from the member objects or branching based on object type to avoid unnecessary per-member lookups. - Both user and inactive-user scripts rely on the
SignInActivityproperty, which is only available via the beta profile in Microsoft Graph PowerShell; consider explicitly switching to the beta profile (e.g.,Select-MgProfile -Name 'beta') or documenting/enforcing the required profile in the connection logic to avoid runtime property errors.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Connect-M365.ps1 is dot-sourced as a plain script, so using `Export-ModuleMember` at the end will fail because it only works in module (.psm1) context; consider removing it or converting this into a proper module.
- In Get-M365Groups.ps1, when `-IncludeMembers` is used you call `Get-MgUser` for each `Get-MgGroupMember` result, which will be slow for large groups and can fail for non-user members (e.g., groups, service principals); consider either selecting properties directly from the member objects or branching based on object type to avoid unnecessary per-member lookups.
- Both user and inactive-user scripts rely on the `SignInActivity` property, which is only available via the beta profile in Microsoft Graph PowerShell; consider explicitly switching to the beta profile (e.g., `Select-MgProfile -Name 'beta'`) or documenting/enforcing the required profile in the connection logic to avoid runtime property errors.
## Individual Comments
### Comment 1
<location> `4-m365-admin-scripts/common/Connect-M365.ps1:116-117` </location>
<code_context>
+ }
+}
+
+# Export functions
+Export-ModuleMember -Function Connect-M365Services, Disconnect-M365Services, Test-M365Connection
</code_context>
<issue_to_address>
**issue (bug_risk):** Export-ModuleMember in a dot-sourced .ps1 will fail; this should only be used in a .psm1 module.
Because this file is dot-sourced (e.g. `. (Join-Path ... 'common\Connect-M365.ps1')`), `Export-ModuleMember` will always fail with `The Export-ModuleMember cmdlet can only be called from inside a script module`. Please either remove this call or convert the script to a `.psm1` module and load it via `Import-Module` instead of dot-sourcing.
</issue_to_address>
### Comment 2
<location> `4-m365-admin-scripts/groups/Get-M365Groups.ps1:108-110` </location>
<code_context>
+ }
+
+ # Statisztikák / Statistics
+ $m365Groups = ($results | Where-Object { $_.Type -eq "Microsoft 365" }).Count
+ $securityGroups = ($results | Where-Object { $_.SecurityEnabled }).Count
+
+ Write-Host "`nStatisztikák / Statistics:" -ForegroundColor Cyan
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Security group statistics may double-count Microsoft 365 groups that are also security-enabled.
Because `Type` is set to `"Microsoft 365"` when `GroupTypes` contains `"Unified"`, but `securityGroups` is counted solely via `SecurityEnabled`, unified security-enabled groups are included in both stats. If these statistics are meant to be mutually exclusive, consider basing `securityGroups` on something like `Type -ne "Microsoft 365" -and $_.SecurityEnabled`, or introduce a dedicated type for pure security groups and count against that.
```suggestion
# Statisztikák / Statistics
# Microsoft 365 csoportok (Unified) / Microsoft 365 groups (Unified)
$m365Groups = ($results | Where-Object { $_.Type -eq "Microsoft 365" }).Count
# Biztonsági csoportok, M365 csoportok nélkül / Security groups excluding M365 groups
# Így a statisztikák kölcsönösen kizáróak maradnak / Keeps the statistics mutually exclusive
$securityGroups = ($results | Where-Object { $_.SecurityEnabled -and $_.Type -ne "Microsoft 365" }).Count
```
</issue_to_address>
### Comment 3
<location> `4-m365-admin-scripts/users/Get-M365Users.ps1:108-109` </location>
<code_context>
+ }
+
+ # Statisztikák / Statistics
+ $enabledCount = ($users | Where-Object { $_.AccountEnabled }).Count
+ $disabledCount = $users.Count - $enabledCount
+
+ Write-Host "`nStatisztikák / Statistics:" -ForegroundColor Cyan
</code_context>
<issue_to_address>
**suggestion (bug_risk):** The statistics label 'Inactive users' is actually counting disabled accounts, which can be misleading.
Here `disabledCount` is based on `AccountEnabled`, but the label is `Inaktív felhasználók / Inactive users`. Elsewhere, “inactive” means no recent sign-ins, not disabled accounts. Please either rename this label (e.g., `Letiltott felhasználók / Disabled users`) or change the logic to reflect sign-in inactivity, so terminology stays consistent across reports.
```suggestion
Write-Host " Aktív felhasználók / Active users: $enabledCount" -ForegroundColor Green
Write-Host " Letiltott felhasználók / Disabled users: $disabledCount" -ForegroundColor Yellow
```
</issue_to_address>
### Comment 4
<location> `4-m365-admin-scripts/README.md:145` </location>
<code_context>
+```
+
+**Statisztikák / Statistics:**
+- Inaktív aktív fiókok / Inactive enabled accounts
+- Letiltott fiókok / Disabled accounts
+- Soha nem bejelentkezett / Never signed in
</code_context>
<issue_to_address>
**suggestion (typo):** Clarify the Hungarian wording "Inaktív aktív fiókok", which reads contradictory.
Since "inaktív" and "aktív" contradict each other, consider aligning more closely with the English "Inactive enabled accounts", e.g. "Inaktív, engedélyezett fiókok", to convey that the accounts are enabled but currently inactive.
```suggestion
- Inaktív, engedélyezett fiókok / Inactive enabled accounts
```
</issue_to_address>
### Comment 5
<location> `4-m365-admin-scripts/README.md:245` </location>
<code_context>
+
+### Kapcsolódási Hiba / Connection Error
+```powershell
+# Module ellenőrzés / Check module
+Get-Module Microsoft.Graph -ListAvailable
+
</code_context>
<issue_to_address>
**nitpick (typo):** Align the Hungarian comment with the rest by using "Modul" instead of "Module".
This seems like a minor consistency issue with the Hungarian terminology. Please change it to "Modul ellenőrzés" to match the rest of the Hungarian text.
```suggestion
# Modul ellenőrzés / Check module
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| # Export functions | ||
| Export-ModuleMember -Function Connect-M365Services, Disconnect-M365Services, Test-M365Connection |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (bug_risk): Export-ModuleMember in a dot-sourced .ps1 will fail; this should only be used in a .psm1 module.
Because this file is dot-sourced (e.g. . (Join-Path ... 'common\Connect-M365.ps1')), Export-ModuleMember will always fail with The Export-ModuleMember cmdlet can only be called from inside a script module. Please either remove this call or convert the script to a .psm1 module and load it via Import-Module instead of dot-sourcing.
| # Statisztikák / Statistics | ||
| $m365Groups = ($results | Where-Object { $_.Type -eq "Microsoft 365" }).Count | ||
| $securityGroups = ($results | Where-Object { $_.SecurityEnabled }).Count |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (bug_risk): Security group statistics may double-count Microsoft 365 groups that are also security-enabled.
Because Type is set to "Microsoft 365" when GroupTypes contains "Unified", but securityGroups is counted solely via SecurityEnabled, unified security-enabled groups are included in both stats. If these statistics are meant to be mutually exclusive, consider basing securityGroups on something like Type -ne "Microsoft 365" -and $_.SecurityEnabled, or introduce a dedicated type for pure security groups and count against that.
| # Statisztikák / Statistics | |
| $m365Groups = ($results | Where-Object { $_.Type -eq "Microsoft 365" }).Count | |
| $securityGroups = ($results | Where-Object { $_.SecurityEnabled }).Count | |
| # Statisztikák / Statistics | |
| # Microsoft 365 csoportok (Unified) / Microsoft 365 groups (Unified) | |
| $m365Groups = ($results | Where-Object { $_.Type -eq "Microsoft 365" }).Count | |
| # Biztonsági csoportok, M365 csoportok nélkül / Security groups excluding M365 groups | |
| # Így a statisztikák kölcsönösen kizáróak maradnak / Keeps the statistics mutually exclusive | |
| $securityGroups = ($results | Where-Object { $_.SecurityEnabled -and $_.Type -ne "Microsoft 365" }).Count |
| Write-Host " Aktív felhasználók / Active users: $enabledCount" -ForegroundColor Green | ||
| Write-Host " Inaktív felhasználók / Inactive users: $disabledCount" -ForegroundColor Yellow |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (bug_risk): The statistics label 'Inactive users' is actually counting disabled accounts, which can be misleading.
Here disabledCount is based on AccountEnabled, but the label is Inaktív felhasználók / Inactive users. Elsewhere, “inactive” means no recent sign-ins, not disabled accounts. Please either rename this label (e.g., Letiltott felhasználók / Disabled users) or change the logic to reflect sign-in inactivity, so terminology stays consistent across reports.
| Write-Host " Aktív felhasználók / Active users: $enabledCount" -ForegroundColor Green | |
| Write-Host " Inaktív felhasználók / Inactive users: $disabledCount" -ForegroundColor Yellow | |
| Write-Host " Aktív felhasználók / Active users: $enabledCount" -ForegroundColor Green | |
| Write-Host " Letiltott felhasználók / Disabled users: $disabledCount" -ForegroundColor Yellow |
Summary / Összefoglaló
🇬🇧 English
PowerShell automation scripts for Microsoft 365 administration using Graph API:
Connection Module:
User Management:
Group Management:
License Management:
Reports:
🇭🇺 Magyar
PowerShell automatizálási scriptek Microsoft 365 adminisztrációhoz Graph API használatával.
Features
Test Plan / Tesztterv
🤖 Generated with Claude Code
Summary by Sourcery
Add a new suite of PowerShell-based Microsoft 365 admin scripts built on Microsoft Graph for connections, user/group/license reporting, and inactive user analysis, documented in a bilingual README.
New Features:
Summary by CodeRabbit
New Features
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.