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.