Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Build/Build-Module.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Invoke-ModuleBuild -ModuleName 'GraphEssentials' {
# Usual defaults as per standard module
$Manifest = [ordered] @{
ModuleVersion = '0.0.X'
ModuleVersion = '0.0.61'
Comment thread
PrzemyslawKlys marked this conversation as resolved.
Outdated
CompatiblePSEditions = @('Desktop', 'Core')
GUID = '75ef812f-6d8e-4898-81bb-8029e0560ef3'
Author = 'Przemyslaw Klys'
Expand Down
7 changes: 6 additions & 1 deletion CHANGELOG.MD
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
### 0.0.58
### 0.0.61

- Add fail-closed Teams collection for reports that require complete data.
- Page Microsoft 365 group owners and preserve user, service-principal, ownerless, and unavailable-owner states.

### 0.0.58

- Added `Remove-MyAutopilotDevice` for explicit Windows Autopilot device identity removal.

Expand Down
3 changes: 1 addition & 2 deletions GraphEssentials.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
Description = 'GraphEssentials is a PowerShell module that helps with Office 365 / Azure AD using mostly Graph'
FunctionsToExport = @('Disable-MyDevice', 'Get-MgToken', 'Get-MyApp', 'Get-MyAppCredentials', 'Get-MyConditionalAccess', 'Get-MyDefenderDeploymentKey', 'Get-MyDefenderHealthIssues', 'Get-MyDefenderSecureScore', 'Get-MyDefenderSecureScoreProfile', 'Get-MyDefenderSensor', 'Get-MyDefenderSummary', 'Get-MyDevice', 'Get-MyDeviceIntune', 'Get-MyGuest', 'Get-MyLicense', 'Get-MyRole', 'Get-MyRoleHistory', 'Get-MyRoleUsers', 'Get-MyTeam', 'Get-MyTenantName', 'Get-MyUsageReports', 'Get-MyUser', 'Get-MyUserAuthentication', 'Invoke-MyDeviceRetire', 'Invoke-MyGraphEssentials', 'Invoke-MyGraphUsageReports', 'New-MyApp', 'New-MyAppCredentials', 'Register-FIDO2Key', 'Remove-MyAppCredentials', 'Remove-MyAutopilotDevice', 'Remove-MyDevice', 'Remove-MyDeviceIntuneRecord', 'Send-MyApp', 'Show-MyApp', 'Show-MyConditionalAccess', 'Show-MyDefender', 'Show-MyRole', 'Show-MyUserAuthentication')
GUID = '75ef812f-6d8e-4898-81bb-8029e0560ef3'
ModuleVersion = '0.0.60'
ModuleVersion = '0.0.61'
PowerShellVersion = '5.1'
PrivateData = @{
PSData = @{
Expand Down Expand Up @@ -87,5 +87,4 @@
ModuleVersion = '1.0.0'
})
RootModule = 'GraphEssentials.psm1'
ScriptsToProcess = @()
}
46 changes: 41 additions & 5 deletions Public/Get-MyTeam.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ function Get-MyTeam {
.PARAMETER AsHashtable
When specified, returns data as a hashtable instead of objects.

.PARAMETER RequireCompleteData
Stops with a terminating error when the team list, extended team details, or owner information
cannot be collected. Use this for reports and automation that must not treat incomplete data as
an empty or authoritative result.

.EXAMPLE
Get-MyTeam
Returns a list of all Teams with their properties.
Expand All @@ -35,16 +40,27 @@ function Get-MyTeam {
[CmdletBinding()]
param(
[switch] $PerOwner,
[switch] $AsHashtable
[switch] $AsHashtable,
[switch] $RequireCompleteData
)

$Today = Get-Date
$OwnerShip = [ordered] @{}
$CompleteResults = $null
if ($RequireCompleteData -and -not $PerOwner) {
$CompleteResults = [System.Collections.Generic.List[object]]::new()
}

try {
$Teams = Get-MgTeam -All -ErrorAction Stop
} catch {
Write-Warning -Message "Get-MyTeam - Couldn't get list of teams. Error: $($_.Exception.Message)"
$Message = "Get-MyTeam - Couldn't get list of teams. Error: $($_.Exception.Message)"
if ($RequireCompleteData) {
$Exception = [System.InvalidOperationException]::new($Message, $_.Exception)
$ErrorRecord = [System.Management.Automation.ErrorRecord]::new($Exception, 'GetMyTeamListFailed', [System.Management.Automation.ErrorCategory]::ResourceUnavailable, $null)
$PSCmdlet.ThrowTerminatingError($ErrorRecord)
}
Write-Warning -Message $Message
return
}

Expand All @@ -53,7 +69,13 @@ function Get-MyTeam {
try {
$TeamDetails = Get-MgTeam -TeamId $Team.Id -Property DisplayName, Description, CreatedDateTime, GuestSettings, MemberSettings, Summary -ErrorAction Stop
} catch {
Write-Warning -Message "Get-MyTeam - Couldn't get extended details for team $($Team.DisplayName) / $($Team.Id): $($_.Exception.Message)"
$Message = "Get-MyTeam - Couldn't get extended details for team $($Team.DisplayName) / $($Team.Id): $($_.Exception.Message)"
if ($RequireCompleteData) {
$Exception = [System.InvalidOperationException]::new($Message, $_.Exception)
$ErrorRecord = [System.Management.Automation.ErrorRecord]::new($Exception, 'GetMyTeamDetailsFailed', [System.Management.Automation.ErrorCategory]::ResourceUnavailable, $Team.Id)
$PSCmdlet.ThrowTerminatingError($ErrorRecord)
}
Write-Warning -Message $Message
}

$Owners = @()
Expand All @@ -62,7 +84,13 @@ function Get-MyTeam {
$Owners = @(Get-GraphEssentialsGroupOwner -GroupId $Team.Id -ErrorAction Stop)
$OwnersRetrieved = $true
} catch {
Write-Warning -Message "Get-MyTeam - Couldn't get owners for team $($Team.DisplayName) / $($Team.Id): $($_.Exception.Message)"
$Message = "Get-MyTeam - Couldn't get owners for team $($Team.DisplayName) / $($Team.Id): $($_.Exception.Message)"
if ($RequireCompleteData) {
$Exception = [System.InvalidOperationException]::new($Message, $_.Exception)
$ErrorRecord = [System.Management.Automation.ErrorRecord]::new($Exception, 'GetMyTeamOwnersFailed', [System.Management.Automation.ErrorCategory]::ResourceUnavailable, $Team.Id)
$PSCmdlet.ThrowTerminatingError($ErrorRecord)
}
Write-Warning -Message $Message
}

if ($TeamDetails.CreatedDateTime) {
Expand Down Expand Up @@ -123,6 +151,7 @@ function Get-MyTeam {
GuestsCount = $TeamDetails.Summary.GuestsCount
HasGuests = $HasGuests
Description = $Team.Description
Owners = $Owners
OwnerDisplayName = $Owners.DisplayName
OwnerMail = $Owners.Mail
OwnerUserPrincipalName = $Owners.UserPrincipalName
Expand Down Expand Up @@ -174,15 +203,22 @@ function Get-MyTeam {
}
}
} else {
if ($AsHashtable) {
$Result = if ($AsHashtable) {
$TeamInformation
} else {
[PSCustomObject] $TeamInformation
}
if ($RequireCompleteData) {
$CompleteResults.Add($Result)
} else {
$Result
}
}
}

if ($PerOwner) {
$OwnerShip
} elseif ($RequireCompleteData) {
$CompleteResults
}
}
85 changes: 85 additions & 0 deletions Tests/Get-MyTeam.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ Describe 'Get-MyTeam' {
$result.GuestsCount | Should -Be 2
$result.HasGuests | Should -BeTrue
$result.OwnerCount | Should -Be 1
$result.Owners.Count | Should -Be 1
$result.Owners[0].Id | Should -Be 'owner-1'
$result.OwnerUserPrincipalName | Should -Be 'owner@contoso.com'
$result.OwnerObjectType | Should -Be '#microsoft.graph.user'
}
Expand All @@ -106,6 +108,52 @@ Describe 'Get-MyTeam' {
$result.HasGuests | Should -BeNullOrEmpty
}

It 'stops when extended team details fail and complete data is required' {
Mock Get-MgTeam {
if ($All) {
[PSCustomObject] @{
Id = 'team-1'
DisplayName = 'Operations'
Description = 'Operations team'
Visibility = 'Private'
}
} else {
throw 'details unavailable'
}
}

{ Get-MyTeam -RequireCompleteData } | Should -Throw '*details unavailable*'
}

It 'does not emit earlier team rows when a later team fails in complete-data mode' {
Mock Get-MgTeam {
if ($All) {
@(
[PSCustomObject] @{ Id = 'team-1'; DisplayName = 'Operations'; Visibility = 'Private' }
[PSCustomObject] @{ Id = 'team-2'; DisplayName = 'Broken'; Visibility = 'Private' }
)
} elseif ($TeamId -eq 'team-2') {
throw 'second team details unavailable'
} else {
[PSCustomObject] @{
CreatedDateTime = (Get-Date).AddDays(-5)
GuestSettings = [PSCustomObject] @{}
MemberSettings = [PSCustomObject] @{}
Summary = [PSCustomObject] @{ MembersCount = 8; GuestsCount = 0 }
}
}
}

$script:RowsObserved = 0
try {
Get-MyTeam -RequireCompleteData | ForEach-Object { $script:RowsObserved++ }
} catch {
$_.Exception.Message | Should -BeLike '*second team details unavailable*'
}

$script:RowsObserved | Should -Be 0
}

It 'keeps the team and reports owner state as unknown when owner retrieval fails' {
Mock Get-GraphEssentialsGroupOwner { throw 'owners unavailable' }

Expand All @@ -117,6 +165,12 @@ Describe 'Get-MyTeam' {
$result.MembersCount | Should -Be 8
}

It 'stops when owner retrieval fails and complete data is required' {
Mock Get-GraphEssentialsGroupOwner { throw 'owners unavailable' }

{ Get-MyTeam -RequireCompleteData } | Should -Throw '*owners unavailable*'
}

It 'counts non-user group owners returned through the beta owner projection' {
Mock Get-GraphEssentialsGroupOwner {
[PSCustomObject] @{
Expand All @@ -133,6 +187,7 @@ Describe 'Get-MyTeam' {
$result.OwnerDisplayName | Should -Be 'Automation owner'
$result.OwnerId | Should -Be 'service-principal-1'
$result.OwnerObjectType | Should -Be '#microsoft.graph.servicePrincipal'
$result.Owners[0].Id | Should -Be 'service-principal-1'
}

It 'keeps a team in the unavailable-owner bucket when per-owner retrieval fails' {
Expand Down Expand Up @@ -189,6 +244,36 @@ Describe 'Get-MyTeam' {
$result['owner@contoso.com'].Count | Should -Be 1
$result['owner@contoso.com'][0].Team | Should -Be 'Operations'
}

It 'stops when the team list fails and complete data is required' {
Mock Get-MgTeam { throw 'team list unavailable' }

{ Get-MyTeam -RequireCompleteData } | Should -Throw '*team list unavailable*'
}

It 'turns a team-list permission failure into a terminating collection error' {
Mock Get-MgTeam { throw [System.UnauthorizedAccessException]::new('Team.ReadBasic.All is missing') }

try {
Get-MyTeam -RequireCompleteData
throw 'Expected Get-MyTeam to terminate.'
} catch {
$_.FullyQualifiedErrorId | Should -BeLike 'GetMyTeamListFailed*'
$_.CategoryInfo.Category | Should -Be 'ResourceUnavailable'
$_.Exception.Message | Should -BeLike '*Team.ReadBasic.All is missing*'
}
}

It 'preserves the compatibility warning when the team list fails by default' {
Mock Get-MgTeam { throw 'team list unavailable' }

$warnings = @()
$result = Get-MyTeam -WarningVariable warnings -WarningAction SilentlyContinue

$result | Should -BeNullOrEmpty
@($warnings).Count | Should -Be 1
[string] $warnings[0] | Should -BeLike '*team list unavailable*'
}
}

Describe 'Get-GraphEssentialsGroupOwner' {
Expand Down