Powershell: Difference between revisions
Jump to navigation
Jump to search
Line 462: | Line 462: | ||
=== Read / Write / Modify file line by line === |
=== Read / Write / Modify file line by line === |
||
* Reading a file is done through <code>Get-Content</code>. |
|||
* Writing to a file is done through <code>Set-Content</code>. |
|||
:* DO NOT USE <code>Out-File</code>, because it does SLICE the long lines, hence modifying output, and also output non-matching (ie. empty) lines. |
|||
<source lang="powershell"> |
<source lang="powershell"> |
||
# This is slow on big file |
# This is slow on big file |
||
Set-Content |
Set-Content .\newfile.txt -Value (Get-content .\file.txt | Select-String -Pattern 'H\|159' -NotMatch) |
||
# Another way using pipe and |
|||
Get-Content .\file.txt | Where-Object { -not $_.Contains('H|159') } | Set-Content .\newfile.txt |
|||
</source> |
|||
* Use <code>foreach</code> to process the file line by line, or <code>ForEach-Object</code> with a pipeline (see [https://devblogs.microsoft.com/scripting/getting-to-know-foreach-and-foreach-object/ this blog]). |
|||
:* <code>foreach</code> is a statement like <code>if</code>. It will always load the ENTIRE COLLECTION in an object before starting processing. |
|||
:* <code>ForEach-Object</code> is however pipeline aware, and have a lower memory usage (but is less performant). |
|||
<source lang="powershell"> |
|||
# however foreach loads the ENTIRE file in memory first. |
|||
foreach($line in Get-Content .\file.txt) { |
|||
if($line -match $regex){ |
|||
# Work here |
|||
} |
|||
} |
|||
# Pipe-aware - Less memory, slower. |
|||
Get-Content .\file.txt | ForEach-Object { |
|||
if($_ -match $regex){ |
|||
# Work here |
|||
} |
|||
} |
|||
# Or use pipe and Where-Object |
|||
Get-Content .\file.txt | Where-Object {$_ -match $regex} | ForEach-Object { |
|||
# Work here |
|||
} |
|||
# ... short syntax ... |
|||
gc 'file.txt' | ?{ $_ -match $regex } | %{ # Work here } |
|||
</source> |
|||
* Use <code>swith -regex -file</code> to filter a file with regular expression. |
|||
<source lang="powershell"> |
|||
'one |
|||
two |
|||
three' > file |
|||
$regex = '^t' |
|||
switch -regex -file file { |
|||
$regex { "line is $_" } |
|||
} |
|||
</source> |
|||
* With .NET library, use <code>[System.IO.File]</code> for better performance: |
|||
<source lang="powershel"> |
|||
# Low memory and good performance |
|||
[System.IO.File]::ReadLines("C:\path\to\file.txt") | ForEach-Object { |
|||
$_ |
|||
} |
|||
# This is faster, but still foreach will load the whole file in memory first |
|||
foreach($line in [System.IO.File]::ReadLines("C:\yourpathhere\file.txt")) |
|||
{ |
|||
Write-Output $line |
|||
} |
|||
# ... |
|||
[System.IO.StreamReader]$sr = [System.IO.File]::Open($file, [System.IO.FileMode]::Open) |
|||
while (-not $sr.EndOfStream){ |
|||
$line = $sr.ReadLine() |
|||
} |
|||
$sr.Close() |
|||
</source> |
</source> |
Revision as of 13:35, 8 March 2022
Links
- Learn powershell in Y minutes — A MUST-READ to learn rapidly.
- Everything you wanted to know about arrays - microsoft.com
- Everything you wanted to know about hashtables - microsoft.com
- About preference variables - microsoft.com
Reference
Files
Powershell scripts have a .ps1 extension.
Environment
Powershell uses preference variables [1]:
$ErrorActionPreference
: value can be'SilentlyContinue'
,'Stop'
,'Continue'
(default),'Inquire'
,'Ignore'
,'Suspend'
, or'Break'
.
To get powershell version:
Get-Host
Get-Host | select version
Syntax
✐ | See learn Powershell in Y minutes for more! |
Primitive types / operators
Strings / arrays / hashtables / ...
# Strings
$hello = "Hello"
$world = "World"
'Hello, World!' # No interpolation
"Hello, $world!" # 'Hello, World!'
"Hello, World!".Length # Length
"{0}, {1}!" -f $hello, $world # f-string
"$world is $($world.length) char long"
'Hello, World!'[0] # 'H'
'Hello, World!'[0..5] # 'H', 'e', 'l', 'l', 'o'
'Hello, World!'[0,2,4] # 'H', 'l', 'o'
'Hello, World!'.Substring(0,5) # 'Hello'
'Hello, ' + 'World!' # 'Hello, World!'
'First line`nSecond line' # Escape with backtick
"foo" | gm # Get all methods / properties
# Arrays
# TBC
# Dictionaries
# TBC
Control flow
IO / Files
Functions
# Functions: keep 'Verb-Noun' convention!
function Add-Numbers { # No explicit args
$args[0] + $args[1]
}
Add-Numbers 1 2 # 3
function Add-Numbers($first,$second) { # Explicit args
$first + $second
}
function Add-Numbers { # Explicit args with type
param( [int]$first, [int]$second )
$first + $second
}
Add-Numbers 1 2 # 3, still works
Add-Numbers -first 1 -second 2 # 3
Add-Numbers 1 -second 2 # 3
$params=@{first=1; second=2}
Add-Numbers @params # 3, can pass hashtable as params
$res = $(Add-Numbers 1 2)
$res = Add-Numbers 1 2 # same
# Use Switch to build toggle flag
function Inc-Number($value,[Switch]$Verbose)
{
if ($Verbose) { Write-Output $value }
$value + 1
}
Inc-Number 1 -Verbose
Exec
Miscellaneous
Tips
Measure execution time of a command
Measure-Command { dir }
Measure-Command { dir | Out-default} # To get output
Measure-Command { choco list }
Update help on offline computers
From MS devblogs:
# On online computer
New-Item c:\tmp\help
Save-Help -DestinationPath c:\tmp\help -Module * -Force
# On offline computer
# ... transfer files to c:\tmp\help
# ... start powershell in admin (Win-X-A)
Update-Help -SourcePath c:\tmp\help -Module * -Force
Find all links recursively
From SO:
dir 'd:\Temp' -recurse -force | ?{$_.LinkType} | select FullName,LinkType,Target
# In case of multi targets for hardlinks, this list them separated by TAB
dir 'd:\Temp' -recurse -force | ?{$_.LinkType} | select FullName,LinkType,@{ Name = "Targets"; Expression={$_.Target -join "`t"} }
Test if a path is a file or a folder
# https://devblogs.microsoft.com/scripting/powertip-using-powershell-to-determine-if-path-is-to-file-or-folder/
# Using object type
(Get-Item C:\fso) -is [System.IO.DirectoryInfo] # True
(Get-Item C:\fso\csidl.txt) -is [System.IO.DirectoryInfo] # False
# Using PSIsContainer attribute
(Get-Item -Path C:\fso).PSIsContainer # True
(Get-Item -Path C:\fso\csidl.txt).PSIsContainer # False
Read / Write / Modify file line by line
- Reading a file is done through
Get-Content
. - Writing to a file is done through
Set-Content
.
- DO NOT USE
Out-File
, because it does SLICE the long lines, hence modifying output, and also output non-matching (ie. empty) lines.
- DO NOT USE
# This is slow on big file
Set-Content .\newfile.txt -Value (Get-content .\file.txt | Select-String -Pattern 'H\|159' -NotMatch)
# Another way using pipe and
Get-Content .\file.txt | Where-Object { -not $_.Contains('H|159') } | Set-Content .\newfile.txt
- Use
foreach
to process the file line by line, orForEach-Object
with a pipeline (see this blog).
foreach
is a statement likeif
. It will always load the ENTIRE COLLECTION in an object before starting processing.ForEach-Object
is however pipeline aware, and have a lower memory usage (but is less performant).
# however foreach loads the ENTIRE file in memory first.
foreach($line in Get-Content .\file.txt) {
if($line -match $regex){
# Work here
}
}
# Pipe-aware - Less memory, slower.
Get-Content .\file.txt | ForEach-Object {
if($_ -match $regex){
# Work here
}
}
# Or use pipe and Where-Object
Get-Content .\file.txt | Where-Object {$_ -match $regex} | ForEach-Object {
# Work here
}
# ... short syntax ...
gc 'file.txt' | ?{ $_ -match $regex } | %{ # Work here }
- Use
swith -regex -file
to filter a file with regular expression.
'one
two
three' > file
$regex = '^t'
switch -regex -file file {
$regex { "line is $_" }
}
- With .NET library, use
[System.IO.File]
for better performance:
# Low memory and good performance
[System.IO.File]::ReadLines("C:\path\to\file.txt") | ForEach-Object {
$_
}
# This is faster, but still foreach will load the whole file in memory first
foreach($line in [System.IO.File]::ReadLines("C:\yourpathhere\file.txt"))
{
Write-Output $line
}
# ...
[System.IO.StreamReader]$sr = [System.IO.File]::Open($file, [System.IO.FileMode]::Open)
while (-not $sr.EndOfStream){
$line = $sr.ReadLine()
}
$sr.Close()