Behind the PowerShell Pipeline logo

Behind the PowerShell Pipeline

Subscribe
Archives
July 18, 2025

I Stream of PowerShell

Before I dive into today's content, I want to spend a moment to talk about this newsletter. First, although I refer to this as a newsletter, it is a newsletter in format only. I use a mailing service to deliver it to you and provide archive access. I typically think of a newsletter as a collection of related items of interest. The wrap-up I send out at the end of each month is a newsletter in that sense. I approach the other "issues" as a series of articles. I think of them as short book chapters.

Even though I write about topics like creating HTML reports, I'm not necessarily writing about the best way to accomplish that task. My goal with this newsletter is to teach you about PowerShell. I want to show you how to use PowerShell to solve problems, but I also want to show you how to think about using PowerShell. I want you to learn how to approach problems and how to use PowerShell to solve them. This means focusing on language, concepts, syntax, and techniques rather than specific solutions. Take my recent series of articles on creating HTML reports. I wasn't trying to show the best or only way to create HTML reports. Some of you may have no need to crete such reports. You should view all of my content as learning opportunities. You may not need to use here strings, import file content, or create files to build an HTML report. But those concepts could be useful in other contexts. When you read my content, I want you to think about how you can apply the content to your own work. What gaps in your knowledge are getting filled in? What new ideas are you getting about how to use PowerShell?

Focus on the journey I lead you through in each issue, not the destination or end product.

Today's topic, which I'm sure will encompass multiple issues, is a great example. I expect many of you aren't looking for where I end up, but there is value in exploring the process.

Alternate Data Streams

In Windows, we typically think of files and their content. We open a file, read its content, modify it, and save it. The content is saved with the file. However, there is much more to files in Windows than content alone.

Alternate data streams (ADS) are a feature of the NTFS file system that allows you to store additional metadata or content within a file without affecting the primary data stream. This means you can have multiple streams of data associated with a single file, each identified by a unique name. PowerShell provides cmdlets and parameters to work with these streams, allowing you to create, read, and manage them. I'm sure I've mentioned alternate data streams in the past.

In PowerShell, we can use commands with the -Stream parameter to work with these alternate data streams.

Get-Command -ParameterName Stream | Select Name

Name
----
Add-Content
Clear-Content
Get-Content
Get-Item
Out-String
Remove-Item
Set-Content

However you should still read help for these commands as -Stream can have other meanings.

Finding Streams

First, let's look at how to find streams associated with a file. Here's how we typically work with a file in PowerShell:

PS C:\temp> Get-tIem .\walrus.txt

    Directory: C:\temp

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---           7/10/2025  9:58 AM             29 walrus.txt

PS C:\temp> Get-Item .\walrus.txt | Get-Content
I am the PowerShell walrus!

When you read the help for Get-Item, you will see that it has a -Stream parameter. This parameter allows you to specify a stream to retrieve. You can use a wildcard character to retrieve all streams associated with the file.

PS C:\temp> Get-Item .\walrus.txt -Stream *

PSPath        : Microsoft.PowerShell.Core\FileSystem::C:\temp\walrus.txt::$DATA
PSParentPath  : Microsoft.PowerShell.Core\FileSystem::C:\temp
PSChildName   : walrus.txt::$DATA
PSDrive       : C
PSProvider    : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : False
FileName      : C:\temp\walrus.txt
Stream        : :$DATA
Length        : 29

The stream name is :$DATA, which is the default data stream for files in Windows.

PS C:\temp> Get-Item .\n.txt -Stream :`$DATA

PSPath        : Microsoft.PowerShell.Core\FileSystem::C:\temp\n.txt::$DATA
PSParentPath  : Microsoft.PowerShell.Core\FileSystem::C:\temp
PSChildName   : n.txt::$DATA
PSDrive       : C
PSProvider    : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : False
FileName      : C:\temp\n.txt
Stream        : :$DATA
Length        : 449

Note that this specific stream name begins with a colon (:) and is followed by $DATA. Because $DATA looks like a PowerShell variable, I am using the backtick to escape the $ so PowerShell treats it as a literal character.

Getting Stream Content

If you look at the output, notice the PSChildName property. This property contains the file name and the stream name. You can use this property to retrieve the content of the stream.

PS C:\temp> Get-Content -path .\walrus.txt::`$DATA
I am the PowerShell walrus!

Or after reading help for Get-Content, you can use the -Stream parameter to retrieve the content of the stream.

PS C:\temp> Get-Content -path .\walrus.txt -Stream :`$data
I am the PowerShell walrus!

As you can see, the stream name is case-insensitive.

Creating Streams

Don't worry about the default data stream. That will be updated with normal file editing. However, let's have some fun and create a new stream. You can use the Add-Content cmdlet to create a new stream. The -Stream parameter allows you to specify the name of the stream you want to create.

Set-Content -Path .\walrus.txt -Stream jeff -Value Footastic123!

Now that I've added the new stream, I can retrieve it using the Get-Item cmdlet with the -Stream parameter.

PS C:\temp> Get-Item -path .\walrus.txt -Stream *

PSPath        : Microsoft.PowerShell.Core\FileSystem::C:\temp\walrus.txt::$DATA
PSParentPath  : Microsoft.PowerShell.Core\FileSystem::C:\temp
PSChildName   : walrus.txt::$DATA
PSDrive       : C
PSProvider    : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : False
FileName      : C:\temp\walrus.txt
Stream        : :$DATA
Length        : 29

PSPath        : Microsoft.PowerShell.Core\FileSystem::C:\temp\walrus.txt:jeff
PSParentPath  : Microsoft.PowerShell.Core\FileSystem::C:\temp
PSChildName   : walrus.txt:jeff
PSDrive       : C
PSProvider    : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : False
FileName      : C:\temp\walrus.txt
Stream        : jeff
Length        : 15

Or reference in directly by the stream name.

PS C:\temp> Get-Item -path .\walrus.txt -Stream jeff

PSPath        : Microsoft.PowerShell.Core\FileSystem::C:\temp\walrus.txt:jeff
PSParentPath  : Microsoft.PowerShell.Core\FileSystem::C:\temp
PSChildName   : walrus.txt:jeff
PSDrive       : C
PSProvider    : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : False
FileName      : C:\temp\walrus.txt
Stream        : jeff
Length        : 15

I could have used Add-Content to create the stream. If I use it now, it will append to the stream.

PS C:\temp> Add-Content -Path .\walrus.txt -Stream jeff -Value barbar999
PS C:\temp> Get-Content -path .\walrus.txt -Stream jeff
Footastic123!
barbar999

Modifying stream content is considered a file modification so time stamps will be updated. However, the content of the alternate data stream is not reflected in the file's size. The file size will only reflect the size of the default data stream.

Based on what I've shown thus far, how do you think you would list files with additional streams?

PS C:\temp> dir -file | Get-Item -Stream * |
Where Stream -ne ':$DATA' | Select FileName,Stream,Length

FileName                Stream        Length
--------                ------        ------
C:\temp\2112.mp3        01APIC_00.jpg   8842
C:\temp\png.zip         Demo               5
C:\temp\RollChanges.mp3 01APIC_00.jpg   6388
C:\temp\walrus.txt      jeff              15
C:\temp\walrus.txt      ps             20172

Get-ChildItem doesn't have a parameter for streams so I use Get-Item instead, filtering out the default data stream. I also select the properties I want to display. The length is the size of the stream, not the file.

Removing Streams

To remove an alternate data stream, you can use the Remove-Item cmdlet with the -Stream parameter. This will remove the specified stream from the file.

PS C:\temp> Remove-Item -Path .\walrus.txt -Stream jeff -WhatIf
What if: Performing the operation "Remove-Item" on target "Stream 'jeff' of file 'C:\temp\walrus.txt'.".
PS C:\temp> Remove-Item -Path .\walrus.txt -Stream jeff
PS C:\temp> Get-Item -Path .\walrus.txt -Stream *

PSPath        : Microsoft.PowerShell.Core\FileSystem::C:\temp\walrus.txt::$DATA
PSParentPath  : Microsoft.PowerShell.Core\FileSystem::C:\temp
PSChildName   : walrus.txt::$DATA
PSDrive       : C
PSProvider    : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : False
FileName      : C:\temp\walrus.txt
Stream        : :$DATA
Length        : 29

Curiously, this operation does not update the file's last write time.

Stream Limitations

There are a few limitations to be aware of when working with alternate data streams in PowerShell. First, this is an NTFS feature. If you copy the file to a non-NTFS file system like FAT32, the alternate streams are lost. Obviously the file contents are preserved, but the alternate data streams are not. When you copy a file from a non-NTFS file system to an NTFS file system, the :$DATA stream is created with the file contents. If you copy a file to an NTFS file system on a different volume or disk, you will lose the alternate data streams.

If you archive a file, you may lose the alternate data streams. Using the native Compress-Archive cmdlet will not preserve alternate data streams. Depending on the tooling you use to archive the file, you may have options. For example, WinRar has an option to preserve alternate data streams.

WinRAR save streams
figure 1

If you find yourself using alternate data streams, you might check your backup solution to see if it preserves them or has options to preserve them.

Summary

Alternate data streams offer an intriguing management feature which we'll start diving into next time. In the mean time, poke around your folders. You might be surprised what other information available.

(c) 2022-2025 JDH Information Technology Solutions, Inc. - all rights reserved
Don't miss what's next. Subscribe to Behind the PowerShell Pipeline:
Start the conversation:
GitHub Bluesky LinkedIn About Jeff
Powered by Buttondown, the easiest way to start and grow your newsletter.