Skip to content

Conversation

@jborean93
Copy link
Collaborator

PR Summary

Fixes the COM definitions of some WSMan classes that stopped Set-WSManInstance and New-WSManInstance from working. As the COM definition places the put/set method for ResourceUri before the get, it is important we do the same otherwise .NET will call the wrong method in the VTable throwing an exception around an invalid URI. This has been broken since v6 when the CORECLR directives changed it from an IDispatch implementation to IUnknown with the IDispatch methods in the interface. With the move to IUnknown the definition order is important as that is how C# sets up the VTable to invoke these methods.

You can see the header which defines the methods with put_ResourceUri before get_ResourceUri https://github.com/nihon-tc/Rtest/blob/8246e4d21323802fb84c406edc1a005991304f5a/header/Microsoft%20SDKs/Windows/v7.0A/Include/wsmandisp.h#L2426-L2430. Also fixes MustUnderstandOptions even though we don't use it in the codebase.

PR Context

Fixes: #26647
ansible/ansible-documentation#2709
MicrosoftDocs/PowerShell-Docs#5687
https://stackoverflow.com/questions/73780191/set-wsmaninstance-fails-in-powershell-7

Unfortuantely I cannot write a test because New-WSManInstance and Set-WSManInstance requires WSMan to be setup which we cannot guarantee in CI. I have manually tested it with

Set-WSManInstance -ResourceURI winrm/config -ValueSet @{
    MaxEnvelopeSizekb = (Get-WSManInstance -ResourceURI winrm/config).MaxEnvelopeSizekb
}

PR Checklist

Fixes the COM definitions of some WSMan classes that stopped
Set-WSManInstance and New-WSManInstance from working. As the COM
definition places the put/set method for `ResourceUri` before the get,
it is important we do the same otherwise .NET will call the wrong method
in the VTable throwing an exception around an invalid URI. This has been
broken since v6 when the CORECLR directives changed it from an IDispatch
implementation to IUnknown with the IDispatch methods in the interface.
@jborean93 jborean93 requested a review from a team as a code owner January 15, 2026 04:29
Copilot AI review requested due to automatic review settings January 15, 2026 04:29
Copy link
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 COM interface definitions for WSMan classes that has prevented Set-WSManInstance and New-WSManInstance from working since PowerShell v6. The issue stems from incorrect property accessor ordering in the C# interface definitions that don't match the native COM vtable order.

Changes:

  • Swapped ResourceUri property accessor order (set before get) to match COM vtable ordering
  • Swapped MustUnderstandOptions property accessor order (set before get) to match COM vtable ordering
  • Added StyleCop suppressions to document the intentional ordering
Comments suppressed due to low confidence (2)

src/Microsoft.WSMan.Management/Interop.cs:787

  • The FragmentPath property likely has the same COM vtable ordering issue as ResourceUri and MustUnderstandOptions. If the native COM interface defines put_FragmentPath before get_FragmentPath (as is typical for COM interfaces), this property should also have its accessors swapped with set before get. This property is actively used in WsManHelper.cs line 582 where it's assigned a value, so the incorrect ordering could cause similar runtime failures.
        string FragmentPath
        {
            // IDL: HRESULT FragmentPath ([out, retval] BSTR* ReturnValue);

            [DispId(4)]
            [return: MarshalAs(UnmanagedType.BStr)]
            get;
            // IDL: HRESULT FragmentPath (BSTR value);

            [DispId(4)]
            set;
        }

src/Microsoft.WSMan.Management/Interop.cs:805

  • The FragmentDialect property likely has the same COM vtable ordering issue as ResourceUri and MustUnderstandOptions. If the native COM interface defines put_FragmentDialect before get_FragmentDialect (as is typical for COM interfaces), this property should also have its accessors swapped with set before get. This property is actively used in WsManHelper.cs line 587 where it's assigned a value, so the incorrect ordering could cause similar runtime failures.
        string FragmentDialect
        {
            // IDL: HRESULT FragmentDialect ([out, retval] BSTR* ReturnValue);

            [DispId(5)]
            [return: MarshalAs(UnmanagedType.BStr)]
            get;
            // IDL: HRESULT FragmentDialect (BSTR value);

            [DispId(5)]
            set;
        }

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

@iSazonov
Copy link
Collaborator

@jborean93 Thanks for your investigations!

Could you please look line 708 in Interop.cs. I guess now we can remove the old workaround and probably get more reliable result.

#if CORECLR
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
#else
    [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)]
#endif

I found CORECLR 33 times in the file but I hope these don't related to the issue.

@jborean93
Copy link
Collaborator Author

jborean93 commented Jan 15, 2026

@iSazonov based on the git history and the structure of the file it looks to have been autogenerated using something like CsWin32. It was done when PowerShell was migrated to .NET (Core) so was built to support .NET Framework and the newer .NET at the same time. At the time InterfaceIsIDispatch most likely didn't work in newer .NET versions so it was written to based on IUnknown with the IDisptatch methods manually added for the vtable slots.

When testing this change it seems like .NET now has added support back for InterfaceIsIDispatch (at least in the way it's used here). So technically the CORECLR stuff can be removed and either the IUnknown code is used as it is today or we go back to the old IDispatch. But the fix for this specific problem was simple enough without making a massive change so I did just that. The fundamental issue was that this property was specified in the wrong order which is critical to get right for IUnknown definitions. All the order ones are fine, it's just ResourceUri was wrong.

@iSazonov
Copy link
Collaborator

@jborean93 Yes, it was at porting time due to limitations of early .Net version. Later .Net 5 was improved and @daxian-dbw backported COM Interop code to the repo. Description README.md
Workaround from WSMan code was not removed at the time but this could be done now.
I don't ask you to do this. I only say that instead of reordering we could simply remove old workaround only in the place.

And it is a question for @daxian-dbw is it time to remove old workarounds in the code?

@jborean93
Copy link
Collaborator Author

jborean93 commented Jan 15, 2026

We certainly can, when debugging this problem I found that we could either keep using the InterfaceIsIUnknown and remove the old IDispatch stuff removed not in the CORECLR directives or do the opposite and use the InterfaceIsIDispatch. Seems like the former is probably safer as we know it works today (excluding the bug this fixes). It's just a larger change than this which moves 2 different lines.

Also the COM Interop stuff you linked to is AFAIK for New-Object -ComObject and the COM binders used in PowerShell code. The stuff is this file/PR is unrelated to the copies @daxian-dbw made.

@iSazonov
Copy link
Collaborator

Also the COM Interop stuff you linked to is AFAIK for New-Object -ComObject and the COM binders used in PowerShell code. The stuff is this file/PR is unrelated to the copies @daxian-dbw made.

@jborean93 To be clear I did not say that it is related. The link was for your information to point that COM support was improved in .Net 5 (and as result in pwsh too) five years ago.

@jborean93
Copy link
Collaborator Author

jborean93 commented Jan 15, 2026

I’m just somewhat confused then. The code as is today is fine, it just had a bug that this fixed. It certainly can be cleaned up to remove the netfx specific branches as we aren’t building for netfx but that’s a bigger change so I didn’t do it. Better to get a smaller fix merged then wait months for someone to review the larger stuff which would still require this fix anyway.

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