mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Make PowerShell Core enumerate COM collections (#4553)
Make PowerShell Core enumerate COM collections
This commit is contained in:
@@ -15,6 +15,18 @@ namespace System.Management.Automation
|
||||
/// </summary>
|
||||
internal class ComTypeInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// A member with a DISPID equal to –4 is found on a collection interface.
|
||||
/// This special member, often called '_NewEnum', returns an interface that enables clients to enumerate objects in a collection.
|
||||
/// </summary>
|
||||
internal const int DISPID_NEWENUM = -4;
|
||||
|
||||
/// <summary>
|
||||
/// A member with a DISPID equal to 0 is considered a default member.
|
||||
/// Default members in COM can be transformed to default members in .NET (indexers in C#).
|
||||
/// </summary>
|
||||
internal const int DISPID_DEFAULTMEMBER = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Member variables.
|
||||
/// </summary>
|
||||
@@ -44,7 +56,7 @@ namespace System.Management.Automation
|
||||
/// <summary>
|
||||
/// Collection of properties in the COM object.
|
||||
/// </summary>
|
||||
public Dictionary<String, ComProperty> Properties
|
||||
internal Dictionary<String, ComProperty> Properties
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -55,7 +67,7 @@ namespace System.Management.Automation
|
||||
/// <summary>
|
||||
/// Collection of methods in the COM object.
|
||||
/// </summary>
|
||||
public Dictionary<String, ComMethod> Methods
|
||||
internal Dictionary<String, ComMethod> Methods
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -63,12 +75,10 @@ namespace System.Management.Automation
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the string of the GUID for the type information.
|
||||
/// </summary>
|
||||
public string Clsid
|
||||
internal string Clsid
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -76,6 +86,12 @@ namespace System.Management.Automation
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If 'DISPID_NEWENUM' member is present, return the InvokeKind;
|
||||
/// otherwise, return null.
|
||||
/// </summary>
|
||||
internal COM.INVOKEKIND? NewEnumInvokeKind { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the typeinfo object
|
||||
/// </summary>
|
||||
@@ -91,6 +107,8 @@ namespace System.Management.Automation
|
||||
for (int i = 0; i < typeattr.cFuncs; i++)
|
||||
{
|
||||
COM.FUNCDESC funcdesc = GetFuncDesc(_typeinfo, i);
|
||||
if (funcdesc.memid == DISPID_NEWENUM) { NewEnumInvokeKind = funcdesc.invkind; }
|
||||
|
||||
if ((funcdesc.wFuncFlags & 0x1) == 0x1)
|
||||
{
|
||||
// http://msdn.microsoft.com/en-us/library/ee488948.aspx
|
||||
|
||||
@@ -5,6 +5,7 @@ Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Management.Automation.ComInterop;
|
||||
using System.Text;
|
||||
using System.Collections;
|
||||
using System.Collections.ObjectModel;
|
||||
using COM = System.Runtime.InteropServices.ComTypes;
|
||||
|
||||
@@ -355,4 +356,87 @@ namespace System.Management.Automation
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines an enumerator that represent a COM collection object.
|
||||
/// </summary>
|
||||
internal class ComEnumerator : IEnumerator
|
||||
{
|
||||
private COM.IEnumVARIANT _enumVariant;
|
||||
private object[] _element;
|
||||
|
||||
private ComEnumerator(COM.IEnumVARIANT enumVariant)
|
||||
{
|
||||
_enumVariant = enumVariant;
|
||||
_element = new object[1];
|
||||
}
|
||||
|
||||
public object Current
|
||||
{
|
||||
get { return _element[0]; }
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
_element[0] = null;
|
||||
int result = _enumVariant.Next(1, _element, IntPtr.Zero);
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_element[0] = null;
|
||||
_enumVariant.Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to create an enumerator for a COM object.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A 'ComEnumerator' instance, or null if we cannot create an enumerator for the COM object.
|
||||
/// </returns>
|
||||
internal static ComEnumerator Create(object comObject)
|
||||
{
|
||||
if (comObject == null || !comObject.GetType().IsCOMObject) { return null; }
|
||||
|
||||
// The passed-in COM object could already be a IEnumVARIANT interface.
|
||||
// e.g. user call '_NewEnum()' on a COM collection interface.
|
||||
var enumVariant = comObject as COM.IEnumVARIANT;
|
||||
if (enumVariant != null)
|
||||
{
|
||||
return new ComEnumerator(enumVariant);
|
||||
}
|
||||
|
||||
// The passed-in COM object could be a collection.
|
||||
var enumerable = comObject as IEnumerable;
|
||||
var target = comObject as IDispatch;
|
||||
if (enumerable != null && target != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var comTypeInfo = ComTypeInfo.GetDispatchTypeInfo(comObject);
|
||||
if (comTypeInfo != null && comTypeInfo.NewEnumInvokeKind.HasValue)
|
||||
{
|
||||
// The COM object is a collection and also a IDispatch interface, so we try to get a
|
||||
// IEnumVARIANT interface out of it by invoking its '_NewEnum (DispId: -4)' function.
|
||||
var result = ComInvoker.Invoke(target, ComTypeInfo.DISPID_NEWENUM,
|
||||
args: Utils.EmptyArray<object>(), byRef: null,
|
||||
invokeKind: comTypeInfo.NewEnumInvokeKind.Value);
|
||||
enumVariant = result as COM.IEnumVARIANT;
|
||||
if (enumVariant != null)
|
||||
{
|
||||
return new ComEnumerator(enumVariant);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Ignore exceptions. In case of exception, no enumerator can be created
|
||||
// for the passed-in COM object, and we will return null eventually.
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3199,23 +3199,18 @@ namespace System.Management.Automation
|
||||
internal static IEnumerator GetCOMEnumerator(object obj)
|
||||
{
|
||||
object targetValue = PSObject.Base(obj);
|
||||
try
|
||||
{
|
||||
IEnumerable enumerable = targetValue as IEnumerable;
|
||||
if (enumerable != null)
|
||||
{
|
||||
var enumerator = enumerable.GetEnumerator();
|
||||
if (enumerator != null)
|
||||
{
|
||||
return enumerator;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
return targetValue as IEnumerator ?? NonEnumerableObjectEnumerator.Create(obj);
|
||||
// We use ComEnumerator to enumerate COM collections because the following code doesn't work in .NET Core
|
||||
// IEnumerable enumerable = targetValue as IEnumerable;
|
||||
// if (enumerable != null)
|
||||
// {
|
||||
// var enumerator = enumerable.GetEnumerator();
|
||||
// ...
|
||||
// }
|
||||
// The call to 'GetEnumerator()' throws exception because COM is not supported in .NET Core.
|
||||
// See https://github.com/dotnet/corefx/issues/19731 for more information.
|
||||
// When COM support is back to .NET Core, we need to change back to the original implementation.
|
||||
return ComEnumerator.Create(targetValue) ?? NonEnumerableObjectEnumerator.Create(obj);
|
||||
}
|
||||
|
||||
internal static IEnumerator GetGenericEnumerator<T>(IEnumerable<T> enumerable)
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
|
||||
try {
|
||||
$defaultParamValues = $PSdefaultParameterValues.Clone()
|
||||
$PSDefaultParameterValues["it:skip"] = ![System.Management.Automation.Platform]::IsWindowsDesktop
|
||||
|
||||
Describe 'Basic COM Tests' -Tags "CI" {
|
||||
BeforeAll {
|
||||
$null = New-Item -Path $TESTDRIVE/file1 -ItemType File
|
||||
$null = New-Item -Path $TESTDRIVE/file2 -ItemType File
|
||||
$null = New-Item -Path $TESTDRIVE/file3 -ItemType File
|
||||
}
|
||||
|
||||
It "Should enumerate files from a folder" {
|
||||
$shell = New-Object -ComObject "Shell.Application"
|
||||
$folder = $shell.Namespace("$TESTDRIVE")
|
||||
$items = $folder.Items()
|
||||
|
||||
## $items is a collection of all items belong to the folder, and it should be enumerated.
|
||||
$items | Measure-Object | ForEach-Object Count | Should Be $items.Count
|
||||
}
|
||||
|
||||
It "Should enumerate IEnumVariant interface object without exception" {
|
||||
$shell = New-Object -ComObject "Shell.Application"
|
||||
$folder = $shell.Namespace("$TESTDRIVE")
|
||||
$items = $folder.Items()
|
||||
|
||||
## $enumVariant is an IEnumVariant interface of all items belong to the folder, and it should be enumerated.
|
||||
$enumVariant = $items._NewEnum()
|
||||
$enumVariant | Measure-Object | ForEach-Object Count | Should Be $items.Count
|
||||
}
|
||||
|
||||
It "Should enumerate drives" {
|
||||
$fileSystem = New-Object -ComObject scripting.filesystemobject
|
||||
$drives = $fileSystem.Drives
|
||||
|
||||
## $drives is a read-only collection of all available drives, and it should be enumerated.
|
||||
$drives | Measure-Object | ForEach-Object Count | Should Be $drives.Count
|
||||
## $element should be the first drive from the enumeration. It shouldn't be the same as $drives,
|
||||
## but it should be the same as '$drives.Item($element.DriveLetter)'
|
||||
$element = $drives | Select-Object -First 1
|
||||
[System.Object]::ReferenceEquals($element, $drives) | Should Be $false
|
||||
$element | Should Be $drives.Item($element.DriveLetter)
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
$global:PSdefaultParameterValues = $defaultParamValues
|
||||
}
|
||||
Reference in New Issue
Block a user