continue statement confusing in powershell, looks like break statement - powershell

My code looks like:
1..10 | % {
$i=$_
10..20 | % {
if ($_ -gt 12) {
continue
}
Write-Host $i $_
}
}
Why the output is:
1 10
1 11
1 12
It seems the continue statement in PoweShell is not different with other language, why PowerShell is designed like this?
If I change the continue to return, then I get the expect result.

As PeterSerAI pointed out in his comment, you don't use a loop in your code, you are instead using the Foreach-Object cmdlet which is different.
Just use a foreach loop instead:
foreach($obj in 1.. 10)
{
$i = $obj
foreach($obj2 in 10 ..20) {
if ($obj2 -gt 12) {
continue
}
Write-Host $obj $obj2
}
}

Related

powershell, foreach, get the number of line

Is there any possibility to get the number of a $_. Variable in a foreach pipe?
Example:
$a = 1..9
$a | foreach {if ($_ -eq 5) { "show the line number of $_"}}
I hope you know what i mean.
Thanks!
Array.IndexOf Method (.NET Framework)
Searches for the specified object and returns the index of its first
occurrence in a one-dimensional array or in a range of elements in the
array.
Examples:
PS D:\PShell> $a = "aa","bb","cc","dd","ee"
PS D:\PShell> $a.IndexOf('cc')
2
PS D:\PShell> $a=101..109
PS D:\PShell> $a.IndexOf(105)
4
PS D:\PShell> $a |foreach {if ($_ -eq 105) {"$($a.IndexOf($_)) is the line number of $_"}}
4 is the line number of 105
PS D:\PShell>
If you are trying to get script file name and line numbers you can use the $MyInvocation variable in a function call to get the line number in the script file where the function was called.
Save the following in a ps1 script file:
function Get-CurrentLineNumber {
return $MyInvocation.ScriptLineNumber
}
function Get-CurrentFileName {
return $MyInvocation.ScriptName
}
1..9 | % {
if ($_ -eq 5) {
"{0}:{1} value is {2}" -f (Get-CurrentFileName), (Get-CurrentLineNumber), $_
}
}
After running the script, you should get something like the following output:
// c:\Users\bob\Desktop\Test.ps1:11 value is 5
Shamelessly borrowed from poshoholic's blog here:
https://poshoholic.com/2009/01/19/powershell-quick-tip-how-to-retrieve-the-current-line-number-and-file-name-in-your-powershell-script/
For the original question as it sounds:
foreach ($x in 1..10) {
Write-Host "Current item number is $($foreach.current)"
}

Converting strings to timespans, $PSItem in 'switch'?

I have a bunch of strings, in the form of:
'3m 36s', '24m 38s', '59s'
, to be converted to timespans. My current "solution" is:
'3m 36s', '24m 38s', '59s' |ForEach-Object {
$s = 0
$m = 0
$h = 0
$PSItem.Split(' ') |ForEach-Object {
$item = $PSItem
switch ($PSItem[-1])
{
's'
{
$s = $item.TrimEnd('s')
}
'm'
{
$m = $item.TrimEnd('m')
}
'h'
{
$h = $item.TrimEnd('h')
}
Default
{
Write-Error 'Ooops...' -ErrorAction Stop
}
}
}
$timespan = New-TimeSpan -Hours $h -Minutes $m -Seconds $s
# ToString() is used just to get some easy to read output
$timespan.ToString()
}
While it seems to work for me, I have two issues with the above:
Is the general approach
ForEach -> Split(' ') -> ForEach -> switch
OK-ish? Are there any alternative/better ways of doing the conversion?
I tried using $PSItem in the switch
It seems that the switch construct has it's "own pipeline"
# $item = $PSItem
switch ($PSItem[-1])
{
's'
{
$PSItem
}
}
-- in the above $PSItem evaluates to 's'(, 'm', the value matched). What is actually going on? (internaly?)
I would take one ForEach loop out of things by performing that loop with the Switch command. Here's what I'd end up with:
'3m 36s', '59s', '24m 38s' |%{
$TSParams = #{}
Switch($_.Split()){
{$_[-1] -eq 's'}{$TSParams.Add('Seconds', ([int]$_.trim('s')))}
{$_[-1] -eq 'm'}{$TSParams.Add('Minutes', ([int]$_.trim('m')))}
{$_[-1] -eq 'h'}{$TSParams.Add('Hours', ([int]$_.trim('h')))}
}
New-TimeSpan #TSParams
}
For each string it creates an empty hashtable, then loops through each item of the Split() method, adding the appropriate time to the hashtable. Then it splats that to the New-TimeSpan command, and moves to the next item in the ForEach loop. I tried it locally and had some issues initially when the numbers did not cast as an int, and it tried to convert them to a DateTime, which is why I type cast them in the above code.

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.

How to enable OGV in a powershell function

How to enable Out-GridView in a function.
I mean,
"Hello" | Out-GridView
Works.
But if I have a simple function like this
function Count ([int]$times)
{
for ($i=1; $i -le $times;$i++)
{
Write-Host $i
}
}
Why calling Count 5 doest not support a pipe to Out-GridView?
The problem you are having is that Write-Host does not output to the pipeline at all. It writes directly to the screen. Replace Write-Host with Write-Output and it should work fine.
BTW, Write-Output is the default so you could just use:
function Count ([int]$times)
{
for ($i=1; $i -le $times;$i++)
{
$i
}
}
or even more simply:
function Count([int]$times)
{
1..$times
}

Detect number of processes running with the same name

Any ideas of how to write a function that returns the number of instances of a process is running?
Perhaps something like this?
function numInstances([string]$process)
{
$i = 0
while(<we can get a new process with name $process>)
{
$i++
}
return $i
}
Edit: Started to write a function... It works for a single instance but it goes into an infinite loop if more than one instance is running:
function numInstances([string]$process)
{
$i = 0
$ids = #()
while(((get-process $process) | where {$ids -notcontains $_.ID}) -ne $null)
{
$ids += (get-process $process).ID
$i++
}
return $i
}
function numInstances([string]$process)
{
#(get-process -ea silentlycontinue $process).count
}
EDIT: added the silently continue and array cast to work with zero and one processes.
This works for me:
function numInstances([string]$process)
{
#(Get-Process $process -ErrorAction 0).Count
}
# 0
numInstances notepad
# 1
Start-Process notepad
numInstances notepad
# many
Start-Process notepad
numInstances notepad
Output:
0
1
2
Though it is simple there are two important points in this solution: 1) use -ErrorAction 0 (0 is the same as SilentlyContinue), so that it works well when there are no specified processes; 2) use the array operator #() so that it works when there is a single process instance.
Its much easier to use the built-in cmdlet group-object:
get-process | Group-Object -Property ProcessName
There is a nice one-liner : (ps).count
(Get-Process | Where-Object {$_.Name -eq 'Chrome'}).count
This will return you the number of processes with same name running. you can add filters to further format your data.