Some time back I wrote a couple of scripts (tokenGroupsDump.vbs and tokenGroupsLogon.vbs) for myself and a customer using the idea of the tokenGroups attribute to access a user or computers group membership without the hassle of having to programmatically do the work especially with all the problems of looking up the primary group ID.

As an exercise I decided to convert the scripts into PowerShell partly to see what would be involved as they were more than a few lines and I could see some real time savings especially in the formatting of the dump script and some interesting learning opportunities in using the WSH components for drive mapping etc.

What I had not expected was to spend the afternoon figuratively banging my head of the wall and spending a considerable part of it reading MSDN documentation without once opening Visual Studio.

As no doubt all of you are aware PowerShell is a .NET console application designed to take full advantage of the .NET framework and is routinely talked of as a replacement for CMD, VBScript etc. In deed most people learning PowerShell quickly come across various examples using [ADSI] to access Active Directory and of invocation of COM objects such as Internet Explorer to open web pages so when they see VBScript such as ShowUserName.vbs below the assumption will be fairly similar to mine that it will take a couple of minutes to convert.

Set oADSystemInfo = CreateObject("ADSystemInfo")
WScript.Echo oADSystemInfo.UserName

You could not be further from the truth if you tried - nearly four hours of digging around PowerShell, MSDN and the internet.

A minutes quick typing produced the following and a confused feeling when also trying the tab completion showed none of the expected methods or properties exposed by ADSystemInfo I was intrigued and honestly a bit pissed this was going to take more than the expected couple of minutes as I had a lot more lines to convert.

PS P:\> $oADSystemInfo = new-object -COM ADSystemInfo
PS P:\> $oADSystemInfo.UserName
PS P:\>

OK back to PowerShell basics and the eponymous Get-Member command which I honestly hoped would show me something but had the sinking feeling before I even hit return. Nothing none of the ever so useful domain, site or user information.

PS P:\> Get-Member -InputObject $oADSystemInfo

TypeName: System.__ComObject

Name                      MemberType Definition
----                      ---------- ----------
CreateObjRef              Method     System.Runtime.Remoting.ObjRef CreateObjRef(Type requestedType)
Equals                    Method     System.Boolean Equals(Object obj)
GetHashCode               Method     System.Int32 GetHashCode()
GetLifetimeService        Method     System.Object GetLifetimeService()
GetType                   Method     System.Type GetType()
InitializeLifetimeService Method     System.Object InitializeLifetimeService()
ToString                  Method     System.String ToString()

PS P:\>

Maybe I had the wrong COM name or PowerShell was somehow mangling the one I typed so .GetType() to see what was being returned in case I how one of the annoying problems from the wrappers like PSBASE introduced. OK now I saw the problem PowerShell is not picking up any of the type info for some reason.

PS P:\> $oADSystemInfo.GetType()

IsPublic  IsSerial  Name         BaseType
--------  --------  ----         --------
True      False     __ComObject  System.MarshalByRefObject

PS P:\>

Have a look at the following comparison of the .Psobject output from creating $ie for the Internet Explorer Application object and the .Psobject from our $oADSystemInfo object that is being the problem. Key field is the last one TypeNames where $ie is returning the COM GUID d30c1661-cdaf-11d0-8a3e-00c04fc9e26e but the creation of the $oADSystemInfo by New-Object is not similarly populating the required information.

PS P:\> $ie = New-Object -COM internetexplorer.application
PS P:\> $ie.Psobject

Members             : {Application, Parent, Container, Document...}
Properties          : {Application, Parent, Container, Document...}
Methods             : {GoBack, GoForward, GoHome, GoSearch...}
ImmediateBaseObject : System.__ComObject
BaseObject          : System.__ComObject
TypeNames           : {System.__ComObject#{d30c1661-cdaf-11d0-8a3e-00c04fc9e26e}, System.__ComObject, System.MarshalByRefObject, System.Object}

PS P:\> $oADSystemInfo.Psobject

Members             : {GetLifetimeService, InitializeLifetimeService, CreateObjRef, GetType...}
Properties          : {}
Methods             : {GetLifetimeService, InitializeLifetimeService, CreateObjRef, GetType...}
ImmediateBaseObject : System.__ComObject
BaseObject          : System.__ComObject
TypeNames           : {System.__ComObject, System.MarshalByRefObject, System.Object}

PS P:\>

Now this rings a bell with some problems I had a few years back doing some VB.NET programming and ended up solving using the reflection classes in .NET to get at the 'buried' methods and properties rather than creating an interop library chose to make the calls directly as I wanted to keep the distribution simple and single file. The method to call is InvokeMember from the .NET Type routines.

The call is .InvokeMember(<name>,<invokeAttr>,<binder>,<target>,<args>) where

We can access .InvokeMember from [System.__ComObject] and as long as we know the name and any arguments required for methods we can now make the call directly to the COM object bypassing the layer default layer PowerShell and .NET provide.

<binder> is normally either [System.Reflection.BindingFlags]::GetProperty or [System.Reflection.BindingFlags]::InvokeMethod.

So we can do the following to return the required username information once $oADSystemInfo has been created.

PS P:\> [System.__ComObject].InvokeMember("UserName",[System.Reflection.BindingFlags]::GetProperty,$null,$oADSystemInfo,$null)
CN=Scotty McLeod,OU=Users,OU=Home,DC=foxnet,DC=local
PS P:\>

Why do we need to jump through these hoops? Because this COM object like quite a few others has no type library defined and no interop class that wrappers the calls and makes accessing from .NET systems such as PowerShell more straightforward. Other solutions to using the above would be to use tlbimp.exe create a runtime callable wrapper and then do a [Reflection.Assembly]::LoadWithPartialName to get access to the methods and properties with tab completion etc.