Session 9 · Computer Systems
PowerShell scripts: parameters, error handling and text files
The frame around every useful script: the skeleton that takes parameters, checks them and reads a file; typed parameters versus a string checked with a regular expression; $args and array parameters; try/catch; the lines of a file as an array; and rewriting text with -replace. Thirteen tasks to practise on.
A useful script is mostly not about its task. It takes parameters and refuses the wrong ones, it finds out whether the file it was given exists, it survives the thing that goes wrong half-way – and somewhere in the middle are the few lines that do the actual work. This session is about that frame: how a PowerShell script receives and checks its input, how it reports and handles errors, and how it works with the lines of a text file.
The language itself – variables, operators, loops, functions, Get-Content – is covered in PowerShell: objects, cmdlets and scripts. Here those pieces are put to work, and thirteen tasks at the end let you practise each of them.
Every script is the same script with a different middle. Take the parameters, refuse bad input withWrite-Errorandexit 1, read the file with one well-known loop – and then write the three to ten lines that are actually about the task.
The skeleton of every script
A script that processes a file has three parts before it does anything interesting: it takes its parameters, it checks them, and it reads the file line by line. Written out once, this is the opening of almost every script below – worth knowing by heart.
param(
[string]$File
)
if (-not $File) {
Write-Error "Usage: $($MyInvocation.MyCommand.Name) <file>"
exit 1
}
if (-not (Test-Path -Path $File -PathType Leaf)) {
Write-Error "File '$File' does not exist"
exit 1
}
foreach ($line in Get-Content -Path $File -Encoding UTF8) {
$line = $line.Trim()
if ($line -eq '') {
continue
}
Write-Output $line
}| Part | What it takes care of |
|---|---|
param( [string]$File ) | The parameter of the script, with a name and a type. It must be the first statement of the script. |
if (-not $File) | The parameter is missing (empty). With several parameters: $PSBoundParameters.Count -ne 2. |
Write-Error + exit 1 | The message goes to the error stream, not into the results, and the script ends with a non-zero exit code. |
Test-Path -PathType Leaf | It exists and it is a file. For a directory: -PathType Container. |
Get-Content … -Encoding UTF8 | The lines of the file. CRLF endings and a missing final newline are handled for you; -Encoding UTF8 is there for accented text in Windows PowerShell 5.1. |
.Trim() + skipping empty lines | Stray spaces, tabs and empty lines are the two things Get-Content doesn't clean up for you. |
Checking parameters: two ways
A script that expects a number can make PowerShell do the checking, or do it itself (Fig. 1).
| Typed parameter | String + regular expression | |
|---|---|---|
| Declaration | param( [int]$Number ) | param( [string]$Number ) |
| Who checks? | PowerShell, before the script starts | you: if ($Number -notmatch '^[0-9]+$') |
| Error message | PowerShell's: Cannot convert value "abc" to type "System.Int32" | your own |
| Was it given? | $PSBoundParameters.ContainsKey('Number') – an [int] defaults to 0! | -not $Number |
typed
param(
[int]$Number
)
if (-not $PSBoundParameters.ContainsKey('Number')) { # was it given at all? 0 is a valid value
Write-Error "Usage: $($MyInvocation.MyCommand.Name) <integer>"
exit 1
}PS> ./square.ps1 abc
square.ps1: Cannot process argument transformation on parameter 'Number'. Cannot convert value "abc" to type "System.Int32". Error: "The input string 'abc' was not in a correct format."string + regex
param(
[string]$Number
)
if (-not $Number) {
Write-Error "Usage: $($MyInvocation.MyCommand.Name) <integer>"
exit 1
}
if ($Number -notmatch '^-?[0-9]+$') {
Write-Error "'$Number' is not an integer"
exit 1
}
[int]$n = $Number # from here on it is a numberPS> ./square.ps1 abc
Write-Error: 'abc' is not an integerBoth are good solutions. The first is shorter; with the second the message is yours, and you decide exactly what counts as valid – '^[0-9]+$' refuses -5, which an [int] would happily accept.
Any number of parameters
Some scripts take a list: numbers to add up, files to delete. There are two ways to receive "however many there are". The simple one: leave out the param() block, and every parameter lands in the $args array.
# no param() block: every parameter lands in the $args array
if ($args.Count -eq 0) {
Write-Error "Usage: $($MyInvocation.MyCommand.Name) <number> [number ...]"
exit 1
}
foreach ($arg in $args) {
Write-Output "Parameter: $arg"
}The other is a typed array parameter, which PowerShell validates like any typed parameter. Its elements are separated by commas on the command line:
param(
[double[]]$Numbers
)
# ./sum.ps1 1,2,3,4,5 $Numbers has five elements
# ./sum.ps1 -Numbers 1,2,3,4,5 the same, by name
# ./sum.ps1 1 2 3 4 5 $Numbers has ONE element; 2 3 4 5 end up in $argsNumbers and yes/no functions
PowerShell has several number types, and a toolbox – [math] – for roots, rounding and absolute values:
10 / 4 # 2.5 – division keeps the fraction
[math]::Truncate(10 / 4) # 2 – drop the fraction
[math]::Round(2.123456, 4) # 2.1235
[math]::Sqrt(16) # 4
[math]::Abs(-5) # 5
7 % 2 # 1 – the remainder
"1" + "2" # 12 – two strings are glued together
[double]"1" + [double]"2" # 3| Type | Holds | Use it for |
|---|---|---|
[int] | whole numbers up to about ±2 billion | counters, indexes, most parameters |
[long] | whole numbers up to about ±9 · 10¹⁸ | file sizes in bytes |
[double] | decimal numbers | anything with a fraction; what / returns |
[bigint] | whole numbers of any size | factorials and other results that outgrow 64 bits |
An ordinary integer has a largest value. When a calculation outgrows it, PowerShell quietly switches to a decimal number, which keeps the magnitude and loses the last digits. When every digit matters – a factorial, say – ask for [bigint]:
$result = 1
for ($i = 2; $i -le 25; $i++) { $result = $result * $i }
$result # 1.5511210043331E+25 – silently became a decimal, digits lost
[bigint]$result = 1
for ($i = 2; $i -le 25; $i++) { $result = $result * $i }
$result # 15511210043330985984000000 – every digitA function that returns true or false
A check you need more than once – is it prime, is it even, is the code valid – belongs in a function that returns $true or $false. By convention its name starts with Test-. It then goes directly into an if, or into Where-Object as a filter:
function Test-Prime {
param([int]$N)
if ($N -lt 2) {
return $false
}
for ($i = 2; $i * $i -le $N; $i++) { # divisors up to the square root are enough
if ($N % $i -eq 0) {
return $false
}
}
return $true
}
if (Test-Prime 97) { Write-Output "97 is a prime number" }
10..50 | Where-Object { Test-Prime $_ } # the same function as a filter in a pipelineError handling: try and catch
Some failures can't be ruled out in advance. For those there is try/catch: an error inside the try block doesn't stop the script – execution jumps to the catch block, where the error is in the variable $_:
try {
$result = $a / $b # integers: dividing by zero is an error
Remove-Item -Path $file -ErrorAction Stop # a cmdlet: catchable only with -ErrorAction Stop
} catch {
Write-Error "Error: $($_.Exception.Message)" # $_ is the error that was caught
exit 1
}catch – and -ErrorAction Stop makes a cmdlet's error that kind.When should you use which? Check first when you can – Test-Path before opening a file, a regular expression before converting – because a check gives the clearest message. Use try/catch for what can't be known in advance: a file that exists but may not be deleted, a disk that is full, a division whose divisor arrives at run time.
The lines of a file as an array
Reading a file line by line is one way to process it. The other is to take all the lines at once: what Get-Content returns is an array, and an array can be counted, indexed and walked in any direction.
$lines = @(Get-Content -Path $File -Encoding UTF8) # @( ): an array even for a one-line file
$lines.Count # the number of lines
$lines[0] # the first line
$lines[-1] # the last line
$lines[$lines.Count - 1] # the last line again, written out
for ($i = $lines.Count - 1; $i -ge 0; $i--) { # from the back to the front
Write-Output $lines[$i]
}@( ): a one-line file comes back as a string, and a string can be indexed too.The @( ) is not decoration. With a file of a single line, Get-Content returns one string, and indexing a string gives a character (Fig. 4). A script that works on the five-line sample and prints one letter on a one-line file is a classic bug. A foreach loop over Get-Content is not affected – only indexing and .Count.
Collect, then write once
When the result goes into a file, collect the lines in an array and write the array at the end – one element per line, a newline after the last. Nothing has to be emptied before the loop, and a second run gives the same file. With two outputs, use two arrays:
$oddLines = @()
$evenLines = @()
…
$oddLines += $line # inside the loop: collect, don't write
…
Set-Content -Path odd_lines.txt -Value $oddLines -Encoding UTF8 # after the loop: write onceSplitting a line into words
-split '\s+' cuts a line at every run of whitespace and returns the words as an array. Trim the line first, or a leading space produces an empty first element.
$words = $line -split '\s+' # "asd1 asd2" -> two words, whatever the gap
$words.Count # 2
$word1, $word2 = $line -split '\s+' # or straight into two variables
"a b" -split ' ' # WRONG: three elements – "a", "" and "b"Replacing and matching text
-replace rewrites text: the pattern is a regular expression, every occurrence is replaced, and – like every PowerShell text operator – it ignores case. (It does what sed 's/pattern/replacement/g' does in a Unix shell.)
"gipsz kukac jakab" -replace 'kukac', '@' # gipsz @ jakab every occurrence
"gipsz KUKAC jakab" -replace 'kukac', '@' # gipsz @ jakab case is ignored
"gipsz kukac jakab" -replace '\s*kukac\s*', '@' # gipsz@jakab the spaces are part of the match
"a1b22c333" -replace '[0-9]+', '' # abc replacing with nothing deletesA worked example. emails_encoded.txt holds e-mail addresses written so that address harvesters don't recognise them, in several spellings. (kukac is the Hungarian name of the @ sign, pont means dot.)
emails_encoded.txt
gipsz kukac jakab pont hu
gipsz(kukac)jakab(pont)hu
gipsz [at] jakab [dot] hu
gipsz KUKAC jakab PONT hu
teszt.elek kukac inf pont elte pont hu
kovacs.anna at example dot com One pattern has to cover kukac, (kukac), [at] and KUKAC, with or without spaces around them. Build it from the inside out (Fig. 5): the word itself, a word boundary on each side, an optional bracket on each side, and any spaces on each side – all of which should vanish together.
@.$email = $line -replace '\s*[\(\[]?\b(kukac|at)\b[\)\]]?\s*', '@'
$email = $email -replace '\s*[\(\[]?\b(pont|dot)\b[\)\]]?\s*', '.'The \b looks optional and isn't. It marks a word boundary: the place between a letter and a non-letter. Without it, at also matches in the middle of a word:
"kata kukac x" -replace '\s*(kukac|at)\s*', '@' # k@a@x – the "at" inside "kata" matched too
"kata kukac x" -replace '\s*\b(kukac|at)\b\s*', '@' # kata@x – \b: only as a whole wordContains: -like
For "does the line contain this text" no regular expression is needed. -like compares with a wildcard pattern, and the stars on both sides do the "anywhere":
"ASD32B" -like "*as*" # True – a wildcard pattern; case is ignored
"ASD32B" -clike "*as*" # False – the c prefix makes it case-sensitive
"ASD32B" -like "as" # False – without the stars the whole string must be "as"
if ($line -like "*$Text*") { … } # contains the text of a variable: double quotesPractice
Thirteen tasks in three groups. Solve each one yourself first and open the hint only when you have something that runs. Work inside pwsh, in the folder where you put the input files.
Downloads
Input files for the text-file tasks
Small on purpose, and with the usual traps: lines.txt has no newline after its last line, emails_encoded.txt and neptun_codes.txt have Windows (CRLF) line endings, an empty line and stray spaces.
Or all at once, from the terminal – the files land in the current directory:
Linux, macOS, the course server
curl -O "https://laszlonemes.com/files/computer-systems/powershell-practice/{lines.txt,emails_encoded.txt,word_pairs.txt,neptun_codes.txt}"Windows (PowerShell, cmd)
curl.exe -O "https://laszlonemes.com/files/computer-systems/powershell-practice/{lines.txt,emails_encoded.txt,word_pairs.txt,neptun_codes.txt}"Look at the data files freely, but don't save them from an editor: their line endings and last bytes are part of the exercise, and an editor may "fix" them.
For every task:
- the script checks its parameters, and the file if it reads one
- error messages go through
Write-Error, and the script stops withexit 1 - files are read with the skeleton at the top of this page
Numbers and functions
Task 1
Factorial
Compute the factorial of the number given as a parameter. A function does the calculation. The result must be exact for large numbers too.
PS> ./factorial.ps1 5
Factorial of 5 is 120
PS> ./factorial.ps1 -Number 25
Factorial of 25 is 15511210043330985984000000Approach
A [string] parameter checked with '^[0-9]+$', then a function with a for loop. Without a type the result turns into an inexact decimal on the way – 25! comes out as 1.5511210043331E+25; with [bigint] every digit is there.
function Get-Factorial {
param([int]$N)
[bigint]$result = 1 # 0! and 1! are right without a special case
for ($i = 2; $i -le $N; $i++) {
$result = $result * $i
}
return $result
}
$factorial = Get-Factorial $Number # called like a command: no parenthesesTask 2
The largest parameter
Print the largest of the script's parameters. It can get any number of them; a parameter that isn't a number is an error.
PS> ./max_parameters.ps1 3 17 5
Largest of the 3 parameter(s): 17
PS> ./max_parameters.ps1 -2.5 -7 -3
Largest of the 3 parameter(s): -2.5
PS> ./max_parameters.ps1 4 x
Write-Error: 'x' is not a numberApproach
No param() block on purpose, so everything lands in $args. Validate each element as text, convert it to [double], and keep the largest so far – starting from the first element, not from 0, or a list of negative numbers gives the wrong answer.
$first = $true
foreach ($arg in $args) {
if ("$arg" -notmatch '^[+-]?[0-9]*\.?[0-9]+$') {
Write-Error "'$arg' is not a number"
exit 1
}
[double]$number = "$arg"
if ($first -or $number -gt $largest) {
$largest = $number
$first = $false
}
}With an array parameter
Declare param( [double[]]$Numbers ) and call the script with commas: ./max_parameters.ps1 -Numbers 3,17,5. PowerShell then does the validation, and ($Numbers | Measure-Object -Maximum).Maximum the search. Write this version too; Fig. 2 shows what happens if you call it with spaces.
Task 3
Quadratic equation
Solve ax² + bx + c = 0. Read the three coefficients from the keyboard.
a: 1
b: -3
c: 2
Two real roots: x1 = 2, x2 = 1Approach
Three Read-Host calls, each converted to [double] (validate the text first if you want your own error message). Refuse a = 0. The sign of the discriminant decides between two roots, one root and no real root; [math]::Round(…, 4) keeps the output tidy.
[double]$a = Read-Host "a" # Read-Host returns text – convert before you calculate
$discriminant = $b * $b - 4 * $a * $c
if ($discriminant -gt 0) {
$x1 = (-$b + [math]::Sqrt($discriminant)) / (2 * $a)
…
Write-Output "Two real roots: x1 = $([math]::Round($x1, 4)), x2 = $([math]::Round($x2, 4))"
} elseif ($discriminant -eq 0) { … } else { … }Try the other two cases as well: 1, 2, 1 has one root, -1; 1, 0, 4 has no real root.
Task 4
Is it prime?
Decide whether an integer given as a parameter is a prime number.
PS> ./is_prime.ps1 97
97 is a prime number
PS> ./is_prime.ps1 91
91 is not a prime numberApproach
The Test-Prime function from the section on numbers, called in an if. Divisors up to the square root are enough – the loop condition $i * $i -le $N says that without a square root. 91 looks prime and isn't: 7 · 13.
Task 5
Greatest common divisor
Find the greatest common divisor of two integers.
PS> ./gcd.ps1 24 36
The greatest common divisor of 24 and 36 is 12
PS> ./gcd.ps1 -First 17 -Second 5
The greatest common divisor of 17 and 5 is 1Approach
This time the parameters are typed, so PowerShell validates them – try ./gcd.ps1 abc 4. Count the passed parameters with $PSBoundParameters.Count. The calculation is Euclid's algorithm; take absolute values first so negative input works too.
param(
[int]$First,
[int]$Second
)
if ($PSBoundParameters.Count -ne 2) { … } # [int] defaults to 0, so -not $First can't tell
while ($B -ne 0) { # Euclid: replace the pair by (B, A mod B)
$remainder = $A % $B
$A = $B
$B = $remainder
}Task 6
Primes in an interval
Print the prime numbers of an interval given by two parameters, and how many there are. The prime test is a function.
PS> ./primes_in_interval.ps1 10 50
Prime numbers between 10 and 50: 11 13 17 19 23 29 31 37 41 43 47
Count: 11Approach
Reuse Test-Prime, collect the hits in an array, and print them on one line with -join. Refuse an interval whose start is greater than its end. Note the braces in ${To}: – a colon follows the variable name.
$primes = @()
for ($n = $From; $n -le $To; $n++) {
if (Test-Prime $n) {
$primes += $n
}
}
Write-Output "Prime numbers between $From and ${To}: $($primes -join ' ')"As a pipeline it is one line: $From..$To | Where-Object { Test-Prime $_ }.
Text files
Task 7
Odd and even lines
Copy the odd-numbered lines of a file into odd_lines.txt and the even-numbered ones into even_lines.txt. Check that the file exists. Input: lines.txt – five lines, the last without a newline.
PS> ./odd_even_lines.ps1 lines.txt
Odd lines: odd_lines.txt (3 line(s))
Even lines: even_lines.txt (2 line(s))
PS> Get-Content odd_lines.txt
asd
asd3
asd5Approach
Two arrays and your own line counter, written out after the loop. The last line arrives although no newline follows it, and since the arrays are written in one go, the output files need no emptying first. No .Trim() here: the task is to copy the lines as they are.
$lineNumber = 1
foreach ($line in Get-Content -Path $File -Encoding UTF8) {
if ($lineNumber % 2 -eq 1) {
$oddLines += $line
} else {
$evenLines += $line
}
$lineNumber++
}Task 8
Encoded e-mail addresses
Turn every encoded address in a file into a real one, print them, and write them to a second file as well. Both file names are parameters. Input: emails_encoded.txt.
PS> ./decode_emails.ps1 emails_encoded.txt emails_decoded.txt
gipsz@jakab.hu
gipsz@jakab.hu
gipsz@jakab.hu
gipsz@jakab.hu
teszt.elek@inf.elte.hu
kovacs.anna@example.com
6 address(es) written to 'emails_decoded.txt'Approach
The skeleton, then the two -replace lines of Fig. 5 – one for the @, one for the dots. The file has an empty line and a line wrapped in spaces; .Trim() and the empty-line check deal with both before the pattern sees the line.
$decoded = @()
…
$email = … # the two -replace lines above
Write-Output $email # to the terminal …
$decoded += $email # … and remembered for the file
…
Set-Content -Path $OutFile -Value $decoded -Encoding UTF8Task 9
The last lines of a file
Print the last n lines of a file, each with its line number. The file is the first parameter, n the second, with 3 as its default. If the file has fewer lines, print them all. Input: lines.txt.
PS> ./last_lines.ps1 lines.txt 2
4: asd4
5: asd5
PS> ./last_lines.ps1 lines.txt
3: asd3
4: asd4
5: asd5Approach
@(Get-Content …) gives the array, .Count its length. Compute the index to start from and don't let it go below 0; the line number is the index plus one.
param(
[string]$File,
[int]$Lines = 3
)
…
$all = @(Get-Content -Path $File -Encoding UTF8)
$start = $all.Count - $Lines
if ($start -lt 0) {
$start = 0
}
for ($i = $start; $i -lt $all.Count; $i++) {
Write-Output "$($i + 1): $($all[$i])"
}Task 10
Swap two words
Every line of a file has two words. Print each line with its words swapped. Input: word_pairs.txt.
PS> ./swap_words.ps1 word_pairs.txt
asd2 asd1
asd4 asd3
ketto egyApproach
Split at whitespace and print the two elements in the other order. A line that doesn't have exactly two words gets a warning and is skipped – one odd line shouldn't stop the script.
$words = $line -split '\s+'
if ($words.Count -ne 2) {
Write-Warning "Line skipped (not exactly two words): '$line'"
continue
}
Write-Output "$($words[1]) $($words[0])"The last task of this group works on Neptun codes – the six-character student identifiers. This is what cat -et shows of the input: CRLF endings, an empty line, a code wrapped in spaces, two invalid codes, and no newline at the end.
cat -et neptun_codes.txt
RVR55V^M$
ASD32B^M$
abc123^M$
^M$
GFGDDD^M$
AS55VF ^M$
TOOLONG1^M$
ab-12c^M$
X1Y2Z3Task 11
Filter the Neptun codes
From a file of Neptun codes, copy those that contain a given text into another file. Parameters: the input file, the text, the output file. Input: neptun_codes.txt.
PS> ./filter_neptun.ps1 neptun_codes.txt 55 matches.txt
2 identifier(s) containing '55' copied to 'matches.txt'
PS> Get-Content matches.txt
RVR55V
AS55VFApproach
The skeleton with three parameters, -like "*$Text*" for the test, an array for the hits. AS55VF sits between spaces in the file – without .Trim() they would be copied along. Since -like ignores case, searching for as finds ASD32B and AS55VF.
$line = $line.Trim() # " AS55VF " -> "AS55VF"
if ($line -eq '') { continue }
if ($line -like "*$Text*") {
$found += $line
}Errors you can't rule out
Task 12
Division with error handling
Divide the first parameter by the second. Check that you got two integers. Guard the division by zero with try/catch.
PS> ./divide.ps1 10 4
10 / 4 = 2.5
PS> ./divide.ps1 10 0
Write-Error: Cannot divide 10 by 0: Attempted to divide by zero.
PS> ./divide.ps1 10 abc
Write-Error: 'abc' is not an integerApproach
Two [string] parameters validated with '^-?[0-9]+$' – a foreach over both saves writing the check twice – then converted to [int]. The result is not an integer division: 10 / 4 is 2.5.
[int]$a = $Dividend
[int]$b = $Divisor
try {
$result = $a / $b
Write-Output "$a / $b = $result"
} catch {
Write-Error "Cannot divide $a by ${b}: $($_.Exception.Message)"
exit 1
}Task 13
Delete a list of files
Delete the files given as a list. The script must finish without an error even if one of the files doesn't exist, and report what it did.
PS> New-Item a.tmp, b.tmp, c.tmp -ItemType File
PS> ./delete_files.ps1 a.tmp,b.tmp,missing.tmp,c.tmp
Deleted: a.tmp
Deleted: b.tmp
Skipped (not an existing file): missing.tmp
Deleted: c.tmp
3 file(s) deleted, 1 skippedApproach
An array parameter, [string[]]$Files, passed with commas. Check each file with Test-Path and skip the missing ones; wrap the deletion in try/catch for whatever else can go wrong (a file in use, no permission) – with -ErrorAction Stop, or the catch is dead code.
param(
[string[]]$Files
)
…
foreach ($file in $Files) {
if (-not (Test-Path -Path $file -PathType Leaf)) {
Write-Output "Skipped (not an existing file): $file"
$skipped++
continue
}
try {
Remove-Item -Path $file -ErrorAction Stop
$deleted++
} catch {
Write-Warning "Could not delete '$file': $($_.Exception.Message)"
$skipped++
}
}The list can come from a file too: ./delete_files.ps1 (Get-Content delete_list.txt). The output of the command in parentheses becomes the parameter.
Common mistakes
| Wrong | Right | Why |
|---|---|---|
./script.ps1 1 2 3 for an [int[]]$Numbers parameter | ./script.ps1 1,2,3 | Array elements are separated by commas; with spaces only the first lands in the array. |
Get-Factorial(5) | Get-Factorial 5 | A function is called without parentheses. |
if ($Number) to see whether an [int] was given | $PSBoundParameters.ContainsKey('Number') | 0 is false too, and it can be a valid value. |
param([int]$Count), then $PSBoundParameters.Count | another name, e.g. $Times | A parameter called Count hides the Count property. |
$sum = $a + $b on what Read-Host returned | [double]$a = Read-Host "a" | For strings + glues: "1" + "2" is "12". |
try { Remove-Item $f } catch { … } | Remove-Item $f -ErrorAction Stop | Without it catch doesn't see the cmdlet's error. |
$lines = Get-Content $f, then $lines[0] or $lines.Count - 1 | $lines = @(Get-Content $f) | A one-line file comes back as a string, not an array. |
if ($line -like $Text) for "contains" | $line -like "*$Text*" | Without the stars only an identical line matches. |
$line -split ' ' | $line -split '\s+' | With several spaces in a row, empty elements end up in the array. |
-replace 'at', '@' for the word at | -replace '\bat\b', '@' | Without the word boundaries it also matches inside kata. |
[int]$result for a factorial | [bigint]$result | A typed integer stops with an error at 13!; an untyped variable silently turns into an inexact decimal. |
… > out.txt in a script | … | Set-Content -Path out.txt -Encoding UTF8 | In Windows PowerShell 5.1, > writes a UTF-16 file. |
Summary
$PSBoundParameters- the parameters that were actually passed:
.Count,.ContainsKey('Name') $args- the parameters not named in
param()– all of them if there is noparam()block - array parameter
[int[]]$Numbers; passed with commas:1,2,3- terminating error
- an error that jumps to
catch; a cmdlet's error becomes one with-ErrorAction Stop @( )- forces an array, even from one value or none
- word boundary
\bin a regular expression: the edge between a word and what surrounds it
- Every script opens the same way:
param(), usage on a missing parameter,Test-Path, then theforeachoverGet-Content. - A typed parameter is validated by PowerShell; a
[string]plus a regular expression gives you your own message. - An
[int]defaults to 0 – ask$PSBoundParameterswhether it was given. /is a real division,Read-Hostreturns text, and[bigint]keeps every digit.catchonly receives a cmdlet's error with-ErrorAction Stop; check withTest-Pathwhere you can.@(Get-Content …)is an array you can index and walk backwards; collect output lines in an array and write once.-replacetakes a regular expression and replaces every match;-like "*text*"tests "contains". Both ignore case.
Further reading
- about_Try_Catch_Finally –
try,catchandfinally - about_Functions_Advanced_Parameters – parameter types, defaults, switches
- about_Regular_Expressions – regular expressions in PowerShell
- regex101 – try a pattern before you put it into a script (".NET (C#)" flavour)