How to call a GetInstance from Powershell

Viewed 198

I'm reverse engineering a control plugin for a laptop and trying to figure out how to call the methods that emulate what the "app" is doing. It's written in C#, so I thought I could just use PowerShell to call methods on it. I've used DotPeek to figure out that everything is handled into a single DLL which implements the methods that I want to access.

The class looks like:

namespace Vendor.package1.package2.SomePlugin
{
  internal class SomeClass : IInterfaceThatIWant, ...
  {

    ~SomeClass()
    {
    }
  

    public RequestObject SetSomething()
    {
      ... method that I want to use
    }

    public ResponseObject GetSomething()
    {
      ...
    }
  
    private SomeClass()
    {
      ...
    }

    public static SomeClass GetInstance()
    {
      ...
    }
  }

Inside PowerShell, I've tried:

Add-Type -Path "c:\pathtodll\x64\vendor.dll"

and

[System.Reflection.Assembly]::LoadFrom("c:\pathtodll\x64\vendor.dll")

...which did not show any error, so I assume both worked.

Next I'm trying:

[Vendor.package1.package2.SomePlugin.SomeClass]::GetInstance()
   Unable to find type [Vendor.package1.package2.SomePlugin.SomeClass].  
New-Object Vendor.package1.package2.SomePlugin.SomeClass
   New-Object : Cannot find type [Vendor.package1.package2.SomePlugin.SomeClass]: verify that the assembly containing this type is loaded.
[Vendor.package1.package2.SomePlugin.SomeClass]::GetInstance
   Unable to find type [Vendor.package1.package2.SomePlugin.SomeClass].  

and various other permutations but I cannot get a class handle.

1 Answers

To obtain a reference to the non-public SomeClass type, there are a couple of options:

1. Have Add-Type return all types from the assembly

Add-Type has a -PassThru switch that'll return all types found in the loaded assembly, regardless of access:

# Import assembly out emit types
$VendorTypes = Add-Type -Path "c:\pathtodll\x64\vendor.dll" -PassThru

# Filter the types for the one we need
$SomeClassType = $VendorTypes.Where({$_.FullName -eq 'Vendor.package1.package2.SomePlugin.SomeClass'})

# Invoke public static `GetInstance()` method
$SomeInstance = $SomeClassType::GetInstance()

$SomeInstance.SetSomething()

2. Discover type metadata via Assembly.GetTypes()

As you've already found out, [Assembly]::LoadFrom(...) also returns an Assembly object through which we can discovery the types it contains:

# Load assembly, hold on to Assembly reference
$VendorAssembly = [System.Reflection.Assembly]::LoadFrom("c:\pathtodll\x64\vendor.dll")

# Get all contained types
$VendorTypes = $VendorAssembly.GetTypes()

# Same steps as above ...

[...] the dll is successfully loaded, I can access the public classes

In this case you can also get to the Assembly via one of the public types:

$VendorAssembly = [Vendor.package1.package2.SomePublicClass].Assembly

3. Discover assembly metadata from AppDomain

What if you didn't call Add-Type or [Assembly]::LoadFrom() directly, but are trying to discover non-public types exposed by an assembly in the GAC or loaded indirectly by a module?

Thankfully, we can make the runtime tell us all the assemblies that have already been loaded via the [AppDomain] class:

# Enumerate all loaded assemblies
$AllAssemblies = [System.AppDomain]::CurrentDomain.GetAssemblies()

# Filter for the vendor assembly in question
$VendorAssembly = $AllAssemblies.Where({ $_.Location -like "C:\pathtodll\x64\vendor.dll" })

# Same steps as above

Invoking non-public members

If GetInstance is also non-public you might have to use reflection to access it:

# Use reflection to obtain a reference to the GetInstance method
$GetInstanceMethod = $SomeClassType.GetMethod("GetInstance", [System.Reflection.BindingFlags]'Static,NonPublic')

# Static methods need no invocation target, second argument is parameter args for the target method
$SomeInstance = $GetInstanceMethod.Invoke($null, @())

For invoking instance methods you need to pass a reference to the target instance:

# Use reflection to obtain a reference to the SetSomething method, 
# Notice `Instance` instead of `Static` in the list of binding flags
$SetSomethingMethod = $SomeClassType.GetMethod("SetSomething", [System.Reflection.BindingFlags]'Instance,NonPublic')

# Pass the SomeClass instance we created in the previous example as the invocation target:
$RequestObject = $SetSomethingMethod.Invoke($SomeInstance, @())
Related