Use PowerShell to wrap an existing COM object

2019-01-23 18:49发布

问题:

Using PowerShell and System.DirectoryServices, I've been given an object that looks like this:

   TypeName: System.__ComObject

Name                      MemberType Definition
----                      ---------- ----------
CreateObjRef              Method     System.Runtime.Remoting.ObjRef CreateObjRef(type requestedType)
Equals                    Method     bool Equals(System.Object obj)
GetHashCode               Method     int GetHashCode()
GetLifetimeService        Method     System.Object GetLifetimeService()
GetType                   Method     type GetType()
InitializeLifetimeService Method     System.Object InitializeLifetimeService()
ToString                  Method     string ToString()

All example code that I can find deals with creating new COM objects from PowerShell, not wrapping existing objects that have been returned. How can I usefully deal with this object (enumerate and use the actual properties and methods)?

Note: this object actually does have a type library ("ActiveDs"), but for some reason I am unable to use it out of the box, as a different question (Loading a Type Library via PowerShell and scripting Windows Live Writer) suggests should be the case.

Here is a one-liner showing how to get such an object:

((new-object DirectoryServices.DirectoryEntry -a '
LDAP://somedc').Properties.GetEnumerator() |?{$_.PropertyName -eq 'usnChanged' }).Value[0] | Get-Member

回答1:

PowerShell reflection doesn't properly "see" these objects' properties and methods. To get to the properties and methods, I use some wrapper functions. Here is an example:

function Get-Property {
  param(
    [__ComObject] $object,
    [String] $propertyName
  )
  $object.GetType().InvokeMember($propertyName,"GetProperty",$NULL,$object,$NULL)
}

function Set-Property {
  param(
    [__ComObject] $object,
    [String] $propertyName,
    $propertyValue
  )
  [Void] $object.GetType().InvokeMember($propertyName,"SetProperty",$NULL,$object,$propertyValue)
}

function Invoke-Method {
  param(
    [__ComObject] $object,
    [String] $methodName,
    $methodParameters
  )
  $output = $object.GetType().InvokeMember($methodName,"InvokeMethod",$NULL,$object,$methodParameters)
  if ( $output ) { $output }
}

$ADS_ESCAPEDMODE_ON = 2      # see ADS_ESCAPE_MODE_ENUM
$ADS_SETTYPE_DN = 4          # see ADS_SETTYPE_ENUM
$ADS_FORMAT_X500_PARENT = 8  # see ADS_FORMAT_ENUM

$Pathname = New-Object -ComObject "Pathname"
# store initial EscapedMode
$escapedMode = Get-Property $PathName "EscapedMode"
# Enable all escaping
Set-Property $PathName "EscapedMode" @($ADS_ESCAPEDMODE_ON)
Invoke-Method $Pathname "Set" @("CN=Ken Dyer,OU=H/R,DC=fabrikam,DC=com",$ADS_SETTYPE_DN)
Invoke-Method $Pathname "Retrieve" @($ADS_FORMAT_X500_PARENT)
# outputs 'OU=H\/R,DC=fabrikam,DC=com'
$escapedMode = Set-Property $PathName "EscapedMode" @($escapedMode)
# set EscapedMode property back to initial value

Note that the Set-Property and Invoke-Method use an array as their final parameter, so I use @( ) when calling those functions.



回答2:

Just a little different approach then Bill Stewart’s:

The idea is that usually you do not need/want to create multiple instances of the ComObject:

Function Invoke-ComObject([Parameter(Mandatory = $true)]$ComObject, [Switch]$Method, [Parameter(Mandatory = $true)][String]$Property, $Value) {
    If ($ComObject -IsNot "__ComObject") {
        If (!$ComInvoke) {$Global:ComInvoke = @{}}
        If (!$ComInvoke.$ComObject) {$ComInvoke.$ComObject = New-Object -ComObject $ComObject}
        $ComObject = $ComInvoke.$ComObject
    }
    If ($Method) {$Invoke = "InvokeMethod"} ElseIf ($MyInvocation.BoundParameters.ContainsKey("Value")) {$Invoke = "SetProperty"} Else {$Invoke = "GetProperty"}
    [__ComObject].InvokeMember($Property, $Invoke, $Null, $ComObject, $Value)
}; Set-Alias ComInvoke Invoke-ComObject

If it concerns a method, you need to add the –Method switch, in the case of a property, the cmdlet will automatically determine whether the property need to be get or set depending on whether a value is supplied. With this cmdlet you do not require the to create the ComObject first and retrieve e.g. to get the ComputerName (DN) from ADSystemInfo in a simple oneliner:

ComInvoke ADSystemInfo ComputerName

To do a the same with the PathName:

$EscapedMode = ComInvoke PathName EscapedMode
ComInvoke PathName EscapedMode @($ADS_ESCAPEDMODE_ON)
ComInvoke Pathname -Method Set @("CN=Ken Dyer,OU=H/R,DC=fabrikam,DC=com", $ADS_SETTYPE_DN)
ComInvoke Pathname -Method Retrieve @($ADS_FORMAT_X500_PARENT)
ComInvoke PathName EscapedMode @($EscapedMode)

A name NameTranslate example:

ComInvoke -Method NameTranslate Init @(1, "domain.com")
ComInvoke -Method NameTranslate Set @(8, "User001")
ComInvoke -Method NameTranslate Get @(1)

Or if you do want to have multiple instances you can first create the ComObject instance and then supply it to the ComInvoke function:

$NameTranslate = New-Object -ComObject NameTranslate
ComInvoke -Method $NameTranslate Init @(1, "domain.com")
ComInvoke -Method $NameTranslate Set @(8, "User001")
ComInvoke -Method $NameTranslate Get @(1)

For the latest Invoke-ComObject version, see: https://powersnippets.com/invoke-comobject/