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

问题描述 投票:0回答:4

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

“ 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中管道命令的服务器文章,并且它是基于对象的,但是我迷失了如何使用一个函数的返回对象并将其管道化为另一个函数的方法,尽管我确信当您知道如何使用时它很简单

arrays powershell object pipe powershell-2.0
4个回答
2
投票
我喜欢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返回“ ProcessID”。

但是还有第二个问题。传入的属性值必须是接收函数/ cmdlet可接受的。不幸的是,在Win32_Process中通常返回带有“ .exe”后缀的Name,而Get-Process将不接受。

Theo的回答非常好,并且可以与Stop-Process一起使用,因为他的新对象具有一个名为ID的属性,该属性被管道接受并且是默认参数集的一部分,这意味着它比name属性更受欢迎。

但是,如果将那些对象通过管道传递到Get-Process,它将无法正常工作。 Get-Process优先使用名称而不是ID,并且期望Win32_Process返回的值类似于“ notepad”而不是“ Notepad.exe”。在这种情况下,Get-Process将无法找到该进程,并且会出错。

注:以上内容是根据与Theo的合作而更正的,您可以查看以前的修订和注释以供参考。

要使对象也与Get-Process一起使用,只需修改进入“ Name”属性的值以删除结尾的'.exe'。我编辑了Theo的答案只是为了补充一点。您应该看到他是否批准。

我意识到这不是您最初的问题的一部分,而是要说明不同工具/ cmdlet /功能等之间管道的附加警告。

注意:可能仍然有几个例外。例如:Win32_Process返回“系统空闲进程”,但Get-Process返回“空闲”。为了您的目的,这可能不是问题。当然,您永远不会停止该过程!

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

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

您的代码具有正确的“名称”属性:$ 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-Wmi ...但是这些概念应适用于整个PowerShell,包括Get-CimInstance ...

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


2
投票
在您的函数内部,无需执行两次ForEach-Object循环。如果我正确阅读了您的问题,那么您要做的就是停止所有者名称与从“ oldusers.txt”文件读取的内容匹配的进程。

1
投票
{$_.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 } } }


0
投票
现有答案中有很多信息;让我通过解释

immediate问题来补充它:

© www.soinside.com 2019 - 2024. All rights reserved.