Behind the PowerShell Pipeline logo

Behind the PowerShell Pipeline

Archives
Log in
Subscribe
August 4, 2026

Another Script Tagging Technique

In this issue:

  • Parsing with the AST
    • ParseInput()
    • AST Token Filtering
  • _getTags
  • Get-FunctionTag
  • Summary

In previous newsletters we've explored ways to tag or label script files. As much as I enjoy using alternate data streams, data can be lost when moving files across drives. Recently, while updating the PSScriptTools module, I tried something new to tag things. Instead of tagging a file, I wanted to tag the function. The module includes commands which can be categorized. In the project's README file, I document commands based on a category or description. I realized these could be tags.

The end result was an updated PSScriptTools object.

PS C:\> Get-PSScriptTools -Verb add | Select Name,Tags
___ ___ ___         _      _  _____        _
| _ \ __/ __|__ _ _(_)_ __| |__   _|__ ___| |___
|  _\__ \__ \ _| '_| | '_ \  _|| |/ _ \ _ \ (_-<
|_| |___/___\__|_| |_|_.__/\__||_|\___\___/_/__/
|_|                  |_|
v3.2.0

Name       Tags
----       ----
Add-Border {console, ansi}

I was even able to create a new table view.

PSScriptTools Tag view
figure 1 - PSScriptTools Tag view

I even updated Get-PSScriptTools to filter by tag.

Filtering by tag
figure 2 - Filtering by tag

I thought I would spend a little time showing you how I made this work.

Parsing with the AST

The secret ingredient is PowerShell's Abstract Syntax Tree, commonly referred to as the AST. I've written about this in the past but let's do a quick review.

This is a .NET class that will require a little scripting to work with. You begin with initializing two variables.

New-Variable astTokens -Force
New-Variable astErr -Force

The first variable will eventually contain code tokens. The other variable will contain any parsing errors. You can use the AST to parse a file or a script block. For my purposes, I wanted to parse the code inside a function. Here's a sample.

function Get-FooDemo {
    [cmdletbinding()]
    param(
        [string]$Name = 'PowerShell'
    )
    begin {
        #cmdTags = demo,foo  <--my target
        Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN  ] Starting $($MyInvocation.MyCommand)"
    } #begin
    process {
        Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Processing"
        $r = $name.ToUpper()
        #write ANSI formatted output
        '{0}{1}{2}' -f $PSStyle.Foreground.BrightGreen, $r, $PSStyle.Reset
    } #process
    end {
        Write-Verbose "[$((Get-Date).TimeOfDay) END    ] Ending $($MyInvocation.MyCommand)"
    } #end
}

You can see that I have inserted a comment called #cmdTags The comment technically can exist anywhere inside the function definition. I decided to place it at the beginning of code, generally the first line of the Begin block. This felt more comfortable to me but it isn't a requirement. The comment just needs to between function and the closing curly brace. As I work with this more I might change my mind for now I'll use the Begin block.

ParseInput()

The AST Parser has a method called ParseInput.

PS C:\> [System.Management.Automation.Language.Parser]::ParseInput.OverloadDefinitions
static System.Management.Automation.Language.ScriptBlockAst ParseInput(string input, [ref] System.Management.Automation.Language.Token[] tokens, [ref] System.Management.Automation.Language.ParseError[] errors)
static System.Management.Automation.Language.ScriptBlockAst ParseInput(string input, string fileName, [ref] System.Management.Automation.Language.Token[] tokens, [ref] System.Management.Automation.Language.ParseError[] errors)

I want to parse a script block because I can get that from a loaded function using the Function: PSDrive.

$sb = (Get-Item Function:\Get-FooDemo).ScriptBlock

The script block is everything inside the function definition.

PS C:\> $sb

    [cmdletbinding()]
    param(
        [string]$Name = 'PowerShell'
    )
    begin {
        #cmdTags = demo,foo
        Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN  ] Starting $($MyInvocation.MyCommand)"
    } #begin
    process {
        Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Processing"
        $r = $name.ToUpper()
        #write ANSI formatted output
        '{0}{1}{2}' -f $PSStyle.Foreground.BrightGreen, $r, $PSStyle.Reset
    } #process
    end {
        Write-Verbose "[$((Get-Date).TimeOfDay) END    ] Ending $($MyInvocation.MyCommand)"
    } #end

I can use this, along with the [ref] variables to parse the script block.

PS C:\> $ast = [System.Management.Automation.Language.Parser]::ParseInput($sb, [ref]$astTokens, [ref]$astErr)

The method will return all the AST information, but I don't need it. What I want is in $astTokens.

Want to read the full issue?
Already a paid subscriber? Click here to log in.
GitHub
Bluesky
LinkedIn
Mastodon
jdhitsolutions.github.io
Powered by Buttondown, the easiest way to start and grow your newsletter.