如何在管道上使用 PSCustomObject ScriptMethod

问题描述

PS 版本:5.1


希望

我希望能奏效
$obj = [PSCustomObject]@{
   PSTypeName = 'MyObject'
   con        = 'local'
}
add-member -MemberType ScriptMethod -InputObject $obj -Name MyMethod -Value {
   param([parameter(ValueFromPipeline=$true)]$x)
   begin  {write-host 'begin'}
   process{write-host "$($this.con): $x"}
   end    {write-host 'end'}
}
1..5 | $obj.MyMethod

给予

begin
local: 1
local: 2
local: 3
local: 4
local: 5
end

但是报错

At line:1 char:8
+ 1..5 | $obj.MyMethod
+        ~~~~~~~~~~~~~
Expressions are only allowed as the first element of a pipeline.
    + CategoryInfo          : ParserError: (:) [],ParentContainsErrorRecordException
    + FullyQualifiedErrorId : ExpressionsMustBeFirstInPipeline

试过

什么不起作用,因为:idk,tl;dr 原因?
1..5 | %{$obj.MyMethod($_)}

什么不起作用,因为:开始/结束

$obj = [PSCustomObject]@{
   PSTypeName = 'MyObject'
   con        = 'local'
}
add-member -MemberType ScriptMethod -InputObject $obj -Name MyMethod -Value {
   param($x)
   write-host "$($this.con): $x"
}
1..5 | %{$obj.MyMethod($_)}

什么不起作用,因为:$this 不再附加

$obj = [PSCustomObject]@{
   PSTypeName = 'MyObject'
   con        = 'local'
   MyMethodSB  = {
      param()
      begin  {write-host 'begin'}
      process{write-host "$($obj.con): $_"}
      end    {write-host 'end'}
   }
}
1..5 | &$obj.MyMethodSB

什么不起作用,因为:idk,也许 Stepper 的范围过早消亡

$obj = [PSCustomObject]@{
   PSTypeName = 'MyObject'
   con        = 'local'
   Stepper    = $null
}
add-member -MemberType ScriptMethod -InputObject $obj -Name MyMethod -Value {
   param($x)
   if($null -eq $this.Stepper){
      $t = $this
      $sb = {
         param($o)
         begin  {write-host 'begin'}
         process{write-host "$($o.con): $_"}
         end    {write-host 'end'}
      }
      $this.Stepper = {&$sb $t}.GetSteppablePipeline()
      $this.Stepper.Begin($true)
   }
   $this.Stepper.Process($x)
}
1..5 | %{$obj.MyMethod($_)} 
$obj.Stepper.End()
$obj.Stepper = $null

差不多了

什么是有效的
$Data = @{
   ItemNo1 = @(
      @{loc = 'WH';  qty = 20}
      @{loc = 'DK';  qty = 0}
      @{loc = 'ST1'; qty = 3}
      @{loc = 'ST2'; qty = 2}
   )
   ItemNo2 = @(
      @{loc = 'WH'; qty = 6}
      @{loc = 'DK'; qty = 0}
   )
   ItemNo3 = @(
      @{loc = 'WH'; qty = 100}
      @{loc = 'ST1'; qty = 5}
      @{loc = 'DK'; qty = 0}
   )
   ItemNo4 = @(
      @{loc = 'WH'; qty = 0}
      @{loc = 'DK'; qty = 0}
      @{loc = 'ST2'; qty = 15}
   )
   ItemNo5 = @(
      @{loc = 'WH'; qty = 0}
      @{loc = 'DK'; qty = 15}
   )
}
$QueryParameter = [PSCustomObject]@{Value = $null}
function MockDataQuery{
   $Item = $QueryParameter.Value
   foreach($iDat in $Data.$Item){
      "   $Item;$($iDat.loc);$($iDat.qty)"
   }
}
$InvObj = [PSCustomObject]@{
   PSTypeName = 'Data.Connection'
   con        = 'item.inventory'
}
add-member -MemberType ScriptMethod -InputObject $InvObj -Name FetchItemData -Value {
   $p = $this
   $qPipe = [PSCustomObject]@{
      PSTypeName = 'Data.Connection.QueryPipe'
      parent  = $p
      qHeader = 'Item;Location;Quantity'
      query   = 'select * from inventory where item = ?'
      param   = $null
   }
   add-member -MemberType ScriptMethod -InputObject $qPipe -Name begin   -Value {
      param([bool]$Header=$true)
      write-host "connect to: $($this.parent.con)"
      write-host "compile command: $($this.query)"
      $this.param = $QueryParameter
      write-host 'begin transaction'
      if($Header){
         write-host "   $($this.qHeader)"
      }
   }
   add-member -MemberType ScriptMethod -InputObject $qPipe -Name process -Value {
      param($itm)
      $this.param.Value = $itm
      MockDataQuery | write-host
   }
   add-member -MemberType ScriptMethod -InputObject $qPipe -Name end     -Value {
      write-host 'end transaction'
      write-host 'dispose command'
      write-host 'close connection'
   }
   return $qPipe
}
1..5 | %{"ItemNo$_"} | &{
   begin  {$stp = $InvObj.FetchItemData();$stp.begin()}
   process{$stp.process($_)}
   end    {$stp.end()}
}

虽然这似乎是一个功能模型,但如果有一种解决方案不需要连接对象的用户每次使用时都必须在他们的末尾编写开始/处理/结束脚本,那就更好了对象的方法

解决方法

几天后喝了太多咖啡;我想我在这里学到的是,如果可以,请尝试重构为一个很好的旧直接高级功能/cmdlet

$Data = @{
   ItemNo1 = @(
      @{loc = 'WH';  qty = 20}
      @{loc = 'DK';  qty = 0}
      @{loc = 'ST1'; qty = 3}
      @{loc = 'ST2'; qty = 2}
   )
   ItemNo2 = @(
      @{loc = 'WH'; qty = 6}
      @{loc = 'DK'; qty = 0}
   )
   ItemNo3 = @(
      @{loc = 'WH'; qty = 100}
      @{loc = 'ST1'; qty = 5}
      @{loc = 'DK'; qty = 0}
   )
   ItemNo4 = @(
      @{loc = 'WH'; qty = 0}
      @{loc = 'DK'; qty = 0}
      @{loc = 'ST2'; qty = 15}
   )
   ItemNo5 = @(
      @{loc = 'WH'; qty = 0}
      @{loc = 'DK'; qty = 15}
   )
}
$QueryParameter = [PSCustomObject]@{Value = $null}
function MockDataQuery{
   $Item = $QueryParameter.Value
   foreach($iDat in $Data.$Item){
      "   $Item;$($iDat.loc);$($iDat.qty)"
   }
}
filter Write-PassThru{
   write-host $_ -ForegroundColor Cyan
   $_
}
$InvObj = [PSCustomObject]@{
   PSTypeName    = 'Data.Connection'
   con           = 'item.inventory'
   FetchItemData = 'Get-ItemData'
}

function Get-ItemData{
   param(
      [parameter(mandatory=$true)]
      [PSTypeName('Data.Connection')]
      $t,[bool]
      $Header=$true,[Parameter(ValueFromPipeline=$true)]
      $itm
   )
   begin{
      $query   = 'select * from inventory where item = ?'
      $qHeader = 'Item;Location;Quantity'
      $param   = $null

      write-host "connect to: $($t.con)"
      write-host "compile command: $query"
      $param = $QueryParameter
      write-host 'begin transaction'
      if($Header){
         "   $qHeader" | write-passthru
      }
   }process{
      $param.Value = $itm
      MockDataQuery | write-passthru
   }end{
      write-host 'end transaction'
      write-host 'dispose command'
      write-host 'close connection'
   }
}

1..5                        `
   | %{"ItemNo$_"}          `
   | &$InvObj.FetchItemData $InvObj

但是如果你不能,你也可以尝试做一些像这样非常hacky的事情
$Data = @{
   ItemNo1 = @(
      @{loc = 'WH';  qty = 20}
      @{loc = 'DK';  qty = 0}
      @{loc = 'ST1'; qty = 3}
      @{loc = 'ST2'; qty = 2}
   )
   ItemNo2 = @(
      @{loc = 'WH'; qty = 6}
      @{loc = 'DK'; qty = 0}
   )
   ItemNo3 = @(
      @{loc = 'WH'; qty = 100}
      @{loc = 'ST1'; qty = 5}
      @{loc = 'DK'; qty = 0}
   )
   ItemNo4 = @(
      @{loc = 'WH'; qty = 0}
      @{loc = 'DK'; qty = 0}
      @{loc = 'ST2'; qty = 15}
   )
   ItemNo5 = @(
      @{loc = 'WH'; qty = 0}
      @{loc = 'DK'; qty = 15}
   )
}
$QueryParameter = [PSCustomObject]@{Value = $null}
function MockDataQuery{
   $Item = $QueryParameter.Value
   foreach($iDat in $Data.$Item){
      "   $Item;$($iDat.loc);$($iDat.qty)"
   }
}
filter Write-PassThru{
   write-host $_ -ForegroundColor Cyan
   $_
}
$InvObj = [PSCustomObject]@{
   PSTypeName    = 'Data.Connection'
   con           = 'item.inventory'
   FetchItemData = {
      param(
         [bool]
         $Header=$true,[Parameter(ValueFromPipeline=$true)]
         $itm
      )
      begin{
         $pm = $MyInvocation.PositionMessage -split "`n"
         [int]$at = $pm[0] -replace '.*char:(\d+).*','$1'
         $l = ($pm[2] -replace '[^~]','').Length
         $myCall = $pm[1].substring($at+1,$l)
         $thus = get-variable -Name ($myCall -replace '.*?\${?([^}.]+)}?\..*','$1') -ValueOnly

         $query   = 'select * from inventory where item = ?'
         $qHeader = 'Item;Location;Quantity'
         $param   = $null

         write-host "connect to: $($thus.con)"
         write-host "compile command: $query"
         $param = $QueryParameter
         write-host 'begin transaction'
         if($Header){
            "   $qHeader" | write-passthru
         }
      }process{
         $param.Value = $itm
         MockDataQuery | write-passthru
      }end{
         write-host 'end transaction'
         write-host 'dispose command'
         write-host 'close connection'
      }
   }
}

function Annotate-Flow{
   param(
      [string]
      $tag = 'Flow',[switch]
      $head,[switch]
      $tail
   )
   begin{
      if($head){
         $Global:FlowSequence = 0
      }else{$Global:FlowSequence++}
      write-host ("${Tag} Seq[{0,2}]: Begin" -f $Global:FlowSequence) -ForegroundColor DarkCyan
   }process{
      $Global:FlowSequence++
      write-host ("${Tag} Seq[{0,2}]: Process($_)" -f $Global:FlowSequence)-ForegroundColor DarkCyan
      $_
   }end{
      $Global:FlowSequence++
      write-host ("${Tag} Seq[{0,2}]: End" -f $Global:FlowSequence) -ForegroundColor DarkCyan
      if($tail){
         remove-variable -Scope Global -Name FlowSequence
      }
   }
}

1..5                        | Annotate-Flow Source -head `
   | %{"ItemNo$_"}          | Annotate-Flow Format       `
   | &$InvObj.FetchItemData | Annotate-Flow Lookup -tail

您可能(不)想阅读的一些内容:

虽然说实话,这更像是一个凑合的答案,而不是一个优雅的解决方案。