From 5f7022928b15a8c28152d0c57f331df83097c5c9 Mon Sep 17 00:00:00 2001 From: sharpchen Date: Tue, 3 Dec 2024 01:28:30 +0800 Subject: [PATCH] main --- .../{Units of Size.md => File Size.md} | 0 .../Powershell/docs/Language/Control Flow.md | 4 +- .../Powershell/docs/Language/Function.md | 75 +++++++++++-- .../Powershell/docs/Language/Invocation.md | 6 +- .../Object Manipulation/Object Members.md | 36 +++--- .../docs/Object Manipulation/Select.md | 20 ++++ .../Powershell/docs/Understanding Pipeline.md | 103 +++++++++++++++++- 7 files changed, 204 insertions(+), 40 deletions(-) rename docs/document/Powershell/docs/File System/{Units of Size.md => File Size.md} (100%) diff --git a/docs/document/Powershell/docs/File System/Units of Size.md b/docs/document/Powershell/docs/File System/File Size.md similarity index 100% rename from docs/document/Powershell/docs/File System/Units of Size.md rename to docs/document/Powershell/docs/File System/File Size.md diff --git a/docs/document/Powershell/docs/Language/Control Flow.md b/docs/document/Powershell/docs/Language/Control Flow.md index 343f4ef7..4a60d59f 100644 --- a/docs/document/Powershell/docs/Language/Control Flow.md +++ b/docs/document/Powershell/docs/Language/Control Flow.md @@ -31,11 +31,11 @@ gci ` > [!TIP] > Stop using backticks! They're ugly! Use hashtable splatting instead. >```ps1 ->$table = { +>$table = @{ > Filter = '*.mp4'; > File = $true >} ->gci -file @table +>gci @table >``` ## Multi-Line Piping diff --git a/docs/document/Powershell/docs/Language/Function.md b/docs/document/Powershell/docs/Language/Function.md index 189caa9c..45a67fa0 100644 --- a/docs/document/Powershell/docs/Language/Function.md +++ b/docs/document/Powershell/docs/Language/Function.md @@ -101,16 +101,6 @@ Foo -Foo foo -Bar bar Foo foo bar # it's the same # [!code highlight] ``` -### Default Parameter - -```ps1 -function Foo { - param ( - [string]$foo = "foo" - ) -} -``` - ### Flags Defining flags that represents a toggle needs a special type called `switch`. @@ -135,6 +125,32 @@ Manual assignment is also available: Foo -f:$false -b $true ``` +### Default Parameter + +- Explicitly typed parameters can have implicit default value. + - `switch` and `bool` is `$false` by default. + - `string` is `[string]::Empty` by default. + - Numeric types are zero value by default. +- Parameters without type annotation are always typed as `object` which has the default value `$null`. +- Can override default value by `=` in declaration. + +```ps1 + +& { param([string]$name) $name -eq [string]::Empty } # True +& { param($name) $name -eq $null } # True +& { param([int]$age) $age -eq 0 } # True +& { param([switch]$is) $is -eq $false } # True + +function Foo { + param ( + [string]$foo = "foo" + ) +} +``` + +> [!NOTE] +> For overriding default parameter outside the function, see [$PSDefaultParameterValues](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_parameters_default_values?view=powershell-7.4#long-description) + ### Required Parameter All parameters are optional by default. Use `[Parameter(Mandatory=$true)]` to mark it as required. @@ -196,14 +212,49 @@ But things will explode when we're dealing with a pipeline input which might bri The pipeline mechanism is essentially based on the `Enumerator` so if we collect all items into a new collection as parameter value, it can be a huge performance issue. So named blocks are essentially to defined a shared process logic for each object in the pipeline input, and other logic like initializationa and finalization. -> [!NOTE] -> When no named block were specified, `end` block is used to represent the whole logic of a simple function. +- `begin`: state initializationa for the pipeline iteration. +- `process`: logic for each pipeline iteration. +- `end`: final action for the completed pipeline iteration. +- `clean`: a `finally` block to execute clean up no matter what happens.(PowerShell 7.3+) ```ps1 function Foo { begin {} process {} end {} + clean {} +} +``` + +> [!NOTE] +> When no named block were specified, `end` block is used to represent the whole logic of a simple function. +>```ps1 +>function Foo { +> end { +> echo hello +> } +>} +># equivalent to +>function Foo { +> echo hello +>} +>``` + +## Filter Function + +Filter is a special kind of function that implicitly accepts pipeline to perform transformation(select) or filtering(where). +Filter is useful when you need to reuse the same logic for unknown pipeline manipulations, reducing hard coding. + +```ps1 +filter Foo { + "$_" # can transform +} + +# equivalent to +function Foo { + process { + "$_" + } } ``` diff --git a/docs/document/Powershell/docs/Language/Invocation.md b/docs/document/Powershell/docs/Language/Invocation.md index 912d17a4..d9524431 100644 --- a/docs/document/Powershell/docs/Language/Invocation.md +++ b/docs/document/Powershell/docs/Language/Invocation.md @@ -9,11 +9,11 @@ Call operator `&` can be used for invoking one of the following: - Command without any option. - A `.ps1` script file +- External executable - Script block -**`&` is awared of the context of current session, it does not start a new process.** - -It's more like a system execution +> [!NOTE] +> **`&` is awared of the context of current session, it does not start a new process when executing.** ```ps1 & 'gps' diff --git a/docs/document/Powershell/docs/Object Manipulation/Object Members.md b/docs/document/Powershell/docs/Object Manipulation/Object Members.md index 71082d1e..d7dc7169 100644 --- a/docs/document/Powershell/docs/Object Manipulation/Object Members.md +++ b/docs/document/Powershell/docs/Object Manipulation/Object Members.md @@ -2,35 +2,35 @@ ## Member Inspection +### Inspect from Pipeline + +`Get-Member` can insepct objects from a pipeline input, which means by iterating them one by one. + > [!TIP] > Use `gm` alias for `Get-Member`. +**Only members from a unique type in that pipeline input enumeration will be collected as the result.** + ```ps1 -ls | Get-Member +(gci -file | select -first 1 | gm).Length # 53 +(gci -dir | select -first 1 | gm).Length # 47 +# assuming current directory has both directory and file +(gci | gm).Length # 53 + 47 = 100 ``` -When inspecting an array of objects, `gm` only returns members for distinct types. +The whole array returned from `Get-Member` is `object[]`, each item inside is a `MemberDefinition` ```ps1 -@(123, '123') | gm | select -ExpandProperty TypeName -Unique -# System.Int32 -# System.String - -(@('123', 123) | Get-Member).Length # 82 -(@('123', 123, 123) | Get-Member).Length # still 82 - +(gci | gm) -is [object[]] # True +(gci | gm | select -first 1) -is [MemberDefinition] # True ``` -`Get-Member` returns a `MemberDefinition[]` or a `MemberDefinition` with following properties. +### Inspect from Object + +To treat a whole collection as the object to be inspected, do not pipe it, pass it to `-InputObject` instead. -```cs -class MemberDefinition -{ - public string Definition { get; } // expression or value or signature of the member - public System.Management.Automation.PSMemberTypes MemberType { get; } // type of member - public string Name { get; } // member name - public string TypeName { get; } // fullname of type or return type of the member -} +```ps1 +gm -InputObject (gci -file) # TypeName: System.Object[] ``` ## Member Types diff --git a/docs/document/Powershell/docs/Object Manipulation/Select.md b/docs/document/Powershell/docs/Object Manipulation/Select.md index 38e30506..6c09418f 100644 --- a/docs/document/Powershell/docs/Object Manipulation/Select.md +++ b/docs/document/Powershell/docs/Object Manipulation/Select.md @@ -118,6 +118,26 @@ $partner['Status'] # Poisoned > [!NOTE] > `-ExpandProperty` can only take one property. +### Calculated Property + +`Select-Object` can generate new NoteProperty to the `PSCustomObject` returned by specifying a HashTable with following shape: +- `Name`(or `N`, `Lable`, `L`): the name of the new property. +- `Expression`(`E`): the calculation logic for the new property represented as a script block. + +```ps1 +$person = @{ + FirstName = 'John' + LastName = 'Smith' +} + +# return a new PSCustomObject that has FirstName, LastName and FullName. +$person | select *, @{ Name = 'FullName'; Expression = { "$($_.FirstName) $($_.LastName)" } } +``` + +> [!NOTE] +> All selected properties from a HashTable will be regenerated as `NoteProperty`. +> Those selected properties are essentially `Keys` of the HashTable, not real properties. + ## Take a Count `Select-Object` can also take specific count of items from a collection, from start or end. diff --git a/docs/document/Powershell/docs/Understanding Pipeline.md b/docs/document/Powershell/docs/Understanding Pipeline.md index c51259b2..87c3a1b4 100644 --- a/docs/document/Powershell/docs/Understanding Pipeline.md +++ b/docs/document/Powershell/docs/Understanding Pipeline.md @@ -1,4 +1,4 @@ -# +# Understanding Pipeline Overview of pipeline in powershell: @@ -9,6 +9,7 @@ Overview of pipeline in powershell: ## Pipeline Parameter Binding + ## How Cmdlet Accept Pipeline Input There's two solution when a pipeline input comes in as a fallback: @@ -29,10 +30,6 @@ spps -Name (gci -File | foreach Name) gci -File | spps ``` - -> [!WARNING] -> If multiple matches exist on ByPropertyName solution, powershell throws an error since these paramters might not be allowed to be used together. - ByValue is always tried first, and then use ByPropertyName, or it finally throws. A parameter accepts pipeline input does not necessarily have both solutions, it can have at least one of them. @@ -54,3 +51,99 @@ $table = @{ Name = 'foo'; Age = 18 } This is simply because these types are more likely to be treated as a whole object, even when dictionaries are `IEnumerable>`. + +## Enumerate Pipeline Items + +You can use `$input` to refer to the enumerator passed to the function. This is another way to access pipeline input items but with more control. +Another option is use `$_` to represent the current item in `process` block, this is way more limited but commonly used. + +- `$input` represents a enumerator for pipeline input in `process` block. +- `$input` represents the whole collection for pipeline input in `end` block. +- `$input` will be consumed after being used once in either `process` or `end`. Use `Reset` to get it back. +- You can't use `$input` in both `process` and `end`. + +### Access Current Item + +`$input.Current` have to manually invoke `MoveNext` before you access `Current` in `process` block since it's not a `while` loop. + +```ps1 +function Test { + begin { + $input -is [System.Collections.IEnumerator] # True + } + + process { + # $input.Current before MoveNext in each iteration is always $null + # How weird! + $input.Current -eq $null # True because we haven't start the enumeration! + $input.MoveNext() | Out-Null # [!code highlight] + $input.Current -eq $null # False + } +} + +1,2,3 | Test +``` + +> [!WARNING] +> Before you read the following content, please keep in mind that `$input` behaves slightly different from general `IEnumerator` for `Reset`. + +`$input` itself is a wrapper of current item in `process` block, invoke `Reset` to get it back to current value. + +```ps1 +function Test { + process { + + } +} + +1,2,3 | Test +``` + +## Implicit Pipeline Input + +Function can accepts pipeline input without any specific parameter. +Inside the `process` block, `$_` represents the current object from the pipeline input. + +```ps1 +function Test { + process { + $_ -is [System.IO.FileInfo] # True + } +} + +gci -file | Test +``` + +> [!NOTE] +> `$_` is only available in `process` block. + +## Explicit Pipeline Input + +If you write a custom function that have one or more parameters accept pipeline input, what is going on inside? + +- In `begin` block, there's no value assigned to each `ByPropertyName` parameter, they remain default. +- In `process` block, each + +```ps1 +function Foo { + param ( + [Parameter(ValueFromPipelineByPropertyName)] + [string]$Name + [Parameter(ValueFromPipelineByPropertyName)] + [string]$Length + ) + + begin { + $Name -eq [string]::Empty # True + $Length -eq 0 # True + } + + process { + $Name + $Length + } +} + +gci -file | Foo +``` +