Created
February 11, 2026 02:04
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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