将 powershell 中的对象重定向到其他函数

redirecting objects in powershell into other functions

我有一个函数可以将进程与用户进行比较,但是当我尝试将输出通过管道传输到停止进程时,出现以下错误:

"Stop-Process: 无法计算参数 'InputObject' 因为它的参数被指定为脚本块 并且没有输入。没有输入就无法评估脚本块。 "

 Function Proc {

$TargetUsers = get-content oldusers.txt
$WmiArguments = @{

                'Class' = 'Win32_process'
            }

            $processes = Get-WMIobject @WmiArguments |      ForEach-Object {
                $Owner = $_.getowner();
                $Process = New-Object PSObject
                $Process | Add-Member Noteproperty 'ComputerName' $Computer
                $Process | Add-Member Noteproperty 'ProcessName' $_.ProcessName
                $Process | Add-Member Noteproperty 'ProcessID' $_.ProcessID
                $Process | Add-Member Noteproperty 'Domain' $Owner.Domain
                $Process | Add-Member Noteproperty 'User' $Owner.User
                $Process
            }



      ForEach ($Process in $Processes) {
               if ($TargetUsers -Contains $Process.User) {
               stop-process -id {$_.processid}

                    }

            }
              }

我已经阅读了多篇关于 powershell 中的管道命令的文章,它是基于对象的,但我不知道如何使用一个函数的返回对象并将管道传递到另一个函数,尽管我确信当你知道这很简单时如何

{$_.processid}更改为$Process.ProcessID

Function Proc {
    $TargetUsers = get-content oldusers.txt
    $WmiArguments = @{
        'Class' = 'Win32_process'
    }
    $processes = Get-WMIobject @WmiArguments | ForEach-Object {
        $Owner = $_.getowner();
        $Process = New-Object PSObject
        $Process | Add-Member Noteproperty 'ComputerName' $Computer
        $Process | Add-Member Noteproperty 'ProcessName' $_.ProcessName
        $Process | Add-Member Noteproperty 'ProcessID' $_.ProcessID
        $Process | Add-Member Noteproperty 'Domain' $Owner.Domain
        $Process | Add-Member Noteproperty 'User' $Owner.User
        $Process
    }
    ForEach ($Process in $Processes) {
        if ($TargetUsers -Contains $Process.User) {
            stop-process -id $Process.ProcessID
        }
    }
}

在您的函数内部,无需执行两次 ForEach-Object 循环。如果我正确阅读了你的问题,你想要它做的就是停止所有者用户名与从 'oldusers.txt' 文件中读取的任何人匹配的进程。

简化后,您的函数可能如下所示:

function Stop-OldUserProcess {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [ValidateScript({Test-Path -Path $_ -PathType Leaf})]
        [Alias('FullName', 'FilePath')]
        [string]$SourceFile
    )

    $TargetUsers = Get-Content $SourceFile

    Get-WMIobject -Class Win32_Process | ForEach-Object {
        $Owner = $_.GetOwner()
        if ($TargetUsers -contains $Owner.User) {
            Write-Verbose "Stopping process $($_.ProcessName)"
            Stop-Process -Id $_.ProcessID -Force
        }
    }
}

你这样称呼它:

Stop-OldUserProcess -SourceFile 'oldusers.txt' -Verbose

另一种方法可能是您创建一个函数来收集旧用户拥有的进程,return该信息作为调用脚本的对象:

function Get-OldUserProcess {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [ValidateScript({Test-Path -Path $_ -PathType Leaf})]
        [Alias('FullName', 'FilePath')]
        [string]$SourceFile
    )

    $TargetUsers = Get-Content $SourceFile

    Get-WMIobject -Class Win32_Process | ForEach-Object {
        $Owner = $_.GetOwner()
        if ($TargetUsers -contains $Owner.User) {
            # output a PSObject
            [PsCustomObject]@{
                'Name'   = $_.ProcessName
                'Id'     = $_.ProcessID
                'Domain' = $Owner.Domain
                'User'   = $Owner.User
            }
        }
    }
}

我选择使用 'Name' 和 'Id' 作为 属性 名称,因为 Stop-Process cmdlet 可以通过管道获取对象,并且 'Name' 和 'Id' 属性 被接受为管道输入 ByPropertyName.

然后调用函数来接收(一组)对象并用它做你需要做的事情:

Get-OldUserProcess -SourceFile 'oldusers.txt' | Stop-Process -Force

我更改了函数名称以符合 PowerShell Verb-Noun 命名约定。


P.S。如果您有 PowerShell 版本 3.0 或更高版本,您可以更改行

Get-WMIobject -Class Win32_Process | ForEach-Object {
    $Owner = $_.GetOwner()

进入

Get-CimInstance -ClassName Win32_Process | ForEach-Object {
    $Owner = Invoke-CimMethod -InputObject $_ -MethodName GetOwner

以获得更好的性能。参见 Get-CIMInstance Vs Get-WMIObject

我喜欢 Theo 的回答。我只想添加一些肯定不适合评论的额外内容...

最初我认为我们都在调试您的代码,但要保持您最初设计的模式。严格来说这没什么不妥。

我认为这里有些丢失的是您的实际问题;为什么不能简单地将一个函数的输出通过管道传递给另一个函数?答案在于接收 cmdlet 或函数如何期待数据。

如果您查看 Get-Help Stop-Process -Parameter Id(或参数名称),您会发现如果 属性 命名正确,它将通过管道获取 属性:

-Id <Int32[]>
    Specifies the process IDs of the processes to stop. To specify multiple IDs, use commas to separate the IDs. To find the PID of a process, type `Get-Process`.

    Required?                    true
    Position?                    0
    Default value                None
    Accept pipeline input?       True (ByPropertyName)
    Accept wildcard characters?  false

因此,如果您的自定义对象有一个 属性 名为“Id”的对象,您就可以通过管道传输。

Stop-Process 将接受进程 ID,但它正在寻找 属性 名称为“Id”和 Win32_Process returns “ProcessID”。

但是还有第二个问题。传入的 属性 的值必须能够被接收 function/cmdlet 接受。不幸的是,Win32_Process 通常 returns 带有“.exe”后缀的名称,Get-Process 不会接受。

Theo 的回答非常好并且可以与 Stop-Process 一起使用,因为他的新对象有一个名为“ID”的 属性,它被管道接受并且是默认参数集的一部分,这意味着它是首选名字 属性.

但是,如果您要将这些对象通过管道传输到 Get-Process,那是行不通的。 Get-Process 更喜欢名称而不是 ID,并且期望像“记事本”这样的值而不是“Notepad.exe,Win32_Process returns。在那种情况下,Get-Process 不会能够找到进程,会报错。

注:以上为与Theo合作修正,可参考之前的修正和评论

要使对象也与 Get-Process 一起工作,只需修改进入“名称”属性 的值以删除尾随的“.exe”。我编辑了 Theo 的答案只是为了添加那一点。你应该看看他是否同意。

我知道这不是你原来问题的一部分,但它是为了说明不同 tools/cmdlets/functions 等之间管道的额外警告...

注意:可能仍有一些例外。例如:Win32_Processreturns“系统空闲进程”但是Get-Processreturns“空闲”。出于您的目的,这可能不是问题。当然你永远不会停止这个过程!

注意:Get-Process 更喜欢名称而 Stop-Process 更喜欢 ID 的可能原因是名称不是唯一的,但 ID 是。 Stop-Process 记事本将杀死记事本的所有实例,这通常(在您的情况下)不是预期的。

关于一般方法。我要指出有几种方法可以扩展对象和创建 PS 自定义对象。如果您需要或希望实例类型保持不变,Add-Member 是一个好方法;我会考虑扩展一个对象。但是,在您的情况下,您正在创建一个自定义对象,然后向其添加成员。在这种情况下,我通常使用 Select-Object 已经转换为 PSCustomObjects.

您的代码更正了“名称”属性: $Processes = Get-WmiObject win32_process

$Processes | 
ForEach-Object{
    $Owner = $_.getowner()
    $Process = New-Object PSObject
    $Process | Add-Member NoteProperty 'ComputerName' $_.CSName
    $Process | Add-Member NoteProperty 'ProcessName' $_.ProcessName
    $Process | Add-Member NoteProperty 'ProcessID' $_.ProcessID
    $Process | Add-Member NoteProperty 'Domain' $Owner.Domain
    $Process | Add-Member NoteProperty 'User' $Owner.User
    $Process | Add-Member NoteProperty 'Name' -Value ( $_.ProcessName -Replace '\.exe$' )
    $Process
}

注意:为简洁起见,我删除了一些周围的代码。

使用 select 看起来像:

$Processes = Get-WmiObject win32_process |
Select-Object ProcessID,
    @{Name = 'ComputerName'; Expression = { $_.CSName }},
    @{Name = 'Name ';        Expression = { $_.ProcessName -Replace '\.exe$' } },
    @{Name = 'Id';           Expression = { $_.ProcessID } },
    @{Name = 'Domain';       Expression = { $_.GetOwner().Domain} },
    @{Name = 'User';         Expression = { $_.GetOwner().User} }

然后可以将其直接传送到 where 子句以过滤您要查找的进程,然后再次传送到 Stop-Process cmdlet:

Get-WmiObject win32_process |
Select-Object ProcessID,
    @{Name = 'ComputerName'; Expression = { $_.CSName }},
    @{Name = 'Name ';        Expression = { $_.ProcessName -Replace '\.exe$' } },
    @{Name = 'Id';           Expression = { $_.ProcessID } },
    @{Name = 'Domain';       Expression = { $_.GetOwner().Domain} },
    @{Name = 'User';         Expression = { $_.GetOwner().User} } |
Where-Object{ $TargetUsers -contains $_.User } |
Stop-Process

注意:这甚至会降低对 $Processes 的分配。您仍然需要填充 $TargetUsers 变量。

此外:之前的评论指出,鉴于您正在做的事情,您不需要所有的道具,所以类似:

Get-WmiObject win32_process |
Select-Object @{Name = 'Name '; Expression = { $_.ProcessName -Replace '\.exe$' } },
    @{Name = 'User'; Expression = { $_.GetOwner().User} } |    
Where-Object{ $TargetUsers -contains $_.User } |
Stop-Process

但是,如果您在代码中做其他事情,例如记录终止的进程,建立和维护更多属性相对无害。

仅供说明,通过 ForEach-Object 也可以相对轻松地促进管道传输,无需偏离原始对象:

Get-WmiObject win32_process | 
Where{$TargetUsers -contains $_.GetOwner().User } |
ForEach-Object{ Stop-Process -Id $_.ProcessID }

PowerShell 最棒的地方之一就是有很多方法可以做事。最后一个示例非常简洁,但是添加诸如日志记录或控制台输出之类的内容将不是最佳选择(尽管可行)...

此外,Theo 关于 Get-CimInstance 的说法是正确的。如果我没记错的话 Get-WmiObject 已被弃用。旧习惯很难打破,所以我所有的例子都使用 Get-WmiObject 然而,这些概念应该适用于整个 PowerShell,包括 Get-CimInstance...

无论如何,我希望我在这里添加了一些内容。有几篇文章讨论了不同的对象创建和操作功能的优缺点等...如果我有时间,我会尝试追踪它们。

现有答案中包含大量信息;让我通过解释即时问题来补充它:

I get the following errors: "Stop-Process : Cannot evaluate parameter 'InputObject' because its argument is specified as a script block and there is no input. A script block cannot be evaluated without input. "

{$_.processid} 是具有 管道输入 .

script block, which is an apparent attempt to use that script block as a

在您的代码中,您没有为您的 Stop-Process 调用提供管道输入,这正是错误消息试图告诉您的内容。

相反,您使用的是 foreach loop to loop over input using $Process as the iteration variable, and you therefore need to specify the target ID as regular, direct argument based on that variable, using $Process.ProcessID, as shown in