I've been experimenting with the different forms of operators/expressions involving parentheses, but I can't find an explanation for an interaction I'm running into. Namely, ( ) and $( ) (subexpression operator) are not equivalents. Nor is it equivalent to #( ) (array operator). For most cases this doesn't matter, but when trying to evaluate the contents of the parentheses as an expression (for example, variable assignment), they're different. I'm looking for an answer on what parentheses are doing when they aren't explicitly one operator or another and the about_ documents don't call this out.
($var = Test-Something) # -> this passes through
$($var = Test-Something) # -> $null
#($var = Test-Something) # -> $null
about_Operators
For the array and subexpression operators, the parentheses are simply needed syntactically. Their only purpose is to wrap the expression the operator should be applied on.
Some examples:
# always return array, even if no child items found
#(Get-ChildItem -Filter "*.log").Count
# if's don't work inside regular parentheses
$(if ($true) { 1 } else { 0 })
When you put (only) parentheses around a variable assignment, this is called variable squeezing.
$v = 1 # sets v to 1 and returns nothing
($v = 1) # sets v to 1 and returns assigned value
You can get the pass-thru version for all your examples by combining variable squeezing with the subexpression operator syntax (that is, adding a second pair of parentheses):
($var = Test-Something)
$(($var = Test-Something))
#(($var = Test-Something))
$( ) is unique. You can put multiple statements inside it:
$(echo hi; echo there) | measure | % count
2
You can also put things you can't normally pipe from, like foreach () and if, although the values won't come out until the whole thing is finished. This allows you to put multiple statements anywhere that just expects a value.
$(foreach ($i in 1..5) { $i } ) | measure | % count
5
$x = 10
if ( $( if ($x -lt 5) { $false } else { $x } ) -gt 20)
{$false} else {$true}
for ($i=0; $($y = $i*2; $i -lt 5); $i++) { $y }
$err = $( $output = ls foo ) 2>&1
Related
I'm looking to have a function in script where I can use a ScriptBlock passed in as either a predicate or with Where-Object.
I can write
cat .\.gitignore | Where-Object { $_.contains('pp') }
and this works; as does:
$f = { $_.contains('pp') }; cat .gitignore | Where-Object $f
however trying
$f.Invoke( 'apple' )
results in
MethodInvocationException: Exception calling "Invoke" with "1" argument(s): "You cannot call a method on a null-valued expression.
Whereas I expected True. So clearly $_ wasn't set.
Likewise
$ff = { echo "args: $args`nauto: $_" }; $ff.Invoke( 'apple' )
outputs
args: apple
auto:
So $_ is clearly not getting set.
'apple' | %{ $_.contains('pp') }
Works, but I want the scriptblock to be a variable and
$f = { $_.contains('pp') }; 'apple' | %$f
Is a compile error.
tl;dr: So how do I set/pass the value of $_ inside a scriptblock I am invoking?
Note, this answer only covers how does $_ gets populated in the context of a process block of a script block. Other use cases can be found in the about_PSItem documentation.
In the context of a process block of a Script Block, the $_ ($PSItem) variable is automatically populated and represents each element coming from the pipeline, i.e.:
$f = { process { $_.contains('pp') }}
'apple' | & $f # True
You can however achieve the same using InvokeWithContext method from the ScriptBlock Class:
$f = { $_.contains('pp') }
$f.InvokeWithContext($null, [psvariable]::new('_', 'apple')) # True
Do note, this method always returns Collection`1. Output is not enumerated.
Worth noting as zett42 points out, the scoping rules of script blocks invoked via it's methods or via the call operator & still apply.
Script Blocks are able to see parent scope variables (does not include Remoting):
$foo = 'hello'
{ $foo }.Invoke() # hello
But are not able to update them:
$foo = 'hello'
{ $foo = 'world' }.Invoke()
$foo # hello
Unless using a scope a modifier (applies only to Value Types):
$foo = 'hello'
{ $script:foo = 'world' }.Invoke()
$foo # world
Or via the dot sourcing operator .:
$foo = 'hello'
. { $foo = 'world' }
$foo # world
# still applies with pipelines!
$foo = 'hello'
'world' | . { process { $foo = $_ }}
$foo # world
See about Scopes for more details.
Using the .Invoke() method (and its variants, .InvokeReturnAsIs() and .InvokeWithContext()) to execute a script block in PowerShell code is best avoided, because it changes the semantics of the call in several respects - see this answer for more information.
While the PowerShell-idiomatic equivalent is &, the call operator, it is not enough here, given that you want want the automatic $_ variable to be defined in your script block.
The easiest way to define $_ based on input is indeed ForEach-Object (one of whose built-in aliases is %):
$f = { $_.contains('pp') }
ForEach-Object -Process $f -InputObject 'apple' # -> $true
Note, however, that -InputObject only works meaningfully for a single input object (though you may pass an array / collection in which case $_ then refers to it as a whole); to provide multiple ones, use the pipeline:
'apple', 'pear' | ForEach-Object $f # $true, $false
# Equivalent, with alias
'apple', 'pear' | % $f
If, by contrast, your intent is simply for your script block to accept arguments, you don't need $_ at all and can simply make your script either formally declare parameter(s) or use the automatic $args variable which contains all (unbound) positional arguments:
# With $args: $args[0] is the first positional argument.
$f = { $args[0].contains('pp') }
& $f 'apple'
# With declared parameter.
$f = { param([string] $fruit) $fruit.contains('pp') }
& $f 'apple'
For more information about the parameter-declaration syntax, see the conceptual about_Functions help topic (script blocks are basically unnamed functions, and only the param(...) declaration style can be used in script blocks).
I got it to work by wrapping $f in () like
$f = { $_.contains('pp') }; 'apple' | %($f)
...or (thanks to #zett42) by placing a space between the % and $ like
$f = { $_.contains('pp') }; 'apple' | % $f
Can even pass in the value from a variable
$f = { $_.contains('pp') }; $a = 'apple'; $a | %($f)
Or use it inside an If-statement
$f = { $_.contains('pp') }; $a = 'apple'; If ( $a | %($f) ){ echo 'yes' }
So it appears that $_ is only set by having things 'piped' (aka \) into it? But why this is and how it works, and if this can be done through .invoke() is unknown to me. If anyone can explain this please do.
From What does $_ mean in PowerShell? and the related documentation, it seems like $PSItem is indeed a better name since it isn't like Perl's $_
I have a nice string that looks like this:
Get-Azsubscription | ForEach-Object {"$($a).) $($_.Name)"; $a++}
It allows me to count how many subscriptions I have on Azure and it adds a number to it. The problem is that the counter starts from 0, which is in my case .) :
How to make that counter start to count from 1 and not from 0?
The ForEach-Object cmdlet takes an additional -Begin block you can use to initialize any variable you want to use:
Get-Azsubscription | ForEach-Object -Begin { $a = 1 } -Process {"$a.) $($_.Name)"; $a++}
To complement Mathias R. Jessen's effective solution:
Indeed you should explicitly initialize your $a variable with the desired starting value.
If $a is uninitialized, as it was in your case:
Using it inside an expandable string ("$a") makes it expand to the empty string.
The first time you apply ++ to it, it is fist implicitly initialized (as an [int] with) value 0, so that it receives value 1. Subsequent ++ operations then perform as usual.
While using a -Begin block with ForEach-Object in order to initialize the variable is conceptually clear, the caveat is that the variable is not scoped to that statement, and lives on afterwards.
Thus, the alternative is to simply initialize it before calling ForEach-Object.
You can also streamline the command a bit by incorporating the ++ operation into the expandable string, but note that you then need to enclose it in (...), in addition to $(...); the reason is that a ++ operation produces no output by default, except if you apply (...), the grouping operator, to pass the value through.
Therefore:
$a = 0 # Initialize the sequence number
Get-Azsubscription | ForEach-Object { "$((++$a)).) $($_.Name)" }
Note the initialization to 0 in combination with the prefix version of ++, the increment operator, so that incrementing to 1 and outputting that value happens on the first iteration.
A simplified example:
$a = 0 # Initialize the sequence number
[pscustomobject] #{ Name='foo' }, [pscustomobject] #{ Name='bar' } |
ForEach-Object { "$((++$a)).) $($_.Name)" }
Output:
1.) foo
2.) bar
Finally, a perhaps more PowerShell-idiomatic solution that constructs custom objects for display, which PowerShell automatically formats nicely for you:
$a = 0 # Initialize the sequence number
[pscustomobject] #{ Name='foo' }, [pscustomobject] #{ Name='bar' } |
ForEach-Object { [pscustomobject] #{ '#' = ++$a; Name = $_.Name } }
Output:
# Name
- ----
1 foo
2 bar
I have trouble of getting index of the current element for multiple elements that are exactly the same object:
$b = "A","D","B","D","C","E","D","F"
$b | ? { $_ -contains "D" }
Alternative version:
$b = "A","D","B","D","C","E","D","F"
[Array]::FindAll($b, [Predicate[String]]{ $args[0] -contains "D" })
This will return:
D
D
D
But this code:
$b | % { $b.IndexOf("D") }
Alternative version:
[Array]::FindAll($b, [Predicate[String]]{ $args[0] -contains "D" }) | % { $b.IndexOf($_) }
Returns:
1
1
1
so it's pointing at the index of the first element. How to get indexes of the other elements?
You can do this:
$b = "A","D","B","D","C","E","D","F"
(0..($b.Count-1)) | where {$b[$_] -eq 'D'}
1
3
6
mjolinor's answer is conceptually elegant, but slow with large arrays, presumably due to having to build a parallel array of indices first (which is also memory-inefficient).
It is conceptually similar to the following LINQ-based solution (PSv3+), which is more memory-efficient and about twice as fast, but still slow:
$arr = 'A','D','B','D','C','E','D','F'
[Linq.Enumerable]::Where(
[Linq.Enumerable]::Range(0, $arr.Length),
[Func[int, bool]] { param($i) $arr[$i] -eq 'D' }
)
While any PowerShell looping solution is ultimately slow compared to a compiled language, the following alternative, while more verbose, is still much faster with large arrays:
PS C:\> & { param($arr, $val)
$i = 0
foreach ($el in $arr) { if ($el -eq $val) { $i } ++$i }
} ('A','D','B','D','C','E','D','F') 'D'
1
3
6
Note:
Perhaps surprisingly, this solution is even faster than Matt's solution, which calls [array]::IndexOf() in a loop instead of enumerating all elements.
Use of a script block (invoked with call operator & and arguments), while not strictly necessary, is used to prevent polluting the enclosing scope with helper variable $i.
The foreach statement is faster than the Foreach-Object cmdlet (whose built-in aliases are % and, confusingly, also foreach).
Simply (implicitly) outputting $i for each match makes PowerShell collect multiple results in an array.
If only one index is found, you'll get a scalar [int] instance instead; wrap the whole command in #(...) to ensure that you always get an array.
While $i by itself outputs the value of $i, ++$i by design does NOT (though you could use (++$i) to achieve that, if needed).
Unlike Array.IndexOf(), PowerShell's -eq operator is case-insensitive by default; for case-sensitivity, use -ceq instead.
It's easy to turn the above into a (simple) function (note that the parameters are purposely untyped, for flexibility):
function get-IndicesOf($Array, $Value) {
$i = 0
foreach ($el in $Array) {
if ($el -eq $Value) { $i }
++$i
}
}
# Sample call
PS C:\> get-IndicesOf ('A','D','B','D','C','E','D','F') 'D'
1
3
6
You would still need to loop with the static methods from [array] but if you are still curious something like this would work.
$b = "A","D","B","D","C","E","D","F"
$results = #()
$singleIndex = -1
Do{
$singleIndex = [array]::IndexOf($b,"D",$singleIndex + 1)
If($singleIndex -ge 0){$results += $singleIndex}
}While($singleIndex -ge 0)
$results
1
3
6
Loop until a match is not found. Assume the match at first by assigning the $singleIndex to -1 ( Which is what a non match would return). When a match is found add the index to a results array.
I've checked and tried a few of the suggestions on StackOverflow but none of them seem to work. I put together an example of what I am trying to accomplish.
[System.Random] $rand = New-Object System.Random
$randomNumbers = New-Object int[] 10;
[int[]] $randomNumbers;
for($i = 0; $i -lt $randomNumbers.Length; $i++)
{
($randomNumbers[$i] = $rand.Next(256)) 2>&1 | Out-Null;
}
I've tried the
> $Null
|Out-Null
2>&1
But none of them seem to suppress the output. It's showing 10 zero's in a row. One for each assignment. How can I suppress that output?
Remove int[]] $randomNumbers;. It is not the assignment that is printed, but the empty array.
other solution for replace your code ;)
[int[]] $randomNumbers=1..10 | %{ Get-Random -maximum 256 }
To complement Andrey Marchuk's effective answer:
[int[]] $randomNumbers looks like a type-bound PowerShell variable declaration, but, since no value is assigned, it is merely a cast: the preexisting value of $randomNumbers - a 10-element array of 0 values - is simply cast to [int[]] (a no-op in this case), and then output - yielding 10 lines with a 0 on each in this case.
A true type-bound assignment that is the (inefficient) equivalent of your New-Object int[] 10 statement is [int[]] $randomNumbers = #( 0 ) * 10.
Note that it is the presence of = <value> that makes this statement an assignment that implicitly creates the variable.
PowerShell has no variable declarations in the conventional sense, it creates variables on demand when you assign to them.
You can, however, use the New-Variable cmdlet to explicitly create variables, which allows you to control additional aspects, such as the variable's scope.
Variable assignments in PowerShell do NOT output anything by default, so there's no need to suppress any output (with | Out-Null, >$null, ...).
That said, you can force a variable assignment to output the assigned value by enclosing the assignment in (...).
$v = 'foo' # no output
($v = 'foo') # enclosed in () -> 'foo' is output
As you've discovered, actively suppressing the output in ($randomNumbers[$i] = $rand.Next(256)) 2>&1 | Out-Null; is unnecessary, because simply omitting the parentheses makes the statement quiet: $randomNumbers[$i] = $rand.Next(256)
Finally, you could simplify your code using the Get-Random cmdlet:
[int[]] $randomNumbers = 1..10 | % { Get-Random -Maximum 256 }
This single pipeline does everything your code does (not sure about performance, but it may not matter).
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
A while back I was reading about multi-variable assignments in PowerShell. This lets you do things like this
64 > $a,$b,$c,$d = "A four word string".split()
65 > $a
A
66 > $b
four
Or you can swap variables in a single statement
$a,$b = $b,$a
What little known nuggets of PowerShell have you come across that you think may not be as well known as they should be?
The $$ command. I often have to do repeated operations on the same file path. For instance check out a file and then open it up in VIM. The $$ feature makes this trivial
PS> tf edit some\really\long\file\path.cpp
PS> gvim $$
It's short and simple but it saves a lot of time.
By far the most powerful feature of PowerShell is its ScriptBlock support. The fact that you can so concisely pass around what are effectively anonymous methods without any type constraints are about as powerful as C++ function pointers and as easy as C# or F# lambdas.
I mean how cool is it that using ScriptBlocks you can implement a "using" statement (which PowerShell doesn't have inherently). Or, pre-v2 you could even implement try-catch-finally.
function Using([Object]$Resource,[ScriptBlock]$Script) {
try {
&$Script
}
finally {
if ($Resource -is [IDisposable]) { $Resource.Dispose() }
}
}
Using ($File = [IO.File]::CreateText("$PWD\blah.txt")) {
$File.WriteLine(...)
}
How cool is that!
A feature that I find is often overlooked is the ability to pass a file to a switch statement.
Switch will iterate through the lines and match against strings (or regular expressions with the -regex parameter), content of variables, numbers, or the line can be passed into an expression to be evaluated as $true or $false
switch -file 'C:\test.txt'
{
'sometext' {Do-Something}
$pwd {Do-SomethingElse}
42 {Write-Host "That's the answer."}
{Test-Path $_} {Do-AThirdThing}
default {'Nothing else matched'}
}
$OFS - output field separator. A handy way to specify how array elements are separated when rendered to a string:
PS> $OFS = ', '
PS> "$(1..5)"
1, 2, 3, 4, 5
PS> $OFS = ';'
PS> "$(1..5)"
1;2;3;4;5
PS> $OFS = $null # set back to default
PS> "$(1..5)"
1 2 3 4 5
Always guaranteeing you get an array result. Consider this code:
PS> $files = dir *.iMayNotExist
PS> $files.length
$files in this case may be $null, a scalar value or an array of values. $files.length isn't going to give you the number of files found for $null or for a single file. In the single file case, you will get the file's size!! Whenever I'm not sure how much data I'll get back I always enclose the command in an array subexpression like so:
PS> $files = #(dir *.iMayNotExist)
PS> $files.length # always returns number of files in array
Then $files will always be an array. It may be empty or have only a single element in it but it will be an array. This makes reasoning with the result much simpler.
Array covariance support:
PS> $arr = '127.0.0.1','192.168.1.100','192.168.1.101'
PS> $ips = [system.net.ipaddress[]]$arr
PS> $ips | ft IPAddressToString, AddressFamily -auto
IPAddressToString AddressFamily
----------------- -------------
127.0.0.1 InterNetwork
192.168.1.100 InterNetwork
192.168.1.101 InterNetwork
Comparing arrays using Compare-Object:
PS> $preamble = [System.Text.Encoding]::UTF8.GetPreamble()
PS> $preamble | foreach {"0x{0:X2}" -f $_}
0xEF
0xBB
0xBF
PS> $fileHeader = Get-Content Utf8File.txt -Enc byte -Total 3
PS> $fileheader | foreach {"0x{0:X2}" -f $_}
0xEF
0xBB
0xBF
PS> #(Compare-Object $preamble $fileHeader -sync 0).Length -eq 0
True
Fore more stuff like this, check out my free eBook - Effective PowerShell.
Along the lines of multi-variable assignments.
$list = 1,2,3,4
While($list) {
$head, $list = $list
$head
}
1
2
3
4
I've been using this:
if (!$?) { # if previous command was not successful
Do some stuff
}
and I also use $_ (current pipeline object) quite a bit, but these might be more known than other stuff.
The fact that many operators work on arrays as well and return the elements where a comparison is true or operate on each element of the array independently:
1..1000 -lt 800 -gt 400 -like "?[5-9]0" -replace 0 -as "int[]" -as "char[]" -notmatch "\d"
This is faster than Where-Object.
Not a language feature but super helpful
f8 -- Takes the text you have put in already and searches for a command that starts with that text.
Tab-search through your history with #
Example:
PS> Get-Process explorer
PS> "Ford Explorer"
PS> "Magellan" | Add-Content "great explorers.txt"
PS> type "great explorers.txt"
PS> #expl <-- Hit the <tab> key to cycle through history entries that have the term "expl"
Love this thread. I could list a ton of things after reading Windows Powershell in Action. There's a disconnect between that book and the documentation. I actually tried to list them all somewhere else here, but got put on hold for "not being a question".
I'll start with foreach with three script blocks (begin/process/end):
Get-ChildItem | ForEach-Object {$sum=0} {$sum++} {$sum}
Speaking of swapping two variables, here's swapping two files:
${c:file1.txt},${c:file2.txt} = ${c:file2.txt},${c:file1.txt}
Search and replace a file:
${c:file.txt} = ${c:file.txt} -replace 'oldstring','newstring'
Using assembly and using namespace statements:
using assembly System.Windows.Forms
using namespace System.Windows.Forms
[messagebox]::show('hello world')
A shorter version of foreach, with properties and methods
ps | foreach name
'hi.there' | Foreach Split .
Use $() operator outside of strings to combine two statements:
$( echo hi; echo there ) | measure
Get-content/Set-content with variables:
$a = ''
get-content variable:a | set-content -value there
Anonymous functions:
1..5 | & {process{$_ * 2}}
Give the anonymous function a name:
$function:timestwo = {process{$_ * 2}}
Anonymous function with parameters:
& {param($x,$y) $x+$y} 2 5
You can stream from foreach () with these, where normally you can't:
& { foreach ($i in 1..10) {$i; sleep 1} } | out-gridview
Run processes in background like unix '&', and then wait for them:
$a = start-process -NoNewWindow powershell {timeout 10; 'done a'} -PassThru
$b = start-process -NoNewWindow powershell {timeout 10; 'done b'} -PassThru
$c = start-process -NoNewWindow powershell {timeout 10; 'done c'} -PassThru
$a,$b,$c | wait-process
Or foreach -parallel in workflows:
workflow work {
foreach -parallel ($i in 1..3) {
sleep 5
"$i done"
}
}
work
Or a workflow parallel block where you can run different things:
function sleepfor($time) { sleep $time; "sleepfor $time done"}
workflow work {
parallel {
sleepfor 3
sleepfor 2
sleepfor 1
}
'hi'
}
work
Three parallel commands in three more runspaces with the api:
$a = [PowerShell]::Create().AddScript{sleep 5;'a done'}
$b = [PowerShell]::Create().AddScript{sleep 5;'b done'}
$c = [PowerShell]::Create().AddScript{sleep 5;'c done'}
$r1,$r2,$r3 = ($a,$b,$c).begininvoke()
$a.EndInvoke($r1); $b.EndInvoke($r2); $c.EndInvoke($r3) # wait
($a,$b,$c).Streams.Error # check for errors
($a,$b,$c).dispose() # cleanup
Parallel processes with invoke-command, but you have to be at an elevated prompt with remote powershell working:
invoke-command localhost,localhost,localhost { sleep 5; 'hi' }
An assignment is an expression:
if ($a = 1) { $a }
$a = $b = 2
Get last array element with -1:
(1,2,3)[-1]
Discard output with [void]:
[void] (echo discard me)
Switch on arrays and $_ on either side:
switch(1,2,3,4,5,6) {
{$_ % 2} {"Odd $_"; continue}
4 {'FOUR'}
default {"Even $_"}
}
Get and set variables in a module:
'$script:count = 0
$script:increment = 1
function Get-Count { return $script:count += $increment }' > counter.psm1 # creating file
import-module .\counter.psm1
$m = get-module counter
& $m Get-Variable count
& $m Set-Variable count 33
See module function definition:
& $m Get-Item function:Get-Count | foreach definition
Run a command with a commandinfo object and the call operator:
$d = get-command get-date
& $d
Dynamic modules:
$m = New-Module {
function foo {"In foo x is $x"}
$x=2
Export-ModuleMember -func foo -var x
}
flags enum:
[flags()] enum bits {one = 1; two = 2; three = 4; four = 8; five = 16}
[bits]31
Little known codes for the -replace operator:
$number Substitutes the last submatch matched by group number.
${name} Substitutes the last submatch matched by a named capture of the form (?).
$$ Substitutes a single "$" literal.
$& Substitutes a copy of the entire match itself.
$` Substitutes all the text from the argument string before the matching portion.
$' Substitutes all the text of the argument string after the matching portion.
$+ Substitutes the last submatch captured.
$_ Substitutes the entire argument string.
Demo of workflows surviving interruptions using checkpoints. Kill the window or reboot. Then start PS again. Use get-job and resume-job to resume the job.
workflow test1 {
foreach ($b in 1..1000) {
$b
Checkpoint-Workflow
}
}
test1 -AsJob -JobName bootjob
Emacs edit mode. Pressing tab completion lists all the options at once. Very useful.
Set-PSReadLineOption -EditMode Emacs
Any command that begins with "get-", you can leave off the "get-":
date
help
End parsing --% and end of parameters -- operators.
write-output --% -inputobject
write-output -- -inputobject
Tab completion on wildcards:
cd \pro*iles # press tab
Compile and import a C# module with a cmdlet inside, even in Osx:
Add-Type -Path ExampleModule.cs -OutputAssembly ExampleModule.dll
Import-Module ./ExampleModule.dll
Iterate backwards over a sequence just use the len of the sequence with a 1 on the other side of the range:
foreach( x in seq.length..1) { Do-Something seq[x] }