@@ -9,10 +9,6 @@ function Invoke-DbatoolsFormatter {
9
9
. PARAMETER Path
10
10
The path to the ps1 file that needs to be formatted
11
11
12
- . PARAMETER SkipInvisibleOnly
13
- Skip files that would only have invisible changes (BOM, line endings, trailing whitespace, tabs).
14
- Use this to avoid unnecessary version control noise when only non-visible characters would change.
15
-
16
12
. PARAMETER EnableException
17
13
By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
18
14
This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
@@ -35,15 +31,14 @@ function Invoke-DbatoolsFormatter {
35
31
Reformats C:\dbatools\public\Get-DbaDatabase.ps1 to dbatools' standards
36
32
37
33
. EXAMPLE
38
- PS C:\> Invoke-DbatoolsFormatter -Path C:\dbatools\public\ *.ps1 -SkipInvisibleOnly
34
+ PS C:\> Get-ChildItem *.ps1 | Invoke-DbatoolsFormatter
39
35
40
- Reformats all ps1 files but skips those that would only have BOM/line ending changes
36
+ Reformats all . ps1 files in the current directory, showing progress for the batch operation
41
37
#>
42
38
[CmdletBinding ()]
43
39
param (
44
40
[parameter (Mandatory , ValueFromPipeline )]
45
41
[object []]$Path ,
46
- [switch ]$SkipInvisibleOnly ,
47
42
[switch ]$EnableException
48
43
)
49
44
begin {
@@ -72,147 +67,137 @@ function Invoke-DbatoolsFormatter {
72
67
$OSEOL = " `r`n "
73
68
}
74
69
75
- function Test-OnlyInvisibleChanges {
76
- param (
77
- [string ]$OriginalContent ,
78
- [string ]$ModifiedContent
79
- )
80
-
81
- # Normalize line endings to Unix style for comparison
82
- $originalNormalized = $OriginalContent -replace ' \r\n' , " `n " -replace ' \r' , " `n "
83
- $modifiedNormalized = $ModifiedContent -replace ' \r\n' , " `n " -replace ' \r' , " `n "
70
+ # Collect all paths for progress tracking
71
+ $allPaths = @ ()
72
+ }
73
+ process {
74
+ if (Test-FunctionInterrupt ) { return }
75
+ # Collect all paths from pipeline
76
+ $allPaths += $Path
77
+ }
78
+ end {
79
+ if (Test-FunctionInterrupt ) { return }
84
80
85
- # Split into lines
86
- $originalLines = $originalNormalized -split " `n "
87
- $modifiedLines = $modifiedNormalized -split " `n "
81
+ $totalFiles = $allPaths.Count
82
+ $currentFile = 0
83
+ $processedFiles = 0
84
+ $updatedFiles = 0
88
85
89
- # Normalize each line: trim trailing whitespace and convert tabs to spaces
90
- $originalLines = $originalLines | ForEach-Object { $_.TrimEnd ().Replace(" `t " , " " ) }
91
- $modifiedLines = $modifiedLines | ForEach-Object { $_.TrimEnd ().Replace(" `t " , " " ) }
86
+ foreach ($p in $allPaths ) {
87
+ $currentFile ++
92
88
93
- # Remove trailing empty lines from both
94
- while ( $originalLines .Count -gt 0 -and $originalLines [ -1 ] -eq ' ' ) {
95
- $originalLines = $originalLines [ 0 .. ( $originalLines .Count - 2 )]
96
- }
97
- while ( $modifiedLines .Count -gt 0 -and $modifiedLines [ -1 ] -eq ' ' ) {
98
- $modifiedLines = $modifiedLines [ 0 .. ( $modifiedLines .Count - 2 )]
89
+ try {
90
+ $realPath = ( Resolve-Path - Path $p - ErrorAction Stop).Path
91
+ } catch {
92
+ Write-Progress - Activity " Formatting PowerShell files " - Status " Error resolving path: $p " - PercentComplete (( $currentFile / $totalFiles ) * 100 ) - CurrentOperation " File $currentFile of $totalFiles "
93
+ Stop-Function - Message " Cannot find or resolve $p " - Continue
94
+ continue
99
95
}
100
96
101
- # Compare the normalized content
102
- if ($originalLines.Count -ne $modifiedLines.Count ) {
103
- return $false
97
+ # Skip directories
98
+ if (Test-Path - Path $realPath - PathType Container) {
99
+ Write-Progress - Activity " Formatting PowerShell files" - Status " Skipping directory: $realPath " - PercentComplete (($currentFile / $totalFiles ) * 100 ) - CurrentOperation " File $currentFile of $totalFiles "
100
+ Write-Message - Level Verbose " Skipping directory: $realPath "
101
+ continue
104
102
}
105
103
106
- for ($i = 0 ; $i -lt $originalLines.Count ; $i ++ ) {
107
- if ($originalLines [$i ] -ne $modifiedLines [$i ]) {
108
- return $false
104
+ $fileName = Split-Path - Leaf $realPath
105
+ Write-Progress - Activity " Formatting PowerShell files" - Status " Processing: $fileName " - PercentComplete (($currentFile / $totalFiles ) * 100 ) - CurrentOperation " File $currentFile of $totalFiles "
106
+
107
+ $originalContent = Get-Content - Path $realPath - Raw - Encoding UTF8
108
+ $content = $originalContent
109
+
110
+ if ($OSEOL -eq " `r`n " ) {
111
+ # See #5830, we are in Windows territory here
112
+ # Is the file containing at least one `r ?
113
+ $containsCR = ($content -split " `r " ).Length -gt 1
114
+ if (-not ($containsCR )) {
115
+ # If not, maybe even on Windows the user is using Unix-style endings, which are supported
116
+ $OSEOL = " `n "
109
117
}
110
118
}
111
119
112
- return $true
113
- }
120
+ # strip ending empty lines
121
+ $content = $content -replace " (?s) $OSEOL \s*$ "
114
122
115
- function Format-ScriptContent {
116
- param (
117
- [string ]$Content ,
118
- [string ]$LineEnding
119
- )
123
+ # Preserve aligned assignments before formatting
124
+ # Look for patterns with multiple spaces before OR after the = sign
125
+ $alignedPatterns = [regex ]::Matches($content , ' (?m)^\s*(\$\w+|\w+)\s{2,}=\s*.+$|^\s*(\$\w+|\w+)\s*=\s{2,}.+$' )
126
+ $placeholders = @ { }
120
127
121
- # Strip ending empty lines
122
- $Content = $Content -replace " (?s)$LineEnding \s*$"
128
+ foreach ($match in $alignedPatterns ) {
129
+ $placeholder = " ___ALIGNMENT_PLACEHOLDER_$ ( $placeholders.Count ) ___"
130
+ $placeholders [$placeholder ] = $match.Value
131
+ $content = $content.Replace ($match.Value , $placeholder )
132
+ }
123
133
124
134
try {
125
- # Save original lines before formatting
126
- $originalLines = $Content -split " `n "
127
-
128
- # Run the formatter
129
- $formattedContent = Invoke-Formatter - ScriptDefinition $Content - Settings CodeFormattingOTBS - ErrorAction Stop
130
-
131
- # Automatically restore spaces before = signs
132
- $formattedLines = $formattedContent -split " `n "
133
- for ($i = 0 ; $i -lt $formattedLines.Count ; $i ++ ) {
134
- if ($i -lt $originalLines.Count ) {
135
- # Check if original had multiple spaces before =
136
- if ($originalLines [$i ] -match ' ^(\s*)(.+?)(\s{2,})(=)(.*)$' ) {
137
- $indent = $matches [1 ]
138
- $beforeEquals = $matches [2 ]
139
- $spacesBeforeEquals = $matches [3 ]
140
- $rest = $matches [4 ] + $matches [5 ]
141
-
142
- # Apply the same spacing to the formatted line
143
- if ($formattedLines [$i ] -match ' ^(\s*)(.+?)(\s*)(=)(.*)$' ) {
144
- $formattedLines [$i ] = $matches [1 ] + $matches [2 ] + $spacesBeforeEquals + ' =' + $matches [5 ]
145
- }
146
- }
147
- }
135
+ $formattedContent = Invoke-Formatter - ScriptDefinition $content - Settings CodeFormattingOTBS - ErrorAction Stop
136
+ if ($formattedContent ) {
137
+ $content = $formattedContent
148
138
}
149
- $Content = $formattedLines -join " `n "
150
139
} catch {
151
- Write-Message - Level Warning " Unable to format content "
140
+ # Just silently continue - the formatting might still work partially
152
141
}
153
142
154
- # Match the ending indentation of CBH with the starting one
155
- $CBH = $CBHRex.Match ($Content ).Value
143
+ # Restore the aligned patterns
144
+ foreach ($key in $placeholders.Keys ) {
145
+ $content = $content.Replace ($key , $placeholders [$key ])
146
+ }
147
+
148
+ # match the ending indentation of CBH with the starting one, see #4373
149
+ $CBH = $CBHRex.Match ($content ).Value
156
150
if ($CBH ) {
151
+ # get starting spaces
157
152
$startSpaces = $CBHStartRex.Match ($CBH ).Groups[' spaces' ]
158
153
if ($startSpaces ) {
154
+ # get end
159
155
$newCBH = $CBHEndRex.Replace ($CBH , " $startSpaces #>" )
160
156
if ($newCBH ) {
161
- $Content = $Content.Replace ($CBH , $newCBH )
157
+ # replace the CBH
158
+ $content = $content.Replace ($CBH , $newCBH )
162
159
}
163
160
}
164
161
}
165
-
166
- # Apply case corrections and clean up lines
167
- $correctCase = @ (' DbaInstanceParameter' , ' PSCredential' , ' PSCustomObject' , ' PSItem' )
162
+ $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
163
+ $correctCase = @ (
164
+ ' DbaInstanceParameter'
165
+ ' PSCredential'
166
+ ' PSCustomObject'
167
+ ' PSItem'
168
+ )
168
169
$realContent = @ ()
169
- foreach ($line in $Content .Split (" `n " )) {
170
+ foreach ($line in $content .Split (" `n " )) {
170
171
foreach ($item in $correctCase ) {
171
172
$line = $line -replace $item , $item
172
173
}
174
+ # trim whitespace lines
173
175
$realContent += $line.Replace (" `t " , " " ).TrimEnd()
174
176
}
175
177
176
- return ($realContent -Join $LineEnding )
177
- }
178
- }
179
- process {
180
- if (Test-FunctionInterrupt ) { return }
181
- foreach ($p in $Path ) {
182
- try {
183
- $realPath = (Resolve-Path - Path $p - ErrorAction Stop).Path
184
- } catch {
185
- Stop-Function - Message " Cannot find or resolve $p " - Continue
186
- }
178
+ $newContent = $realContent -Join " $OSEOL "
187
179
188
- # Read file once
189
- $originalBytes = [ System.IO.File ]::ReadAllBytes( $realPath )
190
- $originalContent = [ System.IO.File ]::ReadAllText( $realPath )
180
+ # Compare without empty lines to detect real changes
181
+ $originalNonEmpty = ( $originalContent -split " [\r\n]+ " | Where-Object { $_ .Trim () }) -join " "
182
+ $newNonEmpty = ( $newContent -split " [\r\n]+ " | Where-Object { $_ .Trim () }) -join " "
191
183
192
- # Detect line ending style from original file
193
- $detectedOSEOL = $OSEOL
194
- if ($psVersionTable.Platform -ne ' Unix' ) {
195
- # We're on Windows, check if file uses Unix endings
196
- $containsCR = ($originalContent -split " `r " ).Length -gt 1
197
- if (-not ($containsCR )) {
198
- $detectedOSEOL = " `n "
199
- }
184
+ if ($originalNonEmpty -ne $newNonEmpty ) {
185
+ [System.IO.File ]::WriteAllText($realPath , $newContent , $Utf8NoBomEncoding )
186
+ Write-Message - Level Verbose " Updated: $realPath "
187
+ $updatedFiles ++
188
+ } else {
189
+ Write-Message - Level Verbose " No changes needed: $realPath "
200
190
}
201
191
202
- # Format the content
203
- $formattedContent = Format-ScriptContent - Content $originalContent - LineEnding $detectedOSEOL
192
+ $processedFiles ++
193
+ }
204
194
205
- # If SkipInvisibleOnly is set, check if formatting would only change invisible characters
206
- if ($SkipInvisibleOnly ) {
207
- if (Test-OnlyInvisibleChanges - OriginalContent $originalContent - ModifiedContent $formattedContent ) {
208
- Write-Verbose " Skipping $realPath - only invisible changes (BOM/line endings/whitespace)"
209
- continue
210
- }
211
- }
195
+ # Complete the progress bar
196
+ Write-Progress - Activity " Formatting PowerShell files" - Status " Complete" - PercentComplete 100 - CurrentOperation " Processed $processedFiles files, updated $updatedFiles "
197
+ Start-Sleep - Milliseconds 500 # Brief pause to show completion
198
+ Write-Progress - Activity " Formatting PowerShell files" - Completed
212
199
213
- # Save the formatted content
214
- $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
215
- [System.IO.File ]::WriteAllText($realPath , $formattedContent , $Utf8NoBomEncoding )
216
- }
200
+ # Summary message
201
+ Write-Message - Level Verbose " Formatting complete: Processed $processedFiles files, updated $updatedFiles files"
217
202
}
218
203
}
0 commit comments