Exiting the function while in loops like Foreach-Object in PowerShell - powershell

I have a function like this in Powershell:
function F()
{
$something | Foreach-Object {
if ($_ -eq "foo"){
# Exit from F here
}
}
# do other stuff
}
if I use Exit in the if statement, it exits powershell, I don't want this behavior. If I use return in the if statement, foreach keeps executing and the rest of the function is also executed. I came up with this:
function F()
{
$failed = $false
$something | Foreach-Object {
if ($_ -eq "foo"){
$failed = $true
break
}
}
if ($failed){
return
}
# do other stuff
}
I basically introduced a sentinel variable holding if I broke out of the loop or not. Is there a cleaner solution?

Any help?
function F()
{
Trap { Return }
$something |
Foreach-Object {
if ($_ -eq "foo"){ Throw }
else {$_}
}
}
$something = "a","b","c","foo","d","e"
F
'Do other stuff'
a
b
c
Do other stuff

I'm not entirely sure of your specific requirements, but I think you can simplify this by looking at it a different way. It looks like you just want to know if any $something == "foo" in which case this would make things a lot easier:
if($something ? {$_ -eq 'foo')) { return }
? is an alias for Where-Object. The downside to this is that it will iterate over every item in the array even after finding a match, so...
If you're indeed searching a string array, things can get even simpler:
if($something -Contains 'foo') { return }
If the array is more costly to iterate over, you might consider implementing an equivalent of the LINQ "Any" extension method in Powershell which would allow you to do:
if($something | Test-Any {$_ -eq 'foo'}) { return }
As an aside, while exceptions in the CLR aren't that costly, using them to direct procedural flow is an anti-pattern as it can lead to code that's hard to follow, or, put formally, it violates the principal of least surprise.

Related

Return not behaving as i expect

I'm solving the problem
here is the solution i made first
using namespace System.Linq
$DebugPreference = "Continue"
class kata{
static [string] FirstNonRepeatingLetter ([String]$string_){
[Enumerable]::GroupBy([char[]]$string_, [func[char, char]]{$args[0]}).Foreach{
if ($_.Count -eq 1){
Write-Debug ($_.key)
return $_.Key
}
}
return "~~~"
}
}
Write-Debug ([kata]::FirstNonRepeatingLetter("stress"))
it's return
DEBUG: t
DEBUG: r
DEBUG: e
DEBUG: ~~~
this is not what i expected
I try this
using namespace System.Linq
$DebugPreference = "Continue"
class kata{
static [string] FirstNonRepeatingLetter ([String]$string_){
$groups = [Enumerable]::GroupBy([char[]]$string_, [func[char, char]]{$args[0]})
foreach ($group in $groups)
{
if ($group.Count -eq 1){
return $group.Key
}
}
return "~~~"
}
}
Write-Debug ([kata]::FirstNonRepeatingLetter("stress"))
and then what I wanted to see
DEBUG: t
and now I don't understand why these codes work differently...
You're executing return inside a script block ({ ... }) you've passed to the .ForEach() array method method:
In script blocks passed as arguments (as opposed to blocks that are integral to statements such as if and foreach), return exits only the block itself.
Therefore, your .ForEach() enumeration continues after each return; that is, while execution inside the script block is terminated, execution continues with the next .ForEach() iteration.
There actually is no direct way to stop the enumeration of .ForEach() (by contrast, the related .Where() array method has an optional parameter that accepts a 'First' to stop enumerating after finding the first match).
You have two options:
Set a Boolean flag inside your script block to indicate whether a result has been found, and exit the script block right away if the flag is found to be set.
The obvious downside to this is that the enumeration always runs to completion.
Use a dummy loop so that you can repurpose a break statement to break out of the loop and thereby also out of the .ForEach() enumeration.
Note: Without the dummy loop, break would be looking up the entire call stack for a loop to break out of; if it finds none, execution would terminate overall - see this answer for more information.
Either way, you need a return statement outside to script block in order to return a value from your class method.
Here's how to implement the dummy-loop approach:
using namespace System.Linq
$DebugPreference = 'Continue'
class kata{
static [string] FirstNonRepeatingLetter ([String]$string_){
$result = '~~~'
do { # dummy loop
[Enumerable]::GroupBy([char[]]$string_, [func[char, char]]{$args[0]}).Foreach{
if ($_.Count -eq 1){
Write-Debug ($_.key)
$result = $_.Key
break # break out of the dummy loop
}
}
} while ($false)
return $result
}
}
Write-Debug ([kata]::FirstNonRepeatingLetter('stress'))
If you can live with doing that without a class, but a normal function instead, this should work:
function Get-FirstNonRepeatingCharacter ([string]$string) {
($string.ToCharArray() | Group-Object | Where-Object { $_.Count -eq 1 } | Select-Object -First 1).Name
}
Get-FirstNonRepeatingCharacter "sTress" # --> T

Filter processes by module's FileName

If I need to filter processes by a module's FileName, the following code does the job:
Get-Process | where { $_.Modules.FileName -eq "xxx\yyy.dll”) }
But if I need to filter modules by FileName starting with a string, the following code doesn't seem to work:
Get-Process | where { $_.Modules.FileName.StartsWith("xxx\yyy.dll”)) }
As result, I see all the processes in the output. I'm very confused why filtering doesn't seem to work in case of StartsWith
The member modules might be a collection. Thus it needs to be iterated too. Like so,
(get-process) | % {
if($_.modules -ne $null) { # No modules, no action
$_.modules | ? { $_.filename.tolower().startswith("c:\program") }
}
}
As for the question, there are actually two iterations. Let's use explicit variables instead of pipelining and printing the acutal module files. Passing multiple $_s around is not easy to read syntax anyway. Like so,
foreach ($p in get-process) {
if ($p.modules -ne $null){
write-host $p.id $p.ProcessName
foreach($m in $p.modules){
if ($m.filename.tolower().startswith("c:\program") ) {
write-host `t $m.moduleName $m.FileName # ` markdown bug
}
}
write-host
}
}

PowerShell how to use $MyInvocation for recursion

I am trying to implement some recursive functions with PowerShell. Here is the basic function:
function MyRecursiveFunction {
param(
[parameter(Mandatory=$true,ValueFromPipeline=$true)]
$input
)
if ($input -is [System.Array] -And $input.Length -eq 1) {
$input = $input[0]
}
if ($input -is [System.Array]) {
ForEach ($i in $input) {
$i | ##### HOW DO I USE $MyInvocation HERE TO CALL MyRecursiveFunction??? #####
}
return
}
# Do something with the single object...
}
I have looked at Invoke-Expression and Invoke-Item but have not been able to get the syntax right. For instance I tried
$i | Invoke-Expression $MyInvocation.MyCommand.Name
I'm guessing there is an easy way to do this if you know the right syntax :-)
A bit of an old question, but since there's no satisfactory answer and I've had the same question, here's my experience.
Calling the function by name will break if the name is changed, or if the function is out of scope. It's also not very nice to manage as changing the function name requires editing all the recursive calls, and it'll likely break in modules imported using the prefix option.
# A simple recursive countdown function
function countdown {
param([int]$count)
$count
if ($count -gt 0) { countdown ($count - 1) }
}
# And a way to break it
$foo = ${function:countdown}
function countdown { 'failed' }
& $foo 5
$MyInvocation.InvocationName is slightly nicer to work with, but will still break in the above example (although for different reasons).
The best way would seem to be calling the scriptblock of the function, $MyInvocation.MyCommand.ScriptBlock. That way it'll still work regardless of the function name/scope.
function countdown {
param([int]$count)
$count
if ($count -gt 0) { & $MyInvocation.MyCommand.ScriptBlock ($count - 1) }
}
Just Call the function:
$i | MyRecursiveFunction
To call it without knowing the name of the function you should be able to call it with $myInvocation.InvocationName:
Invoke-Expression "$i | $($myInvocation.InvocationName)"

Powershell: idiomatic way of mutating items passing through a stream?

When processing an input stream it's sometimes necessary to effect changes on the objects passing through the stream. It's also useful to allow those objects to pass out the other end so that they may be piped into other processes. Is there a more idiomatic/concise way than this to mutate and pass along?
$input | % {
if ($_.Office.length -ne 11) { $_.Errors += "Bad office" }
$_ #needed to allow additional piping
}
Some other operator? Something like... %!
$input | %! {
if ($_.Office.length -ne 11) { $_.Errors += "Bad office" }
} #whatever goes in must come out
I don't see anything wrong with the first example there, but you could write your own function and then use that:
function Do-Modification {
[CmdletBinding()]
param(
[Parameter(
ValueFromPipeline=$true
)]
$myObject
)
Process {
if ($myObject.Office.length -ne 11) {
$myObject.Errors += "Bad Office"
}
$myObject
}
}
$inputs | Do-Modification
# or to use it in a larger ForEach-Object (even though it's redundant here)
$inputs | ForEach-Object {
$_ | Do-Modification
}
Edit to add:
This is a contrived example; it's probably not worth it to reinvent the wheel (writing your own pipeline function) except for code re-use (where it is very much worth it in my opinion). It's up to you to decide where one approach or the other makes sense.

Is there a short circuit 'or' that returns the first 'true' value?

Scheme has a short-circuiting or that will return the first non-false value:
> (or 10 20 30)
10
> (or #f 20 30)
20
> (or #f)
#f
It does not evaluate its arguments until needed.
Is there something like this already in PowerShell?
Here's an approximation of it:
function or ()
{
foreach ($arg in $args) {
$val = & $arg; if ($val) { $val; break }
}
}
Example:
PS C:\> or { 10 } { 20 } { 30 }
10
Example:
PS C:\> $abc = $null
PS C:\> or { $abc } { 123 }
123
PS C:\> $abc = 456
PS C:\> or { $abc } { 123 }
456
You could do something like this:
10, $false, 20 | ? { $_ -ne $false } | select -First 1
The result is either the first value from the input list that isn't $false, or $null. Since $null is among the values that PowerShell treats as $false in comparisons, the above should do what you want.
As far as I know, there isn't anything like this built in. I think your function looks pretty good.
It might be more idiomatic to make it take pipelined input:
function or
{
foreach ($x in $input) {
$val = & $x; if ($val) { $val; break }
}
}
Example:
PS > $abc = $null
PS > { $abc },{ 123 } | or
123
PS > $abc = 456
PS > { $abc },{ 123 } | or
456
You're trying to make PowerShell use a Scheme-like syntax by way of your function. Don't do this. Write idiomatic PowerShell. Trying to coerce one language into looking like another language just makes things harder on yourself, introduces lots of room for bugs, and will confuse the %$^%&^*( out of whoever has to maintain your code after you're gone.
PowerShell does appear to short-circuit. Put this code in the ISE and set a breakpoint on the write-output lines in each function, then start the debugger (F5):
function first () {
write-output "first"
}
function second() {
write-output "second"
}
$true -or $(first) -or $(second);
$false -or $(first) -or $(second);
$false -or $(second) -or $(first);
$true evaluates to true (obviously), so it doesn't attempt to process the expression beyond that point. When the next to last line processes, only the breakpoint in first processes. When the last line processes, only the breakpoint in second() is hit.
As long as you give the function a good name, I think creating such a function is perfectly idiomatic Powershell. One tweak I would make to OP's implementation is to make passing in a script block optional:
function Select-FirstValue {
$args |
foreach { if ($_ -is [scriptblock]) { & $_ } else { $_ } } |
where { $_ } |
select -First 1
}
Then the caller only has to add { } brackets around arguments that could have side-effects or be performance costly.
I'm using such a function as a simple way to provide default values for params from a config file. It looks something like:
function Do-Something {
param([string]$Arg1)
$Arg1 = Select-FirstValue $Arg1 { Get-ConfigValue 'arg1' } 'default arg1 val'
[...]
}
This way, the config file only is only attempted to be read if the user does not pass in the argument.