PowerShell classes leak to other runspaces on macOS

Created on 7 Apr 2019  路  6Comments  路  Source: PowerShell/PowerShell

Steps to reproduce

class聽MyClass聽{
聽聽聽聽#聽Property:聽Holds聽name
聽聽聽聽[String]聽$Name

聽聽聽聽#聽Constructor:聽Creates聽a聽new聽MyClass聽object,聽with聽the聽specified聽name
聽聽聽聽MyClass([String]聽$NewName)聽{
聽聽聽聽#聽Set聽name聽for聽MyClass
聽聽聽聽聽聽$this.Name聽=聽$NewName
聽聽聽聽}

聽聽聽聽#聽Method:聽Method聽that聽changes聽$Name聽to聽the聽default聽name
聽聽聽聽[void]聽ChangeNameToDefault()聽{
聽聽聽聽聽聽$this.Name聽=聽"DefaultName"
聽聽聽聽}
聽聽}
[MyClass].GetType()
[PowerShell]::Create().AddScript('[MyClass]::new("ffff")').Invoke()

Expected behavior

Class types should not leak to other runspaces.

Actual behavior

Type was able to be created in other runspace.
image

Environment data

Name                           Value
----                           -----
PSVersion                      6.2.0
PSEdition                      Core
GitCommitId                    6.2.0
OS                             Darwin 18.2.0 Darwin Kernel Version 18.2.0: Thu Dec 20 20:46:53 PST 2018; root:xnu-4903.241.1~1/RELEASE_X86_64
Platform                       Unix
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0鈥
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Notes

This doesn't repro without _using_ the class in some way (i.e. calling [MyClass].GetType()). That part is important.

cc @daxian-dbw who wasn't able to repro this (I assume on Windows? Using PowerShell Core 6.2?)

Issue-Bug Resolution-Fixed

Most helpful comment

I'm pretty sure they're in a new runspace because Get-Runspace returns another runspace. Just to be sure, I tried manually creating a new runspace and the issue still repros:

class聽MyClass聽{
    #聽Property:聽Holds聽name
  聽聽[String]聽$Name

  聽聽#聽Constructor:聽Creates聽a聽new聽MyClass聽object,聽with聽the聽specified聽name
 聽聽聽MyClass([String]聽$NewName)聽{
     聽聽聽#聽Set聽name聽for聽MyClass
  聽聽聽聽聽聽$this.Name聽=聽$NewName
  聽聽}

  聽聽#聽Method:聽Method聽that聽changes聽$Name聽to聽the聽default聽name
  聽聽[void]聽ChangeNameToDefault()聽{
  聽聽聽聽聽聽$this.Name聽=聽"DefaultName"
  聽聽}
}
$rs = [runspacefactory]::CreateRunspace()
$rs.Open()
[MyClass].GetType()
[PowerShell]::Create($rs).AddScript('[MyClass]::new("ffff")').Invoke()

All 6 comments

@TylerLeonhardt been a while since I played with it, but don't new [powershell] instances execute in the current runspace unless you manually assign them a new one?

I'm pretty sure they're in a new runspace because Get-Runspace returns another runspace. Just to be sure, I tried manually creating a new runspace and the issue still repros:

class聽MyClass聽{
    #聽Property:聽Holds聽name
  聽聽[String]聽$Name

  聽聽#聽Constructor:聽Creates聽a聽new聽MyClass聽object,聽with聽the聽specified聽name
 聽聽聽MyClass([String]聽$NewName)聽{
     聽聽聽#聽Set聽name聽for聽MyClass
  聽聽聽聽聽聽$this.Name聽=聽$NewName
  聽聽}

  聽聽#聽Method:聽Method聽that聽changes聽$Name聽to聽the聽default聽name
  聽聽[void]聽ChangeNameToDefault()聽{
  聽聽聽聽聽聽$this.Name聽=聽"DefaultName"
  聽聽}
}
$rs = [runspacefactory]::CreateRunspace()
$rs.Open()
[MyClass].GetType()
[PowerShell]::Create($rs).AddScript('[MyClass]::new("ffff")').Invoke()

Windows PowerShell 5.1 behaves the same way. A loaded type definition is available process wide unless maybe the process is using AppDomains for isolation but 5.1 doesn't appear to use AppDomains. And there is no support for AppDomains in .NET Core anyway.

@rkeithhill I'd expect the type to still be loaded, but I wouldn't expect PowerShell to resolve it since resolution is scope based:

& {
    class MyClass {}
}

# Returns the above class
[AppDomain]::CurrentDomain.GetAssemblies().
    Where{
        [Reflection.CustomAttributeExtensions]::IsDefined(
            $_,
            [Management.Automation.DynamicClassImplementationAssemblyAttribute])
    }.ForEach{
        # The types sometimes have a distinguishing namespace
        $_.Modules[0].FindTypes([Reflection.Module]::FilterTypeName, 'MyClass')
    }

# Throws type not found
[MyClass]

Edit: Side note, I can't repro on 5.1 nor 6.2 on Windows. I get an Unable to find type [MyClass]. error in Streams.Errors

Also @vexx32

been a while since I played with it, but don't new [powershell] instances execute in the current runspace unless you manually assign them a new one?

Only when you pass [System.Management.Automation.RunspaceMode]::CurrentRunspace

PSReadLine plays a role in the repo. To get consistent repro on all platforms (including powershell.exe), do the following:

  1. start pwsh/powershell.exe with -noprofile
  2. copy the following to pwsh/powershell.exe:
class MyClass {
    # Property: Holds name
    [String] $Name

    # Constructor: Creates a new MyClass object, with the specified name
    MyClass([String] $NewName) {
    # Set name for MyClass
      $this.Name = $NewName
    }

    # Method: Method that changes $Name to the default name
    [void] ChangeNameToDefault() {
      $this.Name = "DefaultName"
    }
  }
  1. copy the following to pwsh/powershell:
[MyClass].GetType()
[PowerShell]::Create().AddScript('[MyClass]::new("ffff")').Invoke()

Then you will see the Name: ffff printed out.


The moment you remove PSReadLine, this won't repro. This is because PSReadLine always parse the input before it's sent to engine to execute. The parsing involves resolving [MyClass], and put it in the type cache with the wrong TypeResolutionState.

:tada:This issue was addressed in #11273, which has now been successfully released as v7.0.0-rc.1.:tada:

Handy links:

Was this page helpful?
0 / 5 - 0 ratings