Name Value
---- -----
PSVersion 7.0.0-rc.2
PSEdition Core
GitCommitId 7.0.0-rc.2
OS Microsoft Windows 10.0.18363
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
The code in the attached script runs OK on V5 but generates the following error on V7:
MethodInvocationException: S:\PowerShellScripts\TestUserIngroup_1.ps1:5
Line |
5 | … t -Process {$_.GetType().InvokeMember('Name', 'GetProperty', $null, $ …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Exception calling "InvokeMember" with "5" argument(s): "Unknown name. (0x80020006 (DISP_E_UNKNOWNNAME))"
$user = $env:USERNAME
$group = 'Users'
$groupObj = [ADSI]"WinNT://./$group,group"
$membersObj = @($groupObj.psbase.Invoke('Members'))
$members = ($membersObj | ForEach-Object -Process {$_.GetType().InvokeMember('Name', 'GetProperty', $null, $_, $null)})
If ($members -contains $user)
{Write-Host -Object "$user exists in the group $group"}
Else
{Write-Host -Object "$user does not exists in the group $group"}
@SeeminglyScience Have you any thoughts?
I am somewhat new to GitHub so I am not sure what you are expecting when you say "Have you any thoughts" My only thought at this time would be expecting it to work on PSV7* or there to be a better/alternative way to achieve the same result?
@RG255 References like "@RG255" means contacting the owner of this name. So my question was for @SeeminglyScience who could share useful thoughts about the issue.
You could look other issues/discussion in the repo to get more experience about workflows.
@iSazonov Looks like PowerShell isn't able to determine that IADsMembers (the collection returned by Members) is enumerable. So instead of $membersObj enumerating into member objects like in Windows PowerShell, the attempt is made to call a Name property on the IADsMembers interface which doesn't exist.
You can kinda see this by attempting to get the property Count instead, which will throw not implemented instead of unknown name.
So playing with it a bit, my guess is that this happens because if you query IDispatch.GetIDsOfNames with the names _NewEnum and ppEnumerator then that exception is thrown. However, if you manually invoke GetIDsOfNames with a preserved sig (no automatic HResult handling) you'll see the HResult above returned, but it'll also show DISPID_NEWENUM for _NewEnum. If _NewEnum was really not found, it would be -1.
Not sure atm where this needs to be fixed though.
Tested with this code, click to expand
Add-Type -TypeDefinition '
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
namespace Testing
{
public class DispatchWrapper
{
private readonly IDispatch _real;
internal DispatchWrapper(IDispatch real) => _real = real;
public static DispatchWrapper Create(object obj) => new DispatchWrapper((IDispatch)obj);
public int GetTypeInfoCount(out int info) => _real.GetTypeInfoCount(out info);
public int GetTypeInfo(int iTInfo, int lcid, out ITypeInfo ppTInfo) => _real.GetTypeInfo(iTInfo, lcid, out ppTInfo);
public int GetIDsOfNames(Guid iid, string[] rgszNames, int cNames, int lcid, int[] rgDispId)
=> _real.GetIDsOfNames(iid, rgszNames, cNames, lcid, rgDispId);
public void Invoke(
int dispIdMember,
Guid iid,
int lcid,
INVOKEKIND wFlags,
DISPPARAMS[] paramArray,
out object pVarResult,
out EXCEPINFO pExcepInfo,
out uint puArgErr)
=> _real.Invoke(
dispIdMember,
iid,
lcid,
wFlags,
paramArray,
out pVarResult,
out pExcepInfo,
out puArgErr);
}
[Guid("00020400-0000-0000-c000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComImport]
internal interface IDispatch
{
[PreserveSig]
int GetTypeInfoCount(out int info);
[PreserveSig]
int GetTypeInfo(int iTInfo, int lcid, out ITypeInfo ppTInfo);
[PreserveSig]
int GetIDsOfNames(
[MarshalAs(UnmanagedType.LPStruct)] Guid iid,
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr)] string[] rgszNames,
int cNames,
int lcid,
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.I4)] [Out] int[] rgDispId);
void Invoke(
int dispIdMember,
[MarshalAs(UnmanagedType.LPStruct)] Guid iid,
int lcid,
INVOKEKIND wFlags,
[MarshalAs(UnmanagedType.LPArray)] [In] [Out] DISPPARAMS[] paramArray,
out object pVarResult,
out EXCEPINFO pExcepInfo,
out uint puArgErr);
}
}'
$group = 'Users'
$groupObj = [ADSI]"WinNT://./$group,group"
$membersObj = @($groupObj.psbase.Invoke('Members'))
$disp = [Testing.DispatchWrapper]::Create($membersObj[0])
$nullGuid = [activator]::CreateInstance([guid])
$enUS = [cultureinfo]::CurrentCulture.LCID
$namesToQuery = '_NewEnum', 'ppEnumerator'
$dispIds = [int[]]::new($namesToQuery.Length)
$hr = $disp.GetIDsOfNames($nullGuid, $namesToQuery, $namesToQuery.Length, $enUS, $dispIds)
'HResult: 0x{0:X}' -f $hr
for ($i = 0; $i -lt $namesToQuery.Length; $i++) {
'{0} ID: {1}' -f $namesToQuery[$i], $dispIds[$i]
}
Should return:
HResult: 0x80020006
DispIDs: -4, -1
Another update, here's the code that needs to change:
It fails because IADsMembers implements IDispatch, but throws E_NOTIMPL when trying to get type info. The other members work fine though.
Instead of (or in addition to) querying type info, it needs to directly invoke GetIDsOfNames with two names. The first has to be _NewEnum and the second can apparently be anything. It needs two though, otherwise it returns unknown. If the first ID is DISPID_NEWENUM then _NewEnum can be invoked. The GetIDsOfNames implementation needs PreserveSig and it's return type changed to int. If DISPID_NEWENUM is found and the HResult is DISP_E_UNKNOWNNAME, then the HResult should be ignored.
Example invoke using the above code in the details pane:
$results = $null
$excep = [Activator]::CreateInstance([Runtime.InteropServices.ComTypes.EXCEPINFO])
$argErr = 0u;
$disp.Invoke(-4, $nullGuid, $enUS, 'INVOKE_PROPERTYGET', @(), [ref] $res, [ref] $excep, [ref] $argErr)
# yield
$results
Which returns:
System.__ComObject
System.__ComObject
System.__ComObject
System.__ComObject
System.__ComObject
System.__ComObject
/cc @SteveL-MSFT I'm pretty sure I've seen quite a few folks on with similar issues on the PS slack. This might be the first issue for it here, but it's potentially very impactful for folks still using COM objects frequently (and who aren't super likely to be vocal here). If possible, it may be worth resolving before 7.0 GA.
@SeeminglyScience Thanks for your investigations!
I think we need to have more tests for COM interactions.
Thanks @SeeminglyScience for analyzing the issue and pinpoint the cause ❤️
The ComEnumerator was introduced back in early .NET Core 2.0 preview period of time (#4553), because GetEnumerator() didn't work on COM object even if the object can be cast to IEnumerable and .NET Core team said it was by design at the time (https://github.com/dotnet/runtime/issues/21690).
With .NET Core 3.1, GetEnumerator() works on the COM objects that can be cast to IEnumerable 🎉
However, for the COM object that can be cast to IEnumerator, exception is thrown when calling MoveNext() on it.
So we still need the ComEnumerator, but much simplified to just cover the case where the COM object implements COM.IEnumVARIANT interface.
PR was submitted: #11795
:tada:This issue was addressed in #11795, which has now been successfully released as v7.0.0-rc.3.:tada:
Handy links:
Most helpful comment
Thanks @SeeminglyScience for analyzing the issue and pinpoint the cause ❤️
The
ComEnumeratorwas introduced back in early .NET Core 2.0 preview period of time (#4553), becauseGetEnumerator()didn't work on COM object even if the object can be cast toIEnumerableand .NET Core team said it was by design at the time (https://github.com/dotnet/runtime/issues/21690).With .NET Core 3.1,
GetEnumerator()works on the COM objects that can be cast toIEnumerable🎉However, for the COM object that can be cast to
IEnumerator, exception is thrown when callingMoveNext()on it.So we still need the
ComEnumerator, but much simplified to just cover the case where the COM object implementsCOM.IEnumVARIANTinterface.PR was submitted: #11795