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
21 changes: 20 additions & 1 deletion .github/instructions/source-connection--csharp.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ Every `.cs` starts with the Apache 2.0 header exactly as in the neighbouring fil
`internal sealed`; the shared base is `internal abstract`. **Exceptions to know:**
`InfoShareOpenIdConnectSystemBrowser` is `public` (it implements Duende's `IBrowser`),
`InfoShareOpenIdConnectLocalHttpEndpoint` and `IshConnectionConfiguration` are `internal` (not
sealed). Don't widen visibility without a reason.
sealed), `RetryOnFaultProxy<T>` is `public` (not sealed) because .NET Framework's
`DispatchProxy.Create<T, TProxy>()` fails to load a non-public `TProxy` with "Access is denied".
Don't widen visibility without a reason.
- Constructor signature for the three connection front-ends is uniform:
`(ILogger logger, HttpClient httpClient, <…ConnectionParameters> parameters)`. The `HttpClient` is
**created once by `IshSession` and reused** (TLS/SSL already initialized) — never new up your own
Expand Down Expand Up @@ -120,6 +122,21 @@ and `#if NET10_0_OR_GREATER` for narrower cases). Both arms must compile **and b
(`OpenApiISH30Client`, `OpenApiAM10Client`), setting the `Bearer` header from `GetAccessToken()`;
**inherits** the OIDC base; `IDisposable`.

**Cross-cutting helper**
- `RetryOnFaultProxy.cs` — generic `System.Reflection.DispatchProxy`-based wrapper used only by
`InfoShareWcfSoapWithOpenIdConnectConnection`'s `Get*25Channel()` accessors: catches
`CommunicationException`/`FaultException` on the actual SOAP call, rebuilds the channel via the
caller-supplied delegate, and retries exactly once, unwrapping reflection's
`TargetInvocationException` so the original exception type/stack trace reaches the caller
unchanged. Wraps only the plain service-contract interface, never `ICommunicationObject`/
`IDisposable` — those keep operating on the private raw channel field, unaffected. Each public
`Get*25Channel()` is split from a private `Ensure*25Channel()` that does the state-check/rebuild
and returns the **raw, unwrapped** channel; `Get*25Channel()` wraps exactly once and passes
`Ensure*25Channel` (not itself) as the rebuild delegate. **Never** pass a rebuild delegate that
returns an already-wrapped proxy — each retry would then recurse into a brand-new
`RetryOnFaultProxy` with its own full retry budget, chaining unbounded on a persistent (non-
transient) fault and causing a stack overflow instead of one bounded retry.

**Diagram**
- `__ConnectionClassDiagram.cd` — Visual Studio class diagram. **Keep it in sync** when you add,
remove, or rename a class in this folder.
Expand Down Expand Up @@ -151,6 +168,8 @@ When you copy `Connection/` out, these are the only external seams to satisfy
own generated service references.
- The NSwag OpenAPI clients `Trisoft.ISHRemote.OpenApiISH30` / `…OpenApiAM10`.
- NuGet: `Duende.IdentityModel` + `Duende.IdentityModel.OidcClient`, and `Newtonsoft.Json`.
- NuGet: `System.Reflection.DispatchProxy` on `net48` only (built into the BCL on `net6.0`+), used by
`RetryOnFaultProxy.cs`.

If you keep those seams thin, the WS-Trust, OIDC-over-SOAP and OIDC-over-OpenAPI flavours — with
Client Credentials or interactive browser auth — all come across intact.
1 change: 1 addition & 0 deletions Doc/ReleaseNotes-ISHRemote-8.3.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ The below text describes the delta compared to fielded release ISHRemote v8.2.
* Fixed `Start-IshRemoteMcpServer` failing to connect on Windows with newer MCP clients (e.g. OpenCode 1.18.11, protocol `2025-11-25`) with errors `MCP error -32001: Request timed out` and `Failed to get tools`. Three root causes: (1) `initialize` requests with `"id":0` were silently dropped because PowerShell treats `0` as falsy; (2) `[Console]::InputEncoding` defaults to OEM code page (`ibm437`) when `pwsh.exe` is spawned with redirected stdio on Windows, causing `ReadLine()` to block forever on UTF-8 JSON — fixed by explicitly setting UTF-8 encoding and replacing `Console.Out` with an auto-flushing `StreamWriter` via `[Console]::SetOut()`; (3) `Register-IshRemoteMcpTool` emitted an invalid `type: "object"` field in `ToolAnnotations` and used string `"true"`/`"false"` instead of boolean `$true`/`$false` for hint values, causing strict MCP schema validation to reject the tools list. Server name updated from `"PowerShell MCP Server (Template)"` to `"ISHRemote MCP Server"` and version bumped to `0.3.0`. Also fixed the server looping forever on stdin EOF (orphaned `pwsh` processes) by breaking the while loop when `ReadLine()` returns `$null`. See #243 and #261. Thanks @ddemeyer
* Migrated all 58 `*.Tests.ps1` files from Pester v5 to Pester v6 (`Should -Be` to `Should-Be`, `Should -BeExactly` to `Should-BeString -CaseSensitive`, `Should -Not -BeNullOrEmpty` to `Should-NotBeNull`, `Should -Throw "msg"` to `Should-Throw -ExceptionMessage "msg"`, etc.). CI install gates updated to `-MinimumVersion 6.0.0`. Classic `Should -Not -Throw` retained as there is no `Should-NotThrow` equivalent in Pester 6. Hardened the library for parallel test execution by replacing the process-wide `TrisoftCmdletLogger` singleton with per-cmdlet `ILogger` routing and adding a double-checked lock on `IshSession._ishTypeFieldSetup` to eliminate Collection was modified races under `Run.Parallel = $true`. CI Pester invocations now use `New-PesterConfiguration` (with `Run.Parallel = $false`) so parallel mode can be toggled in one place when ready. See #242, #265, #266.
* Fixed `New-IshSession` (protocol `WcfSoapWithOpenIdConnect`, PowerShell 7.2+/.NET 6.0+) throwing `FileLoadException: Could not load file or assembly 'Microsoft.IdentityModel.Tokens, Version=8.14.0.0, ...'. The located assembly's manifest definition does not match the assembly reference.` on machines where a different build of `Microsoft.IdentityModel.Tokens` (and related `Duende.IdentityModel.OidcClient`) is registered in the Global Assembly Cache (GAC) — observed on machines with Microsoft Intune Management Extension installed. `AppDomainModuleAssemblyInitializer` now force-loads ISHRemote's own bundled copies of `Duende.IdentityModel`, `Duende.IdentityModel.OidcClient`, `Microsoft.IdentityModel.Abstractions/.Logging/.Tokens/.Tokens.Saml/.Xml` as early as possible during module import, and `SessionCmdlet.BeginProcessing` now reports the full forced list over `-Verbose`. Root cause for the `Duende.IdentityModel.OidcClient` variant: `InfoShareOpenIdConnectSystemBrowser` was `public` and implemented `Duende.IdentityModel.OidcClient.Browser.IBrowser`, which put it in `Trisoft.ISHRemote.dll`'s exported types, forcing PowerShell's own binary-module cmdlet discovery (`Assembly.GetExportedTypes()`) to resolve `Duende.IdentityModel.OidcClient` before `IModuleAssemblyInitializer.OnImport()` ever ran — see Breaking Changes - Code. A new `TestPrerequisite.Tests.ps1` check asserts no assembly is ever loaded from the GAC on PowerShell Core. See #272. Thanks @ddemeyer
* Fixed cmdlets over protocol `WcfSoapWithOpenIdConnect` occasionally throwing `An unsecured or incorrectly secured fault was received from the other party` on the first SOAP call after a channel fault, requiring the user to re-run the same cmdlet for it to succeed (the existing #201/#219 rebuild-on-next-call logic only kicked in on a second, separate call). Each `Get*25Channel()` method in `InfoShareWcfSoapWithOpenIdConnectConnection` now returns the channel wrapped in a new `RetryOnFaultProxy<T>` (`System.Reflection.DispatchProxy`) that catches `CommunicationException`/`FaultException` on the actual SOAP call, rebuilds the channel via the existing rebuild logic, and retries exactly once within the same cmdlet invocation before propagating any further failure — with the original exception type/stack trace preserved. Requires a new `net48`-only NuGet dependency, `System.Reflection.DispatchProxy` (built into the BCL on `net6.0`/`net10.0`). See #273. Thanks @ddemeyer



Expand Down
Loading
Loading