Review of the v4.0 module in Module/IntuneAssignmentChecker/ (introduced in b2378c5). Findings are ordered by priority.
1. Error handling — unprotected Invoke-MgGraphRequest calls in main category loops
Several mandatory category fetches run bare Invoke-MgGraphRequest without a try/catch. A single transient network blip or throttling response aborts the entire user/device/group run partway through, leaving no partial results.
Representative locations:
Module/IntuneAssignmentChecker/Public/Get-IntuneUserAssignment.ps1:227-233 — Applications fetch + @odata.nextLink pagination.
Module/IntuneAssignmentChecker/Public/Get-IntuneUserAssignment.ps1:246-247 — per-app assignments fetch.
- Same pattern repeats in
Get-IntuneAllPolicies.ps1, Get-IntuneAllUsersAssignment.ps1, Get-IntuneAllDevicesAssignment.ps1, Get-IntuneDeviceAssignment.ps1, Get-IntuneGroupAssignment.ps1, Test-IntuneGroupMembership.ps1, Test-IntuneGroupRemoval.ps1, Search-IntunePolicy.ps1.
Compare with the app-protection block at Get-IntuneUserAssignment.ps1:162-207, which already wraps its request in try/catch and reports a per-policy error without aborting the run. The applications block should follow the same pattern (continue to the next category on failure, log the error).
2. Inconsistent $script:GraphEndpoint scoping — fragile
IntuneAssignmentChecker.psm1:5 declares $script:GraphEndpoint. Some files correctly reference $script:GraphEndpoint (Connect-IntuneAssignmentChecker.ps1, Set-Environment.ps1, Update-IntuneSettingDefinition.ps1), but many files read the bare $GraphEndpoint and rely on the scope-chain fallback to find the module-scoped value:
Module/IntuneAssignmentChecker/Private/Get-IntuneEntities.ps1:18,22
Module/IntuneAssignmentChecker/Public/Compare-IntuneGroupAssignment.ps1 — 22 occurrences
Module/IntuneAssignmentChecker/Public/Test-IntuneGroupMembership.ps1 — 12 occurrences
Module/IntuneAssignmentChecker/Public/Test-IntuneGroupRemoval.ps1 — 12 occurrences
Module/IntuneAssignmentChecker/Public/Get-IntuneAllDevicesAssignment.ps1 — 11
Module/IntuneAssignmentChecker/Public/Get-IntuneAllUsersAssignment.ps1 — 11
- Plus 10 other Public/Private files.
This works today, but a local $GraphEndpoint = … introduced anywhere up the call stack would silently shadow the module value and quietly route traffic to the wrong cloud. Make all reads explicit: $script:GraphEndpoint.
3. Quality — $totalCategories magic number drifts from actual step count
Module/IntuneAssignmentChecker/Public/Get-IntuneUserAssignment.ps1:82 sets $totalCategories = 16. The $relevantPolicies hashtable immediately below (lines 86–107) declares 20 named categories, and there are 16 $currentCategory++ calls, so the progress bar is internally consistent but the relationship is invisible — exactly how commit b2378c5 recently had to re-adjust from 17 → 16.
The numbers also disagree between cmdlets that do essentially the same work:
Get-IntuneUserAssignment.ps1:82 → 16
Test-IntuneGroupMembership.ps1:139 → 18
Test-IntuneGroupRemoval.ps1:140 → 18
Search-IntunePolicy.ps1:33 → 18
Either derive the total from the list of steps ($steps.Count) or assemble the category list once and iterate, so the counter can't drift.
4. Quality — App Protection assignment URI switch duplicated across 11 files
The 3-case switch that maps @odata.type → android/ios/windowsManagedAppProtections/.../assignments appears in every cmdlet that walks App Protection policies:
Public/Get-IntuneUserAssignment.ps1:154-159
Public/Get-IntuneDeviceAssignment.ps1
Public/Get-IntuneGroupAssignment.ps1
Public/Get-IntuneAllPolicies.ps1
Public/Get-IntuneAllUsersAssignment.ps1
Public/Get-IntuneAllDevicesAssignment.ps1
Public/Get-IntuneEmptyGroup.ps1:106-108
Public/Get-IntuneUnassignedPolicy.ps1
Public/Search-IntunePolicy.ps1
Public/Test-IntuneGroupMembership.ps1
Public/Test-IntuneGroupRemoval.ps1
html-export.ps1:684-690
Extract a single Get-AppProtectionAssignmentUri -PolicyType -PolicyId private helper. Any future AppProtection subtype (e.g. mdmWindowsInformationProtection) then needs a one-place change.
5. Quality — oversized functions
PowerShell style guides commonly flag functions >200 lines as candidates for decomposition. Current Public/ sizes:
| File |
Lines |
| Get-IntuneUserAssignment.ps1 |
1422 |
| Get-IntuneDeviceAssignment.ps1 |
990 |
| Test-IntuneGroupMembership.ps1 |
955 |
| Test-IntuneGroupRemoval.ps1 |
947 |
| Get-IntuneGroupAssignment.ps1 |
685 |
| Get-IntuneAllUsersAssignment.ps1 |
685 |
| Get-IntuneAllDevicesAssignment.ps1 |
668 |
| Get-IntuneAllPolicies.ps1 |
667 |
| Compare-IntuneGroupAssignment.ps1 |
665 |
| Search-IntunePolicy.ps1 |
515 |
These files implement near-identical "walk all 16–18 category endpoints and collect matches" pipelines with small differences in the match predicate. Consider extracting a shared pipeline (list-of-categories → per-category Invoke-CategoryScan -Predicate {…}) into Private/ so each public cmdlet shrinks to the bit that differs: its predicate, its printing, and its export columns. This will also remove most of the duplicated $totalCategories/App-Protection URI/bare $GraphEndpoint instances in one pass.
Nothing here is a user-visible bug today, but the combination (fragile scoping, duplicated switches, unprotected network calls) is exactly what made the hardcoded graph.microsoft.com / totalCategories drift easy to miss until the USGov fix in #115.
Review of the v4.0 module in
Module/IntuneAssignmentChecker/(introduced in b2378c5). Findings are ordered by priority.1. Error handling — unprotected
Invoke-MgGraphRequestcalls in main category loopsSeveral mandatory category fetches run bare
Invoke-MgGraphRequestwithout atry/catch. A single transient network blip or throttling response aborts the entire user/device/group run partway through, leaving no partial results.Representative locations:
Module/IntuneAssignmentChecker/Public/Get-IntuneUserAssignment.ps1:227-233— Applications fetch +@odata.nextLinkpagination.Module/IntuneAssignmentChecker/Public/Get-IntuneUserAssignment.ps1:246-247— per-app assignments fetch.Get-IntuneAllPolicies.ps1,Get-IntuneAllUsersAssignment.ps1,Get-IntuneAllDevicesAssignment.ps1,Get-IntuneDeviceAssignment.ps1,Get-IntuneGroupAssignment.ps1,Test-IntuneGroupMembership.ps1,Test-IntuneGroupRemoval.ps1,Search-IntunePolicy.ps1.Compare with the app-protection block at
Get-IntuneUserAssignment.ps1:162-207, which already wraps its request intry/catchand reports a per-policy error without aborting the run. The applications block should follow the same pattern (continue to the next category on failure, log the error).2. Inconsistent
$script:GraphEndpointscoping — fragileIntuneAssignmentChecker.psm1:5declares$script:GraphEndpoint. Some files correctly reference$script:GraphEndpoint(Connect-IntuneAssignmentChecker.ps1,Set-Environment.ps1,Update-IntuneSettingDefinition.ps1), but many files read the bare$GraphEndpointand rely on the scope-chain fallback to find the module-scoped value:Module/IntuneAssignmentChecker/Private/Get-IntuneEntities.ps1:18,22Module/IntuneAssignmentChecker/Public/Compare-IntuneGroupAssignment.ps1— 22 occurrencesModule/IntuneAssignmentChecker/Public/Test-IntuneGroupMembership.ps1— 12 occurrencesModule/IntuneAssignmentChecker/Public/Test-IntuneGroupRemoval.ps1— 12 occurrencesModule/IntuneAssignmentChecker/Public/Get-IntuneAllDevicesAssignment.ps1— 11Module/IntuneAssignmentChecker/Public/Get-IntuneAllUsersAssignment.ps1— 11This works today, but a local
$GraphEndpoint = …introduced anywhere up the call stack would silently shadow the module value and quietly route traffic to the wrong cloud. Make all reads explicit:$script:GraphEndpoint.3. Quality —
$totalCategoriesmagic number drifts from actual step countModule/IntuneAssignmentChecker/Public/Get-IntuneUserAssignment.ps1:82sets$totalCategories = 16. The$relevantPolicieshashtable immediately below (lines 86–107) declares 20 named categories, and there are 16$currentCategory++calls, so the progress bar is internally consistent but the relationship is invisible — exactly how commit b2378c5 recently had to re-adjust from 17 → 16.The numbers also disagree between cmdlets that do essentially the same work:
Get-IntuneUserAssignment.ps1:82→16Test-IntuneGroupMembership.ps1:139→18Test-IntuneGroupRemoval.ps1:140→18Search-IntunePolicy.ps1:33→18Either derive the total from the list of steps (
$steps.Count) or assemble the category list once and iterate, so the counter can't drift.4. Quality — App Protection assignment URI switch duplicated across 11 files
The 3-case switch that maps
@odata.type→android/ios/windowsManagedAppProtections/.../assignmentsappears in every cmdlet that walks App Protection policies:Public/Get-IntuneUserAssignment.ps1:154-159Public/Get-IntuneDeviceAssignment.ps1Public/Get-IntuneGroupAssignment.ps1Public/Get-IntuneAllPolicies.ps1Public/Get-IntuneAllUsersAssignment.ps1Public/Get-IntuneAllDevicesAssignment.ps1Public/Get-IntuneEmptyGroup.ps1:106-108Public/Get-IntuneUnassignedPolicy.ps1Public/Search-IntunePolicy.ps1Public/Test-IntuneGroupMembership.ps1Public/Test-IntuneGroupRemoval.ps1html-export.ps1:684-690Extract a single
Get-AppProtectionAssignmentUri -PolicyType -PolicyIdprivate helper. Any future AppProtection subtype (e.g. mdmWindowsInformationProtection) then needs a one-place change.5. Quality — oversized functions
PowerShell style guides commonly flag functions >200 lines as candidates for decomposition. Current Public/ sizes:
These files implement near-identical "walk all 16–18 category endpoints and collect matches" pipelines with small differences in the match predicate. Consider extracting a shared pipeline (list-of-categories → per-category
Invoke-CategoryScan -Predicate {…}) intoPrivate/so each public cmdlet shrinks to the bit that differs: its predicate, its printing, and its export columns. This will also remove most of the duplicated$totalCategories/App-Protection URI/bare$GraphEndpointinstances in one pass.Nothing here is a user-visible bug today, but the combination (fragile scoping, duplicated switches, unprotected network calls) is exactly what made the hardcoded
graph.microsoft.com/totalCategoriesdrift easy to miss until the USGov fix in #115.