How to get one property and output from a child job? - windows

I have a child job in a file test.ps1:
param($unit)
$p = $unit
Start-Job -Name $unit -ScriptBlock {
param($p)
"Hallo $p"
New-Object PSCustomObject -Property #{
Not_found = "agurk"
}
} -ArgumentList $p
When I do:
PS> .\test 2
PS> $a=Get-Job|Receive-Job |Select-Object -Property Not_found
I unfortunately get
PS> $a.count
2
Why 2 and not 1?
And I have to do
PS> $a[1].Not_found
agurk
in order to get the value.
I want to create a child job which produces some output, and one property and only one for the caller to investigate. How can I achieve that?

You're getting a count of 2 because your job returns the string "Hallo 2" and your custom object #{Not_found="agurk"}. From the documentation:
In Windows PowerShell®, the results of each statement are returned as output, even without a statement that contains the Return keyword.
The Select-Object doesn't skip over or remove that string, instead it creates a new custom object with an empty property Not_found, so you end up with a result like this:
[
{ 'Not_found': '' },
{ 'Not_found': 'agurk' }
]
If you don't want the string output to be captured, change this line:
"Hallo $p"
into this:
Write-Host "Hallo $p"
Or don't create undesired output in the first place.
If the additional output is desired and you need to be able to redirect it separately from the returned object you can't use Write-Host, though, since that cmdlet writes directly to the host console (meaning its output cannot be captured or redirected). Instead you need to write to a different stream, e.g. the verbose stream:
Write-Verbose "Hallo $p"
Note that you need PowerShell v3 or newer to be able to redirect streams other than the Success and Error streams.

The count of two comes from the fact the job returns two lines of output, "Hallo $p" and the object.
Because you're doing an unconditional select, it seems Powershell is returning objects whether or not the property exists.
You could change your query to check whether or not the property is populated by using a Where-Object:
$a = Get-Job | Receive-Job | Where-Object { $_.Not_Found -match ".+" }
Then access with $a.Not_Found
Alternatively, directly accessing the property from your result also seems to only return non-empty values:
$a = $(Get-Job | Receive-Job | Select-Object -Property Not_Found).Not_Found

Related

Logging off from all servers in a domain

Occasionally I forget to log off from a server or am disconnected through an error and I don't remember the name of the server. And my domain account starts getting periodically locked out, so I have to access logs on DC to find out which server(s) keep locking my account and log off from it/them. So I wanted to write to script in powershell that would log me off from all servers in a domain (with the exception of the server where I run the script on of course) without me needing to search which to log off from. This is what I have:
$ErrorActionPreference = "Silentlycontinue"
$Servers = (Get-ADComputer -Filter *).Name
$ScriptBlock = {
$Sessions = quser | ?{$_ -match $env:USERNAME}
if (($Sessions).Count -ge 1)
{
$SessionIDs = ($Sessions -split ' +')[2]
Write-Host "Found $(($SessionIDs).Count) user login(s) on $Server."
$SessionIDs | ForEach-Object
{
Write-Host "Logging off session [$($_)]..."
logoff $_
}
}
}
foreach ($Server in $Servers)
{
if ($Server -isnot $env:COMPUTERNAME)
{
Invoke-Command -ComputerName $Server -ScriptBlock {$ScriptBlock}
}
}
But when I launch the script, nothing happens. The script doesn't return any errors but doesn't log me off from any server, nor does it write any of the messages from Write-Host cmdlet, obviously. I noticed the $SessionIDs variable definition only returns ID of the first session. Usually this shouldn't be a problem, since it's unlikely I will have more than one session on a server, but I'd like to have this insurance. Can anyone tell me what's wrong in the script?
I notice a few things...
"First, I don't think quser | Where-Object {$_ -match $env:USERNAME} will ever return anything. The output of quser will not contain the hostname."
Try this for getting logon sessions:
$Sessions = (query user /server:$Env:ComputerName) -split "\n" -replace '\s\s+', ';' |
ConvertFrom-Csv -Delimiter ';'
Next, when you reference the $Server variable on the remote machine in your script block, it is out of scope. You would need to use $Using:Server in the script block.
Lastly, the -isnot operator doesn't compare value, it compares type. So in your last foreach, the if statement evaluates to "if type string is not type string" and will not run. Try -ne or -notlike instead.
Working with objects is much easier if you can just parse the output of QUser.exe. Given your scenario, here's my take on it:
$servers = (Get-ADComputer -Filter '*').Name.Where{$_ -ne $env:COMPUTERNAME}
foreach ($server in $servers)
{
if (-not ($quser = ((QUser.exe /server:$server) -replace '\s{20,39}',',,' -replace '\s{2,}',',' 2>&1) | Where-Object -FilterScript { $_ -match $env:USERNAME })) {
Continue
}
Write-Verbose -Message "$($quser.Count) session(s) found on $server." -Verbose
($quser.Trim() | ConvertFrom-Csv -Header 'USERNAME','SESSIONNAME','ID','STATE','IDLE TIME','LOGON TIME').foreach{
Write-Verbose -Message "Logging user [$($_.UserName)] off." -Verbose
LogOff.exe $_.ID /server:$server
}
}
Filtering should always happen before hand meaning, filter out your computer name on your first call to Get-ADComputer. Since you're using QUser.exe and LogOff.exe to begin with, I'd recommend the use of it all the way through since LogOff accepts an ID value that QUser outputs.
Next, placing the call to quser inside your if statement does two things in this case.
Filters for all users matching $ENV:UserName
Returns $true if anything is found, and $false if not found.
So, switching the results using -not will turn $false into $true allowing the execution of the code block which will just continue to the next server.
This in turn doesn't bother with the rest of the code and continues onto the next computer if no matching names were found.
The use of $quser inside the if statement is so you can save the results to it if more than one name is found; (..) allows this as it turns the variable assignment into an expression having the output pass through onto the pipeline where it is either empty, or not.
Finally, referencing the $quser variable we can convert the strings into objects piping to ConvertFrom-Csv. Only step left to do is iterate through each row and passing it over to LogOff to perform the actual logoff.
If you've noticed, the headers are manually-specified because it is filtered out by the Where-Object cmdlet. This is a better approach seeing as there could be "more than one" RDP Session, now you're just left with those sessions matching the name which can be saved to $quser, so no extra filtering is needed down the line.
So I modified the script this way and it works, sort of. It logs off account from servers, which is the main goal. There are still some glitches, like the message it sends from the first Write-Host doesn't give server's name, the message from second one gives a different value than it should (it gives [1] value after -split instead of [2] for some reason; but those are not really that important things, even though I will try to make at least the first message right) and $SessionIDs still gives only the first value, but usually you shouldn't have more than one RDP session per server. I've seen more sessions of one user, but that is very rare. But I'd also like to fix this if possible. Nevertheless, the script basically does the most important thing. But if someone has a suggestion how to fix the glitches I mentioned I would be grateful.
$ErrorActionPreference = "Silentlycontinue"
$Servers = (Get-ADComputer -Filter *).Name
$ScriptBlock = {
$Sessions = quser | ?{$_ -match $env:USERNAME}
if (($Sessions).Count -ge 1)
{
$SessionIDs = , ($Sessions -split ' +')[2]
Write-Host "Found $(($SessionIDs).Count) user login(s) on $Server."
Foreach ($SessionID in $SessionIDs)
{
Write-Host "Logging off session $SessionID..."
logoff $SessionID
}
}
}
foreach ($Server in $Servers)
{
if ($Server -ne $env:COMPUTERNAME)
{
Invoke-Command -ComputerName $Server -ScriptBlock $ScriptBlock
}
}

Powershell. Difference between piping commands and using Foreach-Object

Sorry if this question is already been answered, I could find similar questions but not the exact one I need to ask.
Let's take two examples:
1. Get-Process -name msedge,putty | Stop-Process
2. Get-Process -name msedge,putty | Foreach-Object {Stop-Process $_}
Both are doing the same operation. What about the methods used in each one? Are they the same in the sense that the first example just omits the Foreach-Object construction for the sake of code readability/aesthetics?
The first example requires the Cmdlet to support binding of the relevant parameters via the pipeline. In your case Stop-Process will bind the Process object from the pipeline to it's -InputObject parameter.
You can check that using get-help stop-process -Parameter * and see which parameters have "Accept pipeline input?" set to true.
In case a Cmdlet does not support the binding of the relevant parameters values you can wrap ForEach-Object around it, like you did in the second example. This way you use the automatic variable $_ to bind the current pipeline object (or information that you derive from it) "manually" to the corresponding parameter.
What approach should you use if a Cmdlet supports the binding of parameter values from the pipeline? That unfortunately depends. It is possible to write a Cmdlet that behaves differently, depending on how the parameter values are bound. Let me illustrate this point:
function Test-BindingFoo {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline)]
[string[]]
$InputParameter
)
begin {
Write-Host "[BEGIN]"
}
process {
foreach ($value in $InputParameter) {
Write-Host "The current value is: $value"
}
}
end {
Write-Host "[END]"
}
}
If you execute this Cmdlet using the pipeline binding the Begin block of the function is executed exactly once:
❯ "foo1", "foo2" | Test-BindingFoo
[BEGIN]
The current value is: foo1
The current value is: foo2
[END]
If you use ForEach-Object the Begin block is executed every time an object passes through the pipeline:
❯ "foo1", "foo2" | ForEach-Object { Test-BindingFoo $_ }
[BEGIN]
The current value is: foo1
[END]
[BEGIN]
The current value is: foo2
[END]
In well implemented Cmdlets the difference here should not matter. But I found it useful to be aware of what happens inside a Cmdlet when parameteres are passed in in the ways that we have discussed here.
You can do it this way too and use the kill method on the process object (operation statement):
Get-Process msedge,putty | Foreach-Object kill
# or
Get-Process msedge,putty | Foreach-Object -membername kill

System.Windows.Forms.TextBox not displaying output

I am trying to create a GUI for my script that will go and delete certain files that match a certain name that is older than a set time period in a directory and show a what if before deleting. Everything was going well until I tried to put the output into a Textbox. The output displays in the console fine but won't display in the textbox. I have narrowed it down to the command I am running, as if I simply remove it and run 'ping google.com' it outputs fine. Please find my code below:
$scanbutton.Location = '380,84'
$scanbutton.text = 'Scan Directory'
$scanbutton.height = 25
$scanbutton.Width = 100
$scanbutton.Add_Click({
$result.Text = get-childitem $folderBrowser.SelectedPath -include "cat*.png" -force -recurse | where-object { (-not $_.PSIsContainer) -and ($_.LastWriteTime -lt (get-date).AddDays(-0)) } | remove-item -whatif
#$result.Text = ping google.com
$Form.Controls.Add($result)
})
Anyone have any ideas why this is? I am still very new to all this so please be nice. Also how can I get the output to follow new lines like in the console? At the moment it just has it as one long string (when I do ping). Please let me know if you need anything else from me.
Thank you in advance.
IC
As commented, the Remove-Item cmdlet does not return anything you can capture as text in a textbox. Neither does the -WhatIf switch, which like Write-Host is designed to not return anything, but write directly onto the console.
In your case, you can create and write your own info in the TextBox. Something like:
$resultBox = [System.Windows.Forms.TextBox]::new()
# do the Location, Size and whatever it takes here
# make the textbox accept multiple lines of text
$resultBox.Multiline = $true
# add the control to the form after you have created it
# NOT inside the $scanbutton.Add_Click() event handler
$Form.Controls.Add($resultBox)
$scanbutton.Add_Click({
# get a list of files to remove (just the FullNames)
$filesToRemove = Get-ChildItem $folderBrowser.SelectedPath -Filter "cat*.png" -File -Force -Recurse |
Where-Object { ($_.LastWriteTime -lt (Get-Date).AddDays(-60).Date) } |
Select-Object -ExpandProperty FullName
# write the file FullNames in the textbox
$resultBox.Text = "Removing files:`r`n{0}" -f ($filesToRemove -join [environment]::NewLine)
$filesToRemove | Remove-Item -WhatIf
})

Breaking foreach loop in PowerShell v2

I'm having trouble terminating a foreach-object loop in PowerShell v2. For a rough idea of the task I'm trying to accomplish, here's the pseudo-code:
Read lists of host machines from a text file
For each host in the text file get Win32_Product (filtered against an exclusion list),
convert output to html and save.
The reason for the script is that I've amassed a text file listing all applications included on standard client images, and would like to periodically scan hosts from another text file to see if there are any unauthorized, sketchy or otherwise unnecessary applications on the host machines.
The code does work in a rough sense, but the main issue I'm having is that the script will not terminate without manual intervention. I guess the component I'm missing here is to run the loop until some condition exists (ie. first line in the host file is encountered for the second time), then terminates the script. Although this is the method I've envisioned, I am always open to other logic, especially if its more efficient.
Here's the actual code:
Get-Content c:\path\to\testhostlist.txt | Foreach-Object {
Get-WmiObject Win32_Product |
Where-Object { $_.Name -f "'C:\path\to\testauthapplist.txt'" |
ConvertTo-Html name,vendor,version -title $name -body "<H2>Unauthorized Applications.</H2>"}} |
Set-Content c:\path\to\unauthapplisttest.html
I don't see how the first line of the host file (I infer you mean testhostlist.tx) would ever be encountered a second time, since you're only listing it once. This doesn't even seem to be an infinite loop that would need an exit condition. Foreach-Object doesn't repeat indefinitely.
It seems to me that the problem is not that the loop doesn't exit without a condition, it's that the syntax is invalid.
Where-Object filters the pipeline by passing only objects that meet a certain condition, but the scriptblock that follows doesn't perform a boolean test.
In fact, the content of the scriptblock doesn't appear valid in and of itself. -f is the format operator, and takes a format string as the left operand, but $_.Name is not a format string.
I'm going to take a guess here, based on your description, that the idea is to filter the results of Get-WmiObject Win32_Product for objects whose Name property isn't listed in testauthapplist.txt (I take it that's the "exclusion list" you're referring to). If so, this is the correct syntax:
Get-Content c:\path\to\testhostlist.txt | %{
Get-WmiObject Win32_Product | ?{
(Get-Content 'C:\path\to\testauthapplist.txt') -notcontains $_.Name
} | ConvertTo-Html name,vendor,version -title $name -body "<H2>Unauthorized Applications.</H2>"
} | Set-Content c:\path\to\unauthapplisttest.html
(Note that %{} and ?{} are just abbreviations for Foreach-Object and Where-Object, respectively.)
If i understood you correctly you are trying to stop your Script completely? If so did you try Break?
If you only want to skip a loop use continue
$hostlist = Get-Content c:\path\to\testhostlist.txt
$a = #()
Foreach($item in $hostlist)
{
$a += "<style>"
$a += "BODY{background-color:gray;}"
$a += "TABLE{margin: auto;border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}"
$a += "TH{border-width: 1px;padding: 4px;border-style: solid;border-color: black;background-color:yellow}"
$a += "TD{border-width: 1px;padding: 4px;border-style: solid;border-color: black;background-color:white}"
$a += "h2{color:#fff;}"
$a += "</style>"
Get-WmiObject Win32_Product | select name,vendor,version | sort name | ConvertTo-Html -head $a -body "<Center><H2>Unauthorized Applications.</H2></Center>" | Out-File c:\path\to\$item"-applist.html"
}

PowerShell equivalent of LINQ Any()?

I would like to find all directories at the top level from the location of the script that are stored in subversion.
In C# it would be something like this
Directory.GetDirectories(".")
.Where(d=>Directories.GetDirectories(d)
.Any(x => x == "_svn" || ".svn"));
I'm having a bit of difficulty finding the equivalent of "Any()" in PowerShell, and I don't want to go through the awkwardness of calling the extension method.
So far I've got this:
Get-ChildItem | ? {$_.PsIsContainer} | Get-ChildItem -force | ? {$_.PsIsContainer -and $_.Name -eq "_svn" -or $_.Name -eq ".svn"
This finds me the svn directories themselves, but not their parent directories - which is what I want. Bonus points if you can tell me why adding
| Select-Object {$_.Directory}
to the end of that command list simply displays a sequence of blank lines.
To answer the immediate question with a PowerShell v3+ solution:
(Get-ChildItem -Force -Directory -Recurse -Depth 2 -Include '_svn', '.svn').Parent.FullName
-Directory limits the matches to directories, -Recurse -Depth 2 recurses up to three levels (children, grandchildren, and great-grandchildren), Include allows specifying multiple (filename-component) filters, and .Parent.FullName returns the full path of the parent dirs. of the matching dirs., using member-access enumeration (implicitly accessing a collection's elements' properties).
As for the bonus question: select-object {$_.Directory} does not work,
because the \[System.IO.DirectoryInfo\] instances returned by Get-ChildItem have no .Directory property, only a .Parent property; Select-Object -ExpandProperty Parent should have been used.
In addition to only returning the property value of interest, -ExpandProperty also enforces the existence of the property. By contrast, Select-Object {$_.Directory} returns a custom object with a property literally named $_.Directory, whose value is $null, given that the input objects have no .Directory property; these $null values print as empty lines in the console.
As for the more general question about a PowerShell equivalent to LINQ's .Any() method, which indicates [with a Boolean result] whether a given enumerable (collection) has any elements at all / any elements satisfying a given condition:
Natively, PowerShell offers no such equivalent, but the behavior can be emulated:
Using the PowerShell v4+ intrinsic.Where() method:
Caveat: This requires collecting the entire input collection in memory first, which can be problematic with large collections and/or long-running input commands.
(...).Where({ $_ ... }, 'First').Count -gt 0
... represents the command of interest, and $_ ... the condition of interest, applied to each input object, where PowerShell's automatic $_ variable refers to the input object at hand; argument 'First' ensures that the method returns once the first match has been found.
For example:
# See if there's at least one value > 1
PS> (1, 2, 3).Where({ $_ -gt 1 }, 'First').Count -gt 0
True
Using the pipeline: Testing whether a command produced at least one output object [matching a condition]:
The advantage of a pipeline-based solution is that it can act on a command's output one by one, as it is being produced, without needing to collect the entire output in memory first.
If you don't mind that all objects are enumerated - even if you only care if there is at least one - use Paolo Tedesco's helpful extension to JaredPar's helpful answer.
The down-side of this approach is that you always have to wait for a (potentially long-running) command to finish producing all output objects, even though - logically - the determination whether there are any output objects can be made as soon as the first object is received.
If you want to exit the pipeline as soon as one [matching] object has been encountered, you have two options:
[Ad-hoc: Easy to understand, but cumbersome to implement]
Enclose the pipeline in a dummy loop and use break to break out of the pipeline and that loop (... represents the command whose output to test, and $_ ... match the condition):
# Exit on first input object.
[bool] $haveAny = do { ... | % { $true; break } } while ($false)
# Exit on first input object that matches a condition.
[bool] $haveAny = do { ... | % { if ($_ ...) { $true ; break } } } while ($false)
[Use a PowerShell v3+ self-contained utility function that is nontrivial to implement]
See the implementation of function Test-Any below.
It can be added to scripts or, for use in interactive sessions, to your $PROFILE file.
PowerShell v3+: Optimized utility function Test-Any
The function is nontrivial, because as of PowerShell (Core) v7.2.x, there is no direct way to exit a pipeline prematurely, so a workaround based on .NET reflection and a private type is currently necessary.
If you agree that there should be such a feature, take part in the conversation in GitHub issue #3821.
#requires -version 3
Function Test-Any {
[CmdletBinding()]
param(
[ScriptBlock] $Filter,
[Parameter(ValueFromPipeline = $true)] $InputObject
)
process {
if (-not $Filter -or (Foreach-Object $Filter -InputObject $InputObject)) {
$true # Signal that at least 1 [matching] object was found
# Now that we have our result, stop the upstream commands in the
# pipeline so that they don't create more, no-longer-needed input.
(Add-Type -Passthru -TypeDefinition '
using System.Management.Automation;
namespace net.same2u.PowerShell {
public static class CustomPipelineStopper {
public static void Stop(Cmdlet cmdlet) {
throw (System.Exception) System.Activator.CreateInstance(typeof(Cmdlet).Assembly.GetType("System.Management.Automation.StopUpstreamCommandsException"), cmdlet);
}
}
}')::Stop($PSCmdlet)
}
}
end { $false }
}
if (-not $Filter -or (Foreach-Object $Filter -InputObject $InputObject)) defaults to true if $Filter wasn't specified, and otherwise evaluates the filter (script block) with the object at hand.
The use of ForEach-Object to evaluate the filter script block ensures that $_ binds to the current pipeline object in all scenarios, as demonstrated in PetSerAl's helpful answer here.
The (Add-Type ... statement uses an ad-hoc type created with C# code that uses reflection to throw the same exception that Select-Object -First (PowerShell v3+) uses internally to stop the pipeline, namely [System.Management.Automation.StopUpstreamCommandsException], which as of PowerShell v5 is still a private type.
Background here:
http://powershell.com/cs/blogs/tobias/archive/2010/01/01/cancelling-a-pipeline.aspx
A big thank-you to PetSerAl for contributing this code in the comments.
Examples:
PS> #() | Test-Any false
PS> Get-EventLog Application | Test-Any # should return *right away* true
PS> 1, 2, 3 | Test-Any { $_ -gt 1 } # see if any object is > 1 true
Background information
JaredPar's helpful answer and Paolo Tedesco's helpful extension fall short in one respect: they don't exit the pipeline once a match has been found, which can be an important optimization.
Sadly, even as of PowerShell v5, there is no direct way to exit a pipeline prematurely.
If you agree that there should be such a feature, take part in the conversation in GitHub issue #3821.
A naïve optimization of JaredPar's answer actually shortens the code:
# IMPORTANT: ONLY EVER USE THIS INSIDE A PURPOSE-BUILT DUMMY LOOP (see below)
function Test-Any() { process { $true; break } end { $false } }
The process block is only entered if there's at least one element in the pipeline.
Small caveat: By design, if there's no pipeline at all, the process block is still entered, with $_ set to $null, so calling Test-Any outside of a pipeline unhelpfully returns $true. To distinguish between between $null | Test-Any and Test-Any, check $MyInvocation.ExpectingInput, which is $true only in a pipeline: Thanks, PetSerAl
function Test-Any() { process { $MyInvocation.ExpectingInput; break } end { $false } }
$true, written to the output stream, signals that at least one object was found.
break then terminates the pipeline and thus prevents superfluous processing of additional objects. HOWEVER, IT ALSO EXITS ANY ENCLOSING LOOP - break is NOT designed to exit a PIPELINEThanks, PetSerAl
.
If there were a command to exit the pipeline, this is where it would go.
Note that return would simply move on to the next input object.
Since the process block unconditionally executes break, the end block is only reached if the process block was never entered, which implies an empty pipeline, so $false is written to the output stream to signal that.
Unfortunately there is no equivalent in PowerShell. I wrote a blog post about this with a suggestion for a general purpose Test-Any function / filter.
function Test-Any() {
begin {
$any = $false
}
process {
$any = $true
}
end {
$any
}
}
Blog post: Is there anything in that pipeline?
A variation on #JaredPar's answer, to incorporate the test in the Test-Any filter:
function Test-Any {
[CmdletBinding()]
param($EvaluateCondition,
[Parameter(ValueFromPipeline = $true)] $ObjectToTest)
begin {
$any = $false
}
process {
if (-not $any -and (& $EvaluateCondition $ObjectToTest)) {
$any = $true
}
}
end {
$any
}
}
Now I can write "any" tests like
> 1..4 | Test-Any { $_ -gt 3 }
True
> 1..4 | Test-Any { $_ -gt 5 }
False
You can use the original LINQ Any:
[Linq.Enumerable]::Any($list)
It's actually quite simple - just select first $true (formatted for clarity):
[bool] ($source `
| foreach { [bool] (<predicate>) } `
| where { $_ } `
| select -first 1)
Alternative way:
($source `
| where { <predicate> } `
| foreach { $true } `
| select -first 1)
My approach now was:
gci -r -force `
| ? { $_.PSIsContainer -and $_.Name -match "^[._]svn$" } `
| select Parent -Unique
The reason why
select-object {$_.Directory}
doesn't return anything useful is that there is no such property on a DirectoryInfo object. At least not in my PowerShell.
To elaborate on your own answer: PowerShell can treat most non-empty collections as $true, so you can simply do:
$svnDirs = gci `
| ? {$_.PsIsContainer} `
| ? {
gci $_.Name -Force `
| ? {$_.PSIsContainer -and ($_.Name -eq "_svn" -or $_.Name -eq ".svn") }
}
I ended up doing it with a count:
$directoryContainsSvn = {
(Get-ChildItem $_.Name -force | ? {$_.PsIsContainer -and $_.Name -eq "_svn" -or $_.Name -eq ".svn"} | Measure-Object).Count -eq 1
}
$svnDirs = Get-ChildItem | ? {$_.PsIsContainer} | ? $directoryContainsSvn
You can tighten this up a bit:
gci -fo | ?{$_.PSIsContainer -and `
(gci $_ -r -fo | ?{$_.PSIsContainer -and $_ -match '[_.]svn$'})}
Note - passing $__.Name to the nested gci is unnecessary. Passing it $_ is sufficent.
I recommend the following solution:
<#
.SYNOPSIS
Tests if any object in an array matches the expression
.EXAMPLE
#( "red", "blue" ) | Where-Any { $_ -eq "blue" } | Write-Host
#>
function Where-Any
{
[CmdletBinding()]
param(
[Parameter(Mandatory = $True)]
$Condition,
[Parameter(Mandatory = $True, ValueFromPipeline = $True)]
$Item
)
begin {
[bool]$isMatch = $False
}
process {
if (& $Condition $Item) {
[bool]$isMatch = $true
}
}
end {
Write-Output $isMatch
}
}
# optional alias
New-Alias any Where-Any
This is the best method that I found so far (does not iterate over all elements if already found a true, and does not break the pipeline):
From LINQ Any() equivalent in PowerShell
It’s possible to use a built-in $input variable that contains the whole pipeline in a scope of function.
So, the desired code could look like the following:
function Test-Any([scriptBlock] $scriptBlock = {$true}, [scriptBlock] $debugOut = $null)
{
if ($debugOut)
{
Write-Host(“{0} | % {{{1}}}” -f $input, $scriptBlock)
}
$_ret = $false;
$_input = ($input -as [Collections.IEnumerator])
if ($_input)
{
while ($_input.MoveNext())
{
$_ = $_input.Current;
Write-Host $_
if ($debugOut)
{
Write-Host(“Tested: [{0}]” -f (&$debugOut))
}
if (&$scriptBlock)
{
if ($debugOut)
{
Write-Host(“Matched: [{0}]” -f (&$debugOut))
}
$_ret = $true
break
}
}
}
$_ret
}
I think that the best answer here is the function proposed by #JaredPar, but if you like one-liners as I do I'd like to propose following Any one-liner:
# Any item is greater than 5
$result = $arr | %{ $match = $false }{ $match = $match -or $_ -gt 5 }{ $match }
%{ $match = $false }{ $match = $match -or YOUR_CONDITION }{ $match } checks that at least one item match condition.
One note - usually the Any operation evaluates the array until it finds the first item matching the condition. But this code evaluates all items.
Just to mention, you can easily adjust it to become All one-liner:
# All items are greater than zero
$result = $arr | %{ $match = $false }{ $match = $match -and $_ -gt 0 }{ $match }
%{ $match = $false }{ $match = $match -and YOUR_CONDITION }{ $match } checks that all items match condition.
Notice, that to check Any you need -or and to check All you need -and.
I took a more linq-style approach.
I know this question is probably super old. I used this to accomplish my needs:
PS> $searchData = "unn"
PS> $StringData = #("unn", "dew", "tri", "peswar", "pymp")
PS> $delegate = [Func[string,bool]]{ param($d); return $d -eq $searchData }
PS> [Linq.Enumerable]::Any([string[]]$StringData, $delegate)
Taken from here:
https://www.red-gate.com/simple-talk/dotnet/net-framework/high-performance-powershell-linq/#post-71022-_Toc482783751

Resources