I searched but didn't find how to do it yet.
I am working on filtering data from large files (~2GB).
I used Where-Object
and when it find match it continues to search for other matches which it makes sense.
Is it possible to stop it on the first match ?
For example (#1):
Get-Process | Where-Object {$_.ProcessName.StartsWith("svchost")}
The output will be:
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
666 38 26928 18672 92 568 svchost
596 28 11516 16560 92 792 svchost
425 14 5364 7036 45 832 svchost
406 17 7032 8416 39 1004 svchost
What I want is to return the output after the first match:
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
666 38 26928 18672 92 568 svchost
This is what I tried (also with Foreach-Object):
Get-Process | Where-Object {if($_.ProcessName.StartsWith("svchost")){return $_}}
Get-Process | Where-Object {if($_.ProcessName.StartsWith("svchost")){return $_;break;}}
Get-Process | ForEach-Object {if($_.ProcessName.StartsWith("svchost")){return $_}}
But it still returns the full output.
Reference:
How to break Foreach loop in Powershell?
Is it possible to terminate or stop a PowerShell pipeline from within a filter
EDIT (explanation about the problem with large data):
Example (#2):
I have two XMLs:
A.xml:
<?xml version="1.0" encoding="UTF-8"?>
<Events>
<Event>
<EventData Name="Time">09/10/2017 12:54:16</EventData>
<EventData Name="WorkstationName">USER2-PC</EventData>
<EventData Name="UserName">user2</EventData>
</Event>
</Events>
B.xml:
<?xml version="1.0" encoding="UTF-8"?>
<Events>
<Event>
<EventData Name="Time">09/10/2017 14:54:16</EventData>
<EventData Name="WorkstationName">USER1-PC</EventData>
<EventData Name="UserName">user1</EventData>
</Event>
<Event>
<EventData Name="Time">09/10/2017 13:54:16</EventData>
<EventData Name="WorkstationName">USER2-PC</EventData>
<EventData Name="UserName">user2</EventData>
</Event>
... (more 100,000 events like the above two)
</Events>
These XMLs are being loaded as objects:
$fileA = "C:\tmp\A.xml"
$a = New-Object Xml.XmlDocument
$a.Load($fileA)
$fileB = "C:\tmp\B.xml"
$b = New-Object Xml.XmlDocument
$b.Load($fileB)
Then I want to search for the first match of the same username:
$result = $b.Events.Event | Where-Object {
(($_.EventData | where-object {$_.Name -eq "UserName"})."#text" -eq $username)
}
$result.EventData
In this case it waste of time to run over the rest of 99,999 events if I have match on the first event.
EDIT (SOLVED):
After reading Nick answer, there wasn't anything new I didn't try.
The command:
Get-Process | Where-Object {if($_.ProcessName.StartsWith("svchost")){ $_;break;}}
Indeed stops the Where-Object
but it doesn't return the item.
This one can be solved by:
Get-Process | Where-Object {if($_.ProcessName.StartsWith("svchost")){ $someVar = $_;break;}}
Therefore I marked his answer.
If efficiency is what you need you can try break it out in to a loop:
Get-Process | foreach {If ($_.ProcessName.StartsWith("svchost")){$_;break}}
You can confirm it works with this check:
$i=0; Get-Process | foreach {$i++;$i; If ($_.ProcessName.StartsWith("svchost")){$_;break}}
It will make the loop print out a number each time it loops, in my case it got to 115, Then if i do (Get-Process).Count
I have 157
Processes, So it looped over my processes found the one we want and then stopped the loop.
As stated here in other answers, You can use [0]
, On any array or list you can select a individual row using the index inside square brackets, Be careful though because attempting this on a null or empty object will throw a exception:
(Get-Process | Where-Object {$_.ProcessName.StartsWith("svchost")})[0]
Or you can you Select-Object
which works in a similar way but has more options than just Index and will not throw any error if the object is null or empty.
Get-Process | Where-Object {$_.ProcessName.StartsWith("svchost")} | Select-Object -First 1
How ever both of these options will still evaluate the entire list before you select the first result.