functions/refactor/Convert-PSMDMessage.ps1
function Convert-PSMDMessage { <# .SYNOPSIS Converts a file's use of PSFramework messages to strings. .DESCRIPTION Converts a file's use of PSFramework messages to strings. .PARAMETER Path Path to the file to convert. .PARAMETER OutPath Folder in which to generate the output ps1 and psd1 file. .PARAMETER EnableException Replaces user friendly yellow warnings with bloody red exceptions of doom! Use this if you want the function to throw terminating errors you want to catch. .EXAMPLE PS C:\> Convert-PSMDMessage -Path 'C:\Scripts\logrotate.ps1' -OutPath 'C:\output' Converts all instances of writing messages in logrotate.ps1 to use strings instead. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true, Position = 0)] [PsfValidateScript('PSFramework.Validate.FSPath.File', ErrorString = 'PSFramework.Validate.FSPath.File')] [string] $Path, [Parameter(Mandatory = $true, Position = 1)] [PsfValidateScript('PSFramework.Validate.FSPath.Folder', ErrorString = 'PSFramework.Validate.FSPath.Folder')] [string] $OutPath, [switch] $EnableException ) begin { #region Utility Functions function Get-Text { [OutputType([string])] [CmdletBinding()] param ( $Value ) if (-not $Value.NestedExpressions) { return $Value.Extent.Text } $expressions = @{ } $expIndex = 0 $builder = [System.Text.StringBuilder]::new() $baseIndex = $Value.Extent.StartOffset $astIndex = 0 foreach ($nestedExpression in $Value.NestedExpressions) { $null = $builder.Append($Value.Extent.Text.SubString($astIndex, ($nestedExpression.Extent.StartOffset - $baseIndex - $astIndex)).Replace("{", "{{").Replace('}', '}}')) $astIndex = $nestedExpression.Extent.EndOffset - $baseIndex if ($expressions.ContainsKey($nestedExpression.Extent.Text)) { $effectiveIndex = $expressions[$nestedExpression.Extent.Text] } else { $expressions[$nestedExpression.Extent.Text] = $expIndex $effectiveIndex = $expIndex $expIndex++ } $null = $builder.Append("{$effectiveIndex}") } $null = $builder.Append($Value.Extent.Text.SubString($astIndex).Replace("{", "{{").Replace('}', '}}')) $builder.ToString() } function Get-Insert { [OutputType([string])] [CmdletBinding()] param ( $Value ) if (-not $Value.NestedExpressions) { return "" } $processed = @{ } $elements = foreach ($nestedExpression in $Value.NestedExpressions) { if ($processed[$nestedExpression.Extent.Text]) { continue } else { $processed[$nestedExpression.Extent.Text] = $true } if ($nestedExpression -is [System.Management.Automation.Language.SubExpressionAst]) { if ( ($nestedExpression.SubExpression.Statements.Count -eq 1) -and ($nestedExpression.SubExpression.Statements[0].PipelineElements.Count -eq 1) -and ($nestedExpression.SubExpression.Statements[0].PipelineElements[0].Expression -is [System.Management.Automation.Language.MemberExpressionAst]) ) { $nestedExpression.SubExpression.Extent.Text } else { $nestedExpression.Extent.Text.SubString(1) } } else { $nestedExpression.Extent.Text } } $elements -join ", " } #endregion Utility Functions $parameterMapping = @{ 'Message' = 'String' 'Action' = 'ActionString' } $insertMapping = @{ 'String' = '-StringValues' 'Action' = '-ActionStringValues' } } process { $ast = (Read-PSMDScript -Path $Path).Ast #region Parse Input $functionName = (Get-Item $Path).BaseName $commandAsts = $ast.FindAll({ if ($args[0] -isnot [System.Management.Automation.Language.CommandAst]) { return $false } if ($args[0].CommandElements[0].Value -notmatch '^Invoke-PSFProtectedCommand$|^Write-PSFMessage$|^Stop-PSFFunction$|^Test-PSFShouldProcess$') { return $false } if (-not ($args[0].CommandElements.ParameterName -match '^Message$|^Action$')) { return $false } $true }, $true) if (-not $commandAsts) { Write-PSFMessage -Level Host -String 'Convert-PSMDMessage.Parameter.NonAffected' -StringValues $Path return } #endregion Parse Input #region Build Replacements table $currentCount = 1 $replacements = foreach ($command in $commandAsts) { $parameter = $command.CommandElements | Where-Object ParameterName -in 'Message', 'Action' $paramIndex = $command.CommandElements.IndexOf($parameter) $parameterValue = $command.CommandElements[$paramIndex + 1] [PSCustomObject]@{ OriginalText = $parameterValue.Value Text = Get-Text -Value $parameterValue Inserts = Get-Insert -Value $parameterValue String = "$($functionName).Message$($currentCount)" StartOffset = $parameter.Extent.StartOffset EndOffset = $parameterValue.Extent.EndOffset OldParameterName = $parameter.ParameterName NewParameterName = $parameterMapping[$parameter.ParameterName] Parameter = $parameter ParameterValue = $parameterValue } $currentCount++ } #endregion Build Replacements table #region Calculate new text body $fileText = [System.IO.File]::ReadAllText((Resolve-PSFPath -Path $Path)) $builder = [System.Text.StringBuilder]::new() $index = 0 foreach ($replacement in $replacements) { $null = $builder.Append($fileText.Substring($index, ($replacement.StartOffset - $index))) $null = $builder.Append("-$($replacement.NewParameterName) '$($replacement.String)'") if ($replacement.Inserts) { $null = $builder.Append(" $($insertMapping[$replacement.NewParameterName]) $($replacement.Inserts)") } $index = $replacement.EndOffset } $null = $builder.Append($fileText.Substring($index)) $newDefinition = $builder.ToString() $testResult = Read-PSMDScript -ScriptCode ([Scriptblock]::create($newDefinition)) if ($testResult.Errors) { Stop-PSFFunction -String 'Convert-PSMDMessage.SyntaxError' -StringValues $Path -Target $Path -EnableException $EnableException return } #endregion Calculate new text body $resolvedOutPath = Resolve-PSFPath -Path $OutPath $encoding = [System.Text.UTF8Encoding]::new($true) $filePath = Join-Path -Path $resolvedOutPath -ChildPath "$functionName.ps1" [System.IO.File]::WriteAllText($filePath, $newDefinition, $encoding) $stringsPath = Join-Path -Path $resolvedOutPath -ChildPath "$functionName.psd1" $stringsText = @" @{ $($replacements | Format-String "`t'{0}' = {1} # {2}" -Property String, Text, Inserts | Join-String -Separator "`n") } "@ [System.IO.File]::WriteAllText($stringsPath, $stringsText, $encoding) } } |