Functions/GenXdev.Coding.PowerShell.Modules/Invoke-GenXdevPSFormatter.ps1
################################################################################ <# .SYNOPSIS Formats PowerShell script files using PSScriptAnalyzer formatting rules. .DESCRIPTION This function applies PowerShell formatting rules to script files using PSScriptAnalyzer's Invoke-Formatter cmdlet. It can process individual files or recursively format multiple files in directories. The function uses customizable formatting settings and provides detailed logging of the formatting process. .PARAMETER Path Specifies the path to the script file or directory to format. Accepts pipeline input and supports various path aliases for compatibility. .PARAMETER Settings A settings hashtable or a path to a PowerShell data file (.psd1) that contains the formatting settings. If not specified, the function will attempt to load settings from a predefined location or use built-in defaults. .PARAMETER Range The range within which formatting should take place as an array of four integers: starting line number, starting column number, ending line number, ending column number. If not specified, the entire file will be formatted. .PARAMETER Recurse Recursively process files in subdirectories when the Path parameter points to a directory. .EXAMPLE Invoke-GenXdevPSFormatter -Path "C:\Scripts\MyScript.ps1" .EXAMPLE Invoke-GenXdevPSFormatter -Path "C:\Scripts" -Recurse .EXAMPLE "MyScript.ps1" | Invoke-GenXdevPSFormatter -Settings @{IncludeRules=@('PSUseCorrectCasing')} #> function Invoke-GenXdevPSFormatter { [CmdletBinding()] param( ############################################################################### [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Specifies the path to the script file to format.' )] [Alias('Name', 'FullName', 'ImagePath', 'FileName', 'ScriptFileName')] [string] $Path, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = ('A settings hashtable or a path to a PowerShell data ' + 'file (.psd1) that contains the formatting settings.') )] [Object] $Settings, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = ('The range within which formatting should take place ' + 'as an array of four integers: starting line number, starting ' + 'column number, ending line number, ending column number.') )] [int[]] $Range, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = 'Recursively process files in subdirectories.' )] [switch] $Recurse ############################################################################### ) begin { # check if settings parameter was provided by the caller if (-not $Settings) { try { # build path to the default formatting settings file $settingsPath = GenXdev.FileSystem\Expand-Path ` "$PSScriptRoot\PSScriptAnalyzerFormattingSettings.psd1" # attempt to load settings from the predefined file if ([IO.File]::Exists($settingsPath)) { # read and evaluate the settings file content $settings = Microsoft.PowerShell.Utility\Invoke-Expression ` ([IO.File]::ReadAllText($settingsPath)) # use formatting-specific settings if available if ($settings.CodeFormatting) { $Settings = $settings.CodeFormatting } elseif ($settings) { $Settings = $settings } } else { # notify user that settings file was not found Microsoft.PowerShell.Utility\Write-Verbose ` 'Settings file not found. Using built-in defaults.' # define default formatting settings $Settings = @{ IncludeRules = @( 'PSUseCorrectCasing', 'PSPlaceOpenBrace', 'PSUseConsistentIndentation', 'PSAvoidUsingDoubleQuotesForConstantString', 'PSAlignAssignmentStatement' ) Rules = @{ PSUseCorrectCasing = @{ Enable = $true } PSPlaceOpenBrace = @{ Enable = $true OnSameLine = $true } PSUseConsistentIndentation = @{ Enable = $true IndentationSize = 4 ContinuationIndentationSize = 4 } PSAvoidUsingDoubleQuotesForConstantString = @{ Enable = $true } PSAlignAssignmentStatement = @{ Enable = $true } } } } } catch { # warn about settings initialization failure and fall back to defaults Microsoft.PowerShell.Utility\Write-Warning ` ("Could not initialize settings: $($_.Exception.Message). " + 'Using defaults.') } } # notify user that formatter has been initialized Microsoft.PowerShell.Utility\Write-Verbose ` 'PowerShell formatter initialized with settings.' } process { try { # store the input path for processing $filePath = $Path # expand the path to handle relative paths and wildcards $filePaths = GenXdev.FileSystem\Find-Item $FilePath -PassThru | Microsoft.PowerShell.Core\ForEach-Object FullName # process each file path found foreach ($filePath in $filePaths) { # get the file extension to determine if it's a powershell file $extension = [IO.Path]::GetExtension($filePath).ToLower() # skip files that are not powershell script files if ($extension -notin @('.ps1', '.psm1', '.psd1')) { Microsoft.PowerShell.Utility\Write-Verbose ` "Skipping non-PowerShell file: $filePath" continue } # notify user about the file being processed Microsoft.PowerShell.Utility\Write-Verbose ` "Processing file: $filePath" # initialize variable to hold script content $scriptDefinition = $null try { # read the entire file content as utf-8 text $scriptDefinition = [IO.File]::ReadAllText($filePath, ` [Text.Encoding]::UTF8) } catch { # warn about file read failure and continue to next file Microsoft.PowerShell.Utility\Write-Warning ` ("Could not read file: $filePath - " + "$($_.Exception.Message)") continue } # skip files that are empty or contain only whitespace if ([string]::IsNullOrWhiteSpace($scriptDefinition)) { Microsoft.PowerShell.Utility\Write-Verbose ` "Skipping empty file: $filePath" continue } # initialize variable to hold formatted script content $formattedScript = $null try { # prepare parameters for the psscriptanalyzer formatter $invocationParams = GenXdev.Helpers\Copy-IdenticalParamValues ` -BoundParameters $PSBoundParameters ` -FunctionName 'PSScriptAnalyzer\Invoke-Formatter' ` -DefaultValues (Microsoft.PowerShell.Utility\Get-Variable ` -Scope Local -ErrorAction SilentlyContinue) # invoke the psscriptanalyzer formatter with the prepared parameters $formattedScript = PSScriptAnalyzer\Invoke-Formatter ` @invocationParams } catch { # warn about formatter error and continue to next file Microsoft.PowerShell.Utility\Write-Warning ` "Formatter error: $($_.Exception.Message)" continue } # skip files where formatter returned empty or whitespace content if ([string]::IsNullOrWhiteSpace($formattedScript)) { Microsoft.PowerShell.Utility\Write-Verbose ` "No formatting changes needed for: $filePath" continue } # check if the formatted content differs from the original if ($formattedScript -eq $scriptDefinition) { Microsoft.PowerShell.Utility\Write-Verbose ` "No formatting changes needed for: $filePath" continue } try { # write the formatted content back to the original file [IO.File]::WriteAllText($filePath, $formattedScript, ` [Text.Encoding]::UTF8) # notify user that the file was successfully formatted Microsoft.PowerShell.Utility\Write-Output ` "Formatted file: $filePath" } catch { # warn about file write failure Microsoft.PowerShell.Utility\Write-Warning ` ("Could not write formatted content to file: $filePath " + "- $($_.Exception.Message)") } } } catch { # warn about general processing error Microsoft.PowerShell.Utility\Write-Warning ` "Error processing file ${filePath}: $($_.Exception.Message)" } } end { # notify user that formatter processing has completed Microsoft.PowerShell.Utility\Write-Verbose ` 'PowerShell formatter processing completed' } } ################################################################################ |