Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
eosfor opened this issue Apr 25, 2017 · 13 comments
Labels
Issue-Enhancement the issue is more of a feature request than a bug Up-for-Grabs Up-for-grabs issues are not high priorities, and may be opportunities for external contributors WG-Language parser, language semantics

Comments

@eosfor
Copy link

eosfor commented Apr 25, 2017

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

@mklement0
Copy link
Contributor

mklement0 commented Apr 25, 2017

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?

@lzybkr
Copy link
Member

lzybkr commented Apr 25, 2017

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.

@lzybkr lzybkr added WG-Language parser, language semantics Issue-Enhancement the issue is more of a feature request than a bug Up-for-Grabs Up-for-grabs issues are not high priorities, and may be opportunities for external contributors labels Apr 25, 2017
@eosfor eosfor 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 Apr 25, 2017
@eosfor
Copy link
Author

eosfor commented Apr 25, 2017

Is this title reflects the issue more precisely?

@mklement0

This comment has been minimized.

@TheMusicMeister
Copy link

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.

@rjmholt
Copy link
Collaborator

rjmholt commented Jul 28, 2020

This is made possible by dotnet/corefx#33201

@rgbav
Copy link

rgbav commented Jan 24, 2023

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...

@aetonsi
Copy link

aetonsi commented Mar 21, 2023

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...

Copy link
Contributor

This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you.

@microsoft-github-policy-service microsoft-github-policy-service bot added Resolution-No Activity Issue has had no activity for 6 months or more labels Nov 16, 2023
Copy link
Contributor

This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you.

Copy link
Contributor

This issue has been marked as "No Activity" as there has been no activity for 6 months. It has been closed for housekeeping purposes.

@aetonsi
Copy link

aetonsi commented Nov 28, 2023

@PowerShellTeam @SteveL-MSFT Please re-open issue as it was not solved

@scarson
Copy link

scarson commented Jan 9, 2024

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.

@kilasuit kilasuit reopened this Jan 9, 2024
@kilasuit kilasuit removed the Resolution-No Activity Issue has had no activity for 6 months or more label Jan 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Issue-Enhancement the issue is more of a feature request than a bug Up-for-Grabs Up-for-grabs issues are not high priorities, and may be opportunities for external contributors WG-Language parser, language semantics
Projects
Developer Experience/SDK
  
Awaiting triage
Development

No branches or pull requests

10 participants