Skip to content

Instantly share code, notes, and snippets.

@santisq
Last active March 20, 2026 20:01
Show Gist options
  • Select an option

  • Save santisq/3e32cb5e9a2dbcfc78c496c4b7ae1870 to your computer and use it in GitHub Desktop.

Select an option

Save santisq/3e32cb5e9a2dbcfc78c496c4b7ae1870 to your computer and use it in GitHub Desktop.
A custom PowerShell variable provider implementation that evaluates script blocks on inspection
// Source - https://stackoverflow.com/a/79911456
// Posted by Santiago Squarzon, modified by community. See post 'Timeline' for change history
// Retrieved 2026-03-20, License - CC BY-SA 4.0
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Management.Automation;
using System.Management.Automation.Provider;
[CmdletProvider(ProviderName, ProviderCapabilities.None)]
public class CustomVariableProvider : ContainerCmdletProvider, IContentCmdletProvider
{
internal const string ProviderName = "CustomVariable";
private Dictionary<string, object?>? _variables;
private Dictionary<string, object?> Variables { get => _variables ??= ((CustomVariableDrive)PSDriveInfo).Variables; }
private sealed class CustomVariableDrive(PSDriveInfo driveInfo) : PSDriveInfo(driveInfo)
{
internal Dictionary<string, object?> Variables { get; } = new(StringComparer.OrdinalIgnoreCase);
}
private sealed class CustomVariableHandler(Dictionary<string, object?> vars, string key)
: IContentReader, IContentWriter
{
private bool _read;
public void Close() { }
public void Dispose() { }
public IList Read(long readCount)
{
if (_read) return Array.Empty<object>();
_read = true;
object? value = vars[key];
return LanguagePrimitives.ConvertTo<object[]>(
value switch
{
PSObject { BaseObject: ScriptBlock sb } => sb.InvokeReturnAsIs(),
ScriptBlock sb => sb.InvokeReturnAsIs(),
_ => value
});
}
public void Seek(long offset, SeekOrigin origin) => throw new NotImplementedException();
public IList Write(IList content)
{
vars[key] = content is { Count: 1 } ? content[0] : content;
return content;
}
}
private static string NormalizePath(string path) =>
string.IsNullOrEmpty(path) ? string.Empty : path.Trim('\\', '/');
protected override PSDriveInfo NewDrive(PSDriveInfo drive) => new CustomVariableDrive(drive);
private KeyValuePair<string, object?> GetKeyValue(string path) => new(path, Variables[path]);
protected override bool ItemExists(string path)
{
string normalized = NormalizePath(path);
return string.IsNullOrEmpty(normalized) || Variables.ContainsKey(normalized);
}
protected static bool IsItemContainer(string path) => string.IsNullOrEmpty(NormalizePath(path));
protected override void GetChildItems(string path, bool recurse)
{
string normalized = NormalizePath(path);
if (string.IsNullOrEmpty(normalized))
{
foreach (KeyValuePair<string, object?> kvp in Variables)
WriteItemObject(kvp, kvp.Key, false);
return;
}
WriteItemObject(GetKeyValue(normalized), normalized, false);
}
protected override void GetItem(string path)
{
string normalized = NormalizePath(path);
if (string.IsNullOrEmpty(normalized))
{
WriteItemObject(Variables, normalized, true);
return;
}
WriteItemObject(GetKeyValue(normalized), normalized, false);
}
protected override bool IsValidPath(string path) => true;
protected override string[] ExpandPath(string path) => [NormalizePath(path)];
protected override void SetItem(string path, object value) =>
Variables[NormalizePath(path)] = value;
protected override void NewItem(string path, string itemTypeName, object newItemValue)
{
string normalized = NormalizePath(path);
// Will throw "An item with the same key has already been added. ..."
Variables.Add(normalized, newItemValue);
WriteItemObject(GetKeyValue(normalized), normalized, false);
}
public IContentReader GetContentReader(string path) =>
new CustomVariableHandler(Variables, NormalizePath(path));
public object? GetContentReaderDynamicParameters(string path) => null;
public IContentWriter GetContentWriter(string path) =>
new CustomVariableHandler(Variables, NormalizePath(path));
public object? GetContentWriterDynamicParameters(string path) => null;
public void ClearContent(string path)
{
string normalized = NormalizePath(path);
if (Variables.ContainsKey(normalized))
Variables[normalized] = null;
}
public object? ClearContentDynamicParameters(string path) => null;
}
Add-Type -Path .\CustomProvider.cs -WA 0 -IgnoreWarnings -PassThru |
    Import-Module -Assembly { $_.Assembly }

$null = New-PSDrive -Name PS -PSProvider CustomVariable -Root ''

# Can use `Set-Item` or `Set-Content` or `Net-Item` if you choose here...
$PS:Now = { Get-Date }
$PS:Now # Friday, March 20, 2026 12:51:59 PM
Start-Sleep 1
$PS:Now # Friday, March 20, 2026 12:52:00 PM

For more details see https://stackoverflow.com/a/79911456/15339544.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment