|  | 
| 1 | 1 | # Function | 
| 2 | 2 | 
 | 
| 3 |  | -## Return | 
| 4 |  | - | 
| 5 |  | -Powershell allows implicit return, and multiple implicit returns. | 
| 6 |  | - | 
| 7 |  | -> [!NOTE] | 
| 8 |  | -> Implicit returns are auto-collected as an array or single value. | 
| 9 |  | -> And it does not print out anything. | 
| 10 |  | -
 | 
| 11 |  | -```ps1 | 
| 12 |  | -[int] function Sum { | 
| 13 |  | -    param([int]$l, [int]$r) | 
| 14 |  | -    $l + $r # implicit return # [!code highlight]  | 
| 15 |  | -} | 
| 16 |  | -
 | 
| 17 |  | -# You won't need to declare an array and append it on each loop! | 
| 18 |  | -# they're collected automatically as they're implicit returns | 
| 19 |  | -function Foo { | 
| 20 |  | -   for($i = 0; $i -lt 10; $i = $i + 1)  { | 
| 21 |  | -        $i | 
| 22 |  | -   } | 
| 23 |  | -} | 
| 24 |  | -
 | 
| 25 |  | -(Foo).GetType().Name # object[] # [!code highlight]  | 
| 26 |  | -``` | 
| 27 |  | - | 
| 28 |  | -Explicit return is surely supported, but more like a necessity to exit inside a flow. | 
| 29 |  | - | 
| 30 |  | -```ps1 | 
| 31 |  | -[int] function Sum { | 
| 32 |  | -    param([int]$l, [int]$r) | 
| 33 |  | -    return $l + $r # explicit return # [!code highlight]  | 
| 34 |  | -    $r + $l # not reachable  # [!code warning]  | 
| 35 |  | -} | 
| 36 |  | -``` | 
| 37 | 3 | 
 | 
| 38 | 4 | ## Parameter | 
| 39 | 5 | 
 | 
| @@ -199,11 +165,38 @@ Will throw an error if any parameter does not satisfies the condition. | 
| 199 | 165 | param ( | 
| 200 | 166 |     [ValidateScript({ ($_ % 2) -ne 0 })] | 
| 201 | 167 |     [int[]]$Odd | 
| 202 |  | -    [ValidateScript({ $_.Length < 5 })] | 
|  | 168 | +    [ValidateScript({ $_.Length < 5 }, ErrorMessage = "{0} is not valid")] # 0 is the input value | 
| 203 | 169 |     [string]$Name | 
| 204 | 170 | ) | 
| 205 | 171 | ``` | 
| 206 | 172 | 
 | 
|  | 173 | +- `ValidateLength(min, max)` | 
|  | 174 | +- `ValidateCount(min, max)` | 
|  | 175 | +- `AllowNull()` | 
|  | 176 | +- `AllowEmptyString()` | 
|  | 177 | +- `AllowEmptyCollection()` | 
|  | 178 | +- `ValidatePattern(regex)` | 
|  | 179 | +- `ValidateRange(min, max)` | 
|  | 180 | +    - or any value of enum `ValidateRangeKind`: `Positive`, `Negative`, `NonNegative`, `NonPositive` | 
|  | 181 | +    ```ps1 | 
|  | 182 | +    param( | 
|  | 183 | +        [ValidateRange("Positive")] | 
|  | 184 | +        [int]$Number | 
|  | 185 | +    ) | 
|  | 186 | +    ``` | 
|  | 187 | +- `ValidateSet(foo, bar, ...)`: provides completion for predefined entries | 
|  | 188 | +- `ValidateNotNull()` | 
|  | 189 | +- `ValidateNotNullOr()` | 
|  | 190 | +- `ValidateNotNullOrEmpty()`: not a empty string or collection or null | 
|  | 191 | +- `ValidateNotNullOrWhiteSpace()` | 
|  | 192 | +- `ValidateDrive(drive, ...)`: check whether specified path is valid from certain PSDrive | 
|  | 193 | +    ```ps1 | 
|  | 194 | +    param( | 
|  | 195 | +        [ValidateDrive("C", "D")] | 
|  | 196 | +        [string]$Path | 
|  | 197 | +    ) | 
|  | 198 | +    ``` | 
|  | 199 | +
 | 
| 207 | 200 | ### Pass By Reference | 
| 208 | 201 | 
 | 
| 209 | 202 | Parameter passed by reference is implemented by a wrapper `System.Management.Automation.PSReference`. | 
| @@ -279,6 +272,139 @@ function Foo { | 
| 279 | 272 | } | 
| 280 | 273 | ``` | 
| 281 | 274 | 
 | 
| 282 |  | -## Lifetime | 
|  | 275 | +## Mimicking Cmdlet | 
|  | 276 | + | 
|  | 277 | +A function would generally not acting a cmdlet unless it was annotated with `CmdletBinding()` attribute. | 
|  | 278 | + | 
|  | 279 | +`CmdletBinding()` can have the following properties: | 
|  | 280 | + | 
|  | 281 | +- `DefaultParameterSetName`: name of implicit Parameter Set | 
|  | 282 | +- `HelpURI`: link to documenetation | 
|  | 283 | +- `SupportsPaging`: implicitly adds parameters `-First`, `-Skip`, `-IncludeTotalCount`, value accessible by `$PSCmdlet.PagingParameters` | 
|  | 284 | +    ```ps1 | 
|  | 285 | +    function foo { | 
|  | 286 | +        [CmdletBinding(SupportsPaging)] | 
|  | 287 | +        param() | 
|  | 288 | +        $PSCmdlet.PagingParameters.Skip | 
|  | 289 | +        $PSCmdlet.PagingParameters.First | 
|  | 290 | +        $PSCmdlet.PagingParameters.IncludeTotalCount | 
|  | 291 | +    } | 
|  | 292 | +    ``` | 
|  | 293 | +- `SupportsShouldProcess`: implicitly adds `-Confirm` and `-WhatIf` | 
|  | 294 | +- `ConfirmImpact`: specify impact of `-Confirm` | 
|  | 295 | +- `PositionalBinding`:  | 
|  | 296 | +
 | 
|  | 297 | +<!-- TODO: complete description for ConfirmImpact and PositionalBinding --> | 
|  | 298 | +
 | 
|  | 299 | +## Parameter Set | 
|  | 300 | +
 | 
|  | 301 | +How a same cmdlet manage different syntax for different usages? The trick is **Parameter Set**. | 
|  | 302 | +Parameter Set is a classification on paramater to distinguish or limit the use of parameters from scenarios. | 
|  | 303 | +
 | 
|  | 304 | +- a parameter set must have at least one unique parameter to others to identify the set | 
|  | 305 | +- a parameter can have multiple Parameter Set | 
|  | 306 | +- a parameter can have different roles in different Parameter Set, can be mandatory in one and optional in another | 
|  | 307 | +- a parameter without explicit Parameter Set belongs to all other Parameter Set | 
|  | 308 | +- at least one parameter in the Parameter Set is mandatory | 
|  | 309 | +- only one parameter in set can accept `ValueFromPipeline` | 
|  | 310 | +
 | 
|  | 311 | +### Parameter Set Idetifier at Runtime | 
|  | 312 | +
 | 
|  | 313 | +`$PSCmdlet.ParameterSetName` reflects the Parameter Set been chosen when a cmdlet is executing with certain syntax. | 
|  | 314 | +
 | 
|  | 315 | +
 | 
|  | 316 | +## Common Parameters | 
|  | 317 | +
 | 
|  | 318 | +Any function or cmdlet applied with `CmdletBinding()` or `Parameter()` attribute has the following implicit parameters added by PowerShell: | 
|  | 319 | +
 | 
|  | 320 | +- ErrorAction (ea): specify action on error | 
|  | 321 | +- ErrorVariable (ev): declare **inline** and store the error on the variable instead of `$Error`. Use `-ErrorVariable +var` to append error to the variable | 
|  | 322 | +    ```ps1 | 
|  | 323 | +    gcm foo -ev bar # inline declaration for $bar | 
|  | 324 | +    $bar # contains error | 
|  | 325 | +    gcm baz -ev +bar | 
|  | 326 | +    $bar # contains two errors | 
|  | 327 | +    ``` | 
|  | 328 | +    > [!NOTE] | 
|  | 329 | +    > The inline variable is an `ArrayList` | 
|  | 330 | +- InformationAction (infa): similar to `ea` | 
|  | 331 | +- InformationVariable (iv): similar to `ev` | 
|  | 332 | +- WarningAction (wa): similar to `ea` | 
|  | 333 | +- WarningVariable (wv): similar to `ev` | 
|  | 334 | +- ProgressAction (proga) | 
|  | 335 | +- OutVariable (ov): declare **inline** and store the output to the variable. Similar to `ev`. | 
|  | 336 | +    It's interesting that `-OutVariable` collects incremnentally. | 
|  | 337 | +    It collects new item from pipeline on iteration. | 
|  | 338 | +    ```ps1 | 
|  | 339 | +    1..5 | % { $_ } -OutVariable foo | % { "I am $foo" } | 
|  | 340 | +    # I am 1 | 
|  | 341 | +    # I am 1,2 | 
|  | 342 | +    # I am 1,2,3 | 
|  | 343 | +    # I am 1,2,3,4 | 
|  | 344 | +    # I am 1,2,3,4,5 | 
|  | 345 | +    ``` | 
|  | 346 | +
 | 
|  | 347 | +- Debug (db): print verbose debug message, overrides `$DebugPreference` | 
|  | 348 | +- OutBuffer (ob) | 
|  | 349 | +- PipelineVariable (pv) <!-- TODO: PipelineVariable --> | 
|  | 350 | +- Verbose (vb): whether display the verbose message from `Write-Verbose` | 
|  | 351 | +
 | 
|  | 352 | +## Mitigation Parameters | 
|  | 353 | +
 | 
|  | 354 | +- WhatIf (wi) | 
|  | 355 | +- Confirm (cf) | 
|  | 356 | +
 | 
|  | 357 | +## Return | 
|  | 358 | +
 | 
|  | 359 | +Powershell allows implicit return, and multiple implicit returns. | 
|  | 360 | +
 | 
|  | 361 | +> [!NOTE] | 
|  | 362 | +> Implicit returns are auto-collected as an array or single value. | 
|  | 363 | +> And it does not print out anything. | 
|  | 364 | +
 | 
|  | 365 | +```ps1 | 
|  | 366 | +function Sum { | 
|  | 367 | +    param([int]$l, [int]$r) | 
|  | 368 | +    $l + $r # implicit return # [!code highlight]  | 
|  | 369 | +} | 
|  | 370 | +
 | 
|  | 371 | +# You won't need to declare an array and append it on each loop! | 
|  | 372 | +# they're collected automatically as they're implicit returns | 
|  | 373 | +function Foo { | 
|  | 374 | +   for($i = 0; $i -lt 10; $i = $i + 1)  { | 
|  | 375 | +        $i | 
|  | 376 | +   } | 
|  | 377 | +} | 
|  | 378 | +
 | 
|  | 379 | +(Foo).GetType().Name # object[] # [!code highlight]  | 
|  | 380 | +``` | 
|  | 381 | + | 
|  | 382 | +Explicit return is surely supported, but more like a necessity to exit inside a flow. | 
| 283 | 383 | 
 | 
| 284 |  | -- Function should be define before it was called. | 
|  | 384 | +```ps1 | 
|  | 385 | +function Sum { | 
|  | 386 | +    param([int]$l, [int]$r) | 
|  | 387 | +    return $l + $r # explicit return # [!code highlight]  | 
|  | 388 | +    $r + $l # not reachable  # [!code warning]  | 
|  | 389 | +} | 
|  | 390 | +``` | 
|  | 391 | + | 
|  | 392 | +### Output Type | 
|  | 393 | + | 
|  | 394 | +`OutputTypeAttribute(type: System.Type | System.String, parameterSet?: System.String)` matters when you need auto generated help for the function you write. | 
|  | 395 | +A function can have different return types for different parameter sets. | 
|  | 396 | +PowerShell never do type checking by this attribute, it's just responsible for help generation, write with carefulness! | 
|  | 397 | + | 
|  | 398 | +```ps1 | 
|  | 399 | +[CmdletBinding(DefaultParameterSetName = 'ID')] | 
|  | 400 | +[OutputType('System.Int32', ParameterSetName = 'ID')] | 
|  | 401 | +[OutputType([string], ParameterSetName = 'Name')] | 
|  | 402 | +param ( | 
|  | 403 | +    [Parameter(Mandatory = $true, ParameterSetName = 'ID')] | 
|  | 404 | +    [int[]] | 
|  | 405 | +    $UserID, | 
|  | 406 | +    [Parameter(Mandatory = $true, ParameterSetName = 'Name')] | 
|  | 407 | +    [string[]] | 
|  | 408 | +    $UserName | 
|  | 409 | +) | 
|  | 410 | +``` | 
0 commit comments