|
| 1 | +These documents are in an extremely rough state, not suitable for inclusion in the main guide yet. |
| 2 | + |
| 3 | +### Using The .Net Framework |
| 4 | +<!-- MarkdownTOC depth=4 autolink=true bracket=round --> |
| 5 | + |
| 6 | +- [Use RequiredAssemblies rather than Add-Type](#use-requiredassemblies-rather-than-add-type) |
| 7 | + - [Use Add-Type rather than Reflection](#use-add-type-rather-than-reflection) |
| 8 | + - [Use Add-Type for small classes or PInvoke calls](#use-add-type-for-small-classes-or-pinvoke-calls) |
| 9 | + - [Prefer shipping binaries over large compilations](#prefer-shipping-binaries-over-large-compilations) |
| 10 | +- [Performance](#performance) |
| 11 | + - [Prefer language features over cmdlets](#prefer-language-features-over-cmdlets) |
| 12 | + - [Know when to use .Net](#know-when-to-use-net) |
| 13 | +- [Error Handling](#error-handling) |
| 14 | +- [General Design Principles](#general-design-principles) |
| 15 | + - [Use custom objects](#use-custom-objects) |
| 16 | + - [Scripts vs Functions](#scripts-vs-functions) |
| 17 | + - [Always write CmdletBinding](#always-write-cmdletbinding) |
| 18 | +- [Include Help](#include-help) |
| 19 | + - [Always ship a about_ModuleName with modules](#always-ship-a-about_modulename-with-modules) |
| 20 | + - [Prefer PipelineByPropertyName parameters.](#prefer-pipelinebypropertyname-parameters) |
| 21 | + - [Specify aliases for pipeline binding](#specify-aliases-for-pipeline-binding) |
| 22 | + - [Never forget the Common Parameters](#never-forget-the-common-parameters) |
| 23 | + - [Specify positional parameters, but don't use them](#specify-positional-parameters-but-dont-use-them) |
| 24 | + - [Specify short aliases, but don't use them](#specify-short-aliases-but-dont-use-them) |
| 25 | + - [Never specify Default values for mandatory parameters](#never-specify-default-values-for-mandatory-parameters) |
| 26 | + - [Always specify HelpText for mandatory parameters](#always-specify-helptext-for-mandatory-parameters) |
| 27 | + - [Use PsCmdlet.ThrowTerminatingError rather than throw](#use-pscmdletthrowterminatingerror-rather-than-throw) |
| 28 | + - [Use PsCmdlet.WriteError rather than Write-Error](#use-pscmdletwriteerror-rather-than-write-error) |
| 29 | + - [Use SupportsShouldProcess when appropriate](#use-supportsshouldprocess-when-appropriate) |
| 30 | + - [Modules ARE the Best Practice](#modules-are-the-best-practice) |
| 31 | + - [Use RequiredModules](#use-requiredmodules) |
| 32 | + - [Persisting Configuration](#persisting-configuration) |
| 33 | + - [Provide aliases in your modules](#provide-aliases-in-your-modules) |
| 34 | +- [GOTCHAS](#gotchas) |
| 35 | + - [Beware string concatenation with +](#beware-string-concatenation-with-) |
| 36 | + - [Beware -match and -like](#beware--match-and--like) |
| 37 | + - [Beware -contains and -in](#beware--contains-and--in) |
| 38 | +- [Use Language Features](#use-language-features) |
| 39 | +- [You should understand the .Net underpinnings](#you-should-understand-the-net-underpinnings) |
| 40 | + - [AVOID appending to arrays in a loop](#avoid-appending-to-arrays-in-a-loop) |
| 41 | + - [EXCEPTIONS:](#exceptions) |
| 42 | + - [RATIONALE:](#rationale) |
| 43 | + - [AVOID appending to string in a loop](#avoid-appending-to-string-in-a-loop) |
| 44 | + - [EXCEPTIONS:](#exceptions-1) |
| 45 | + - [RATIONALE:](#rationale-1) |
| 46 | +- [Strongly type parameters](#strongly-type-parameters) |
| 47 | +- [Don't reinvent the wheel](#dont-reinvent-the-wheel) |
| 48 | +- [Let's talk about Logging](#lets-talk-about-logging) |
| 49 | +- [Let's talk about code signing](#lets-talk-about-code-signing) |
| 50 | +- [Don't reinvent the wheel](#dont-reinvent-the-wheel-1) |
| 51 | +- [Let's talk about Logging](#lets-talk-about-logging-1) |
| 52 | +- [Let's talk about code signing](#lets-talk-about-code-signing-1) |
| 53 | + |
| 54 | +<!-- /MarkdownTOC --> |
| 55 | + |
| 56 | + |
| 57 | +#### Use RequiredAssemblies rather than Add-Type |
| 58 | +#### Use Add-Type rather than Reflection |
| 59 | +Avoid using `[System.Reflection]` to load assemblies when possible. Particularly avoid `LoadWithPartialName` (specify the full name instead). |
| 60 | + |
| 61 | +#### Use Add-Type for small classes or PInvoke calls |
| 62 | +TODO: Is this better than PowerShell Classes, for compatibility? |
| 63 | + |
| 64 | +#### Prefer shipping binaries over large compilations |
| 65 | +With PowerShell 5, security is tighter, and compiling code in memory will be frowned upon. Now that we have PowerShellGet and the PowerShell Gallery, there are few reasons 's no reason to avoid binaries. |
| 66 | + |
| 67 | +TODO: Discuss: when does embedding C# code makes sense more sense than just compiling it every time? |
| 68 | + |
| 69 | + |
| 70 | +### Performance |
| 71 | + |
| 72 | +#### Prefer language features over cmdlets |
| 73 | +Prefer foreach(){} over ForEach-Object |
| 74 | +Prefer .foreach and .where over cmdlets |
| 75 | +Prefer functions with process blocks over ForEach-Object |
| 76 | + |
| 77 | +#### Know when to use .Net |
| 78 | +Prefer writing wrapper functions to just calling .Net APIs |
| 79 | +Discuss: System.IO file loading vs Get-Content (large files) |
| 80 | +Discuss: Other examples of .Net API calls that are clearly faster? |
| 81 | +Discuss: Casting for creating objects |
| 82 | + |
| 83 | +### Error Handling |
| 84 | + |
| 85 | +TODO |
| 86 | +Discuss: Avoid depending on `$?` -- why? |
| 87 | +Discuss: Never use `$Error` in scripts (always use -ErrorVariable) |
| 88 | +Discuss: Interactively, always copy $Error[0] to $e or something |
| 89 | + |
| 90 | + |
| 91 | +### General Design Principles |
| 92 | + |
| 93 | +#### Use custom objects |
| 94 | +Discuss: Add-Type vs PowerShell classes vs creating PSCustomObjects |
| 95 | +Discuss: Casting for creating objects |
| 96 | +Discuss: PSTypeNames and formatting rules |
| 97 | + |
| 98 | +#### Scripts vs Functions |
| 99 | +Discuss: In PowerShell 3+, performance is such that there's very little penalty for using scripts. |
| 100 | +Discuss: In PowerShell 3+, module autoloading means there's no discoverability penalty for writing functions (if you put them in a module) instead (except loading sets of them at a time). |
| 101 | +Discuss: During development, always write scripts, which are automatically re-parsed if you edit the file. |
| 102 | + |
| 103 | +#### Always write CmdletBinding |
| 104 | + |
| 105 | +This is in the Style Guide too, but we should discuss it in more depth here, and link to it from the Style Guide. Scripts should start life as something like this: |
| 106 | + |
| 107 | +``` |
| 108 | +[CmdletBinding()]param() |
| 109 | +process{} |
| 110 | +end{} |
| 111 | +``` |
| 112 | + |
| 113 | +You can always ignore one of the blocks, and add parameters and such, but you should never write a script without CmdletBinding, and you should never write one without at least considering making it take pipeline input |
| 114 | + |
| 115 | + |
| 116 | +### Include Help |
| 117 | + |
| 118 | +TODO: Link to StyleGuide about formatting help |
| 119 | +Discuss: Minimum acceptable comment based help: Synopsis, Parameters, and an example for each parameter set (plus pipeline examples if you can contrive one) |
| 120 | +Discuss: Benefits of MAML help files |
| 121 | + |
| 122 | +#### Always ship a about_ModuleName with modules |
| 123 | + |
| 124 | +Discuss: Other reasons to write about_topics |
| 125 | + |
| 126 | + |
| 127 | + |
| 128 | +#### Prefer PipelineByPropertyName parameters. |
| 129 | +Discuss: This allows the most flexibility: piping objects and using scriptblocks to shape it for parameters. Unless you absolutely need to write a `begin` block and use this parameter in it, you probably should accept it on the pipeline. |
| 130 | + |
| 131 | +Discuss: There is some testing overhead involved in that decision. |
| 132 | + |
| 133 | +Discuss: You MUST do all your work in the process block if you do this. |
| 134 | + |
| 135 | +#### Specify aliases for pipeline binding |
| 136 | +You can use aliases to map parameters to property names of objects which might be piped to you while still keeping your parameter names clear and meaningful. |
| 137 | + |
| 138 | +#### Never forget the Common Parameters |
| 139 | +Particularly when splatting PSBoundParameters to the next function, if that function isn't `[CmdletBinding()]` (it should be!) you must remember to strip the common parameters if they're present. |
| 140 | + |
| 141 | +#### Specify positional parameters, but don't use them |
| 142 | +When writing at the command line, positional parameters are a blessing, but they can be confusing for future readers. You should always expose your parameters positionally when it makes sense, but you should rarely share a script that pases parameters positionally. |
| 143 | + |
| 144 | +#### Specify short aliases, but don't use them |
| 145 | +Again, for the sake of typing, it's particularly useful if you can provide two-letter aliases for each of your parameters such that every parameter has a two-letter or less name which is unique. |
| 146 | + |
| 147 | +#### Never specify Default values for mandatory parameters |
| 148 | +It doesn't do anything, and it confuses future readers. |
| 149 | + |
| 150 | +#### Always specify HelpText for mandatory parameters |
| 151 | +When prompted for a mandatory parameter, a user can request HelpText, but can't look at the documentation. It's frequently useful to duplicate at least the first sentence or two of the parameter help. |
| 152 | + |
| 153 | +``` |
| 154 | +[Parameter(Position=1, Mandatory=$true, ValueFromPipeline=$true, |
| 155 | +ValueFromPipelineByPropertyName=$true, HelpText='The name of the file to read')] |
| 156 | +[Alias('PSPath','FullName','Path')] |
| 157 | +[String]$File |
| 158 | +``` |
| 159 | + |
| 160 | + |
| 161 | +#### Use PsCmdlet.ThrowTerminatingError rather than throw |
| 162 | +#### Use PsCmdlet.WriteError rather than Write-Error |
| 163 | +Discuss: These need example output to explain why they're better |
| 164 | +Discuss: a common template (from the Microsoft team) for throwing errors |
| 165 | + |
| 166 | +``` |
| 167 | +# Utility to throw an errorrecord |
| 168 | +function ThrowError |
| 169 | +{ |
| 170 | + param |
| 171 | + ( |
| 172 | + [parameter(Mandatory = $true)] |
| 173 | + [ValidateNotNullOrEmpty()] |
| 174 | + [System.Management.Automation.PSCmdlet] |
| 175 | + $CallerPSCmdlet, |
| 176 | +
|
| 177 | + [parameter(Mandatory = $true)] |
| 178 | + [ValidateNotNullOrEmpty()] |
| 179 | + [System.String] |
| 180 | + $ExceptionName, |
| 181 | +
|
| 182 | + [parameter(Mandatory = $true)] |
| 183 | + [ValidateNotNullOrEmpty()] |
| 184 | + [System.String] |
| 185 | + $ExceptionMessage, |
| 186 | + |
| 187 | + [System.Object] |
| 188 | + $ExceptionObject, |
| 189 | + |
| 190 | + [parameter(Mandatory = $true)] |
| 191 | + [ValidateNotNullOrEmpty()] |
| 192 | + [System.String] |
| 193 | + $ErrorId, |
| 194 | +
|
| 195 | + [parameter(Mandatory = $true)] |
| 196 | + [ValidateNotNull()] |
| 197 | + [System.Management.Automation.ErrorCategory] |
| 198 | + $ErrorCategory |
| 199 | + ) |
| 200 | + |
| 201 | + $exception = New-Object $ExceptionName $ExceptionMessage; |
| 202 | + $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $ErrorId, $ErrorCategory, $ExceptionObject |
| 203 | + $CallerPSCmdlet.ThrowTerminatingError($errorRecord) |
| 204 | +} |
| 205 | +``` |
| 206 | + |
| 207 | + |
| 208 | +#### Use SupportsShouldProcess when appropriate |
| 209 | +Discuss: when is this critical (-whatif) and optional (-confirm_ |
| 210 | +Discuss: when should you call PSCmdlet.ShouldProcess vs PSCmdlet.ShouldContinue (-Force) |
| 211 | + |
| 212 | +``` |
| 213 | +[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="Medium")] |
| 214 | +param([Switch]$Force) |
| 215 | +
|
| 216 | +$RejectAll = $false; |
| 217 | +$ConfirmAll = $false; |
| 218 | +
|
| 219 | +foreach($file in ls) { |
| 220 | +
|
| 221 | + if($PSCmdlet.ShouldProcess( "Removed the file '$($file.Name)'", |
| 222 | + "Remove the file '$($file.Name)'?", |
| 223 | + "Removing Files" )) { |
| 224 | +
|
| 225 | + if($Force -Or $PSCmdlet.ShouldContinue("Are you REALLY sure you want to remove '$($file.Name)'?", "Removing '$($file.Name)'", [ref]$ConfirmAll, [ref]$RejectAll)) { |
| 226 | + "Removing $File" |
| 227 | +
|
| 228 | +
|
| 229 | + } |
| 230 | + } |
| 231 | +} |
| 232 | +``` |
| 233 | + |
| 234 | +#### Modules ARE the Best Practice |
| 235 | +Discuss: Scope isolation, hiding helper functions, sharing state between functions |
| 236 | +Discuss: Organizing related functions |
| 237 | +Discuss: Discoverability, Packaging, Sharing |
| 238 | +Discuss: Dependency Management, Versioning |
| 239 | +Discuss: Internationalization |
| 240 | + |
| 241 | +#### Use RequiredModules |
| 242 | +The problems with this have gone away with autoloading, and this is the only way to let PowerShellGet manage dependencies for you. |
| 243 | + |
| 244 | +#### Persisting Configuration |
| 245 | +My choice: Configuration module. Otherwise, use clixml (or XAML) to persist to AppData (TEST: you shouldn't store configuration in your module folder, as it may not survive upgrades (in PowerShell 3 & 4 there was no side-by-side loading)) |
| 246 | + |
| 247 | + |
| 248 | +#### Provide aliases in your modules |
| 249 | +You should feel free to create and use aliases within your modules. In some cases, you can even improve readability by using an alias without the verb, or shortening command names. |
| 250 | + |
| 251 | +For exported aliases, follow the guidance of Microsoft ("ip" for import, "s" for set, "g" for get, "r" for remove, etc.), make up somethign for your nouns. |
| 252 | + |
| 253 | +Use `New-Alias ... -ErrorAction SilentlyContinue` to avoid overwriting existing aliases. |
| 254 | + |
| 255 | + |
| 256 | + |
| 257 | + |
| 258 | +### GOTCHAS |
| 259 | + |
| 260 | +#### Beware string concatenation with + |
| 261 | +You should always wrap this with parentheses, because otherwise it can break (e.g. when passing a string as a parameter. |
| 262 | + |
| 263 | +#### Beware -match and -like |
| 264 | +They quietly cast objects to strings (or arrays of strings) |
| 265 | + |
| 266 | +#### Beware -contains and -in |
| 267 | +They work on ARRAYS not strings |
| 268 | + |
| 269 | + |
| 270 | +### Use Language Features |
| 271 | +When writing scripts (as opposed to at the command line), you should almost always choose language features over cmdlets. This includes using if instead of where-object, foreach instead of ForEach-Object, etc. |
| 272 | + |
| 273 | +The language features are always faster, and almost always more readable. Of course, there are always exceptions, and one exception to this rule is when using foreach will force you to collect a lot of items into an array instead of iterating them as they stream through a pipleine. |
| 274 | + |
| 275 | + |
| 276 | +### You should understand the .Net underpinnings |
| 277 | + |
| 278 | +#### AVOID appending to arrays in a loop |
| 279 | +##### INSTEAD assign output from the loop |
| 280 | +#### EXCEPTIONS: |
| 281 | +* Appending to multiple collections |
| 282 | +* Using Lists instead of arrays |
| 283 | + |
| 284 | +#### RATIONALE: |
| 285 | +* Copying is slow |
| 286 | +* Pipeline output uses ArrayList |
| 287 | +* Easier to read and understand |
| 288 | + |
| 289 | + |
| 290 | +#### AVOID appending to string in a loop |
| 291 | +##### INSTEAD assign output from the loop using $OFS |
| 292 | +#### EXCEPTIONS: |
| 293 | +* When it's a really short loop |
| 294 | + |
| 295 | +#### RATIONALE: |
| 296 | +* Copying is slow |
| 297 | +* Joining an array of strings is fast |
| 298 | +* Easier to read and understand |
| 299 | + |
| 300 | + |
| 301 | +### Strongly type parameters |
| 302 | +Although PowerShell is a dynamic language, we have can specify types, and in Parameters, it's particularly useful because it hints to users what they can pass to your command. |
| 303 | + |
| 304 | +Strong types on parameters is also crucial because it's a user-input point. Strong types can help you avoid script injection and various other problems with user inputs, and will allow failures to happen as early as possible (even before your command is called). |
| 305 | + |
| 306 | +Additionally, avoid using `[string]` with ParameterSets because anything can be cast to it, so PowerShell can't distinguish one parameter set from the other. |
| 307 | + |
| 308 | +When passing on parameters to another command, you should be _at least_ as strongly typed as the other command, to avoid casting exceptions within your script. |
| 309 | + |
| 310 | +One notable exception is when you could accept more than one type. In PowerShell you can speficy parameter set overloads, but you can't change the type of a parameter. |
| 311 | + |
| 312 | + |
| 313 | +### Don't reinvent the wheel |
| 314 | +### Let's talk about Logging |
| 315 | +### Let's talk about code signing |
| 316 | +### Don't reinvent the wheel |
| 317 | +### Let's talk about Logging |
| 318 | +### Let's talk about code signing |
0 commit comments