Behind the PowerShell Pipeline logo

Behind the PowerShell Pipeline

Subscribe
Archives
July 22, 2025

I Stream of PowerShell - Part 2

Let's back into alternate data streams and experiment with ways to leverage them using PowerShell. What I like is the fact that you can add information to a file without changing the file itself, other than the last write time property. For a PowerShell script file, something that isn't part of a module, it might be useful to use the alternate data stream to store a file version value.

Set-Content -path C:\temp\Get-PSFoo.ps1 -Stream "!Version" -Value "1.0.0"

The alternate data stream is named !Version. I don't think there are any restrictions on the name. I suppose it is possible some other application might define a Version data stream. By using the ! character, I am hoping to avoid any conflicts with other applications. But I don't think it ultimately matters.

PS C:\> Get-Item -Path C:\temp\Get-PSFoo.ps1 -Stream '!Version'

PSPath        : Microsoft.PowerShell.Core\FileSystem::C:\temp\Get-PSFoo.ps1:!Version
PSParentPath  : Microsoft.PowerShell.Core\FileSystem::C:\temp
PSChildName   : Get-PSFoo.ps1:!Version
PSDrive       : C
PSProvider    : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : False
FileName      : C:\temp\Get-PSFoo.ps1
Stream        : !Version
Length        : 7

PS C:\> Get-Content -Path C:\temp\Get-PSFoo.ps1 -Stream '!Version'
1.0.0

However, be careful with the stream values. You might try storing a [System.Version] object in the stream.

[Version]$ver = '1.1.123'
Set-Content -path C:\temp\Get-PSFoo.ps1 -Stream "!Version" -Value $ver

However the content will be stored as a string.

PS C:\> Get-Content -path C:\temp\Get-PSFoo.ps1 -Stream "!Version" | Tee -Variable v
1.1.123
PS C:\> $v -is [version]
False
PS C:\> $v -is [string]

If the value is not already a string, it appears that PowerShell will convert it to a string using the ubiquitous ToString() method.

PS C:\> set-Content -Path C:\temp\Get-PSFoo.ps1 -Stream "psVer" -Value $PSVersionTable
PS C:\> Get-Content -path C:\temp\Get-PSFoo.ps1 -Stream "psVer"
System.Management.Automation.PSVersionHashTable
PS C:\> $PSVersionTable.ToString()
System.Management.Automation.PSVersionHashTable

This shouldn't be that much of problem, since you are likely to be storing simple string values like a script author.

Set-Content -path C:\temp\Get-PSFoo.ps1 -Stream Author -Value "Jeff Hicks"
Set-Content -path C:\temp\Get-PSFoo.ps1 -Stream Computername -Value $env:Computername

One allowed variant is that you can store an array of strings in the stream.

PowerShell Tooling

Before we get too far, let's add some PowerShell tooling to identify alternate data streams and their values. I could write one function to do everything. But if you think about it, there are really two different tasks. One is to identify the streams for a file and the other is to read the stream values. So I will create two functions. But write them so that they can be used together in the PowerShell pipeline.

The first function will identify alternate data streams other than the default $DATA stream.

function Get-FileStreamInfo {
    [cmdletbinding()]
    [OutputType('PSFileStreamInfo')]
    param(
        [Parameter(Position = 0, Mandatory, ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ Test-Path $_ })]
        [string]$Path
    )
    process {
        $streams = Get-Item -Path $Path -Stream * | Where-Object { $_.Stream -ne ':$DATA' }
        [PSCustomObject]@{
            PSTypeName = 'PSFileStreamInfo'
            Path       = Convert-Path $Path
            Streams    = $streams.Stream
        }
    }
}

The second function will read the stream values. I will use a custom object type for each function so that I can easily identify the output.

Want to read the full issue?
GitHub Bluesky LinkedIn About Jeff
Powered by Buttondown, the easiest way to start and grow your newsletter.