Как передать $ _ ($ PSItem) в ScriptBlock - PullRequest
0 голосов
/ 24 октября 2018

Я в основном строю свою собственную функцию параллельного конвейера foreach, используя пространства выполнения.

Моя проблема: я вызываю свою функцию следующим образом:

somePipeline | MyNewForeachFunction { scriptBlockHere } | pipelineGoesOn...

Как мне передать $_ параметр правильно в ScriptBlock?Он работает, когда ScriptBlock содержит в качестве первой строки

param($_)

Но, как вы могли заметить, встроенные в PowerShell ForEach-Object и Where-Object not нуждаются в таком объявлении параметрав каждом ScriptBlock, который передается им.

Спасибо за ваши ответы заранее fjf2002

РЕДАКТИРОВАТЬ:

Цель: я хочу утешения дляпользователи функции MyNewForeachFunction - им не нужно писать строку param($_) в своих блоках скрипта.

Внутри MyNewForeachFunction, ScriptBlock в настоящее время вызывается через

$PSInstance = [powershell]::Create().AddScript($ScriptBlock).AddParameter('_', $_)
$PSInstance.BeginInvoke()

EDIT2:

Дело в том, как, например, реализация встроенной функции ForEach-Object позволяет добиться того, что $_ не нужно объявлять как параметр в своем ScriptBlockпараметр, и могу ли я использовать эту функциональность тоже?

(Если ответ: ForEach-Object является встроенной функцией и использует некоторую магию, которую я не могу использовать, то это дисквалифицирует язык PowerShell какв целом по моему)

EDIT3:

Благодаря mklement0 я наконец смог построить свой общий цикл foreach.Вот код:

function ForEachParallel {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory)] [ScriptBlock] $ScriptBlock,
        [Parameter(Mandatory=$false)] [int] $PoolSize = 20,
        [Parameter(ValueFromPipeline)] $PipelineObject
    )

    Begin {
        $RunspacePool = [runspacefactory]::CreateRunspacePool(1, $poolSize)
        $RunspacePool.Open()
        $Runspaces = @()
    }

    Process {
        $PSInstance = [powershell]::Create().
            AddCommand('Set-Variable').AddParameter('Name', '_').AddParameter('Value', $PipelineObject).
            AddCommand('Set-Variable').AddParameter('Name', 'ErrorActionPreference').AddParameter('Value', 'Stop').
            AddScript($ScriptBlock)

        $PSInstance.RunspacePool = $RunspacePool

        $Runspaces += New-Object PSObject -Property @{
            Instance = $PSInstance
            IAResult = $PSInstance.BeginInvoke()
            Argument = $PipelineObject
        }
    }

    End {
        while($True) {
            $completedRunspaces = @($Runspaces | where {$_.IAResult.IsCompleted})

            $completedRunspaces | foreach {
                Write-Output $_.Instance.EndInvoke($_.IAResult)
                $_.Instance.Dispose()
            }

            if($completedRunspaces.Count -eq $Runspaces.Count) {
                break
            }

            $Runspaces = @($Runspaces | where { $completedRunspaces -notcontains $_ })
            Start-Sleep -Milliseconds 250
        }

        $RunspacePool.Close()
        $RunspacePool.Dispose()
    }
}

Код частично от MathiasR.Jessen, Почему рабочий процесс PowerShell значительно медленнее, чем сценарий без рабочего процесса для анализа файлов XML

Ответы [ 3 ]

0 голосов
/ 26 октября 2018

Ключ в том, чтобы определить $_ как переменную , которую ваш блок скрипта может увидеть, вызвав Set-Variable.

Вот простой пример:

function MyNewForeachFunction {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory)]
    [scriptblock] $ScriptBlock
    ,
    [Parameter(ValueFromPipeline)]
    $InputObject
  )

  process {
    $PSInstance = [powershell]::Create()

    # Add a call to define $_ based on the current pipeline input object
    $null = $PSInstance.
      AddCommand('Set-Variable').
        AddParameter('Name', '_').
        AddParameter('Value', $InputObject).
      AddScript($ScriptBlock)

    $PSInstance.Invoke()
  }

}

# Invoke with sample values.
1, (Get-Date) | MyNewForeachFunction { "[$_]" }

Выше приведено что-то вроде:

[1]
[10/26/2018 00:17:37]
0 голосов
/ 02 мая 2019
# I was looking for an easy way to do this in a scripted function,
# and the below worked for me in PSVersion 5.1.17134.590

function Test-ScriptBlock {
    param(
        [string]$Value,
        [ScriptBlock]$FilterScript={$_}
    )
    $_ = $Value
    & $FilterScript
}
Test-ScriptBlock -Value 'unimportant/long/path/to/foo.bar' -FilterScript { [Regex]::Replace($_,'unimportant/','') }
0 голосов
/ 24 октября 2018

Может быть, это может помочь.Обычно я запускаю автоматически сгенерированные задания следующим образом:

Get-Job | Remove-Job

foreach ($param in @(3,4,5)) {

 Start-Job  -ScriptBlock {param($lag); sleep $lag; Write-Output "slept for $lag seconds" } -ArgumentList @($param)

}

Get-Job | Wait-Job | Receive-Job

Если я вас правильно понимаю, вы пытаетесь избавиться от param () внутри блока скриптов.Вы можете попытаться обернуть этот SB другим.Ниже представлен обходной путь для моего образца:

Get-Job | Remove-Job

#scriptblock with no parameter
$job = { sleep $lag; Write-Output "slept for $lag seconds" }

foreach ($param in @(3,4,5)) {

 Start-Job  -ScriptBlock {param($param, $job)
  $lag = $param
  $script = [string]$job
  Invoke-Command -ScriptBlock ([Scriptblock]::Create($script))
 } -ArgumentList @($param, $job)

}

Get-Job | Wait-Job | Receive-Job
...