1
+ function Format-PSMDParameter
2
+ {
3
+ <#
4
+ . SYNOPSIS
5
+ Formats the parameter block on commands.
6
+
7
+ . DESCRIPTION
8
+ Formats the parameter block on commands.
9
+ This function will convert legacy functions that have their parameters straight behind their command name.
10
+ It also fixes missing CmdletBinding attributes.
11
+
12
+ Nested commands will also be affected.
13
+
14
+ . PARAMETER FullName
15
+ The file to process
16
+
17
+ . PARAMETER DisableCache
18
+ By default, this command caches the results of its execution in the PSFramework result cache.
19
+ This information can then be retrieved for the last command to do so by running Get-PSFResultCache.
20
+ Setting this switch disables the caching of data in the cache.
21
+
22
+ . PARAMETER Confirm
23
+ If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
24
+
25
+ . PARAMETER WhatIf
26
+ If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
27
+
28
+ . EXAMPLE
29
+ PS C:\> Get-ChildItem .\functions\*\*.ps1 | Set-PSMDCmdletBinding
30
+
31
+ Updates all commands in the module to have a cmdletbinding attribute.
32
+ #>
33
+ [CmdletBinding (SupportsShouldProcess = $true )]
34
+ param (
35
+ [Parameter (Mandatory = $true , ValueFromPipeline = $true , ValueFromPipelineByPropertyName = $true )]
36
+ [string []]
37
+ $FullName ,
38
+
39
+ [switch ]
40
+ $DisableCache
41
+ )
42
+
43
+ begin
44
+ {
45
+ # region Utility functions
46
+ function Invoke-AstWalk
47
+ {
48
+ [CmdletBinding ()]
49
+ param (
50
+ $Ast ,
51
+
52
+ [string []]
53
+ $Command ,
54
+
55
+ [string []]
56
+ $Name ,
57
+
58
+ [string ]
59
+ $NewName ,
60
+
61
+ [bool ]
62
+ $IsCommand ,
63
+
64
+ [bool ]
65
+ $NoAlias
66
+ )
67
+
68
+ # Write-PSFMessage -Level Host -Message "Processing $($Ast.Extent.StartLineNumber) | $($Ast.Extent.File) | $($Ast.GetType().FullName)"
69
+ $typeName = $Ast.GetType ().FullName
70
+
71
+ switch ($typeName )
72
+ {
73
+ " System.Management.Automation.Language.FunctionDefinitionAst"
74
+ {
75
+ # region Has no param block
76
+ if ($null -eq $Ast.Body.ParamBlock )
77
+ {
78
+ $baseIndent = $Ast.Extent.Text.Split (" `n " )[0 ] -replace " ^(\s{0,}).*" , ' $1'
79
+ $indent = $baseIndent + " `t "
80
+
81
+ # Kill explicit parameter section behind name
82
+ $startIndex = " function " .Length + $Ast.Name.Length
83
+ $endIndex = $Ast.Extent.Text.IndexOf (" {" )
84
+ Add-FileReplacement - Path $ast.Extent.File - Start ($Ast.Extent.StartOffset + $startIndex ) - Length ($endIndex - $startIndex ) - NewContent " `n "
85
+
86
+ $baseParam = @"
87
+ $ ( $indent ) [CmdletBinding()]
88
+ $ ( $indent ) param (
89
+ {0}
90
+ $ ( $indent ) )
91
+ "@
92
+ $parameters = @ ()
93
+ $paramIndent = $indent + " `t "
94
+ foreach ($parameter in $Ast.Parameters )
95
+ {
96
+ $defaultValue = " "
97
+ if ($parameter.DefaultValue ) { $defaultValue = " = $ ( $parameter.DefaultValue.Extent.Text ) " }
98
+ $values = @ ()
99
+ foreach ($attribute in $parameter.Attributes )
100
+ {
101
+ $values += " $ ( $paramIndent ) $ ( $attribute.Extent.Text ) "
102
+ }
103
+ $values += " $ ( $paramIndent ) $ ( $parameter.Name.Extent.Text ) $ ( $defaultValue ) "
104
+ $parameters += $values -join " `n "
105
+ }
106
+ $baseParam = $baseParam -f ($parameters -join " ,`n`n " )
107
+
108
+ Add-FileReplacement - Path $ast.Extent.File - Start $Ast.Body.Extent.StartOffset - Length 1 - NewContent " {`n $ ( $baseParam ) "
109
+ }
110
+ # endregion Has no param block
111
+
112
+ # region Has a param block, but no cmdletbinding
113
+ if (($null -ne $Ast.Body.ParamBlock ) -and (-not ($Ast.Body.ParamBlock.Attributes | Where-Object TypeName -Like " CmdletBinding" )))
114
+ {
115
+ $text = [System.IO.File ]::ReadAllText($Ast.Extent.File )
116
+
117
+ $index = $Ast.Body.ParamBlock.Extent.StartOffset
118
+ while (($index -gt 0 ) -and ($text.Substring ($index , 1 ) -ne " `n " )) { $index = $index - 1 }
119
+
120
+ $indentIndex = $index + 1
121
+ $indent = $text.Substring ($indentIndex , ($Ast.Body.ParamBlock.Extent.StartOffset - $indentIndex ))
122
+ Add-FileReplacement - Path $Ast.Body.ParamBlock.Extent.File - Start $indentIndex - Length ($Ast.Body.ParamBlock.Extent.StartOffset - $indentIndex ) - NewContent " $ ( $indent ) [CmdletBinding()]`n $ ( $indent ) "
123
+ }
124
+ # endregion Has a param block, but no cmdletbinding
125
+
126
+ Invoke-AstWalk - Ast $Ast.Body - Command $Command - Name $Name - NewName $NewName - IsCommand $false
127
+ }
128
+ default
129
+ {
130
+ foreach ($property in $Ast.PSObject.Properties )
131
+ {
132
+ if ($property.Name -eq " Parent" ) { continue }
133
+ if ($null -eq $property.Value ) { continue }
134
+
135
+ if (Get-Member - InputObject $property.Value - Name GetEnumerator - MemberType Method)
136
+ {
137
+ foreach ($item in $property.Value )
138
+ {
139
+ if ($item.PSObject.TypeNames -contains " System.Management.Automation.Language.Ast" )
140
+ {
141
+ Invoke-AstWalk - Ast $item - Command $Command - Name $Name - NewName $NewName - IsCommand $IsCommand
142
+ }
143
+ }
144
+ continue
145
+ }
146
+
147
+ if ($property.Value.PSObject.TypeNames -contains " System.Management.Automation.Language.Ast" )
148
+ {
149
+ Invoke-AstWalk - Ast $property.Value - Command $Command - Name $Name - NewName $NewName - IsCommand $IsCommand
150
+ }
151
+ }
152
+ }
153
+ }
154
+ }
155
+
156
+ function Add-FileReplacement
157
+ {
158
+ [CmdletBinding ()]
159
+ param (
160
+ [string ]
161
+ $Path ,
162
+
163
+ [int ]
164
+ $Start ,
165
+
166
+ [int ]
167
+ $Length ,
168
+
169
+ [string ]
170
+ $NewContent
171
+ )
172
+ Write-PSFMessage - Level Verbose - Message " Change Submitted: $Path | $Start | $Length | $NewContent " - Tag ' update' , ' change' , ' file'
173
+
174
+ if (-not $globalFunctionHash.ContainsKey ($Path ))
175
+ {
176
+ $globalFunctionHash [$Path ] = @ ()
177
+ }
178
+
179
+ $globalFunctionHash [$Path ] += New-Object PSObject - Property @ {
180
+ Content = $NewContent
181
+ Start = $Start
182
+ Length = $Length
183
+ }
184
+ }
185
+
186
+ function Apply-FileReplacement
187
+ {
188
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute (" PSUseApprovedVerbs" , " " )]
189
+ [CmdletBinding ()]
190
+ param (
191
+
192
+ )
193
+
194
+ foreach ($key in $globalFunctionHash.Keys )
195
+ {
196
+ $value = $globalFunctionHash [$key ] | Sort-Object Start
197
+ $content = [System.IO.File ]::ReadAllText($key )
198
+
199
+ $newString = " "
200
+ $currentIndex = 0
201
+
202
+ foreach ($item in $value )
203
+ {
204
+ $newString += $content.SubString ($currentIndex , ($item.Start - $currentIndex ))
205
+ $newString += $item.Content
206
+ $currentIndex = $item.Start + $item.Length
207
+ }
208
+
209
+ $newString += $content.SubString ($currentIndex )
210
+
211
+ [System.IO.File ]::WriteAllText($key , $newString )
212
+ # $newString
213
+ }
214
+ }
215
+
216
+ function Write-Issue
217
+ {
218
+ [CmdletBinding ()]
219
+ param (
220
+ $Extent ,
221
+
222
+ $Data ,
223
+
224
+ [string ]
225
+ $Type
226
+ )
227
+
228
+ New-Object PSObject - Property @ {
229
+ Type = $Type
230
+ Data = $Data
231
+ File = $Extent.File
232
+ StartLine = $Extent.StartLineNumber
233
+ Text = $Extent.Text
234
+ }
235
+ }
236
+ # endregion Utility functions
237
+ }
238
+ process
239
+ {
240
+ foreach ($path in $FullName )
241
+ {
242
+ $globalFunctionHash = @ { }
243
+
244
+ $tokens = $null
245
+ $parsingError = $null
246
+ $ast = [System.Management.Automation.Language.Parser ]::ParseFile($path , [ref ]$tokens , [ref ]$parsingError )
247
+
248
+ Write-PSFMessage - Level VeryVerbose - Message " Ensuring Cmdletbinding for all functions in $path " - Tag ' start' - Target $Name
249
+ $issues += Invoke-AstWalk - Ast $ast - Command $Command - Name $Name - NewName $NewName - IsCommand $false
250
+
251
+ Set-PSFResultCache - InputObject $issues - DisableCache $DisableCache
252
+ if ($PSCmdlet.ShouldProcess ($path , " Set CmdletBinding attribute" ))
253
+ {
254
+ Apply- FileReplacement
255
+ }
256
+ $issues
257
+ }
258
+ }
259
+ }
0 commit comments