functions/public/Export-FunctionFromFile.ps1

Function Export-FunctionFromFile {
    [cmdletbinding(SupportsShouldProcess, DefaultParameterSetName = "All")]
    [alias("eff")]
    [OutputType("None", "System.IO.FileInfo")]
    Param(
        [Parameter(Position = 0, Mandatory, HelpMessage = "Specify the .ps1 or .psm1 file with defined functions.")]
        [ValidateScript({
            If (Test-Path $_ ) {
                $True
                If ($_ -match "\.ps(m)?1$") {
                    $True
                }
                Else {
                    Throw "The path must be to a .ps1 or .psm1 file."
                    $False
                }
            }
            Else {
                Throw "Can't validate that $_ exists. Please verify and try again."
                $False
            }
        })]
        [string]$Path,
        [Parameter(HelpMessage = "Specify the output path. The default is the same directory as the .ps1 file.")]
        [string]$OutputPath,
        [Parameter(HelpMessage = "Specify a function by name", ParameterSetName = "byName")]
        [string[]]$Name,
        [Parameter(HelpMessage = "Export all detected functions.", ParameterSetName = "all")]
        [switch]$All,
        [Parameter(HelpMessage = "Pass the output file to the pipeline.")]
        [switch]$PassThru
    )
    DynamicParam {

        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'
            $attributes.HelpMessage = 'Delete the function from the source file.'
            $attributeCollection.Add($attributes)

            # Adding a parameter alias
            $dynalias = New-Object System.Management.Automation.AliasAttribute -ArgumentList 'rm'
            $attributeCollection.Add($dynalias)

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

            return $paramDictionary
        } # end if
    } #end DynamicParam

    Begin {

        Write-Verbose "[BEGIN ] Starting $($MyInvocation.MyCommand) [$($PSCmdlet.ParameterSetName)]"
        Write-Verbose ($PSBoundParameters | Out-String)

        if (-Not $OutputPath) {
            #use the parent path of the file unless the user specifies a different path
            $OutputPath = Split-Path -Path $Path -Parent
        }
        if ($Name) {
            Write-Verbose "[BEGIN ] Looking for functions $($name -join ',')"
        }

        #insert a temporary line for each line of the function
        #this will be deleted at the end. Define this in the Begin block so
        #that it remains static
        $line = "DEL-FUNCTION-$(Get-Date -f 'hhmmss')`n"
    } #begin
    Process {
        #Convert the path to a full file system path
        $path = Convert-Path -Path $path
        Write-Verbose "[PROCESS] Processing $path for functions"
        #the file will always be parsed regardless of WhatIfPreference
        $AST = _getAST $path

        #parse out functions using the AST
        $functions = $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true)

        if ($functions.count -gt 0) {
            Write-Verbose "[PROCESS] Found $($functions.count) functions"
            Write-Verbose "[PROCESS] Creating files in $outputpath"
            Foreach ($item in $functions) {
                Write-Verbose "[PROCESS] Detected function $($item.name)"

                Switch ($PSCmdlet.ParameterSetName) {

                    "byName" {
                        if ($Name -contains $item.Name) {
                            #export named function
                            Write-Verbose "[PROCESS] $($item.name) matches by name"
                            $export = $True
                        }
                        else {
                            $export = $False
                        }
                    }

                    "All" {
                        if ($All -OR (Test-FunctionName -name $item.name)) {
                            #only export functions with standard names or if -All is detected.
                            $export = $True
                        }
                        else {
                            $export = $False
                        }
                    }

                } #switch

                If ($export) {
                    $NewFile = Join-Path -Path $OutputPath -ChildPath "$($item.name).ps1"
                    Write-Verbose "[PROCESS] Creating new file $NewFile"
                    Set-Content -Path $NewFile -Value $item.ToString() -Force

                    if ($PSBoundParameters.ContainsKey("Remove")) {
                        $f = $item.extent
                        $span = $f.EndLineNumber - $f.StartLineNumber

                        Switch -Regex ($host.name) {
                            "PowerShell ISE" {
                                Write-Verbose "[PROCESS] Removing from the PowerShell ISE"
                                psedit $path
                                $source = ($psISE.CurrentPowerShellTab.Files).where({ $_.fullpath -eq $path })
                                Write-Verbose "[PROCESS] Selecting a span of $span lines"
                                $source.Editor.Select($f.StartLineNumber, $f.StartColumnNumber, $f.EndLineNumber, $f.EndColumnNumber)
                                if ($PSCmdlet.ShouldProcess($item.name, "Deleting from $Path at $($f.StartLineNumber),$($f.StartColumnNumber),$($f.EndLineNumber),$($f.EndColumnNumber)")) {
                                    $source.Editor.InsertText($line * $span)
                                    #set a flag to save the file at the end
                                    $SaveISE = $True
                                }
                            }
                            "Visual Studio Code" {
                                Write-Verbose "[PROCESS] Removing from VS Code"
                                Open-EditorFile $Path
                                $ctx = $psEditor.getEditorContext()
                                if ($PSCmdlet.ShouldProcess($item.name, "Deleting from $Path at $($f.StartLineNumber),$($f.StartColumnNumber),$($f.EndLineNumber),$($f.EndColumnNumber)")) {
                                    $psEditor.Window.SetStatusBarMessage("Removing lines $($f.StartLineNumber) to $($f.EndLineNumber) from $path", 1000)
                                    $ctx.CurrentFile.InsertText(($line * $span), $f.StartLineNumber, $f.StartColumnNumber, $f.EndLineNumber, $f.EndColumnNumber)
                                    Start-Sleep -Milliseconds 250
                                    #set a flag to save the file at the end
                                    $SaveVSCode = $True
                                }
                            }
                        }
                    }
                    if ($PassThru -AND (-Not $WhatIfPreference)) {
                        Get-Item -Path $NewFile
                    }
                }
                else {
                    Write-Verbose "[PROCESS] Skipping $($item.name)"
                }
            } #foreach item
        }
        else {
            Write-Warning "No PowerShell functions detected in $Path."
        }
    } #process
    End {
        if ($SaveISE) {
            Write-Verbose "[END ] Removing temporary lines $line"
            $rev = $source.editor.Text -replace $line, ''
            $source.editor.text = $rev
            Write-Verbose "[END ] Saving $path in the PowerShell ISE"
            Start-Sleep -Milliseconds 500
            $source.Save()
            #keep the file open in the ISE in case you need to do anything
            #else with it.
            $source.Editor.SetCaretPosition(1, 1)
        }
        elseif ($SaveVSCode) {
            Write-Verbose "[END ] Saving and re-opening $path"
            $ctx.CurrentFile.Save()
            #need to give the file time to close
            Start-Sleep -Seconds 2
            Write-Verbose "[END ] Getting file contents"
            $txt = Get-Content $path -Raw

            # $txt | Out-File d:\temp\t.txt -force
            Write-Verbose "[END ] Filtering for line $($line.TrimEnd())"

            $rev = ([System.Text.RegularExpressions.Regex]::Matches($txt, "^((?!$($line.trimend())).)*$", "multiline")).value.replace("`r", "")

            Write-Verbose "[END ] Saving $path in VS Code"
            $rev | Out-File -FilePath $Path -Force
            #set cursor selection to top of file.
            #not sure how to force scrolling to the top
            $ctx.SetSelection(1, 1, 1, 1)
        }
        Write-Verbose "[END ] Ending $($MyInvocation.MyCommand)"
    } #end
}