What general tips do you have for golfing in Windows PowerShell? I'm looking for ideas that can be applied to code golf problems in general that are at least somewhat specific to PowerShell (e.g. "remove comments" is not an answer). Please post one tip per answer.

—taken nearly verbatim from marcog's question.

  • 2
    When I googled "PowerShell golf" this was the first hit! – Matt Mar 14 '16 at 15:53

37 Answers 37

Powers of 10 literals with scientific notation:

4e6 = 4000000

Powers of 2 literals:

4KB = 4096
4MB = 4194304
4GB = 4294967296

# TB and PB suffixes also exist, but less useful for golf.

Could come in handy.

If you need to run a loop, and you know exactly how many times it needs to run every time, consider piping an array of contiguous integers into ForEach-Object via the % alias instead of using for.

for($x=1;$x-le10;$x++){...}

vs

1..10|%{...}

You can skip spaces a lot in PowerShell. If it feels like it might not be needed, it quite possibly isn't. This is particularly useful in comparisons.

Example:

$x-eq$i-and$x-ne9

vs.

$x -eq $i -and $x -ne 9

If you need to branch your script based on the result of a single test which may have multiple outcomes, switch can sometimes match or beat an if statement.

Example (switch vs. if/else - tie):

if($x%2-eq0){'even'}else{'odd'}

vs.

switch($x%2){0{'even'}1{'odd'}}

Or (switch vs. if/elseif/else - switch wins by 15):

if($x%2-eq0){'even'}elseif($x%2-eq1){'odd'}else{'error'}

vs.

switch($x%2){0{'even'}1{'odd'}2{'error'}}

If the switch is actually based on certain math results, like the modulo operation above, you can replace the switch entirely with an array. Here, it saves another 13 characters and is even shorter than the original two-option if/else statement. (Thanks to Danko Durbic for this bit.)

('even','odd','error')[$x%2]

If you will be using a particular command a lot, especially one without a pre-existing short-hand alias, set up a single-character alias early on.

Example:

nal g Get-Random;g 10;g 10;g 10

vs.

Get-Random 10;Get-Random 10;Get-Random 10

A switch can act like a loop, when given an array. For example:

$FooBarMeh='a','b','c'
switch ($FooBarMeh)
{
    'a'{'FOO'}
    'b'{'BAR'}
    default{'MEH'}
}

Will output:

FOO
BAR
MEH

I'm not totally sure where this will be useful, but I expect it will be handy for someone some time.

  • 2
    It is handy every time you need a ForEach-Object and a switch within that. It's for example (in real-world) code very nice for writing quick parsers of text files where you need to do different things depending on which regex a line matches. – Joey Nov 24 '13 at 12:02
  • This is... damn weird. An array shouldn't match with a char, but it does. – cat Apr 24 '16 at 14:56

Encapsulating the command that defines a variable in parenthesis allows you to feed the variable's definition directly to other commands.

For example, you can set $x and then set $y based on the value of $x in one shot with this:

$y=($x=1)+1

Instead of this:

$x=1;$y=$x+1

You can set $h and output it with this:

($h='Hello World!')

Instead of this:

$h='Hello World!';$h
  • 1
    This is especially useful for calling methods on objects. ($x=New-Object Windows.Forms.Form).Controls.Add($t) – SpellingD Nov 26 '13 at 21:49

Want to find the maximum or minimum of a collection of values? Tried

(...|measure -ma).Maximum

or

(...|measure -mi).Minimum

already?

Just sort and use the last or first item:

(...|sort)[-1]  # maximum
(...|sort)[0]   # minimum
  • 1
    And, if you know the length of the collection of values and it's less than 10... – wizzwizz4 Jan 10 '16 at 14:12
  • @wizzwizz4: Oh, maximum lengths can often be employed creatively, not necessarily being 10. Although for this particular case I don't recall an instance where it ever helped. – Joey Jan 10 '16 at 14:53
  • 1
    I mean, if the final item is known to be 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9, it would save a byte to write the known length instead of -1. – wizzwizz4 Jan 10 '16 at 14:55

Replace [math]::pow with multiplication. Instead of

[math]::pow($a,$b)

you can write

"$a*"*$b+1|iex

This works for integer exponents >= 0.

Comparison operators work on collections of values by returning matching values:

1..5 -gt 2

will yield 3, 4 and 5. In some cases this can help to save an otherwise longer |?{$_...}.

-match is a comparison operator too.

  • 1
    Note that you don't need spaces in that example 1..5-gt2 – Matt Mar 14 '16 at 15:34
  • 1
    I know; this was more about showing the technique. Spaces are in a separate answer. – Joey Mar 14 '16 at 18:08

Use aliases whenever possible. There are a bunch of useful ones:

?        Where-Object
%        ForEach-Object
gu       Get-Unique
sort     Sort-Object
iex      Invoke-Expression

Finding a sum the long way:

(...|measure -s).Sum

A shorter way:

...|%{$s+=$_};$s

And even shorter:

...-join'+'|iex

Semicolons and line breaks are interchangeable. Golfed code is often more readable if not jammed into a single line. And the length is still the same (provided you use U+000A as line break which PowerShell handles without problems).

  • 2
    Wait, we are worried about readability?! – Kevin Cox Nov 22 '13 at 15:50
  • 3
    If it has no effect on the length ... why not? – Joey Nov 22 '13 at 17:02
  • Doesn't it technically make a difference since a semicolon is one less character than \r\n? Only in Unix it is a singular \n. – Vasili Syrakis Jan 9 '14 at 4:47
  • 2
    @Vasili: You can save the files just fine with only U+000A between the lines. PowerShell won't complain. Which I wrote in the answer already, by the way. Line breaks are a property of the file, not of the operating system it is used on. No one says that you cannot use Unix line endings on Windows. – Joey Jan 9 '14 at 5:50

for loops can have anything between 0 and three statements in their header:

Endless loop:

for(){}

Loop with initialization:

for($n=0){}

Loop with initialization and end condition:

for($n=0;$n-lt7){}

In such cases the additional semicolons at the end may be omitted (it's explicitly stated in the language specification, so it's not an implementation detail) in contrast to C-like languages which always require exactly three statements.

This also makes while a bit shorter. Compare

while(...){}

and

for(;...){}

With the added bonus that you can stick in a previous line (if there is one) into the for as well without extra cost (and even saving a character).

The Get verb is implied. This can shorten any Get-Frob to just Frob. Frequent contenders are date or random.

Note that this won't work properly in some cases because you might have GNU utilities in your path (or other native programs that clash). Order of command lookup in that case seems to prefer the native program before it considers cmdlets with the Get- removed:

PS Home:\> date

Freitag, 15. November 2013 07:13:45


PS Home:\> $Env:Path += ';D:\Users\Joey\Apps\GnuWin32\bin'
PS Home:\> date
Fr Nov 15 07:14:13 W. Europe Standard Time 2013
  • This doesn't quite always work out as expected. I have a script that starts with nal g Get-Random to save characters later on. Changing it to nal g Random causes the script to hang indefinitely (or, at least, take an inordinate amount of time to process - I haven't had the patience to wait for it to end, but it's taking several orders of magnitude longer than the original form before I abort). – Iszi Nov 15 '13 at 0:56
  • 1
    Two short Measure-Command invocations (100 times Get-Random vs. random) tell me it's about 500 times slower. I didn't know that before, to be honest. But it's good to keep it in mind, especially in loops with many iterations. That being said, golfed code should be short, not fast (that being said, it sucks to have to wait two days for an answer to a Project Euler problem). – Joey Nov 15 '13 at 6:18
  • 1
    My problem was running 10,000 iterations of the Monty Hall scenario each requiring three iterations of Get-Random. A 50,000% increase in processing time, multiplied across 30,000 runs is pretty nasty. – Iszi Nov 15 '13 at 6:23
  • Wow. It looks like the same probably holds true for any alias. Tested Get-Variable vs. gv and got a similar comparison. – Iszi Nov 15 '13 at 14:43
  • 1
    That could be it. I did some math and figured my Monty Hall script should take about 1.5 hours (normally, 10-11 seconds) to run without Get-. That's a pretty gnarly bloat in run time for a savings of just 4 characters in length. – Iszi Nov 15 '13 at 14:49

If you are assigning an array that you know will only have two values, don't use indexing.

Something like this:

$a="apple","orange"
$a[0] # apple
$a[1] # orange

Can easily be turned into this:

$a,$o="apple","orange"
$a # apple
$o # orange

This can also be useful for if you just need to the first element of an array:

$a,$b=1..10
$a # 1
$b # 2..10
  • In this case (with the strings) $a="apple";$o="orange" is identical in length. It's the longer arrays that can sometimes be optimised fairly nicely, e.g. by putting all elements in a string with a separator and then using -split (best to use whitespace as separator because then the unary -split will suffice). – Joey Jun 2 '14 at 6:03

Casting to string:

[string]$x

vs.

"$x"

Casting to string like this can also be used to flatten an array of strings, instead of joining it:

$a = @('a','b','c')
$a -join ' '

vs.

$a = @('a','b','c')
"$a"

Casting a string to a numeric type:

[int]$x     [float]$x

vs.

+$x

Also very useful to know that PowerShell always takes the type of the left operand to determine the final type of an expression and conversions to apply:

'1'+2    -> '12'
1+'2'    -> 3

which can help determining where needless casts are.

Don't forget that you don't always need to provide the full name of a parameter, and some parameters are positional.

Get-Random -InputObject (0..10)

...can be trimmed to...

Get-Random -I (0..10)

...because "I", in this case, is enough to uniquely identify InputObject from the other valid parameters for this command.

You could trim it further to...

Get-Random (0..10)

...because InputObject is a positional parameter.

Piping is usually shorter than feeding objects as a parameter, especially when it can remove the need for parenthesis. Let's trim our random number generator further...

0..10|Get-Random

Also be on the lookout for other ways to accomplish the same thing, even if you can't change the command. For the above case, you could do this:

Get-Random 11

Or, incorporating another suggestion*:

Random 11

**Note: Omitting Get- from a command name can bloat the run time by about 50,000%. Not bad if you only need the command once, but be careful using it in long loops.*

And that's how can knock a simple command down to a third of its size.

When using a number as an argument to an operator that would otherwise require a string, you can use the number directly. Compare

...-join'0'

vs.

...-join0

Works with -split as well. The argument is always converted to a string first.

  • For reference, this works with -replace as well. – AdmBorkBork Oct 24 '17 at 13:23
  • -replace also works with no second argument wheb you want to remove matches, – Joey Oct 25 '17 at 9:05

Absolute value

With

$n=-123

Instead of

[math]::abs($n)

use

$n-replace'-'

Of course, the savings are cancelled if parentheses are needed.

  • Or if you need the result on the left side of an operator ;-) – Joey Nov 15 '13 at 16:44

If you need to silence errors, the obvious variant would be

try{ <# something that throws errors #> }catch{}

However, this is way too long. A shorter variant is to run the try block as a script block and just redirect the errors into an unset variable ($null would be the usual one, but that's still too long):

.{ <# something that throws errors #> }2>$x

This saves five valuable bytes (if not the world).

Use the $ofs special variable to change the Output Field Separator used when stringifying an array. Useful if you're needing to transform arrays to strings multiple times.

For example:

$a=1,2,3,4
$a-join',';$a-join','
$ofs=',';"$a";"$a"

Saves 2+n characters on the 2nd -join, where n is the length of the separator, and saves an additional 5+n for the 3rd -join and each thereafter.

  • 1
    Sadly very rarely useful (at least in my golfing so far – I tend to strip it down to only a single join at the end). – Joey Mar 4 '16 at 20:07

Automatic variables have booleans for True and False as $true and $false but you can get similar results using the logical not operator ! and the integers 0 and 1( or any non-zero integer.)

PS C:\Users\matt> !1
False

PS C:\Users\matt> !0
True

Near all PowerShell expressions can be evaluated as booleans. So as long as you are aware of how certain data is evaluated you can get booleans and never need to explicitly cast them. Be aware of the LHS value when doing these.

  • Integer 0 is false and non-zero integers are evaluated to true.
  • non-zero length strings are true and empty or null (and nulls themselves) strings are false.

There are other examples but you can easily test by doing a cast

PS C:\Users\matt> [bool]@(0)
False
  • 1
    Many other data types also have similar status. Uninitialized variables default to $null, which is falsey. The empty string (and variables set to the empty string) are falsey. Etc. This can then be used to shortcut indexing into an array (e.g., when doing an if/else), as was used in FizzBuzz. – AdmBorkBork Mar 14 '16 at 16:45
  • @TimmyD Very true. Using ints is just shorter – Matt Mar 14 '16 at 16:53
  • @TimmyD I wanted to answer fizzbuzz until I saw yours.... Can't beat that .... at least not yet – Matt Mar 14 '16 at 16:55
  • Note that in many cases you don't actually need a bool. You can use 0 and 1 just as well. Implicit conversions help a lot in that regard (which you can often force with certain operators). – Joey Mar 14 '16 at 18:09

Fake ternary operator. You can assign straight from an if statement:

$z=if($x-eq$y){"truth"}else{"false"}

But you can use a 2-element array and use the test to index into it. $falsey results get element 0, $truthy results take element 1:

$z=("false","true")[$x-eq$y]

NB. that this is really doing array indexing, and if the test results in a value which can be cast to an integer, you'll ask for an item outside the bounds of the array and get $null back, and will need to do !(test) to force cast the result to a bool, with the options reversed.

  • The first snippet didn't work at one point in time (older PowerShell versions), if I remember correctly, making it necessary to wrap it into $(). There was essentially a distinction of using pipelines made from commands and statements as expressions. Seems that is gone by now, at least in PowerShell 5. – Joey May 14 '16 at 8:47
  • Additionally, if the array elements are non-static, both get parsed and processed as part of the array creation before the indexing happens and the result is assigned. For example. – AdmBorkBork May 25 '16 at 20:12

Shortening Property Names

Sadly, unlike parameters, properties/methods (anything accessed with a dot .) cannot usually be shortened down to its unambiguous form.

But certain cmdlets can operate on property names and take wildcards, and there are little-known parameter sets of % and ? that can be useful.

Usually we pass in a scriptblock and refer to the item with $_, but there's another form of these that takes a property name, and it accepts a wildcard.

$o|select Le*  
$o|%{$_.Length}

With a property like .Length we can't use the v3 magic that would normally work on an array because Length is a property of the array itself, so the above two could be used to get the lengths of the individual members. The select comes in a little bit shorter.

But % can take a property name directly and return that value:

$a|% Length

Which can be shortened with wildcards. The wildcard must resolve to a single property (or method, more on that later), so it will throw a helpful error if it doesn't, indicating exactly which members it could resolve to.

In the case of Length, Le* is typically the shortest. Even on a single string, this method is 1 byte shorter than just using the property.

$a.Length                # 9   #(doesn't work on array)
$a|%{$_.Length}          # 15
$a|% Le*                 # 8

But depending on what you're doing with this, this can be worse. You can do $a.Length*5 but to do it with the pipeline expression you'd have to wrap it ($a|% Le*)*5; might still be worth it if it's against an array, but the point is it's not always appropriate as a straight substitution.

It works with methods too, and you can leave off the () which makes a full name the same length, but same restriction as above about sometimes having to wrap it. The method must have an overload that takes no parameters (you can pass arguments by placing them after the method name, which is really nice):

$a.ToUpper()             # 12
$a|% *per                #  9

With arguments:

'gaga'-replace'g','r'    # 21
'gaga'|% *ce g r         # 16

These aren't strictly the same in that the -replace operator does a regex replace, but if you're just doing a string replace, it can (now) be shorter to use the method; it helps that the strings are cmdlet arguments instead of method arguments so they don't need to be quoted.

Where-Object Properties

? can take (partial) property names as well, and apply an "operator" to it (in the form of switch parameters). Again this can be shorter than using the standard Where-Object scriptblock approach if the property name is sufficiently long and unique.

$a|?{$_.Length-gt5}      # 19
$a|? Le* -GT 5           # 14

($a|% Le*)-gt5           # 14 - Lengths, not objs
  • 1
    One of the few places where whitespace is required. Nice find, though. I can think of several areas where this will shorten things. – AdmBorkBork Feb 27 '17 at 15:48
  • Nice edit Joey! Thanks. – briantist Feb 27 '17 at 15:56
  • Found you a way to get it shorter ;-) ... the sad thing about this is that as a whole it's a pipeline again, which limits its usefulness a bit. Or rather, there are a lot of places where you'd have to wrap it into parentheses again to get an expression. But few golf techniques are without trade-offs ... – Joey Feb 27 '17 at 15:56
  • 1
    @ConnorLSW ah yes I meant to update this as I'd since realized you can pass args that way, which greatly ups its usefulness. Great use of .ToString()! – briantist Apr 3 '17 at 21:29
  • 2
    @briantist this has turned PowerShell into a much more competitive language, really cuts out some of the annoyingly verbose .Net calls. – colsw Apr 4 '17 at 8:19

Invoke-Expression and Get-Random can also get pipeline input instead of arguments.

For iex this allows to save parentheses on some expressions:

iex 1..5-join'+'   # won't work
iex(1..5-join'+')  # does work, but has extra parentheses
1..5-join'+'|iex   # doesn't need the parentheses

In case of random this allows a common case to be optimized a bit:

random -mi 10 31   # gets a random integer between 10 and 30
10..30|random      # much better :-)
(random 21)+10     # if needed in another expression that doesn't require extra
                   # parentheses

The latter way of using it simply selects an item from a list. The -c argument can be given to allow more than a single selection.

Consider storing repeated script blocks in variables, instead of using functions.

I was going to use this to save some characters in my Rock, Paper, Scissors implementation before I realized that re-writing the function as a variable made even the variable unnecessary. This could still be useful for other scripts though, where you're actually running the same code multiple times.

function Hi{echo 'Hello, World!'};Hi

vs.

$Hi={echo 'Hello, World!'};&$Hi
  • 2
    Useful for functions that do not take arguments. Otherwise the params(...) would take up more space than the function definition saves. Related: Use filter over function when you can do so. – Joey Nov 26 '13 at 21:57
  • You could use $args to avoid the need for params; i.e. $x={$args[0]+2} (or even $x={2+"$args"}); may save a character or 2 in some circumstances. You can also combine this with another trick for multiple params: $x={$a,$b=$args;$a+$b+3} – JohnLBevan Jan 1 '17 at 20:35

Use Boolean logic in place of if-counters in a loop

Suppose you're adding all even numbers from 1 to 10 ... 2+4+6+8+10=30

1..10|%{if($_%2-eq0){$o+=$_}};$o

You could use Boolean negation to shorten it to

1..10|%{if(!($_%2)){$o+=$_}};$o

to save a byte, but how about instead use implicit casting of Booleans to ints, and roll the conditional into the accumulator

1..10|%{$o+=$_*!($_%2)};$o

Saves 6 bytes in this example.

  • 2
    Jesus christ. I'm planning to do this in my production code at work, my colleagues will love me for it. – Chavez Nov 4 '16 at 9:13

Converting floating-point numbers to integers in PowerShell is a bit of a minefield. By default the conversion does Bankers Rounding which doesn't always trim off the decimal and leave the smaller whole number, or always round .5 up to the next number like people do casually, it rounds evens one way and odds another way - this can be surprising, e.g.

PS C:\> [int]1.5
2

PS C:\> [int]2.5
2

and break codegolf calculations. Many other common languages do truncation-rounding, therefore golf questions often require truncation. You might reach for [Math]::Floor() as the next best thing, but beware this only behaves the same as truncation for positive numbers, but it takes negative numbers lower - further away from zero. [Math]::Truncate() is what you need to bring PS behaviour in line with other language's default rounding, but it's a lot of characters.

Regex replacing digits after the decimal point can help save a couple of characters:

[Math]::Truncate(1.5)
[Math]::Floor(1.5)
1.5-replace'\..*'

[Math]::Truncate($a)
[Math]::Floor($a)
$a-replace'\..*'
$a.split('.')[0]        # literal character split, not regex pattern split

Starting with PowerShell Core 6, you can also use ranges for characters:

'a'..'z'

which can replace the much more cumbersome

0..25|%{[char]($_+97)}
  • Oh, this is going to come in so handy. – AdmBorkBork Nov 29 '17 at 13:34

If you need an if/else at the end of the program (maybe to handle a special case differently), then instead of

if(foo){a}else{b}

do

if(foo){a;exit}b

which saves a character.

Declaring an anonymous function should come up alot. There are multiple variations of this that are covered here https://stackoverflow.com/questions/10995667/lambda-expression-in-powershell involving scripts blocks. I use a similar one to this

$a={iex([int[]][char[]]$args[0]-join"+")}
&$a 'abcd'

This would save a few character from declaring a function with a name and goes better if used more than once. The one above converts a string into a char array then into an int array. Then creates a string joined with + so that Invoke-Expression will add all the values.

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.