Home > Blockchain >  Can't call a class method inside a `Register-ObjectEvent` scriptblock
Can't call a class method inside a `Register-ObjectEvent` scriptblock

Time:09-30

My goal is to call a class method when the job received or simply to notify outside method that the job is complete.

Is there any way to do this? This is the sample script:

class Test {
  $_Trigger

  $_Event
  $_Job
  $_Result

  Start() {

    $this._Job = Start-Job -Name JobTest -ScriptBlock { Start-Sleep -Seconds 2; return 100; }

    $this._Event = Register-ObjectEvent $this._Job StateChanged -Action {
      $script:_Result = Receive-Job -Id $sender.Id -Keep
      $this.Trigger() # this line here
    }
  }

  Trigger() {
    <#
     CALL OTHER METHODS
    #>
    Write-Host "job complete"
  }
}

I also try passing the method when calling the job like this

Start-Job -Name JobTest -ScriptBlock { Start-Sleep -Seconds 2; return 100; } -ArgumentList $this._Trigger

and want to access it via $Sender since there is the $sender variable as stated by the docs, but my problem is how to call it or how can I "view" this automatic variables inside the script block?

The value of the Action parameter can include the $Event, $EventSubscriber, $Sender, $EventArgs, and $Args automatic variables. These variables provide information about the event to the Action script block. For more information, see about_Automatic_Variables.

CodePudding user response:

Script blocks passed to the -Action parameter of Register-ObjectEvent run in a dynamic module, which doesn't have access to the caller's scope.

However, you can pass arbitrary data to the script block via the -MessageData parameter, which you can access via $Event.MessageData there, so you can pass the custom-class instance at hand as -MessageData $this:

class Test {

  $_Event
  $_Job
  $_Result

  Start() {

    $this._Job = Start-Job -Name JobTest -ScriptBlock { Start-Sleep -Seconds 2; return 100; }

    # Note the use of -MessageData and $Event.MessageData
    $this._Event = 
      Register-ObjectEvent $this._Job StateChanged -MessageData $this -Action {
        $Event.MessageData._Result = Receive-Job -Id $sender.Id -Keep
        $Event.MessageData.Trigger() # this line here
      }
 
  }

  Trigger() {
    <#
     CALL OTHER METHODS
    #>
    Write-Host "job complete"
  }
}

CodePudding user response:

A couple of things.

If you want to debug your script or just your script to work when not ran from VsCode (eg: in a production context), you need to make sure that your script do not exit too soon. The simplest way to make sure this does not happen is to have some sort of loop at the end.

If you attempted to place a breakpoint within the Register-ObjectEvent -Action scriptblock and did have a message stating that the "breakpoint could not be hit", this is because your script already had exited and the debugger detached at the moment when you reached the actual event handler. Since VsCode maintain the session even when your script exited, the event handler will trigger, but that won't be a debuggable version.

$this will be $null from within the Register-ObjectEvent -Action scriptblock context. You need to pass the class as message data -MessageData @{This = $this } and access it from within the action block as $Event.MessageData.This

You don't have any Try/Catch in the Action scriptblock. Just be aware that any error will kill the event handler and it won't trigger afterward if any error is thrown.

Here is your example, adapted to fix these issues. (Note: I also added Cyan)

class Test {
    $_Trigger
    $_Event
    $_Job
    $_Result

    Start() {

        $this._Job = Start-Job -Name JobTest -ScriptBlock { Start-Sleep -Seconds 2; return 100; }

        $this._Event = Register-ObjectEvent $this._Job StateChanged -Action {
            try {
                $MyObject = $Event.MessageData.This
                $MyObject._Result = Receive-Job -Id $sender.Id -Keep
                $MyObject.Trigger() # this line here
            }
            catch {
           
            } 
        } -MessageData @{This = $this }
    }

    Trigger() {
        <#
     CALL OTHER METHODS
    #>
        Write-Host "job complete" -ForegroundColor Cyan
    }
}

$MyTestInstance = [Test]::new()
$MyTestInstance.Start()

# IMPORTANT
# You need some kind of loop to prevent the script of exiting immediately.
# If you don't have any, It will appear to work in vscode but won't work in production contexts
# You also won't be able to debug the code if the script debugger exitted

# We stay in the loop until we have a result.
while (!$$MyTestInstance._Result) {
    Start-Sleep -Milliseconds 100
}

References

VsCode and Powershell: How to debug and trace into code called by Register-ObjectEvent

Register-ObjectEvent

About Automatic Variables

  • Related