Skip to content

Replace ReplicationProgressHandler with IProgress<T> - #229

Merged
MichaelGrafnetter merged 1 commit into
masterfrom
refactor/iprogress-replication
May 28, 2026
Merged

Replace ReplicationProgressHandler with IProgress<T>#229
MichaelGrafnetter merged 1 commit into
masterfrom
refactor/iprogress-replication

Conversation

@MichaelGrafnetter

@MichaelGrafnetter MichaelGrafnetter commented May 28, 2026

Copy link
Copy Markdown
Owner

Summary

  • Replaces the custom ReplicationProgressHandler delegate on DirectoryReplicationClient with the standard IProgress<ReplicationProgress> interface across GetAccounts, ReplicateAllObjects, and FetchFullSchema.
  • Adds optional CancellationToken parameters so long-running replication can be cancelled cooperatively between DRS cycles.
  • Wires Get-ADReplAccount to surface Ctrl+C as cancellation and to convert OperationCanceledException into a clean progress completion.

Why

The custom delegate predates wide adoption of System.IProgress<T>. Switching aligns the public DSInternals.Replication API with idiomatic .NET conventions, pairs naturally with CancellationToken, and makes the surface self-documenting for external consumers.

Notes for reviewers

  • Breaking change in DSInternals.Replication: callers of the affected methods must migrate from ReplicationProgressHandler to IProgress<ReplicationProgress>. The new ReplicationProgress payload keeps the same data the old delegate received (Cookie, ProcessedObjectCount, TotalObjectCount) so external consumers that need the cookie for resumable replication keep working after a one-line adapter. Documented under ### Changed in Documentation/CHANGELOG.md.
  • A new internal SynchronousProgress<T> helper is intentionally used instead of the BCL Progress<T>: PowerShell cmdlets must call WriteProgress on the pipeline thread, but Progress<T> marshals through the captured SynchronizationContext (or the thread pool when none is present), which would move the callback off the pipeline thread.
  • Cancellation is checked between DRS round-trips only — the native DRS call itself is not interruptible. This matches the prior Ctrl+C experience and is intentional.
  • CancellationTokenSource is scoped to GetADReplAccountCommand rather than the shared ADReplCommandBase, since it is the only cmdlet that needs it today.

Test plan

  • dotnet build DSInternals.DotNetSdk.slnf (Release, x64) — 0 errors
  • dotnet test --solution DSInternals.DotNetSdk.slnf
  • Manual smoke: Get-ADReplAccount -All against a lab DC — progress advances; Ctrl+C completes cleanly
  • Manual smoke: Get-ADReplAccount -All -Properties LAPS — schema progress (activity 2) still renders
  • Manual smoke: Get-ADReplAccount -SamAccountName Administrator — non-bulk path still works
  • pwsh -File Scripts/Invoke-SmokeTests.ps1 -Configuration Debug

DirectoryReplicationClient.GetAccounts, ReplicateAllObjects, and
FetchFullSchema now accept IProgress<ReplicationProgress> instead of the
custom delegate, and gain an optional CancellationToken so long-running
replication can be cancelled cooperatively between cycles.

Get-ADReplAccount uses a small SynchronousProgress<T> adapter to dispatch
on the pipeline thread (the BCL Progress<T> marshals through the
SynchronizationContext, which would move WriteProgress off the pipeline
thread). The cmdlet now also forwards Ctrl+C as a CancellationToken and
converts OperationCanceledException into a clean progress completion.

This is a breaking change in the DSInternals.Replication library.
Copilot AI review requested due to automatic review settings May 28, 2026 08:56
@MichaelGrafnetter MichaelGrafnetter self-assigned this May 28, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Replaces the bespoke ReplicationProgressHandler delegate with the idiomatic IProgress<ReplicationProgress> interface across DirectoryReplicationClient's replication methods, and adds optional CancellationToken parameters for cooperative cancellation between DRS cycles. The Get-ADReplAccount cmdlet is wired up to translate Ctrl+C into cancellation and to convert OperationCanceledException into a clean progress completion.

Changes:

  • Introduce ReplicationProgress record struct and remove the legacy delegate.
  • Update GetAccounts, ReplicateAllObjects, and FetchFullSchema signatures to use IProgress<T> + CancellationToken, with cancellation checks between cycles.
  • Add internal SynchronousProgress<T> helper and wire GetADReplAccountCommand to use it, with StopProcessing/Dispose managing a CancellationTokenSource.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
Src/DSInternals.Replication/ReplicationProgressHandler.cs Removes the legacy delegate type.
Src/DSInternals.Replication/ReplicationProgress.cs New record struct carrying cookie + counts for progress reports.
Src/DSInternals.Replication/DirectoryReplicationClient.cs Migrates 3 public methods to IProgress<ReplicationProgress> + CancellationToken.
Src/DSInternals.PowerShell/Commands/Replication/GetADReplAccountCommand.cs Uses SynchronousProgress<T>, adds StopProcessing/Dispose for cancellation, wraps work in try/catch/finally.
Src/DSInternals.PowerShell/Commands/Base/SynchronousProgress.cs New internal pipeline-thread IProgress<T> helper, with rationale in remarks.
Documentation/CHANGELOG.md Documents breaking API change under Unreleased → Changed.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@MichaelGrafnetter
MichaelGrafnetter merged commit 3f0dc9e into master May 28, 2026
1 check passed
@MichaelGrafnetter
MichaelGrafnetter deleted the refactor/iprogress-replication branch May 28, 2026 10:51
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.

2 participants