2

I am adding this type and calling it. It works fine in script/function. As soon as I am trying to call it from powershell class - it gives error "type is not defined".

I have managed to call it using some ugly hacks. Add-Type is called with -PassThru and result is saved in $global:myType. Then I am doing indirect call using $global:myType.GetMethod('GetSymbolicLinkTarget').invoke($null,$dir).

Is there any better solution around?

PS: runspace is reset before each run (Ctrl-T in ISE, automatic in PowerGUI and Powershell Studio).

PS2: Working sample (simplified) below.

#works - returns 2
$Source = ' public class BasicTest { public static int Test(int a) { return (a + 1); } } '
Add-Type -TypeDefinition $Source
[BasicTest]::Test(1)


#gives error: Unable to find type [BasicTest]
$Source = ' public class BasicTest { public static int Test(int a) { return (a + 1); } } '
Add-Type -TypeDefinition $Source
class foo { Static [int]bar() { return [BasicTest]::Test(1) } }
[foo]::bar()


#workaround 1 - indirect call with GetMethod(...).Invoke(...)
# returns 4
$Source = ' public class BasicTest1 { public static int Test(int a) { return (a + 1); } } '
$global:BasicTestType1 = (Add-Type -TypeDefinition $Source -PassThru)
class foo {
    static $BasicTestType2 = (Add-Type -TypeDefinition ' public class BasicTest2 { public static int Test(int a) { return (a + 1); } } ' -PassThru)
    Static [int]bar() {
        $ret = $global:BasicTestType1.GetMethod('Test').Invoke($null, [int]1)
        $ret += [foo]::BasicTestType2.GetMethod('Test').Invoke($null, [int]1)
        return $ret
    }
}
[foo]::bar()


#workaround 2 - invoke-expression; has problems passing parameters
# returns 2
$Source = ' public class BasicTest { public static int Test(int a) { return (a + 1); } } '
Add-Type -TypeDefinition $Source
class foo { Static [int]bar() { return invoke-expression '[BasicTest]::Test(1)' } }
[foo]::bar()

PS3: Two other workarounds are provided here by PetSerAl.

Community
  • 1
  • 1
Anton Krouglov
  • 3,077
  • 2
  • 29
  • 50
  • Did you already try the way they do at the end of the linked code snippet? `[System.Win32]::GetSymbolicLinkTarget($dir)` It's the accepted answer and the OP said it worked... – Matthew Wetmore Feb 09 '17 at 23:17
  • `Add-Type <...>; class foo { Static [void]bar() { [System.Win32]::GetSymbolicLinkTarget('c:\') } }` - it does not even compile. PS: runspace is reset before each run. – Anton Krouglov Feb 09 '17 at 23:30
  • Moving `Add-Type` inside static method does not help either. – Anton Krouglov Feb 09 '17 at 23:37
  • Invoke-Expression 'here goes code using the added type' maybe? – wOxxOm Feb 10 '17 at 01:44
  • 1
    Can you include a sample type and how you are resetting the runspace and when? – TravisEz13 Feb 10 '17 at 02:08
  • And you also looked at the other answers on the thread for alternative approaches? – Matthew Wetmore Feb 10 '17 at 04:21
  • @wOxxOm: `Invoke-Expression` does work but it has problems with arguments and return values. `Invoke-Command` solves arguments problem but has problems with class context and it is much more cumbersome than indirect call using `GetMethod(...).Invoke(...)` – Anton Krouglov Feb 10 '17 at 17:26
  • @TravisEz13: code sample added, runspace reset expained – Anton Krouglov Feb 10 '17 at 19:49
  • Possible duplicate of [Using .Net Objects within a Powershell (V5) Class](http://stackoverflow.com/questions/34625440/using-net-objects-within-a-powershell-v5-class) – user4003407 Feb 10 '17 at 19:53
  • @PetSerAl: Indeed [your answer](http://stackoverflow.com/a/34637458/2746150) provides two other workarounds to the problem. No perfect solution so far. Having more than 2 files in ps project still does no good for me - unstable, unrepeatable, volatile, error-prone, hard-to-debug. – Anton Krouglov Feb 10 '17 at 20:22

2 Answers2

1

The issue is that the class is parsed at compile time and the reference to the type you added doesn't get added until runtime. So, the reference cannot be resolved.

The best solution I see is to force the order that they are compiled in.

# Add the type
$Source = ' public class BasicTest { public static int Test(int a) { return (a + 1); } } '
Add-Type -TypeDefinition $Source

$scriptBlockText = @"
  class foo {
    Static foo() {
    }
   Static [int]bar() { return [BasicTest]::Test(1) } 
  }
"@

# Create a script block from the text
# the class will be compiled at this point
# but is not in scope yet
$scriptBlock = [scriptblock]::Create($scriptBlockText)

# dot source the class
# this brings the class into scope
.([scriptblock]::Create($scriptBlock))

# it would be better if the class was in a separate file
# but I'm using a string to simplify the sample.
# for a file the syntax would be `. .\myClass.ps1`
# this would replace both creating the script block and 
# the dot sourcing statement

# Now you can use the class and the type you addded earlier
[foo]::bar()
TravisEz13
  • 2,263
  • 1
  • 20
  • 28
  • In my case real class is ~300 lines so it is painful to put into a text. At this point I am fed enough with ps multi-file projects. It seems that my C# mindset just not adapted yet with Powershell. So my multi-file ps projects are very volatile - one change and all of sudden they stop working properly. – Anton Krouglov Feb 11 '17 at 00:34
  • As the answer says, you should put the PowerShell class in a separate `ps1` file. The C# class can also be read from a `.CS` file. – TravisEz13 Feb 11 '17 at 00:35
1

Two other workarounds:

#workaround 3 - type variable
# returns 4
$Source = ' public class BasicTest1 { public static int Test(int a) { return (a + 1); } } '
$global:BasicTestType1 = (Add-Type -TypeDefinition $Source -PassThru)
class foo {
    static $BasicTestType2 = (Add-Type -TypeDefinition ' public class BasicTest2 { public static int Test(int a) { return (a + 1); } } ' -PassThru)
    Static [int]bar() {
        $ret = ($global:BasicTestType1)::Test(1)
        $ret += ([foo]::BasicTestType2)::Test(1)
        return $ret
    }
}
[foo]::bar()


#workaround 4 - most elegant so far
# returns 2
class foo {
    static foo() {
        $Source = ' namespace foo { public class BasicTest1 { public static int Test(int a) { return (a + 1); } } } '
        Add-Type -TypeDefinition $Source 
    }
    Static [int]bar() {
        $ret = ([type]'foo.BasicTest1')::Test(1)
        return $ret
    }
}
[foo]::bar()

Workaround 4 is the shortest and cleanest.

It is also possible to play with type accelerators.

Anton Krouglov
  • 3,077
  • 2
  • 29
  • 50