PowerShell is the Windows scripting language and configuration management
framework from Microsoft built on the .NET Framework. Windows 7 and up ship
with PowerShell.
Nearly all examples below can be a part of a shell script or executed directly
in the shell.
A key difference with Bash is that it is mostly objects that you manipulate rather than plain text. After years of evolving, it resembles Python a bit.
Powershell as a Language:
1# Single line comments start with a number symbol.
2
3<#
4 Multi-line comments
5 like so
6#>
7
8
9####################################################
10## 1. Primitive Datatypes and Operators
11####################################################
12
13# Numbers
143 # => 3
15
16# Math
171 + 1 # => 2
188 - 1 # => 7
1910 * 2 # => 20
2035 / 5 # => 7.0
21
22# Powershell uses banker's rounding,
23# meaning [int]1.5 would round to 2 but so would [int]2.5
24# Division always returns a float.
25# You must cast result to [int] to round.
26[int]5 / [int]3 # => 1.66666666666667
27[int]-5 / [int]3 # => -1.66666666666667
285.0 / 3.0 # => 1.66666666666667
29-5.0 / 3.0 # => -1.66666666666667
30[int]$result = 5 / 3
31$result # => 2
32
33# Modulo operation
347 % 3 # => 1
35
36# Exponentiation requires longform or the built-in [Math] class.
37[Math]::Pow(2,3) # => 8
38
39# Enforce order of operations with parentheses.
401 + 3 * 2 # => 7
41(1 + 3) * 2 # => 8
42
43# Boolean values are primitives (Note: the $)
44$True # => True
45$False # => False
46
47# negate with !
48!$True # => False
49!$False # => True
50
51# Boolean Operators
52# Note "-and" and "-or" usage
53$True -and $False # => False
54$False -or $True # => True
55
56# True and False are actually 1 and 0 but only support limited arithmetic.
57# However, casting the bool to int resolves this.
58$True + $True # => 2
59$True * 8 # => '[System.Boolean] * [System.Int32]' is undefined
60[int]$True * 8 # => 8
61$False - 5 # => -5
62
63# Comparison operators look at the numerical value of True and False.
640 -eq $False # => True
651 -eq $True # => True
662 -eq $True # => False
67-5 -ne $False # => True
68
69# Using boolean logical operators on ints casts to booleans for evaluation.
70# but their non-cast value is returned
71# Don't mix up with bool(ints) and bitwise -band/-bor
72[bool](0) # => False
73[bool](4) # => True
74[bool](-6) # => True
750 -band 2 # => 0
76-5 -bor 0 # => -5
77
78# Equality is -eq (equals)
791 -eq 1 # => True
802 -eq 1 # => False
81
82# Inequality is -ne (notequals)
831 -ne 1 # => False
842 -ne 1 # => True
85
86# More comparisons
871 -lt 10 # => True
881 -gt 10 # => False
892 -le 2 # => True
902 -ge 2 # => True
91
92# Seeing whether a value is in a range
931 -lt 2 -and 2 -lt 3 # => True
942 -lt 3 -and 3 -lt 2 # => False
95
96# (-is vs. -eq) -is checks if two objects are the same type.
97# -eq checks if the objects have the same values, but sometimes doesn't work
98# as expected.
99# Note: we called '[Math]' from .NET previously without the preceeding
100# namespaces. We can do the same with [Collections.ArrayList] if preferred.
101[System.Collections.ArrayList]$a = @() # Point a at a new list
102$a = (1,2,3,4)
103$b = $a # => Point b at what a is pointing to
104$b -is $a.GetType() # => True, a and b equal same type
105$b -eq $a # => None! See below
106[System.Collections.Hashtable]$b = @{} # => Point b at a new hash table
107$b = @{'one' = 1
108 'two' = 2}
109$b -is $a.GetType() # => False, a and b types not equal
110
111# Strings are created with " or ' but " is required for string interpolation
112"This is a string."
113'This is also a string.'
114
115# Strings can be added too! But try not to do this.
116"Hello " + "world!" # => "Hello world!"
117
118# A string can be treated like a list of characters
119"Hello world!"[0] # => 'H'
120
121# You can find the length of a string
122("This is a string").Length # => 16
123
124# You can also format using f-strings or formatted string literals.
125$name = "Steve"
126$age = 22
127"He said his name is $name."
128# => "He said his name is Steve"
129"{0} said he is {1} years old." -f $name, $age
130# => "Steve said he is 22 years old"
131"$name's name is $($name.Length) characters long."
132# => "Steve's name is 5 characters long."
133
134# Strings can be compared with -eq, but are case insensitive. We can
135# force with -ceq or -ieq.
136"ab" -eq "ab" # => True
137"ab" -eq "AB" # => True!
138"ab" -ceq "AB" # => False
139"ab" -ieq "AB" # => True
140
141# Escape Characters in Powershell
142# Many languages use the '\', but Windows uses this character for
143# file paths. Powershell thus uses '`' to escape characters
144# Take caution when working with files, as '`' is a
145# valid character in NTFS filenames.
146"Showing`nEscape Chars" # => new line between Showing and Escape
147"Making`tTables`tWith`tTabs" # => Format things with tabs
148
149# Negate pound sign to prevent comment
150# Note that the function of '#' is removed, but '#' is still present
151`#Get-Process # => Fail: not a recognized cmdlet
152
153# $null is not an object
154$null # => None
155
156# $null, 0, and empty strings and arrays all evaluate to False.
157# All other values are True
158function Test-Value ($value) {
159 if ($value) {
160 Write-Output 'True'
161 }
162 else {
163 Write-Output 'False'
164 }
165}
166
167Test-Value ($null) # => False
168Test-Value (0) # => False
169Test-Value ("") # => False
170Test-Value [] # => True
171# *[] calls .NET class; creates '[]' string when passed to function
172Test-Value ({}) # => True
173Test-Value @() # => False
174
175
176####################################################
177## 2. Variables and Collections
178####################################################
179
180# Powershell uses the "Write-Output" function to print
181Write-Output "I'm Posh. Nice to meet you!" # => I'm Posh. Nice to meet you!
182
183# Simple way to get input data from console
184$userInput = Read-Host "Enter some data: " # Returns the data as a string
185
186# There are no declarations, only assignments.
187# Convention is to use camelCase or PascalCase, whatever your team uses.
188$someVariable = 5
189$someVariable # => 5
190
191# Accessing a previously unassigned variable does not throw exception.
192# The value is $null by default
193
194# Ternary Operators exist in Powershell 7 and up
1950 ? 'yes' : 'no' # => no
196
197
198# The default array object in Powershell is an fixed length array.
199$defaultArray = "thing","thing2","thing3"
200# you can add objects with '+=', but cannot remove objects.
201$defaultArray.Add("thing4") # => Exception "Collection was of a fixed size."
202# To have a more workable array, you'll want the .NET [ArrayList] class
203# It is also worth noting that ArrayLists are significantly faster
204
205# ArrayLists store sequences
206[System.Collections.ArrayList]$array = @()
207# You can start with a prefilled ArrayList
208[System.Collections.ArrayList]$otherArray = @(5, 6, 7, 8)
209
210# Add to the end of a list with 'Add' (Note: produces output, append to $null)
211$array.Add(1) > $null # $array is now [1]
212$array.Add(2) > $null # $array is now [1, 2]
213$array.Add(4) > $null # $array is now [1, 2, 4]
214$array.Add(3) > $null # $array is now [1, 2, 4, 3]
215# Remove from end with index of count of objects-1; array index starts at 0
216$array.RemoveAt($array.Count-1) # => 3 and array is now [1, 2, 4]
217# Let's put it back
218$array.Add(3) > $null # array is now [1, 2, 4, 3] again.
219
220# Access a list like you would any array
221$array[0] # => 1
222# Look at the last element
223$array[-1] # => 3
224# Looking out of bounds returns nothing
225$array[4] # blank line returned
226
227# Remove elements from a array
228$array.Remove($array[3]) # $array is now [1, 2, 4]
229
230# Insert at index an element
231$array.Insert(2, 3) # $array is now [1, 2, 3, 4]
232
233# Get the index of the first item found matching the argument
234$array.IndexOf(2) # => 1
235$array.IndexOf(6) # Returns -1 as "outside array"
236
237# You can add arrays
238# Note: values for $array and for $otherArray are not modified.
239$array + $otherArray # => [1, 2, 3, 4, 5, 6, 7, 8]
240
241# Concatenate arrays with "AddRange()"
242$array.AddRange($otherArray) # Now $array is [1, 2, 3, 4, 5, 6, 7, 8]
243
244# Check for existence in a array with "in"
2451 -in $array # => True
246
247# Examine length with "Count" (Note: "Length" on arrayList = each items length)
248$array.Count # => 8
249
250# You can look at ranges with slice syntax.
251$array[1,3,5] # Return selected index => [2, 4, 6]
252$array[1..3] # Return from index 1 to 3 => [2, 3, 4]
253$array[-3..-1] # Return from last 3 to last 1 => [6, 7, 8]
254$array[-1..-3] # Return from last 1 to last 3 => [8, 7, 6]
255$array[2..-1] # Return from index 2 to last (NOT as most expect) => [3, 2, 1, 8]
256$array[0,2+4..6] # Return multiple ranges with the + => [1, 3, 5, 6, 7]
257
258# -eq doesn't compare array but extract the matching elements
259$array = 1,2,3,1,1
260$array -eq 1 # => 1,1,1
261($array -eq 1).Count # => 3
262
263# Tuples are like arrays but are immutable.
264# To use Tuples in powershell, you must use the .NET tuple class.
265$tuple = [System.Tuple]::Create(1, 2, 3)
266$tuple.Item(0) # => 1
267$tuple.Item(0) = 3 # Raises a TypeError
268
269# You can do some of the array methods on tuples, but they are limited.
270$tuple.Length # => 3
271$tuple + (4, 5, 6) # => Exception
272$tuple[0..2] # => $null (in powershell 5) => [1, 2, 3] (in powershell 7)
2732 -in $tuple # => False
274
275
276# Hashtables store mappings from keys to values, similar to (but distinct from) Dictionaries.
277# Hashtables do not hold entry order as arrays do.
278$emptyHash = @{}
279# Here is a prefilled hashtable
280$filledHash = @{"one"= 1
281 "two"= 2
282 "three"= 3}
283
284# Look up values with []
285$filledHash["one"] # => 1
286
287# Get all keys as an iterable with ".Keys".
288$filledHash.Keys # => ["one", "two", "three"]
289
290# Get all values as an iterable with ".Values".
291$filledHash.Values # => [1, 2, 3]
292
293# Check for existence of keys or values in a hash with "-in"
294"one" -in $filledHash.Keys # => True
2951 -in $filledHash.Values # => False (in powershell 5) => True (in powershell 7)
296
297# Looking up a non-existing key returns $null
298$filledHash["four"] # $null
299
300# Adding to a hashtable
301$filledHash.Add("five",5) # $filledHash["five"] is set to 5
302$filledHash.Add("five",6) # exception "Item with key "five" has already been added"
303$filledHash["four"] = 4 # $filledHash["four"] is set to 4, running again does nothing
304
305# Remove keys from a hashtable
306$filledHash.Remove("one") # Removes the key "one" from filled hashtable
307
308
309####################################################
310## 3. Control Flow and Iterables
311####################################################
312
313# Let's just make a variable
314$someVar = 5
315
316# Here is an if statement.
317# This prints "$someVar is smaller than 10"
318if ($someVar -gt 10) {
319 Write-Output "$someVar is bigger than 10."
320}
321elseif ($someVar -lt 10) { # This elseif clause is optional.
322 Write-Output "$someVar is smaller than 10."
323}
324else { # This is optional too.
325 Write-Output "$someVar is indeed 10."
326}
327
328
329<#
330Foreach loops iterate over arrays
331prints:
332 dog is a mammal
333 cat is a mammal
334 mouse is a mammal
335#>
336foreach ($animal in ("dog", "cat", "mouse")) {
337 # You can use -f to interpolate formatted strings
338 "{0} is a mammal" -f $animal
339}
340
341<#
342For loops iterate over arrays and you can specify indices
343prints:
344 0 a
345 1 b
346 2 c
347 3 d
348 4 e
349 5 f
350 6 g
351 7 h
352#>
353$letters = ('a','b','c','d','e','f','g','h')
354for($i=0; $i -le $letters.Count-1; $i++){
355 Write-Host $i, $letters[$i]
356}
357
358<#
359While loops go until a condition is no longer met.
360prints:
361 0
362 1
363 2
364 3
365#>
366$x = 0
367while ($x -lt 4) {
368 Write-Output $x
369 $x += 1 # Shorthand for x = x + 1
370}
371
372# Switch statements are more powerful compared to most languages
373$val = "20"
374switch($val) {
375 { $_ -eq 42 } { "The answer equals 42"; break }
376 '20' { "Exactly 20"; break }
377 { $_ -like 's*' } { "Case insensitive"; break }
378 { $_ -clike 's*'} { "clike, ceq, cne for case sensitive"; break }
379 { $_ -notmatch '^.*$'} { "Regex matching. cnotmatch, cnotlike, ..."; break }
380 default { "Others" }
381}
382
383# Handle exceptions with a try/catch block
384try {
385 # Use "throw" to raise an error
386 throw "This is an error"
387}
388catch {
389 Write-Output $Error.ExceptionMessage
390}
391finally {
392 Write-Output "We can clean up resources here"
393}
394
395
396# Writing to a file
397$contents = @{"aa"= 12
398 "bb"= 21}
399$contents | Export-CSV "$env:HOMEDRIVE\file.csv" # writes to a file
400
401$contents = "test string here"
402$contents | Out-File "$env:HOMEDRIVE\file.txt" # writes to another file
403
404# Read file contents and convert to json
405Get-Content "$env:HOMEDRIVE\file.csv" | ConvertTo-Json
406
407
408####################################################
409## 4. Functions
410####################################################
411
412# Use "function" to create new functions
413# Keep the Verb-Noun naming convention for functions
414function Add-Numbers {
415 $args[0] + $args[1]
416}
417
418Add-Numbers 1 2 # => 3
419
420# Calling functions with parameters
421function Add-ParamNumbers {
422 param( [int]$firstNumber, [int]$secondNumber )
423 $firstNumber + $secondNumber
424}
425
426Add-ParamNumbers -FirstNumber 1 -SecondNumber 2 # => 3
427
428# Functions with named parameters, parameter attributes, parsable documentation
429<#
430.SYNOPSIS
431Setup a new website
432.DESCRIPTION
433Creates everything your new website needs for much win
434.PARAMETER siteName
435The name for the new website
436.EXAMPLE
437New-Website -Name FancySite -Po 5000
438New-Website SiteWithDefaultPort
439New-Website siteName 2000 # ERROR! Port argument could not be validated
440('name1','name2') | New-Website -Verbose
441#>
442function New-Website() {
443 [CmdletBinding()]
444 param (
445 [Parameter(ValueFromPipeline=$true, Mandatory=$true)]
446 [Alias('name')]
447 [string]$siteName,
448 [ValidateSet(3000,5000,8000)]
449 [int]$port = 3000
450 )
451 BEGIN { Write-Output 'Creating new website(s)' }
452 PROCESS { Write-Output "name: $siteName, port: $port" }
453 END { Write-Output 'Website(s) created' }
454}
455
456
457####################################################
458## 5. Modules
459####################################################
460
461# You can import modules and install modules
462# The Install-Module is similar to pip or npm, pulls from Powershell Gallery
463Install-Module dbaTools
464Import-Module dbaTools
465
466$query = "SELECT * FROM dbo.sometable"
467$queryParams = @{
468 SqlInstance = 'testInstance'
469 Database = 'testDatabase'
470 Query = $query
471}
472Invoke-DbaQuery @queryParams
473
474# You can get specific functions from a module
475Import-Module -Function Invoke-DbaQuery
476
477
478# Powershell modules are just ordinary Posh files. You
479# can write your own, and import them. The name of the
480# module is the same as the name of the file.
481
482# You can find out which functions and attributes
483# are defined in a module.
484Get-Command -module dbaTools
485Get-Help dbaTools -Full
486
487
488####################################################
489## 6. Classes
490####################################################
491
492# We use the "class" statement to create a class
493class Instrument {
494 [string]$Type
495 [string]$Family
496}
497
498$instrument = [Instrument]::new()
499$instrument.Type = "String Instrument"
500$instrument.Family = "Plucked String"
501
502$instrument
503
504<# Output:
505Type Family
506---- ------
507String Instrument Plucked String
508#>
509
510
511####################################################
512## 6.1 Inheritance
513####################################################
514
515# Inheritance allows new child classes to be defined that inherit
516# methods and variables from their parent class.
517
518class Guitar : Instrument
519{
520 [string]$Brand
521 [string]$SubType
522 [string]$ModelType
523 [string]$ModelNumber
524}
525
526$myGuitar = [Guitar]::new()
527$myGuitar.Brand = "Taylor"
528$myGuitar.SubType = "Acoustic"
529$myGuitar.ModelType = "Presentation"
530$myGuitar.ModelNumber = "PS14ce Blackwood"
531
532$myGuitar.GetType()
533
534<#
535IsPublic IsSerial Name BaseType
536-------- -------- ---- --------
537True False Guitar Instrument
538#>
539
540
541####################################################
542## 7. Advanced
543####################################################
544
545# The powershell pipeline allows things like High-Order Functions.
546
547# Group-Object is a handy cmdlet that does incredible things.
548# It works much like a GROUP BY in SQL.
549
550<#
551 The following will get all the running processes,
552 group them by Name,
553 and tell us how many instances of each process we have running.
554 Tip: Chrome and svcHost are usually big numbers in this regard.
555#>
556Get-Process | Foreach-Object ProcessName | Group-Object
557
558# Useful pipeline examples are iteration and filtering.
5591..10 | ForEach-Object { "Loop number $PSITEM" }
5601..10 | Where-Object { $PSITEM -gt 5 } | ConvertTo-Json
561
562# A notable pitfall of the pipeline is its performance when
563# compared with other options.
564# Additionally, raw bytes are not passed through the pipeline,
565# so passing an image causes some issues.
566# See more on that in the link at the bottom.
567
568<#
569 Asynchronous functions exist in the form of jobs.
570 Typically a procedural language,
571 Powershell can operate non-blocking functions when invoked as Jobs.
572#>
573
574# This function is known to be non-optimized, and therefore slow.
575$installedApps = Get-CimInstance -ClassName Win32_Product
576
577# If we had a script, it would hang at this func for a period of time.
578$scriptBlock = {Get-CimInstance -ClassName Win32_Product}
579Start-Job -ScriptBlock $scriptBlock
580
581# This will start a background job that runs the command.
582# You can then obtain the status of jobs and their returned results.
583$allJobs = Get-Job
584$jobResponse = Get-Job | Receive-Job
585
586
587# Math is built in to powershell and has many functions.
588$r=2
589$pi=[math]::pi
590$r2=[math]::pow( $r, 2 )
591$area = $pi*$r2
592$area
593
594# To see all possibilities, check the members.
595[System.Math] | Get-Member -Static -MemberType All
596
597
598<#
599 This is a silly one:
600 You may one day be asked to create a func that could take $start and $end
601 and reverse anything in an array within the given range
602 based on an arbitrary array without mutating the original array.
603 Let's see one way to do that and introduce another data structure.
604#>
605
606$targetArray = 'a','b','c','d','e','f','g','h','i','j','k','l','m'
607
608function Format-Range ($start, $end, $array) {
609 [System.Collections.ArrayList]$firstSectionArray = @()
610 [System.Collections.ArrayList]$secondSectionArray = @()
611 [System.Collections.Stack]$stack = @()
612 for ($index = 0; $index -lt $array.Count; $index++) {
613 if ($index -lt $start) {
614 $firstSectionArray.Add($array[$index]) > $null
615 }
616 elseif ($index -ge $start -and $index -le $end) {
617 $stack.Push($array[$index])
618 }
619 else {
620 $secondSectionArray.Add($array[$index]) > $null
621 }
622 }
623 $finalArray = $firstSectionArray + $stack.ToArray() + $secondSectionArray
624 return $finalArray
625}
626
627Format-Range 2 6 $targetArray
628# => 'a','b','g','f','e','d','c','h','i','j','k','l','m'
629
630# The previous method works, but uses extra memory by allocating new arrays.
631# It's also kind of lengthy.
632# Let's see how we can do this without allocating a new array.
633# This is slightly faster as well.
634
635function Format-Range ($start, $end) {
636 while ($start -lt $end)
637 {
638 $temp = $targetArray[$start]
639 $targetArray[$start] = $targetArray[$end]
640 $targetArray[$end] = $temp
641 $start++
642 $end--
643 }
644 return $targetArray
645}
646
647Format-Range 2 6 # => 'a','b','g','f','e','d','c','h','i','j','k','l','m'
Powershell as a Tool:
Getting Help:
1# Find commands
2Get-Command about_* # alias: gcm
3Get-Command -Verb Add
4Get-Alias ps
5Get-Alias -Definition Get-Process
6
7Get-Help ps | less # alias: help
8ps | Get-Member # alias: gm
9
10Show-Command Get-WinEvent # Display GUI to fill in the parameters
11
12Update-Help # Run as admin
If you are uncertain about your environment:
1Get-ExecutionPolicy -List
2Set-ExecutionPolicy AllSigned
3# Execution policies include:
4# - Restricted: Scripts won't run.
5# - RemoteSigned: Downloaded scripts run only if signed by a trusted publisher.
6# - AllSigned: Scripts need to be signed by a trusted publisher.
7# - Unrestricted: Run all scripts.
8help about_Execution_Policies # for more info
9
10# Current PowerShell version:
11$PSVersionTable
1# Calling external commands, executables,
2# and functions with the call operator.
3# Exe paths with arguments passed or containing spaces can create issues.
4C:\Program Files\dotnet\dotnet.exe
5# The term 'C:\Program' is not recognized as a name of a cmdlet,
6# function, script file, or executable program.
7# Check the spelling of the name, or if a path was included,
8# verify that the path is correct and try again
9
10"C:\Program Files\dotnet\dotnet.exe"
11C:\Program Files\dotnet\dotnet.exe # returns string rather than execute
12
13&"C:\Program Files\dotnet\dotnet.exe --help" # fail
14&"C:\Program Files\dotnet\dotnet.exe" --help # success
15# Alternatively, you can use dot-sourcing here
16."C:\Program Files\dotnet\dotnet.exe" --help # success
17
18# the call operator (&) is similar to Invoke-Expression,
19# but IEX runs in current scope.
20# One usage of '&' would be to invoke a scriptblock inside of your script.
21# Notice the variables are scoped
22$i = 2
23$scriptBlock = { $i=5; Write-Output $i }
24& $scriptBlock # => 5
25$i # => 2
26
27invoke-expression ' $i=5; Write-Output $i ' # => 5
28$i # => 5
29
30# Alternatively, to preserve changes to public variables
31# you can use "Dot-Sourcing". This will run in the current scope.
32$x=1
33&{$x=2};$x # => 1
34
35.{$x=2};$x # => 2
36
37
38# Remoting into computers is easy.
39Enter-PSSession -ComputerName RemoteComputer
40
41# Once remoted in, you can run commands as if you're local.
42RemoteComputer\PS> Get-Process powershell
43
44<#
45Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
46------- ------ ----- ----- ------ -- -- -----------
47 1096 44 156324 179068 29.92 11772 1 powershell
48 545 25 49512 49852 25348 0 powershell
49#>
50RemoteComputer\PS> Exit-PSSession
51
52<#
53 Powershell is an incredible tool for Windows management and Automation.
54 Let's take the following scenario:
55 You have 10 servers.
56 You need to check whether a service is running on all of them.
57 You can RDP and log in, or PSSession to all of them, but why?
58 Check out the following
59#>
60
61$serverList = @(
62 'server1',
63 'server2',
64 'server3',
65 'server4',
66 'server5',
67 'server6',
68 'server7',
69 'server8',
70 'server9',
71 'server10'
72)
73
74[scriptblock]$script = {
75 Get-Service -DisplayName 'Task Scheduler'
76}
77
78foreach ($server in $serverList) {
79 $cmdSplat = @{
80 ComputerName = $server
81 JobName = 'checkService'
82 ScriptBlock = $script
83 AsJob = $true
84 ErrorAction = 'SilentlyContinue'
85 }
86 Invoke-Command @cmdSplat | Out-Null
87}
88
89<#
90 Here we've invoked jobs across many servers.
91 We can now Receive-Job and see if they're all running.
92 Now scale this up 100x as many servers :)
93#>
Interesting Projects
- Channel9 PowerShell tutorials
- KevinMarquette’s Powershell Blog Excellent blog that goes into great detail on Powershell
- PSGet NuGet for PowerShell
- PSReadLine A bash inspired readline implementation for PowerShell (So good that it now ships with Windows10 by default!)
- Posh-Git Fancy Git Prompt (Recommended!)
- Oh-My-Posh Shell customization similar to the popular Oh-My-Zsh on Mac
- PSake Build automation tool
- Pester BDD Testing Framework
- ZLocation Powershell
cd
that reads your mind - PowerShell Community Extensions
- More on the Powershell Pipeline Issue