Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
17 changes: 17 additions & 0 deletions src/IdLE.Core/Private/ConvertTo-IdlePlanExportObject.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,23 @@ function ConvertTo-IdlePlanExportObject {
$stepMap.inputs = $redactedInputs
$stepMap.expectedState = $redactedExpectedState

# Per-step planning warnings (e.g. unresolved precondition context paths).
$rawStepWarnings = Get-FirstPropertyValue -Object $step -Names @('Warnings', 'PlanningWarnings', 'StepWarnings')
$stepWarningList = @()
foreach ($sw in @($rawStepWarnings)) {
if ($null -eq $sw) { continue }

$stepWarningMap = New-OrderedMap
$stepWarningMap.code = ConvertTo-NullIfEmptyString -Value (Get-FirstPropertyValue -Object $sw -Names @('Code', 'code'))
$stepWarningMap.type = ConvertTo-NullIfEmptyString -Value (Get-FirstPropertyValue -Object $sw -Names @('Type', 'type'))
$stepWarningMap.source = ConvertTo-NullIfEmptyString -Value (Get-FirstPropertyValue -Object $sw -Names @('Source', 'source'))
$stepWarningMap.paths = Get-FirstPropertyValue -Object $sw -Names @('Paths', 'paths')
$stepWarningMap.message = ConvertTo-NullIfEmptyString -Value (Get-FirstPropertyValue -Object $sw -Names @('Message', 'message'))

$stepWarningList += $stepWarningMap
}
$stepMap.warnings = $stepWarningList

$stepList += $stepMap
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -456,173 +456,171 @@ function New-IdleExchangeOnlineProvider {
$currentPerms = $this.Adapter.GetMailboxPermissions($mailboxSmtp, $accessToken)

# Normalize current delegates (case-insensitive)
$currentFullAccessUsers = @($currentPerms |
Where-Object { $_.AccessRight -eq 'FullAccess' -and -not $_.IsInherited } |
ForEach-Object { $_.User.ToLowerInvariant() })

if ($hasEventSink) {
$null = $this.EventSink.WriteEvent(
'Provider.ExchangeOnline.Permissions.Evaluated',
"FullAccess current state evaluated for '$mailboxSmtp'",
'EnsureMailboxPermissions',
@{ MailboxSmtp = $mailboxSmtp; Right = 'FullAccess'; CurrentUsers = $currentFullAccessUsers }
)
}
$filteredFullAccessPerms = $currentPerms | Where-Object { $_.AccessRight -eq 'FullAccess' -and -not $_.IsInherited }
$currentFullAccessUsers = @($filteredFullAccessPerms | ForEach-Object { $_.User.ToLowerInvariant() })

if ($hasEventSink) {
$null = $this.EventSink.WriteEvent(
'Provider.ExchangeOnline.Permissions.Evaluated',
"FullAccess current state evaluated for '$mailboxSmtp'",
'EnsureMailboxPermissions',
@{ MailboxSmtp = $mailboxSmtp; Right = 'FullAccess'; CurrentUsers = $currentFullAccessUsers }
)
}

foreach ($entry in $desiredFullAccess) {
$userLower = ([string]$entry.AssignedUser).ToLowerInvariant()
$isPresent = $currentFullAccessUsers -contains $userLower

foreach ($entry in $desiredFullAccess) {
$userLower = ([string]$entry.AssignedUser).ToLowerInvariant()
$isPresent = $currentFullAccessUsers -contains $userLower

if ($entry.Ensure -eq 'Present' -and -not $isPresent) {
if ($hasEventSink) {
$null = $this.EventSink.WriteEvent(
'Provider.ExchangeOnline.Permissions.Applying',
"Granting FullAccess on '$mailboxSmtp' to '$($entry.AssignedUser)'",
'EnsureMailboxPermissions',
@{ MailboxSmtp = $mailboxSmtp; Right = 'FullAccess'; User = [string]$entry.AssignedUser; Action = 'Add' }
)
}
$this.Adapter.AddMailboxPermission($mailboxSmtp, [string]$entry.AssignedUser, $accessToken)
$changed = $true
if ($entry.Ensure -eq 'Present' -and -not $isPresent) {
if ($hasEventSink) {
$null = $this.EventSink.WriteEvent(
'Provider.ExchangeOnline.Permissions.Applying',
"Granting FullAccess on '$mailboxSmtp' to '$($entry.AssignedUser)'",
'EnsureMailboxPermissions',
@{ MailboxSmtp = $mailboxSmtp; Right = 'FullAccess'; User = [string]$entry.AssignedUser; Action = 'Add' }
)
}
elseif ($entry.Ensure -eq 'Absent' -and $isPresent) {
if ($hasEventSink) {
$null = $this.EventSink.WriteEvent(
'Provider.ExchangeOnline.Permissions.Applying',
"Revoking FullAccess on '$mailboxSmtp' from '$($entry.AssignedUser)'",
'EnsureMailboxPermissions',
@{ MailboxSmtp = $mailboxSmtp; Right = 'FullAccess'; User = [string]$entry.AssignedUser; Action = 'Remove' }
)
}
$this.Adapter.RemoveMailboxPermission($mailboxSmtp, [string]$entry.AssignedUser, $accessToken)
$changed = $true
$this.Adapter.AddMailboxPermission($mailboxSmtp, [string]$entry.AssignedUser, $accessToken)
$changed = $true
}
elseif ($entry.Ensure -eq 'Absent' -and $isPresent) {
if ($hasEventSink) {
$null = $this.EventSink.WriteEvent(
'Provider.ExchangeOnline.Permissions.Applying',
"Revoking FullAccess on '$mailboxSmtp' from '$($entry.AssignedUser)'",
'EnsureMailboxPermissions',
@{ MailboxSmtp = $mailboxSmtp; Right = 'FullAccess'; User = [string]$entry.AssignedUser; Action = 'Remove' }
)
}
$this.Adapter.RemoveMailboxPermission($mailboxSmtp, [string]$entry.AssignedUser, $accessToken)
$changed = $true
}
}
}

# --- SendAs ---
$desiredSendAs = @($Permissions | Where-Object { $_.Right -eq 'SendAs' })
if ($desiredSendAs.Count -gt 0) {
$currentRecipientPerms = $this.Adapter.GetRecipientPermissions($mailboxSmtp, $accessToken)
# --- SendAs ---
$desiredSendAs = @($Permissions | Where-Object { $_.Right -eq 'SendAs' })
if ($desiredSendAs.Count -gt 0) {
$currentRecipientPerms = $this.Adapter.GetRecipientPermissions($mailboxSmtp, $accessToken)

$filteredSendAsPerms = $currentRecipientPerms | Where-Object { $_.AccessRight -match 'SendAs' -and -not $_.IsInherited }
$currentSendAsTrustees = @($filteredSendAsPerms | ForEach-Object { $_.Trustee.ToLowerInvariant() })

if ($hasEventSink) {
$null = $this.EventSink.WriteEvent(
'Provider.ExchangeOnline.Permissions.Evaluated',
"SendAs current state evaluated for '$mailboxSmtp'",
'EnsureMailboxPermissions',
@{ MailboxSmtp = $mailboxSmtp; Right = 'SendAs'; CurrentUsers = $currentSendAsTrustees }
)
}

$currentSendAsTrustees = @($currentRecipientPerms |
Where-Object { $_.AccessRight -match 'SendAs' -and -not $_.IsInherited } |
ForEach-Object { $_.Trustee.ToLowerInvariant() })
foreach ($entry in $desiredSendAs) {
$trusteeLower = ([string]$entry.AssignedUser).ToLowerInvariant()
$isPresent = $currentSendAsTrustees -contains $trusteeLower

if ($entry.Ensure -eq 'Present' -and -not $isPresent) {
if ($hasEventSink) {
$null = $this.EventSink.WriteEvent(
'Provider.ExchangeOnline.Permissions.Evaluated',
"SendAs current state evaluated for '$mailboxSmtp'",
'Provider.ExchangeOnline.Permissions.Applying',
"Granting SendAs on '$mailboxSmtp' to '$($entry.AssignedUser)'",
'EnsureMailboxPermissions',
@{ MailboxSmtp = $mailboxSmtp; Right = 'SendAs'; CurrentUsers = $currentSendAsTrustees }
@{ MailboxSmtp = $mailboxSmtp; Right = 'SendAs'; User = [string]$entry.AssignedUser; Action = 'Add' }
)
}

foreach ($entry in $desiredSendAs) {
$trusteeLower = ([string]$entry.AssignedUser).ToLowerInvariant()
$isPresent = $currentSendAsTrustees -contains $trusteeLower

if ($entry.Ensure -eq 'Present' -and -not $isPresent) {
if ($hasEventSink) {
$null = $this.EventSink.WriteEvent(
'Provider.ExchangeOnline.Permissions.Applying',
"Granting SendAs on '$mailboxSmtp' to '$($entry.AssignedUser)'",
'EnsureMailboxPermissions',
@{ MailboxSmtp = $mailboxSmtp; Right = 'SendAs'; User = [string]$entry.AssignedUser; Action = 'Add' }
)
}
$this.Adapter.AddRecipientPermission($mailboxSmtp, [string]$entry.AssignedUser, $accessToken)
$changed = $true
}
elseif ($entry.Ensure -eq 'Absent' -and $isPresent) {
if ($hasEventSink) {
$null = $this.EventSink.WriteEvent(
'Provider.ExchangeOnline.Permissions.Applying',
"Revoking SendAs on '$mailboxSmtp' from '$($entry.AssignedUser)'",
'EnsureMailboxPermissions',
@{ MailboxSmtp = $mailboxSmtp; Right = 'SendAs'; User = [string]$entry.AssignedUser; Action = 'Remove' }
)
}
$this.Adapter.RemoveRecipientPermission($mailboxSmtp, [string]$entry.AssignedUser, $accessToken)
$changed = $true
}
$this.Adapter.AddRecipientPermission($mailboxSmtp, [string]$entry.AssignedUser, $accessToken)
$changed = $true
}
elseif ($entry.Ensure -eq 'Absent' -and $isPresent) {
if ($hasEventSink) {
$null = $this.EventSink.WriteEvent(
'Provider.ExchangeOnline.Permissions.Applying',
"Revoking SendAs on '$mailboxSmtp' from '$($entry.AssignedUser)'",
'EnsureMailboxPermissions',
@{ MailboxSmtp = $mailboxSmtp; Right = 'SendAs'; User = [string]$entry.AssignedUser; Action = 'Remove' }
)
}
$this.Adapter.RemoveRecipientPermission($mailboxSmtp, [string]$entry.AssignedUser, $accessToken)
$changed = $true
}
}
}

# --- SendOnBehalf ---
$desiredSendOnBehalf = @($Permissions | Where-Object { $_.Right -eq 'SendOnBehalf' })
if ($desiredSendOnBehalf.Count -gt 0) {
$currentDelegates = $this.Adapter.GetMailboxSendOnBehalf($mailboxSmtp, $accessToken)
$currentDelegatesLower = @($currentDelegates | ForEach-Object { $_.ToLowerInvariant() })
# --- SendOnBehalf ---
$desiredSendOnBehalf = @($Permissions | Where-Object { $_.Right -eq 'SendOnBehalf' })
if ($desiredSendOnBehalf.Count -gt 0) {
$currentDelegates = $this.Adapter.GetMailboxSendOnBehalf($mailboxSmtp, $accessToken)
$currentDelegatesLower = @($currentDelegates | ForEach-Object { $_.ToLowerInvariant() })

if ($hasEventSink) {
$null = $this.EventSink.WriteEvent(
'Provider.ExchangeOnline.Permissions.Evaluated',
"SendOnBehalf current state evaluated for '$mailboxSmtp'",
'EnsureMailboxPermissions',
@{ MailboxSmtp = $mailboxSmtp; Right = 'SendOnBehalf'; CurrentUsers = $currentDelegatesLower }
)
}

# Compute desired final list based on Present/Absent entries
$updatedDelegates = [System.Collections.Generic.List[string]]::new()
foreach ($d in $currentDelegates) { $updatedDelegates.Add($d) }

$sobChanged = $false
foreach ($entry in $desiredSendOnBehalf) {
$userLower = ([string]$entry.AssignedUser).ToLowerInvariant()
$isPresent = $currentDelegatesLower -contains $userLower

if ($entry.Ensure -eq 'Present' -and -not $isPresent) {
if ($hasEventSink) {
$null = $this.EventSink.WriteEvent(
'Provider.ExchangeOnline.Permissions.Evaluated',
"SendOnBehalf current state evaluated for '$mailboxSmtp'",
'Provider.ExchangeOnline.Permissions.Applying',
"Granting SendOnBehalf on '$mailboxSmtp' to '$($entry.AssignedUser)'",
'EnsureMailboxPermissions',
@{ MailboxSmtp = $mailboxSmtp; Right = 'SendOnBehalf'; CurrentUsers = $currentDelegatesLower }
@{ MailboxSmtp = $mailboxSmtp; Right = 'SendOnBehalf'; User = [string]$entry.AssignedUser; Action = 'Add' }
)
}

# Compute desired final list based on Present/Absent entries
$updatedDelegates = [System.Collections.Generic.List[string]]::new()
foreach ($d in $currentDelegates) { $updatedDelegates.Add($d) }

$sobChanged = $false
foreach ($entry in $desiredSendOnBehalf) {
$userLower = ([string]$entry.AssignedUser).ToLowerInvariant()
$isPresent = $currentDelegatesLower -contains $userLower

if ($entry.Ensure -eq 'Present' -and -not $isPresent) {
if ($hasEventSink) {
$null = $this.EventSink.WriteEvent(
'Provider.ExchangeOnline.Permissions.Applying',
"Granting SendOnBehalf on '$mailboxSmtp' to '$($entry.AssignedUser)'",
'EnsureMailboxPermissions',
@{ MailboxSmtp = $mailboxSmtp; Right = 'SendOnBehalf'; User = [string]$entry.AssignedUser; Action = 'Add' }
)
}
$updatedDelegates.Add([string]$entry.AssignedUser)
$sobChanged = $true
}
elseif ($entry.Ensure -eq 'Absent' -and $isPresent) {
if ($hasEventSink) {
$null = $this.EventSink.WriteEvent(
'Provider.ExchangeOnline.Permissions.Applying',
"Revoking SendOnBehalf on '$mailboxSmtp' from '$($entry.AssignedUser)'",
'EnsureMailboxPermissions',
@{ MailboxSmtp = $mailboxSmtp; Right = 'SendOnBehalf'; User = [string]$entry.AssignedUser; Action = 'Remove' }
)
}
# Remove case-insensitively
$toRemove = $updatedDelegates | Where-Object { $_.ToLowerInvariant() -eq $userLower }
foreach ($r in @($toRemove)) { $updatedDelegates.Remove($r) | Out-Null }
$sobChanged = $true
}
}

if ($sobChanged) {
$this.Adapter.SetMailboxSendOnBehalf($mailboxSmtp, [string[]]$updatedDelegates, $accessToken)
$changed = $true
$updatedDelegates.Add([string]$entry.AssignedUser)
$sobChanged = $true
}
elseif ($entry.Ensure -eq 'Absent' -and $isPresent) {
if ($hasEventSink) {
$null = $this.EventSink.WriteEvent(
'Provider.ExchangeOnline.Permissions.Applying',
"Revoking SendOnBehalf on '$mailboxSmtp' from '$($entry.AssignedUser)'",
'EnsureMailboxPermissions',
@{ MailboxSmtp = $mailboxSmtp; Right = 'SendOnBehalf'; User = [string]$entry.AssignedUser; Action = 'Remove' }
)
}
# Remove case-insensitively
$toRemove = $updatedDelegates | Where-Object { $_.ToLowerInvariant() -eq $userLower }
foreach ($r in @($toRemove)) { $updatedDelegates.Remove($r) | Out-Null }
$sobChanged = $true
}
}

if ($hasEventSink) {
$null = $this.EventSink.WriteEvent(
'Provider.ExchangeOnline.Permissions.Result',
"EnsureMailboxPermissions completed for '$mailboxSmtp': Changed=$changed",
'EnsureMailboxPermissions',
@{ MailboxSmtp = $mailboxSmtp; Changed = $changed }
)
}
if ($sobChanged) {
$this.Adapter.SetMailboxSendOnBehalf($mailboxSmtp, [string[]]$updatedDelegates, $accessToken)
$changed = $true
}
}

return [pscustomobject]@{
PSTypeName = 'IdLE.ProviderResult'
Operation = 'EnsureMailboxPermissions'
IdentityKey = $mailboxSmtp
Changed = $changed
}
} -Force
if ($hasEventSink) {
$null = $this.EventSink.WriteEvent(
'Provider.ExchangeOnline.Permissions.Result',
"EnsureMailboxPermissions completed for '$mailboxSmtp': Changed=$changed",
'EnsureMailboxPermissions',
@{ MailboxSmtp = $mailboxSmtp; Changed = $changed }
)
}

return $provider
return [pscustomobject]@{
PSTypeName = 'IdLE.ProviderResult'
Operation = 'EnsureMailboxPermissions'
IdentityKey = $mailboxSmtp
Changed = $changed
}
} -Force

return $provider
}
4 changes: 4 additions & 0 deletions tests/Core/Export-IdlePlan.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ Describe 'Export-IdlePlan' {
@($json.plan.warnings).Count | Should -BeGreaterThan 0
$json.plan.warnings[0].code | Should -Be 'PreconditionContextPathUnresolvedAtPlan'
$json.plan.warnings[0].step | Should -Be 'Check Context'

$json.plan.steps[0].warnings | Should -Not -BeNullOrEmpty
($json.plan.steps[0].warnings | Measure-Object).Count | Should -BeGreaterThan 0
$json.plan.steps[0].warnings[0].code | Should -Be 'PreconditionContextPathUnresolvedAtPlan'
}
}
Context 'Contract invariants' {
Expand Down
Loading