Skip to content

Instantly share code, notes, and snippets.

@mmcintyre123
Created February 11, 2026 02:04
Show Gist options
  • Select an option

  • Save mmcintyre123/ac940364805200d35778bd62496f4508 to your computer and use it in GitHub Desktop.

Select an option

Save mmcintyre123/ac940364805200d35778bd62496f4508 to your computer and use it in GitHub Desktop.
VS Code PowerShell profile: auto-loads powershell script functions with comment-based help for IntelliSense hover tooltips. See https://github.com/PowerShell/vscode-powershell/issues/2472
# VSCode-specific PowerShell profile
# This profile is loaded when PowerShell runs inside VS Code.
#
# It automatically discovers PowerShell files from two sources:
# 1. Recursive scan of the workspace directory
# 2. ALL open .ps1 editor tabs across ALL VS Code windows (via state.vscdb)
#
# All discovered functions (with their comment-based help blocks) are loaded
# into the session so that IntelliSense hover tooltips and Get-Help work.
#
# Requires: PSSQLite module (Install-Module PSSQLite -Scope CurrentUser)
# Load the main PowerShell profile first
if (Test-Path $PROFILE.CurrentUserAllHosts) {
. $PROFILE.CurrentUserAllHosts
}
# ─── Configuration ───────────────────────────────────────────────────────────
# Directories to skip during recursive workspace scanning
$script:ExcludeDirectories = @(
'.git', '.vs', '.vscode', '.github',
'node_modules', 'build', 'dist', 'out',
'bin', 'obj', 'packages',
'__pycache__', 'vendor', '.terraform'
)
# ─── Helper Functions ────────────────────────────────────────────────────────
function Get-VSCodeStorageRoot {
<#
.SYNOPSIS
Returns the VS Code workspaceStorage root path, auto-detecting the edition.
.DESCRIPTION
Checks for "Code - Insiders", then "Code" under %APPDATA%. Returns the first
existing path, or $null if neither is found.
#>
[CmdletBinding()]
param()
foreach ($edition in @('Code - Insiders', 'Code')) {
$path = Join-Path $env:APPDATA "$edition\User\workspaceStorage"
if (Test-Path $path) { return $path }
}
return $null
}
function Get-EditorPathsFromStateDb {
<#
.SYNOPSIS
Extracts open editor file paths from a single VS Code state.vscdb database.
.DESCRIPTION
Copies the database to a temp location (since VS Code locks it), queries the
editor state key, and extracts all fsPath values matching the given extension
from the serialized JSON.
.PARAMETER DbPath
The full path to a state.vscdb file.
.PARAMETER Extension
The file extension to filter for (e.g., '.ps1'). Defaults to '.ps1'.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)][string]$DbPath,
[string]$Extension = '.ps1'
)
$tempDb = Join-Path $env:TEMP "vscode_state_$(Get-Random).vscdb"
$escapedExt = [regex]::Escape($Extension)
$fsPathPattern = "fsPath\\?"":\s*\\?""([^""]*?$escapedExt)"
try {
Copy-Item $DbPath -Destination $tempDb -Force -ErrorAction Stop
$result = Invoke-SqliteQuery -DataSource $tempDb `
-Query "SELECT value FROM ItemTable WHERE key = 'memento/workbench.parts.editor'" `
-ErrorAction SilentlyContinue
if (-not $result -or -not $result.value) { return @() }
$text = if ($result.value -is [byte[]]) {
[System.Text.Encoding]::UTF8.GetString($result.value)
} else { $result.value }
$paths = [System.Collections.Generic.List[string]]::new()
foreach ($m in [regex]::Matches($text, $fsPathPattern)) {
$filePath = $m.Groups[1].Value -replace '\\\\\\\\', '\'
$paths.Add($filePath)
}
return $paths
} catch {
Write-Verbose "Failed to read database ${DbPath}: $_"
return @()
} finally {
Remove-Item $tempDb -Force -ErrorAction SilentlyContinue
}
}
function Assert-PSSQLiteModule {
<#
.SYNOPSIS
Ensures the PSSQLite module is available and imported.
.DESCRIPTION
Checks if PSSQLite is installed. If not, writes a warning with install
instructions and returns $false. Otherwise imports it and returns $true.
#>
[CmdletBinding()]
param()
if (-not (Get-Module -ListAvailable PSSQLite)) {
Write-Warning "PSSQLite module not installed. Run: Install-Module PSSQLite -Scope CurrentUser"
Write-Warning "Open-editor scanning is disabled; only workspace files will be loaded."
return $false
}
Import-Module PSSQLite -ErrorAction SilentlyContinue
return $true
}
function Get-VSCodeOpenPS1Files {
<#
.SYNOPSIS
Finds all .ps1 files currently open in any VS Code window.
.DESCRIPTION
Scans every state.vscdb file in VS Code's workspaceStorage directory and
returns unique .ps1 file paths found across all workspaces. This works
across all VS Code windows, including files opened from outside any workspace.
Requires the PSSQLite module for SQLite access.
#>
[CmdletBinding()]
param()
$storageRoot = Get-VSCodeStorageRoot
if (-not $storageRoot) {
Write-Verbose "VS Code workspaceStorage not found."
return @()
}
if (-not (Assert-PSSQLiteModule)) { return @() }
$uniquePaths = [System.Collections.Generic.HashSet[string]]::new(
[System.StringComparer]::OrdinalIgnoreCase
)
Get-ChildItem -Path $storageRoot -Filter 'state.vscdb' -Recurse -File -ErrorAction SilentlyContinue |
ForEach-Object {
foreach ($path in (Get-EditorPathsFromStateDb -DbPath $_.FullName)) {
[void]$uniquePaths.Add($path)
}
}
return @($uniquePaths)
}
function Get-WorkspacePS1Files {
<#
.SYNOPSIS
Recursively finds .ps1 files in a directory, excluding common non-source directories.
.PARAMETER Path
The root directory to scan.
#>
[CmdletBinding()]
param([Parameter(Mandatory)][string]$Path)
if (-not (Test-Path $Path)) { return @() }
$excludePattern = ($script:ExcludeDirectories | ForEach-Object { [regex]::Escape($_) }) -join '|'
Get-ChildItem -Path $Path -Filter '*.ps1' -Recurse -File -ErrorAction SilentlyContinue |
Where-Object { $_.DirectoryName -notmatch "(\\|/)($excludePattern)(\\|/|$)" }
}
function Get-FunctionDefinitionText {
<#
.SYNOPSIS
Builds the full text of a function definition including any preceding help block.
.DESCRIPTION
Given a FunctionDefinitionAst and the full script content, looks backwards from
the function start for a preceding comment-based help block and prepends it.
The function keyword is also rewritten to 'function global:' so the function
persists in the global scope when loaded via Invoke-Expression.
.PARAMETER FunctionAst
The FunctionDefinitionAst node from the parsed script.
.PARAMETER ScriptContent
The full raw text of the script file.
.PARAMETER HelpSearchDistance
How many characters to look backwards for a help block. Defaults to 1500.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)][System.Management.Automation.Language.FunctionDefinitionAst]$FunctionAst,
[Parameter(Mandatory)][string]$ScriptContent,
[int]$HelpSearchDistance = 1500
)
$funcStart = $FunctionAst.Extent.StartOffset
$searchStart = [Math]::Max(0, $funcStart - $HelpSearchDistance)
$precedingText = $ScriptContent.Substring($searchStart, $funcStart - $searchStart)
# Rewrite to global scope so it persists beyond the calling function
$globalFuncText = $FunctionAst.Extent.Text -replace '^\s*function\s+', 'function global:'
if ($precedingText -match '(?s)(<#.*?#>)\s*$') {
return $Matches[1] + "`n" + $globalFuncText
}
return $globalFuncText
}
function Get-FunctionAsts {
<#
.SYNOPSIS
Parses a .ps1 file and returns its function definition AST nodes.
.DESCRIPTION
Reads a script file, parses it using the PowerShell AST, and returns all
FunctionDefinitionAst nodes found. The script is never executed.
.PARAMETER FilePath
The full path to the .ps1 file to parse.
#>
[CmdletBinding()]
param([Parameter(Mandatory)][string]$FilePath)
$scriptContent = Get-Content $FilePath -Raw -ErrorAction Stop
if ([string]::IsNullOrWhiteSpace($scriptContent)) { return @() }
$ast = [System.Management.Automation.Language.Parser]::ParseInput(
$scriptContent, [ref]$null, [ref]$null
)
$functions = $ast.FindAll(
{ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] },
$true
)
# Return both the ASTs and the raw content (needed for help block extraction)
return @{ Functions = $functions; ScriptContent = $scriptContent }
}
function Import-FunctionsFromScript {
<#
.SYNOPSIS
Parses a .ps1 file and loads its functions (with help blocks) into the global scope.
.DESCRIPTION
Extracts function definitions from a script file via AST parsing, builds each
function's full text (including any preceding comment-based help block), and
loads it into the global scope. The script body is never executed.
.PARAMETER FilePath
The full path to the .ps1 file to parse.
.OUTPUTS
The number of functions loaded from the file.
#>
[CmdletBinding()]
param([Parameter(Mandatory)][string]$FilePath)
$parseResult = Get-FunctionAsts -FilePath $FilePath
if (-not $parseResult.Functions -or $parseResult.Functions.Count -eq 0) { return 0 }
foreach ($func in $parseResult.Functions) {
$definitionText = Get-FunctionDefinitionText -FunctionAst $func -ScriptContent $parseResult.ScriptContent
Invoke-Expression $definitionText
}
return $parseResult.Functions.Count
}
# ─── Import Pipeline ─────────────────────────────────────────────────────────
function Import-FromFileList {
<#
.SYNOPSIS
Imports functions from a list of .ps1 file paths, skipping duplicates.
.DESCRIPTION
Iterates over a list of file paths, skips any already in the ProcessedFiles set,
and calls Import-FunctionsFromScript on each new file. Returns the total number
of functions loaded.
.PARAMETER FilePaths
An array of .ps1 file paths to process.
.PARAMETER ProcessedFiles
A HashSet used to track already-processed files and prevent duplicates.
.PARAMETER Source
A label for verbose output (e.g., "Workspace" or "Open editor").
#>
[CmdletBinding()]
param(
[string[]]$FilePaths,
[Parameter(Mandatory)]
[AllowEmptyCollection()]
[System.Collections.Generic.HashSet[string]]$ProcessedFiles,
[string]$Source = 'Unknown'
)
$loaded = 0
foreach ($filePath in $FilePaths) {
if (-not (Test-Path $filePath)) { continue }
if (-not $ProcessedFiles.Add($filePath)) { continue }
try {
$count = Import-FunctionsFromScript -FilePath $filePath
$loaded += $count
Write-Verbose " ${Source}: $filePath ($count functions)"
} catch {
Write-Warning "Failed to parse ${filePath}: $_"
}
}
return $loaded
}
function Write-ImportSummary {
<#
.SYNOPSIS
Writes a colored summary of how many functions were loaded.
.PARAMETER FunctionCount
Total number of functions loaded.
.PARAMETER FileCount
Total number of files processed.
#>
[CmdletBinding()]
param(
[int]$FunctionCount,
[int]$FileCount
)
if ($FunctionCount -gt 0) {
Write-Host "Loaded $FunctionCount function(s) from $FileCount file(s) for IntelliSense" -ForegroundColor Green
} else {
Write-Host "No PowerShell functions found in workspace or open editors." -ForegroundColor Yellow
}
}
# ─── Main Entry Point ────────────────────────────────────────────────────────
function Import-WorkspaceFunctions {
<#
.SYNOPSIS
Loads PowerShell functions from workspace files and open VS Code editors.
.DESCRIPTION
Discovers .ps1 files from two sources and loads all their function definitions
(with comment-based help) into the current session for IntelliSense support:
1. Recursive scan of the workspace directory (excluding common non-source dirs)
2. All open .ps1 editor tabs across all VS Code windows (via state.vscdb)
This enables hover tooltips and Get-Help for your custom functions without
dot-sourcing entire scripts.
.PARAMETER Path
The workspace root directory to scan. Defaults to $pwd.
.EXAMPLE
Import-WorkspaceFunctions
Scans the workspace and open editors, loading all discovered functions.
.EXAMPLE
Import-WorkspaceFunctions -Path "C:\Projects\MyModule" -Verbose
Loads functions from the specified directory and lists each file processed.
#>
[CmdletBinding()]
param([string]$Path = $pwd.Path)
$processedFiles = [System.Collections.Generic.HashSet[string]]::new(
[System.StringComparer]::OrdinalIgnoreCase
)
# Source 1: All .ps1 files in the workspace directory
$workspacePaths = @((Get-WorkspacePS1Files -Path $Path) | ForEach-Object { $_.FullName })
$fromWorkspace = Import-FromFileList -FilePaths $workspacePaths -ProcessedFiles $processedFiles -Source 'Workspace'
# Source 2: All .ps1 files open in any VS Code editor tab
$openEditorPaths = @(Get-VSCodeOpenPS1Files)
$fromEditors = Import-FromFileList -FilePaths $openEditorPaths -ProcessedFiles $processedFiles -Source 'Open editor'
Write-ImportSummary -FunctionCount ($fromWorkspace + $fromEditors) -FileCount $processedFiles.Count
}
# Auto-load functions when the profile starts
Import-WorkspaceFunctions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment