Skip to content

Add API support for dot sourcing a ScriptBlock #3581

Closed

Description

@lzybkr

It is currently not possible to implement a cmdlet like ForEach-Object or Measure-Command outside of PowerShell because there is no api support for invoking a script block instance in the callers scope - or dot sourcing.

The workaround is quite expensive - it looks something like:

void InvokeInCurrentScope(ScriptBlock sb)
{
    var ps = PowerShell.Create(RunspaceMode.CurrentRunspace);
    ps.AddScript(sb.ToString());
    ps.Invoke();
}

This is expensive we must reparse the script block.

There are a couple possible fixes, maybe we do both:

  • ScriptBlock.Invoke (and family) should expose this parameter in a public api
  • Add a new api or overload to the PowerShell.Add* family of commands, something like AddScriptBlock, or maybe just a new overload on AddCommand.

Activity

  1. added
    WG-Enginecore PowerShell engine, interpreter, and runtime
    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 17, 2017
  2. BrucePay commented on Apr 18, 2017

    @BrucePay
    Collaborator

    Have you looked at $executioncontext.InvokeCommand.InvokeScript($false, {$x=3.14}, @(), @())
    One of the signatures for InvokeScript is
    System.Collections.ObjectModel.Collection[psobject] InvokeScript(bool useLocalScope, scriptblock scriptBlock, System.Collections.IList input, Params System.Object[] args)
    This should do what you want.

  3. removed
    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 18, 2017
  4. BrucePay commented on Apr 18, 2017

    @BrucePay
    Collaborator

    Removed the enhancement/Up-for-grabs tags as there is already an API that does this.

  5. lzybkr commented on Apr 18, 2017

    @lzybkr
    ContributorAuthor

    It looks like that api covers most scenarios - but I vaguely recall a scenario, maybe LINQ filter expressions or something - where you have a script block, but maybe not have easy access to the execution context.

  6. added
    Issue-Enhancementthe issue is more of a feature request than a bug
    and removed
    Issue-Enhancementthe issue is more of a feature request than a bug
    on Apr 18, 2017
  7. mklement0 commented on Nov 18, 2018

    @mklement0
    Contributor

    What said InvokeScript overload doesn't cover is the ability to relay the current pipeline input object via automatic variable $_, which is what's needed to be on par with how cmdlets such as Where-Object and ForEach-Object invoke script-block arguments.

    PetSerAl came up with a robust workaround, but it's cumbersome and far from obvious: it uses an aux. ForEach-Object call to invoke the script block with the object to bind to $_ passed via -InputObject.

    Here's an example of what currently works, but it would certainly be nice to have a more direct way of achieving this:

    Add-Type @'
      using System.Management.Automation;
    
      [Cmdlet("Get", "Foo")]
      public class GetFooCmdlet : PSCmdlet {
    
        [Parameter(ValueFromPipeline=true)]
        public object InputObject { get; set; }
    
        [Parameter(Position=0)]
        public ScriptBlock ScriptBlock { get; set; }
    
        private PowerShell _ps = null;
    
        protected override void BeginProcessing() {
          // Create a PS instance in the current runspace with a ForEach-Object invocation.
          _ps = PowerShell.Create(RunspaceMode.CurrentRunspace).AddCommand("ForEach-Object");
        }
    
        protected override void ProcessRecord() {
          _ps.Commands.Commands[0].Parameters.Clear(); // clear previous parameters
          // Add the script block and the current pipeline input object as parameters for the ForEach-Object call.
          // ForEach-Object takes care of setting $_ in the scope of the target script block (passed to -Process), via
          // -InputObject.
          WriteObject(
            _ps.AddParameter("Process", ScriptBlock).AddParameter("InputObject", InputObject).Invoke()
          );
        }
    
      }
    '@ -PassThru | % Assembly | Import-Module
    
    # Define a local variable.
    $var = 42
    
    # Invoke the cmdlet with pipeline input and a scriptblock that references the
    # latter as well as local variable $var
    @{ Name = 'hi' }, @{ Name = 'ho' } | get-foo { $_.Name; ++$var } # -> 'hi', 'ho'
    # See if $var was changed.
    $var # -> 44 - successfully modified, because the scriptblock was "dot-sourced".

    It's actually easier to do this in PowerShell code, given that you can call ForEach-Object directly there - see this SO answer.

    By contrast, it's easier to invoke a script block in a child scope of the origin scope with pipeline input relayed as $_, the way that delay-bind script blocks and script blocks inside calculated properties do )though it's unclear if that is actually by design) - see next comment.

  8. mklement0 commented on Jan 23, 2019

    @mklement0
    Contributor

    Invoking a caller-scope-bound script block in a child scope - as delay-bind script blocks and calculated properties do - with the current pipeline input object relayed as $_ looks like this (using PS code, with the script block stored in $ScriptBock, and the current pipeline input object in $InputObject):

    $ScriptBlock.InvokeWithContext(
            $null, # extra functions to define
            [psvariable]::new('_', $InputObject) # define $_
          )

    To do the same directly in the caller's scope - as Where-Object and ForEach-Object do - we'd need a new .InvokeWithContext() overload that has a useLocalScope parameter too, as .InvokeScript() already does.

    $ScriptBlock.InvokeWithContext(
            $false, # WISHFUL THINKING: opt into not creating a child scope.
            $null, # extra functions to define
            [psvariable]::new('_', $InputObject) # define $_
          )
  9. microsoft-github-policy-service commented on Nov 16, 2023

    @microsoft-github-policy-service
    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.

  10. microsoft-github-policy-service commented on Nov 16, 2023

    @microsoft-github-policy-service
    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.

  11. microsoft-github-policy-service commented on Nov 16, 2023

    @microsoft-github-policy-service
    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.

  12. microsoft-github-policy-service commented on Nov 23, 2023

    @microsoft-github-policy-service
    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.

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 bugResolution-No ActivityIssue has had no activity for 6 months or moreWG-Enginecore PowerShell engine, interpreter, and runtime

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions