Skip to content

Commit

Permalink
main
Browse files Browse the repository at this point in the history
  • Loading branch information
sharpchen committed Dec 2, 2024
1 parent 6cf07b7 commit 5f70229
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 40 deletions.
4 changes: 2 additions & 2 deletions docs/document/Powershell/docs/Language/Control Flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
75 changes: 63 additions & 12 deletions docs/document/Powershell/docs/Language/Function.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand All @@ -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.
Expand Down Expand Up @@ -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 {
"$_"
}
}
```
Expand Down
6 changes: 3 additions & 3 deletions docs/document/Powershell/docs/Language/Invocation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
36 changes: 18 additions & 18 deletions docs/document/Powershell/docs/Object Manipulation/Object Members.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions docs/document/Powershell/docs/Object Manipulation/Select.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
103 changes: 98 additions & 5 deletions docs/document/Powershell/docs/Understanding Pipeline.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#
# Understanding Pipeline

Overview of pipeline in powershell:

Expand All @@ -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:
Expand All @@ -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.

Expand All @@ -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<KeyValuePair<,>>`.


## 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
```

0 comments on commit 5f70229

Please sign in to comment.