none
Parent class scope within a ScriptBlock via $Using RRS feed

  • Question

  • Hello there,

    I have a nice little OOP framework for doing remote deployments with WinRM, and everything works great until I try to access properties/methods in the parent class of my current instance.

    A tidy example is:

    class TestingSB
    {
        [String] $myval
        TestingSB()
        {
            $this.myval = "my value"
        }
        [ScriptBlock] GetScriptBlock()
        {
            [ScriptBlock] $Script =
            {
                Write-Host $Using:this.myval
            }
            return $Script
        }
    }
    

    This does not work, and I wind up needing to re-define myval within each class method, instead of being able to simply use the class property.

    The same applies to class methods. If my parent class contains a [String] GetMyVal() { return $this.myVal }, and I try to call the method with something like $Using:this.GetMyVal() it complains about not being able to find GetMyVal in the current scope.

    What is the correct way of exposing the scope of the current instance of an object to the ScriptBlock I am passing to a remote server?

    Thanks in advance,

    Adam

    Monday, June 5, 2017 8:19 PM

Answers

  • This is the closest you will get:

    class TestingSB{
    	[String]$myval = "my value"
    	[ScriptBlock]GetScriptBlock() {
    		return [ScriptBlock]::Create("Write-Host $($this.myval)")
    	}
    }
    
    $c = [TestingSB]::new()
    $c.GetScriptBlock().Invoke()
    


    \_(ツ)_/

    Monday, June 5, 2017 9:03 PM
  • But it is only valid within the scope that the script block is executed in.  Outside of the class it can only reference variables in the current scope.

    Also "Using" is only valid in jobs, inlinescript and Invoke-Command 


    \_(ツ)_/


    • Marked as answer by Adamn Daughterson Monday, June 5, 2017 9:45 PM
    • Edited by jrv Monday, June 5, 2017 9:46 PM
    Monday, June 5, 2017 9:43 PM

All replies

  • What makes you thin it will work?  Dynamic variables must be assigned.  You can use the class constructor to do this.

    The code doesn't make much sense.  What is GetScriptBlock supposed to do?  A scriptblock returned knows nothing about the class or its fields and methods.


    \_(ツ)_/

    Monday, June 5, 2017 9:00 PM
  • This is the closest you will get:

    class TestingSB{
    	[String]$myval = "my value"
    	[ScriptBlock]GetScriptBlock() {
    		return [ScriptBlock]::Create("Write-Host $($this.myval)")
    	}
    }
    
    $c = [TestingSB]::new()
    $c.GetScriptBlock().Invoke()
    


    \_(ツ)_/

    Monday, June 5, 2017 9:03 PM
  • Thanks for the suggestion. This is more like the real code I'm working with:

    [String] $myVal class ParentClass { ParentClass([String]$myVal) { $this.myVal = $myVal } } class ChildClass : ParentClass {

    ChildClass([String]$myVal : base($myVal)) { $this.myVal = $myVal }

    [String] doStuff()
    {

    Write-Host "Going to do something remotely with $this.myVal" [ScriptBlock] $Script = { Write-Host "Doing something with $this.myVal" } $this.executeRemotePS ( -Computer "some ip", -Credential "Some credential", -Script $Script)

    }

    }

    From the looks of your snippet, I should be able to access class props in $($this.myVal). Am I seeing that correctly? Also, what about class methods from within the ScriptBlock? Would that be something like $foo = $($this.MethodName()) ?

    Thanks for the feedback.


    Adam




    • Edited by Adamn Daughterson Monday, June 5, 2017 9:30 PM made a pseudomethod to do stuff
    Monday, June 5, 2017 9:23 PM
  • A scriptblock is not a class and  it has no methods.  All functions in a scriptblock are private.

    \_(ツ)_/

    Monday, June 5, 2017 9:27 PM
  • Ugh, so the ScriptBlock I'm generating has no way to retrieve class properties in its surrounding scope?
    Monday, June 5, 2017 9:32 PM
  • The returned script block is not part of the class. It is just a script block.


    \_(ツ)_/

    Monday, June 5, 2017 9:35 PM
  • Right, I get that part. What I'm asking is, much the same one can use $Using:someVariableOutsideScriptBlockScope, I would like to use a class property which exists outside the ScriptBlock scope.

    Ex 

    $myVar = "Hello"
    [ScriptBlock] $Script = {
        Write-Host "Accessing outer scope with ${Using:myVar}"
    }
    Invoke-Command -ComputerName "some ip" -ScriptBlock $Script -etc -etc

    This works, but trying to get to the $this of the outer scope hasn't worked yet.

    I'll give your snippet suggestion a try. Thanks again (again) for your help! :)

    Adam

    Monday, June 5, 2017 9:40 PM
  • But it is only valid within the scope that the script block is executed in.  Outside of the class it can only reference variables in the current scope.

    Also "Using" is only valid in jobs, inlinescript and Invoke-Command 


    \_(ツ)_/


    • Marked as answer by Adamn Daughterson Monday, June 5, 2017 9:45 PM
    • Edited by jrv Monday, June 5, 2017 9:46 PM
    Monday, June 5, 2017 9:43 PM
  • Okay, I got it working by doing this:

    class SomeSubclass : ParentClass
    {
        SomeSubclass([String]$myVal : base($myVal)){}
        [ScriptBlock] doStuff()
        {
            [String] $myVal = $this.myVal
            [ScriptBlock] $Script = {
                Write-Host "Using ${Using:myVal}"
            }
            return $Script
        }
    }

    I'll stop bugging you now.  Thanks!

    Adam

    Monday, June 5, 2017 9:53 PM
  • That won't even compile.

    PS D:\scripts> class SomeSubclass: ParentClass {
    >> SomeSubclass([String]$myVal: base ($myVal)) { }
    At line:2 char:22
    + SomeSubclass([String]$myVal: base ($myVal)) { }
    +                      ~~~~~~~
    Variable reference is not valid. ':' was not followed by a valid variable name character. Consider using ${} to
    delimit the name.


    \_(ツ)_/

    Monday, June 5, 2017 9:56 PM
  • This is the actual code which is working 100% for me now 

    [CmdletBinding()]
    class DeployOrangeAppNode : App_Node_Deployment
    {
    		[String] $Name
    		[String] $Environment
    		[String] $Team
    		[PSCredential] $Credential
        DeployOrangeAppNode([String] $Name,
    					[String] $Environment,
    					[String] $Team,
    					[PSCredential] $Credential)
    					: base($Name,$Environment,$Team,$Credential)
    		{
    				$this.Environment = $Environment
    				$this.Name = $Name
    				$this.Team = $Team
    		}
    		[String] Run()
    		{
    				$ErrorActionPreference = "Stop"
    				[String] $env = $this.Environment
    				Write-Host "Running AppNode Deployment"
    				Write-Host "Launching new App Instance"
    				[ScriptBlock] $Script = {
    							$ErrorActionPreference = "Stop"
    							Write-Host "DEBUG"
    							Write-Host "Reaching into outer scope for Environment : ${Using:env}"
    							Write-Host "DEBUG"
    							...snip
    				}
    				Write-Host "Attempting to run remote script "
    				& $PSScriptRoot\..\RunPowerShellBatch.ps1 `
    					-Script $Script `
              ...snip
    					-Verbose
    				return "Done"
    		}
    }

    Compiles and runs great for me now. I see the DEBUG statements in the remote PS session.

    "Actual" code, scrubbed.


    Monday, June 5, 2017 10:06 PM