Skip to content

Defining a PowerShell class in a script causes a parser error if the class references external types that aren't currently loaded #3641

Open

Description

@eosfor

Strange behavior with "using module"

Steps to reproduce

  1. Install-Module psquickgraph -Scope CurrentUser

  2. save the following script to c:\temp\processgraph.ps1

using module PSQuickGraph

class MyProcess : PSGraph.PSGraphVertex {
    [string]$ProcessName
    [int]$ProcessID
    [int]$ParentProcessId
    [string]get_UniqueKey() { return  $this.ProcessID }
}


$ps = gwmi win32_process

$g = New-Graph -Type AdjacencyGraph

$ps | % {
    $p = [MyProcess]@{
        ProcessName = $_.ProcessName
        ProcessID = $_.ProcessID
        ParentProcessId = $_.ParentProcessId
        Label = $_.ProcessName
    }
    Add-Vertex -Vertex $p -Graph $g
}

$g.Vertices | % {
    $v1 = $_
    $v1.processname
    $v1.ProcessId
    $g.Vertices | % {
        $v2 = $_
        if ($v1.ProcessId -eq $v2.ParentProcessId){
            Add-Edge -From $v2 -To $v1 -Graph $g
        }
    }
}

$graphFile = "c:\temp\testGraph.gv"
$svgOutFile = "c:\temp\testGraph.svg"
$pngOutFile = "c:\temp\testGraph.png"

Export-Graph -Graph $g -Format Graphviz -Path $graphFile -Verbose
$tempFile = Get-Content $graphFile
$tempFile[0] += "`r`n" + "rankdir = LR"
$tempFile | Out-File $graphFile -Encoding ascii
  1. invoke the script by using c:\temp\processgraph.ps1

Expected behavior

a file c:\temp\testGraph.gv should be generated.

Actual behavior

script fails with the following message

At C:\temp\processGraph.ps1:3 char:19
+ class MyProcess : PSGraph.PSGraphVertex {
+                   ~~~~~~~~~~~~~~~~~~~~~
Unable to find type [PSGraph.PSGraphVertex].
    + CategoryInfo          : ParserError: (:) [], ParseException
    + FullyQualifiedErrorId : TypeNotFound

Environment data

Name Value


PSVersion 5.1.14393.953
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.14393.953
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1

> $PSVersionTable

Source code for PSQuickGraph is here

Activity

  1. mklement0 commented on Apr 25, 2017

    @mklement0
    Contributor

    The issue is that PS class definitions are parsed before script execution begins, which means that any types referenced in class definitions must currently already be loaded in the context of the caller.

    In other words: your script breaks in the parsing stage, and no statements are ever executed.

    I presume this is a bug; if it isn't, it's certainly unexpected and an inconvenience.

    The following script demonstrates the problem more succinctly:

    # Module PSQuickGraph can be installed with
    #    Install-Module -Scope CurrentUser PSQuickGraph
    # Importing / `using` it loads type [PSGraph.PSGraphVertex], among others,
    # but that is currently NOT (early) enough for such types to be recognized
    # in the class definition below.
    using module PSQuickGraph
    
    # !! Because type [PSGraph.PSGraphVertex] is referenced in the context of a
    # !! *PS class definition*, this only works if the module that loads that type 
    # !! was imported *before* the script is invoked, because PS classes are
    # !! parsed *before* script execution begins.
    # !! If you execute `Import-Module PSQuickGraph` (or `using module PSQuickGraph`)
    # !! *before* invoking this script, it will work.
    class MyType {
        [string] Foo() { return [PSGraph.PSGraphVertex].FullName }
    }
    
    # Assuming that the script doesn't break in the parsing stage, 
    # this always works, because by the time this statement is reached,
    # the `using module` statement has already taken effect.
    [PSGraph.PSGraphVertex].FullName

    Can I suggest you update the title and body of your original post to focus on the crux of the issue?

  2. lzybkr commented on Apr 25, 2017

    @lzybkr
    Contributor

    It is by design that we detect errors at parse time with classes. Discovering types in binary modules or assemblies is not yet implemented.

    Whomever picks this up - we do not want to load assemblies at parse time - parsing is considered safe - it doesn't execute any code - and loading an assembly can execute code.
    Instead, we will use the metadata reader. PR #3169 actually has most of the code needed.

    Also related issue #2074 - but in that case, it's not using module that is needed, but using assembly.

  3. added
    WG-Languageparser, language semantics
    Issue-Enhancementthe issue is more of a feature request than a bug
    Up-for-GrabsUp-for-grabs issues are not high priorities, and may be opportunities for external contributors
    on Apr 25, 2017
  4. changed the title [-]Strange behavior with "using module"[/-] [+]Script fails on parse when it references external classes from a module and tries to inherit from those classes[/+] on Apr 25, 2017
  5. eosfor commented on Apr 25, 2017

    @eosfor
    Author

    Is this title reflects the issue more precisely?

  6. mklement0 commented on Apr 25, 2017

    @mklement0
  7. changed the title [-]Script fails on parse when it references external classes from a module and tries to inherit from those classes[/-] [+]Defining a PowerShell class in a script causes a parser error if the class references external types that aren't currently loaded[/+] on Apr 26, 2017
  8. TheMusicMeister commented on Nov 8, 2019

    @TheMusicMeister

    Ran into this issue with a GUI project I've been working on. Loaded using namespace System.Windows.Forms so that I could reference the short names of Controls like [TextBox], [Panel], etc, and received a slew of "Unable to find type [Type]" errors.

  9. added this to the Future milestone on Nov 8, 2019
  10. rjmholt commented on Jul 28, 2020

    @rjmholt
    Collaborator

    This is made possible by dotnet/corefx#33201

  11. rgbav commented on Jan 24, 2023

    @rgbav

    Instead, we will use the metadata reader. PR #3169 actually has most of the code needed.

    When will this happen? It is 6 years now...

  12. aetonsi commented on Mar 21, 2023

    @aetonsi

    As mr Klement already knows, i stumbled across this problem while was having troubles with a script because i couldn't load an external (system) before referencing them, which leads to a parsing error ( https://stackoverflow.com/questions/75770407/ ).

    I came here with the brilliant idea of suggesting something like the #Requires -RunAsAdministrator statement that would stop the script before execution. To my surprise, i discovered that #Requires -Assembly XXX was already a thing, and then was deprecated (docs):

    The -Assembly syntax is deprecated. It serves no function. The syntax was added in PowerShell 5.1 but the supporting code was never implemented. The syntax is still accepted for backward compatibility.

    could this be relevant?


    And aside from this, is there any update at all on the matter?

    Stack Overflow
    i'm trying to write a powershell script file that has Parameters whose types require loading. A simple code example is below: [System.Windows.Forms.MessageBoxButtons] requires loading of system.win...
  13. 4 remaining items

  14. scarson commented on Jan 9, 2024

    @scarson

    I found this issue linked from the excellent StackOverflow post PowerShell: Unable to find type when using PS 5 classes and wanted to share how it's complicating something I'm trying to do that I hoped would be quite simple.

    I am trying to write a script that runs different code based on $PSEdition with a simple "if" statement. The "if ($PSEdition -eq 'Desktop')" statement attempts to load a class definition, ICertificatePolicy, that is available in PS Desktop (5) but not PS Core (6+). The code with this class definition is not executed in the "elseif ($PSEdition -eq 'Core')" branch, yet the code won't run at all as PS Core throws:

    ParserError:
    Line |
       3 |  class TrustAllCertsPolicy : System.Net.ICertificatePolicy {
         |                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         | Unable to find type [System.Net.ICertificatePolicy].
    

    Below is a code sample to reproduce. It runs in PS 5 without error but fails to execute with a ParserError in PS Core.
    It implements a basic HTTP health check with Invoke-WebRequest with logic to ignore TLS validation errors based on $PSEdition, as PS 6+ has a native -SkipCertificateCheck option while PS 5 does not and must use a more complicated solution as detailed in Invoke-WebRequest_Ignore_SSL.ps1.

    No normal PowerShell error handling methods I've tried like Try/catch, -ErrorAction Ignore, etc. seem to affect this blocking ParserError.

    $hostname = [System.Net.Dns]::GetHostByName($env:computerName).HostName 
    $uri = 'https://' + $hostname + '/'
    $userAgent = 'HeartbeatMonitor'
    
    if ($PSEdition -eq 'Desktop') {
    	class TrustAllCertsPolicy : System.Net.ICertificatePolicy {
    		[bool] CheckValidationResult (
    			[System.Net.ServicePoint]$srvPoint,
    			[System.Security.Cryptography.X509Certificates.X509Certificate]$certificate,
    			[System.Net.WebRequest]$request,
    			[int]$certificateProblem
    		)  {
    			return $true
    		}
    	}
    }
        [System.Net.ServicePointManager]::CertificatePolicy = New-Object -TypeName TrustAllCertsPolicy
    	
    	[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls13, [System.Net.SecurityProtocolType]::Tls12, [System.Net.SecurityProtocolType]::Tls11, [Net.SecurityProtocolType]::Tls
    	
    	$HeartbeatCheck = Invoke-WebRequest -Uri $uri -UserAgent $userAgent
    } elseif ($PSEdition -eq 'Core') {
    	$HeartbeatCheck = Invoke-WebRequest -Uri $uri -UserAgent $userAgent -SkipCertificateCheck
    	}
    
    

    As a workaround I might end up writing three scripts, a starting one to check $PSEdition, then conditionally execute either .\PSDesktop_Version.ps1 or .\PSCore_Version.ps1. That feels like an unnecessarily complicated way to have to handle an error for code that never runs.

  15. aetonsi commented on Jul 12, 2024

    @aetonsi

    not stale

  16. mrsquish commented on Oct 24, 2024

    @mrsquish

    Yep, encountered this yesterday, pulled my hair out and now I have none left. Still an issue, still annoying.

  17. User1785604260 commented on Apr 23, 2025

    @User1785604260

    Bump. (This is in response to a Microsoft bot comment that will promptly be deleted as soon as I post this)

  18. MiroslavJurkas-CTG commented on Dec 5, 2025

    @MiroslavJurkas-CTG

    The same issue present in PowerShell 7.5

  19. gabriel-vanca commented on Jul 18, 2026

    @gabriel-vanca

    Any progress on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue-Enhancementthe issue is more of a feature request than a bugKeepOpenThe bot will ignore these and not auto-closeUp-for-GrabsUp-for-grabs issues are not high priorities, and may be opportunities for external contributorsWG-Languageparser, language semantics

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions