functions/public/Convert-ScriptToFunction.ps1

Function Convert-ScriptToFunction {
    [cmdletbinding()]
    [OutputType('System.String')]
    [alias('csf')]
    Param(
        [Parameter(
            Position = 0,
            Mandatory,
            ValueFromPipelineByPropertyName,
            HelpMessage = 'Enter the path to your PowerShell script file.'
        )]
        [ValidateScript({ Test-Path $_ })]
        [ValidatePattern('\.ps1$')]
        [string]$Path,

        [Parameter(
            Position = 1,
            Mandatory,
            ValueFromPipelineByPropertyName,
            HelpMessage = 'What is the name of your new function?')]
        [ValidateScript({
                if ($_ -match '^\w+-\w+$') {
                    $true
                }
                else {
                    Throw 'Your function name should have a Verb-Noun naming convention'
                    $False
                }
            })]
        [string]$Name,

        [Parameter(ValueFromPipelineByPropertyName, HelpMessage = 'Specify an optional alias for your new function. You can define multiple aliases separated by commas.')]
        [ValidateNotNullOrEmpty()]
        [string[]]$Alias
    )
    DynamicParam {
        <#
        If running this function in the PowerShell ISE or VS Code,
        define a ToEditor switch parameter
        #>

        If ($host.name -match 'ISE|Code') {
            $paramDictionary = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary

            # Defining parameter attributes
            $attributeCollection = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
            $attributes = New-Object System.Management.Automation.ParameterAttribute
            $attributes.ParameterSetName = '__AllParameterSets'
            $attributeCollection.Add($attributes)

            # Defining the runtime parameter
            $dynParam1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter('ToEditor', [Switch], $attributeCollection)
            $paramDictionary.Add('ToEditor', $dynParam1)

            return $paramDictionary
        } # end if
    } #end DynamicParam
    Begin {
        Write-Verbose "Starting $($MyInvocation.MyCommand)"
        Write-Verbose 'Initializing'
        $new = [System.Collections.Generic.list[string]]::new()
    } #begin
    Process {
        #normalize
        $Path = Convert-Path $path
        $Name = Format-FunctionName $Name

        Write-Verbose "Processing $path"
        $AST = _getAST $path

        if ($ast.extent) {
            Write-Verbose 'Getting any comment based help'
            $ch = $astTokens | Where-Object { $_.kind -eq 'comment' -AND $_.text -match '\.synopsis' }

            if ($ast.ScriptRequirements) {
                Write-Verbose 'Adding script requirements'
                if ($ast.ScriptRequirements.RequiredPSVersion) {
                    $new.Add("#requires -version $($ast.ScriptRequirements.RequiredPSVersion.ToString())")
                }
                if ($ast.ScriptRequirements.RequiredModules) {
                    Foreach ($m in $ast.ScriptRequirements.RequiredModules) {
                        #test for version requirements
                        $ver = $m.PSObject.properties.where({ $_.name -match 'version' -AND $_.value })
                        if ($ver) {
                            $new.Add("#requires -module @{ModuleName = '$($m.name)';$($ver.Name) = '$($ver.value)'}")
                        }
                        else {
                            $new.add("#requires -module $($m.Name)")
                        }
                    }
                }
                if ($ast.ScriptRequirements.IsElevationRequired) {
                    $new.Add('#requires -RunAsAdministrator')
                }
                If ($ast.ScriptRequirements.requiredPSEditions) {
                    $new.add("#requires -PSEdition $($ast.ScriptRequirements.requiredPSEditions)")
                }

                $new.Add("`n")
            }
            else {
                Write-Verbose 'No script requirements found'
            }

            $head = @"
# Function exported from $Path
 
Function $Name {
 
"@

            $new.add($head)

            if ($ch) {
                $new.Add($ch.text)
                $new.Add("`n")
            }
            else {
                Write-Verbose 'Generating new comment based help from parameters'
                if ($ast.ParamBlock) {
                    New-CommentHelp -ParamBlock $ast.ParamBlock | ForEach-Object { $new.Add("$_") }
                }
                else {
                    New-CommentHelp -TemplateOnly | ForEach-Object { $new.Add("$_") }
                }
                $new.Add("`n")
            }

            [regex]$rx = '\[cmdletbinding\(.*\)\]'
            if ($rx.IsMatch($ast.Extent.text)) {
                Write-Verbose 'Using existing cmdletbinding'
                #use the first match
                $cb = $rx.match($ast.extent.text).Value
                $new.Add("`t$cb")
            }
            else {
                Write-Verbose 'Adding [cmdletbinding()]'
                $new.Add("`t[cmdletbinding()]")
            }

            if ($alias) {
                Write-Verbose "Adding function alias definition $($alias -join ',')"
                $new.Add("`t[Alias('$($alias -join "','")')]")
            }
            if ($ast.ParamBlock) {
                Write-Verbose 'Adding defined Param() block'
                [void]($ast.ParamBlock.ToString().split("`n").Foreach({ $new.add("`t$_") }) -join "`n")
                $new.Add("`n")
            }
            else {
                Write-Verbose 'Adding Param() block'
                $new.add("`tParam()")
            }
            if ($ast.DynamicParamBlock) {
                #assumes no more than 1 dynamic parameter
                Write-Verbose 'Adding dynamic parameters'
                [void]($ast.DynamicParamBlock.ToString().split("`n").Foreach({ $new.Add($_) }) -join "`n")
            }

            if ($ast.BeginBlock.Extent.text) {
                Write-Verbose 'Adding defined Begin block'
                [void]($ast.BeginBlock.Extent.ToString().split("`n").Foreach({ $new.Add($_) }) -join "`n")
                $UseBPE = $True
            }

            if ($ast.ProcessBlock.Extent.text) {
                Write-Verbose 'Adding defined Process block'
                [void]($ast.ProcessBlock.Extent.ToString().split("`n").Foreach({ $new.add($_) }) -join "`n")
            }

            if ($ast.EndBlock.Extent.text) {
                if ($UseBPE) {
                    Write-Verbose 'Adding opening End{} block'
                    $new.Add("`tEnd {")
                }
                Write-Verbose 'Adding the remaining code or defined endblock'
                [void]($ast.Endblock.Statements.foreach({ $_.ToString() }).Foreach({ $new.Add($_) }))
                if ($UseBPE) {
                    Write-Verbose 'Adding closing End {} block'
                    $new.Add("`t}")
                }
            }
            else {
                $new.Add('End { }')
            }
            Write-Verbose 'Closing the function'
            $new.Add( "`n} #close $name")

            if ($PSBoundParameters.ContainsKey('ToEditor')) {
                Write-Verbose 'Opening result in editor'
                if ($host.name -match 'ISE') {
                    $NewFile = $psISE.CurrentPowerShellTab.Files.add()
                    $NewFile.Editor.InsertText(($new -join "`n"))
                    $NewFile.editor.select(1, 1, 1, 1)
                }
                elseif ($host.name -match 'Code') {
                    $psEditor.Workspace.NewFile()
                    $ctx = $psEditor.GetEditorContext()
                    $ctx.CurrentFile.InsertText($new -join "`n")
                }
                else {
                    $new -join "`n" | Set-Clipboard
                    Write-Warning "Can't detect the PowerShell ISE or VS Code. Output has been copied to the clipboard."
                }
            }
            else {
                Write-Verbose "Writing output [$($new.count) lines] to the pipeline"
                $new -join "`n"
            }
        } #if ast found
        else {
            Write-Warning 'Failed to find a script body to convert to a function.'
        }

    } #process
    End {
        Write-Verbose "Ending $($MyInvocation.MyCommand)"
    }
}