8

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
Community
  • 1
  • 1
Reinderien
  • 11,755
  • 5
  • 49
  • 77
  • There are cmdlets that designed specifically to work with active directory. http://technet.microsoft.com/en-us/library/ee617195.aspx It could be that you make your life easier if you use those. – Andrew Savinykh Nov 27 '13 at 21:48
  • Could you also please give a code example that returns this particular object you are having problems with – Andrew Savinykh Nov 27 '13 at 21:49
  • Unfortunately I am unable to install any new modules; otherwise I would certainly have used that. – Reinderien Nov 27 '13 at 22:10
  • I have added a code example. – Reinderien Nov 27 '13 at 22:17
  • Thank you for this, now I understand the problem. Bill's answer should work for you, I also would like to offer this: http://stackoverflow.com/a/13475504/284111 as for the explanation why you should not rely on ActiveDs. This problem is not specific for powershell. It's just deficiency of the tlb/interop. The workarounds are as described. – Andrew Savinykh Dec 01 '13 at 22:17
  • That is useful information, but certainly doesn't contain an explanation of why not to rely on ActiveDs, other than "don't do it, just, because." – Reinderien Dec 01 '13 at 23:55
  • This is how I answer to myself: interop and especially tlb/IDispatch interactions has always been clumsy with .net. In most of the cases there are ways to make it workable. In your case it's the AD cmdlets specifically designed for this purpose. Since the problem is largely solved for the most of practical cases nobody really invested in doing more work in this area. – Andrew Savinykh Dec 02 '13 at 03:59

2 Answers2

6

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.

Bill_Stewart
  • 22,916
  • 4
  • 51
  • 62
  • 2
    I already use InvokeMember, but it's ugly. There actually is a TLB, but for some reason PS seems to ignore it. Is there any way to list the 'real' properties and methods available, to get the real type name, or to load the proper TLB? – Reinderien Nov 27 '13 at 20:10
1

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/

iRon
  • 20,463
  • 10
  • 53
  • 79