Skip to content

Fix EXO Mailbox.EnsurePermissions: Changed=True for Ensure=Present, transient retry, diagnostics events#234

Merged
blindzero merged 8 commits intomainfrom
copilot/fix-mailbox-permissions-issue
Feb 24, 2026
Merged

Fix EXO Mailbox.EnsurePermissions: Changed=True for Ensure=Present, transient retry, diagnostics events#234
blindzero merged 8 commits intomainfrom
copilot/fix-mailbox-permissions-issue

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 23, 2026

  • Investigate root cause: EXO Add-MailboxPermission / Add-RecipientPermission / Set-Mailbox return the created/modified object. Adapter write methods called $this.InvokeSafely(...) without $null = , so the output leaked through the uncaptured call chain into EnsureMailboxPermissions's implicit output pipeline → Changed=False
  • Fix InvokeSafely in adapter to detect transient EXO error patterns and mark exceptions with Idle.IsTransient = $true
  • Fix all write-type adapter methods to suppress output: $null = $this.InvokeSafely(...) for all 7 write methods
  • Fix all $this.EventSink.WriteEvent(...) calls in EnsureMailboxPermissions to use $null = $this.EventSink.WriteEvent(...), preventing any EventSink return value from polluting the method's output pipeline
  • Add EventSink = $null property to the EXO provider object and emit diagnostics events in EnsureMailboxPermissions
  • Inject EventSink from context into provider in step
  • All 664 tests pass, ScriptAnalyzer clean
Original prompt

This section details on the original issue you should resolve

<issue_title>Mailbox.EnsurePermissions (ExchangeOnline) reports Changed=false and Ensure=Absent fails</issue_title>
<issue_description>## Description

Mailbox.EnsurePermissions using the ExchangeOnline provider reports Changed: False even when it actually applies changes. Additionally, removing permissions with Ensure = 'Absent' fails (while Present works).

Observed error during removal attempts includes server-side EXO error messages.

Steps to Reproduce

  1. Run Mailbox.EnsurePermissions for ExchangeOnline with a permission assignment that exists.

  2. Apply Ensure = 'Present' -> works.

  3. Apply Ensure = 'Absent' using:

    @{ AssignedUser = 'matthias.fleschuetz@nanotempertech.com'; Right = @('FullAccess'); Ensure = 'Absent' }

  4. Observe:

    • Step result shows Changed: False even when something changed.
    • Ensure=Absent fails with server-side error.

Expected Behavior

  • Step returns Changed: True when permissions were modified.
  • Ensure=Absent removes the specified permission reliably and idempotently (subsequent runs result in Changed: False).

Actual Behavior

  • Changed remains False even when changes occurred.

  • Ensure=Absent fails, e.g.:

    A server side error has occurred because of which the operation could not be completed...

Environment

  • PowerShell version: 7.x
  • OS: (fill in)
  • IdLE version / commit: (fill in)
  • Execution context (CLI / Service / CI): (fill in)
  • ExchangeOnlineManagement version: 3.9.2

Additional Context

Notes / Implementation expectations (agent-safe)

  1. Changed calculation
  • Ensure the step sets Changed = True whenever at least one permission entry is added/removed/updated.
  • Ensure idempotency: when desired state already matches, Changed = False.
  1. Ensure=Absent support
  • Verify which EXO cmdlets are used for permission changes (e.g., Add-MailboxPermission / Remove-MailboxPermission vs EXO equivalents).
  • Ensure the removal path uses the correct parameters (especially for FullAccess) and handles propagation delays gracefully.
  • If EXO sometimes returns transient server-side errors, implement a bounded retry with backoff (only for clearly transient errors), and emit actionable error messages when retries are exhausted.
  1. Diagnostics
  • Emit step/provider diagnostics events for:
    • evaluated current permissions
    • intended delta
    • command invoked (sanitized)
    • result & changed-state decision
  1. Tests
  • Unit tests using mock provider responses:
    • Present adds -> Changed True
    • Absent removes -> Changed True
    • Second run -> Changed False
    • Transient server-side error -> retry path

</issue_description>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits February 23, 2026 21:50
…nts, EventSink injection

Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
…finally for cleanup

Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix Mailbox.EnsurePermissions for ExchangeOnline Fix EXO Mailbox.EnsurePermissions: transient retry, Changed=True, diagnostics events Feb 23, 2026
Copilot AI requested a review from blindzero February 23, 2026 21:55
Copy link
Copy Markdown
Owner

@blindzero blindzero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot was absent error reported from issue fixed?

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 24, 2026

@copilot was absent error reported from issue fixed?

Yes, the Ensure=Absent error is fixed. The root cause was that Remove-MailboxPermission (and other EXO cmdlets) occasionally throw transient "server side error" responses from EXO's REST API. The adapter's InvokeSafely was re-throwing these without marking them as transient, so the plan executor's existing Invoke-IdleWithRetry never retried — the exception propagated before $changed = $true was reached, causing Changed=False.

The fix (commit 690fdbe):

  • InvokeSafely now detects transient EXO error patterns (server side error, throttle, too many requests, service unavailable, bad gateway) and sets $ex.Data['Idle.IsTransient'] = $true on the wrapped exception
  • The plan executor's existing Invoke-IdleWithRetry then automatically retries the enclosing step with backoff, just as it does for other transient errors in the engine
  • Changed=True is returned correctly once the retry succeeds

@blindzero blindzero marked this pull request as ready for review February 24, 2026 06:51
Copilot AI review requested due to automatic review settings February 24, 2026 06:51
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a critical bug in the ExchangeOnline provider's Mailbox.EnsurePermissions functionality where permission removal (Ensure=Absent) failed on transient EXO server errors and always reported Changed=False. The fix enables automatic retry by marking transient EXO errors (server-side 5xx, throttling 429) with Idle.IsTransient=$true, allowing the plan executor's existing Invoke-IdleWithRetry mechanism to handle transient failures without requiring new retry infrastructure.

Changes:

  • Adapter now detects and marks transient EXO error patterns (server-side, throttling, service unavailable) enabling automatic retry
  • Provider emits diagnostics events (Evaluated, Applying, Result) when EventSink is available for improved observability
  • Step injects EventSink into the provider before calling EnsureMailboxPermissions to enable provider-level diagnostics

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/Providers/_testHelpers.Providers.ps1 Adds test helpers to simulate EXO server-side, throttling, and permission errors for testing transient error detection
tests/Providers/ExchangeOnlineProvider.Tests.ps1 Adds comprehensive tests for transient error marking, propagation, and EventSink event emission
src/IdLE.Steps.Mailbox/Public/Invoke-IdleStepMailboxPermissionsEnsure.ps1 Injects EventSink from Context into provider before calling EnsureMailboxPermissions to enable diagnostics
src/IdLE.Provider.ExchangeOnline/Public/New-IdleExchangeOnlineProvider.ps1 Adds EventSink property and emits diagnostics events for permission evaluation, application, and results
src/IdLE.Provider.ExchangeOnline/Private/New-IdleExchangeOnlineAdapter.ps1 Implements transient error pattern detection and marks exceptions with Idle.IsTransient for retry support

Comment thread src/IdLE.Provider.ExchangeOnline/Private/New-IdleExchangeOnlineAdapter.ps1 Outdated
Comment thread src/IdLE.Provider.ExchangeOnline/Private/New-IdleExchangeOnlineAdapter.ps1 Outdated
Comment thread tests/Providers/ExchangeOnlineProvider.Tests.ps1
blindzero and others added 2 commits February 24, 2026 11:02
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Feb 24, 2026

Code Coverage Report

Overall Project 73.6% 🍏

There is no coverage information present for the Files changed

@blindzero
Copy link
Copy Markdown
Owner

@copilot Changed output issue is not fully fixed. Access Rights removal + adding seems to work now both.

A Step with Mailbox.EnsurePermission is given, with two assigned users as testing ground:

@{ AssignedUser = '{{Request.Context.Manager}}'; Right = @('FullAccess'); Ensure = 'Present' }
@{ AssignedUser = 'matthias.fleschuetz@nanotempertech.com'; Right = @('FullAccess'); Ensure = 'Present' }

Reproduce / Steps

  1. Run workflow "add both"

    • ❌ Step report Changed: False --> Wrong
    • ✅ Access rights are set for both users correctly
  2. Re-Run workflow "add both"

    • ✔️ Step report Changed: False --> Technically correct, but not sure if real result as all Present are repoting "False"
    • ✅ Access rights are still set for both users correctly
  3. Run workflow with second assigned user Absent

Permissions = @(
    @{ AssignedUser = '{{Request.Context.Manager}}'; Right = @('FullAccess'); Ensure = 'Present' }
    @{ AssignedUser = 'matthias.fleschuetz@nanotempertech.com'; Right = @('FullAccess'); Ensure = 'Absent' }
)
* ✅ Step report `Changed: True` --> correct
* ❌ Access rights removed for second user only
  1. Run workflow with re-adding both, = Present

    • ❌ Step report Changed: False --> incorrect
    • ✅ Access rights are there for both users
  2. Run workflow with removing both, = Absent

    • ✅ Step report Changed: True --> correct
    • ✅ Access rights removed for both users
  3. Run workflow with re-adding only one user (second) = Present, other (first) user remains = Absent

    • ❌ Step report Changed: False --> incorrect
    • ✅ Access rights set for only second user

have a look at the findings and fix this.

…tput in write adapter methods

Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 24, 2026

@copilot Changed output issue is not fully fixed. Access Rights removal + adding seems to work now both.

A Step with Mailbox.EnsurePermission is given, with two assigned users as testing ground:...

Found and fixed the root cause (commit 85547aa).

Why Changed=False was returned even when permissions were added:

EXO's Add-MailboxPermission and Add-RecipientPermission cmdlets return the created permission object (unlike Remove-* which returns nothing). The adapter methods called $this.InvokeSafely(...) without $null =, so the returned object leaked through the uncaptured call chain into EnsureMailboxPermissions's implicit output pipeline. The method ended up returning an array [<permission-object>, <pscustomobject>] instead of just the expected [pscustomobject]. The step's $result.PSObject.Properties.Name -contains 'Changed' check fails on an array, so $changed = $false was always used for add paths.

Remove operations work fine because Remove-MailboxPermission returns no output — only the [pscustomobject] was returned.

Fix: Added $null = to all write-type adapter methods: AddMailboxPermission, RemoveMailboxPermission, AddRecipientPermission, RemoveRecipientPermission, SetMailboxSendOnBehalf, SetMailboxType, and SetMailboxAutoReplyConfiguration.

Copilot AI changed the title Fix EXO Mailbox.EnsurePermissions: transient retry, Changed=True, diagnostics events Fix EXO Mailbox.EnsurePermissions: Changed=True for Ensure=Present, transient retry, diagnostics events Feb 24, 2026
Copilot AI requested a review from blindzero February 24, 2026 10:34
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

…eline pollution

Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
@blindzero blindzero merged commit 84c396c into main Feb 24, 2026
5 checks passed
@blindzero blindzero deleted the copilot/fix-mailbox-permissions-issue branch February 27, 2026 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mailbox.EnsurePermissions (ExchangeOnline) reports Changed=false and Ensure=Absent fails

3 participants