Powershell Script to capture functions from a directory of scripts and output them to a module RRS feed

  • General discussion

  • Hi guys, I think I'm stumped here!

    I have been delving into powershell over the last couple of years and I've written quite a number of scripts and cmdlets that I use and I've started diving into the power of modules.  I've come to the conclusion that instead of copying those functions I use between scripts, I should put them in a module so then I just need to import the module and i'm off to the races with all of my commonly used functions.  This would also help me for when I make improvements (I don't have to change x # of copies of the functions, I just change it once in the module).  As I started digging into this I thought it would be great if I could have a powershell script that scavenges a directory for all .ps1 files, gets the function list from all the files, then prompts me if I want to add it to my library (the module).  I've found easy ways to get the list of functions I want, and I've figured out how to find the starting point in the file to grab the text.  Where I'm struggling is how I can tell powershell to start at line x, proceed to the end of the function, and save that block of text into a hash so I end up with something like 'function test-iprange = "{.. function text .. }.  I think I could use the various tools to select items once I have the full table built but trying to extract out the functions out of the text is really vexing me because I need to find the matching "}" for the starting "{".  I'm sure there is an easy way to do this but, I'm having a terrible time coming up with a way to do it.  Heck, the ISE does it automatically! Any help on direction would be useful!

    Monday, October 5, 2015 11:03 PM

All replies

  • Sorry but your post is not understandable.  Too many run-on sentences and little focus.

    In forums we need to have you ask a specific question. Post you script or enough of it to demonstrate the issue along with the complete text of any error messages.

    Believe me   I read halfway and could not make an sense out of what you were writing and I am a very good reader.  Run-on and rambing sentences are every hard to follow although I am sure they make sense to you.

    Try simplifying your request according to the guidelines I just noted above.


    Monday, October 5, 2015 11:22 PM
  • You can actually use the same parser that the ISE is using to get the functions. It's exposed via the [System.Management.Automation.Language.Parser] class. If you call the ParseInput() or ParseFile() methods, you'll get an Abstract Syntax Tree (AST) root node as output (you also have access to the tokens and any parsing errors).

    The AST nodes provide incredibly rich information about the relationship the tokens have with each other.

    Each AST node has a Find() and FindAll() method associated with it that allows you to get any child nodes that they contain. The methods each take two parameters: one for a search delegate that allows for filtering (most people simply want to return a particular AST type), and one to specify if you want to look at any nested scriptblocks inside the node.

    If you haven't worked with the AST parser, please look more into it. Below is a quick and dirty tool that I think might work for you (if not, hopefully it provides enough to get you started):

    function Export-FunctionsToModule {
            [Parameter(Mandatory, ValueFromPipeline)]
            [System.IO.FileInfo] $ScriptFile,
            [string] $DestinationModulePath
        begin {
            $YesToAll = $NoToAll = $null
        process {
            $Tokens = $ParserErrors = $null
            $ErrorIndex = 0
            $AstRoot = [System.Management.Automation.Language.Parser]::ParseFile($ScriptFile.FullName, [ref] $Tokens, [ref] $ParserErrors)
            if ($ParserErrors) {
                $CurrentError = $ParserErrors[$ErrorIndex]
            foreach ($Function in $AstRoot.FindAll({$args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst]}, $false)) {
                $StartLine = $Function.Extent.StartLineNumber
                $EndLine = $Function.Extent.EndLineNumber
                # Throw away any errors that occurred before the current function
                while ($ErrorIndex -lt $ParserErrors.Count -and $ParserErrors[$ErrorIndex].Extent.StartLineNumber -lt $StartLine) { 
                    Write-Verbose "Ignoring error on line {0}: {1}" -f $ParserError[$ErrorIndex].Extent.StartLineNumber, $ParserErrors[$ErrorIndex].Message
                $CurrentErrors = while ($ErrorIndex -lt $ParserErrors.Count -and $ParserErrors[$ErrorIndex].Extent.StartLineNumber -gt $StartLine -and $ParserErrors[$ErrorIndex].Extent.EndLineNumber) {
                $Properties = [ordered] @{
                    FileName = $ScriptFile.FullName
                    FunctionName = $Function.Name
                    NumberOfLines = $EndLine - $StartLine + 1
                if ($CurrentErrors) {
                    $Properties."Errors($($CurrentErrors.Count))" = $CurrentErrors.Message -join "`n"
                $Prompt = [PSCustomObject] $Properties | Format-List | Out-String | % Trim
                if ($PSCmdlet.ShouldContinue(
                    "Add the following function to ${DestinationModulePath}?`n`n$Prompt", 
                    "Confirm Addition",
                    [ref] $YesToAll,
                    [ref] $NoToAll
                )) {
                    $Function.Extent.Text | Out-File -FilePath $DestinationModulePath -Append
                    "`n`n" | Out-File -FilePath $DestinationModulePath -Append

    And you'd call it like this:

    dir C:\powershell_scripts *.ps*1 | 
        Export-FunctionsToModule -DestinationModulePath c:\folder\NewModule.psm1

    Tuesday, October 6, 2015 1:50 AM
  • Thank you very much!

    Works perfectly!

    Been looking for something like this for a very long time and will use this code to learn the AST parser.

    Wednesday, May 18, 2016 4:22 PM