1

Here is example code that is causing me lots of headaches at the moment.

if (("Win32.NativeMethods" -as [type]) -eq $null){
    Add-Type -MemberDefinition '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
        ' -name NativeMethods -namespace Win32
}

class AppInstance 
{    
    [string]$App = 'Notepad.exe'
    [IntPtr]$hWnd = 0
    [System.Object]$process

    AppInstance () {
        Start-Process $this.App
        $this.process = get-process ($this.App.split('.'))[0]
        start-sleep -Milliseconds 100
        $this.hWnd = $this.process.MainWindowHandle    
    }

    [void] Show () {
        [Win32.NativeMethods]::ShowWindowAsync($this.hWnd, 3)
    }

    [void] Hide () {
        [Win32.NativeMethods]::ShowWindowAsync($this.hWnd, 2)
    }
}

This class can be used like so

$notepad = [AppInstance]::new()
$notepad.Hide()
$notepad.Show()

Basically, what I'm trying to do is to import a function from user32.dll as type [Win32.NativeMethods] and then use this type in a class.

If I execute the Add-Type statement separately in Powershell_ISE the type gets created and subsequently the script works just fine.

However, when I try to execute the whole script before creating the type manually, I get the following Powershell parser error

At C:\class.ps1:26 char:10
+         [Win32.NativeMethods]::ShowWindowAsync($this.hWnd, 3)
+          ~~~~~~~~~~~~~~~~~~~
Unable to find type [Win32.NativeMethods].
At C:\Uclass.ps1:31 char:10
+         [Win32.NativeMethods]::ShowWindowAsync($this.hWnd, 2)
+          ~~~~~~~~~~~~~~~~~~~
Unable to find type [Win32.NativeMethods].
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : TypeNotFound

Looks like the parser is ignoring the Add-Type statement and exiting before execution.

Is there any way to overcome this issue? Maybe with a using statement? Or, is there any way to tell the parser that the type is dynamically created?

EDIT 1:

I have read the answer to Using .Net Objects within a Powershell (V5) Class and the accepted answer is not an answer to my question. Splitting a simple script into multiple files is not really an answer.

What I'm asking is weather there is a way to tell the parser that the type is dynamically created.

EDIT 2:

To clarify this a little further here is code equivalent to the one above, but implemented using functions instead of classes.

if (("Win32.NativeMethods" -as [type]) -eq $null){
    Add-Type -MemberDefinition '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
        ' -name NativeMethods -namespace Win32
}

[string]$script:App = 'Notepad.exe'

$process = Start-Process $App -PassThru


function Show () {
    [Win32.NativeMethods]::ShowWindowAsync($process.MainWindowHandle, 3)
}

function Hide () {
    [Win32.NativeMethods]::ShowWindowAsync($process.MainWindowHandle, 2)
}

This code will parse and execute perfectly fine. Are classes handled by the parser in a different way to rest of the script?

Community
  • 1
  • 1
Jan Chrbolka
  • 4,184
  • 2
  • 29
  • 38
  • Maybe a duplicate, but the accepted answer on the other question is not really an answer. – Jan Chrbolka Feb 05 '16 at 02:57
  • How is it not an answer? It explains the problem and gives a solution? – Matt Feb 05 '16 at 04:26
  • Mayby I was a bit rush in discarding it as not an answer. I was however hoping to get a bit more in-depth into why the parser will ignore 'Add-Type' with classes but not in a normal script. That's why I asked a specific question. Or maybe I need to reword it? – Jan Chrbolka Feb 05 '16 at 04:32
  • @JanChrbolka The suggested duplicate explains it: "Every PowerShell script is completely parsed before the first statement in the script is executed. An unresolvable type name token *inside a class definition* is considered a parse error." – Mathias R. Jessen Feb 05 '16 at 09:09
  • That was actually my point. I understand that every script is first parsed by the parser. What I was trying to find out is why the parser was not picking up the type deffinition. Your answer bellow cleared that up. Thank you. – Jan Chrbolka Feb 05 '16 at 09:44
  • @JanChrbolka I update my answer with a way, which does not require splitting script into multiple files. Parser does not know what `Add-Type` is. It is just a command name. `function Add-Type {<#I will do nothing#>}`. For type tokens inside class definition parser have to provide `System.Type` instance describing the type, which is not available until `Add-Type` actually invoked. In your particular case you can work around it by using string instead of type token: `([Type]'Win32.NativeMethods')::ShowWindowAsync`. – user4003407 Feb 05 '16 at 10:53
  • Thanks, another perfect solution! Unfortunately there is not much information out there about Powershell `classes`. That's why it's a pity that this question had to be closed as duplicate... I have learned a lot from asking it anyway. – Jan Chrbolka Feb 08 '16 at 00:49

1 Answers1

8

As you've found out yourself, when defining functions the parser is not nearly as strict as when it comes to classes - the simple reason is that function definitions need no compilation, so the parser only checks for syntax not type resolution.

You can use this observation to work around your problem - simply define a function outside the Class definition that wraps the call to [Win32.NativeMethods]::ShowWindowAsync() and then call that function from inside your class method:

function __ShowWindowAsync
{
    param([IntPtr]$WindowHandle,[int]$ShowState)

    if (("Win32.NativeMethods" -as [type]) -eq $null){
        Add-Type -MemberDefinition '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);' -Name NativeMethods -namespace Win32
    }

    [Win32.NativeMethods]::ShowWindowAsync($this.hWnd, $ShowState)
}

class AppInstance 
{    
    [string]$App = 'Notepad.exe'
    [IntPtr]$hWnd = 0
    [System.Object]$process

    AppInstance () {
        # this is way more reliable than running Get-Process subsequently
        $this.process = Start-Process $this.App -PassThru
        start-sleep -Milliseconds 100
        $this.hWnd = $this.process.MainWindowHandle
    }

    [void] Show () {
        $Maximized = 3
        __ShowWindowAsync -WindowHandle $this.hWnd -ShowState $Maximized
    }

    [void] Hide () {
        $Minimized = 2
        __ShowWindowAsync -WindowHandle $this.hWnd -ShowState $Minimized
    }
}
Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206
  • 2
    Thank you. This is exactly what I was looking for. Still don't understand why people here are so quick to call a question a duplicate. Your answer gives more information and a very good solution. – Jan Chrbolka Feb 05 '16 at 09:50