{"id":8888,"date":"2022-02-18T12:40:11","date_gmt":"2022-02-18T17:40:11","guid":{"rendered":"https:\/\/jdhitsolutions.com\/blog\/?p=8888"},"modified":"2022-02-18T12:40:16","modified_gmt":"2022-02-18T17:40:16","slug":"friday-fun-with-powershell-and-alternate-data-streams","status":"publish","type":"post","link":"https:\/\/jdhitsolutions.com\/blog\/scripting\/8888\/friday-fun-with-powershell-and-alternate-data-streams\/","title":{"rendered":"Friday Fun with PowerShell and Alternate Data Streams"},"content":{"rendered":"\n<p>I have always had a peculiar fascination with alternate data streams. This is an NTFS feature that has been around for a long time. The feature, also referred to as ADS, allows a user to write data to a hidden fork of a file. You can store practically anything in an alternate data stream without affecting the reported file size. As with anything, there is always the chance of abuse or worse. But I'm going to assume you have adequate security in place. You cannot disable ADS, so maybe we can do something useful with it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"finding-streams\">Finding Streams<\/h2>\n\n\n\n<p>The first step is to learn how to identify alternate data streams in a file. You can use Get-Item and the Streams parameter. Fortunately, the parameter accepts wildcards.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-style-default\"><a href=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-ads.png\"><img loading=\"lazy\" decoding=\"async\" width=\"769\" height=\"233\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-ads.png\" alt=\"get alternate data stream\" class=\"wp-image-8889\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-ads.png 769w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-ads-300x91.png 300w\" sizes=\"auto, (max-width: 769px) 100vw, 769px\" \/><\/a><\/figure>\n\n\n\n<p>The stream :$DATA is the default stream for the file contents. You'll find this on every file. Here's a file that includes a second data stream.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-style-default\"><a href=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-ads2.png\"><img loading=\"lazy\" decoding=\"async\" width=\"717\" height=\"428\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-ads2.png\" alt=\"an additional alternate data stream\" class=\"wp-image-8890\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-ads2.png 717w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-ads2-300x180.png 300w\" sizes=\"auto, (max-width: 717px) 100vw, 717px\" \/><\/a><\/figure>\n\n\n\n<p>I put together this simple PowerShell script to make it easier to identify extra alternate data streams.<\/p>\n\n\n\n<pre title=\"Get-AlternateDataStream.ps1\" class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">#requires -version 5.1\n\nParam([string]$Path = \"*.*\")\n\nGet-Item -Path $path -stream * | Where-Object {$_.stream -ne ':$DATA'} |\nSelect-Object @{Name=\"Path\";Expression = {$_.filename}},\nStream,@{Name=\"Size\";Expression={$_.length}}<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full is-style-default\"><a href=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-alternatedatastream.png\"><img loading=\"lazy\" decoding=\"async\" width=\"569\" height=\"161\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-alternatedatastream.png\" alt=\"get alternate data stream with PowerShell\" class=\"wp-image-8891\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-alternatedatastream.png 569w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-alternatedatastream-300x85.png 300w\" sizes=\"auto, (max-width: 569px) 100vw, 569px\" \/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"getting-stream-content\">Getting Stream Content<\/h2>\n\n\n\n<p>I can see that these files have alternate data streams. But what is in them? Let's look.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-style-default\"><a href=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-stream.png\"><img loading=\"lazy\" decoding=\"async\" width=\"455\" height=\"77\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-stream.png\" alt=\"get stream content\" class=\"wp-image-8892\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-stream.png 455w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-stream-300x51.png 300w\" sizes=\"auto, (max-width: 455px) 100vw, 455px\" \/><\/a><\/figure>\n\n\n\n<p>The Stream parameter does not accept wildcards, so you need to know the name in advance. No problem. I'll use my script.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">C:\\scripts\\Get-AlternateDataStream.ps1 .\\*.db | Select Path,Stream,@{Name=\"Content\";Expression={Get-Content $_.path -stream $_.stream}}<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full is-style-default\"><a href=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-ads-content.png\"><img loading=\"lazy\" decoding=\"async\" width=\"789\" height=\"166\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-ads-content.png\" alt=\"get alternate stream content\" class=\"wp-image-8893\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-ads-content.png 789w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-ads-content-300x63.png 300w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-ads-content-768x162.png 768w\" sizes=\"auto, (max-width: 789px) 100vw, 789px\" \/><\/a><\/figure>\n\n\n\n<p>You could easily turn my code into a PowerShell function.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"creating-alternate-data-streams\">Creating Alternate Data Streams<\/h2>\n\n\n\n<p>To create your own alternate data streams, you can use the other Content cmdlets.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">dir *.zip | Set-Content -Stream \"source\" -value \"over the rainbow\"<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full is-style-default\"><a href=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-ads-content2.png\"><img loading=\"lazy\" decoding=\"async\" width=\"695\" height=\"184\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-ads-content2.png\" alt=\"getting new alternate data stream content\n\" class=\"wp-image-8894\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-ads-content2.png 695w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-ads-content2-300x79.png 300w\" sizes=\"auto, (max-width: 695px) 100vw, 695px\" \/><\/a><\/figure>\n\n\n\n<p>What kind of information could we store that would be independent of the file contents? How about a version number?<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">set-content .\\Get-AboutOnline.ps1 -Stream version -Value \"0.9.0\"<\/code><\/pre>\n\n\n\n<p>Or maybe author information?<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">set-content .\\Get-AboutOnline.ps1 -Stream author -Value \"Jeff Hicks\"<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full is-style-default\"><a href=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/add-metadata.png\"><img loading=\"lazy\" decoding=\"async\" width=\"848\" height=\"161\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/add-metadata.png\" alt=\"new alternate stream metadata\" class=\"wp-image-8895\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/add-metadata.png 848w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/add-metadata-300x57.png 300w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/add-metadata-768x146.png 768w\" sizes=\"auto, (max-width: 848px) 100vw, 848px\" \/><\/a><\/figure>\n\n\n\n<p>Adding this information does not alter the file contents nor change the file size. But it will update the LastModfiedTime.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"clearing-streams\">Clearing Streams<\/h2>\n\n\n\n<p>You can easily clear the content of a stream.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">clear-content .\\vm.db -Stream secret<\/code><\/pre>\n\n\n\n<p>The stream itself remains with nothing in it. To delete the stream completely, use Remove-Item.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">remove-item .\\vm.db -Stream secret<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"scripting-with-alternate-data-streams\">Scripting with Alternate Data Streams<\/h2>\n\n\n\n<p>With this information, why not build some tools to use alternate data streams with our PowerShell scripting. I wrote a wrapper function around Set-Content to add a version alternate data stream.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">Function Set-VersionStream {\n    [cmdletbinding(SupportsShouldProcess)]\n    Param(\n        [Parameter(\n            Position = 0,\n            Mandatory,\n            ValueFromPipeline,\n            ValueFromPipelineByPropertyName,\n            HelpMessage = \"Specify a file path.\"\n        )]\n        [ValidateScript({ (Test-Path $_) -AND ((Get-Item $_).psprovider.name -eq 'FileSystem') })]\n        [Alias(\"fullname\")]\n        [string[]]$Path,\n        [parameter(Mandatory, HelpMessage = \"Specify a version string\")]\n        [alias(\"version\")]\n        [string]$Value,\n        [Parameter(HelpMessage = \"Specify the name of the version stream. The default is 'versionInfo'.\")]\n        [string]$Stream\n    )\n    Begin {\n        Write-Verbose \"[$((Get-Date).TimeofDay) BEGIN  ] Starting $($myinvocation.mycommand)\"\n        if (-Not ($psboundparameters.containskey[\"Stream\"])) {\n            $psboundparameters.Add(\"Stream\", \"versionInfo\")\n        }\n    } #begin\n\n    Process {\n        Write-Verbose \"[$((Get-Date).TimeofDay) PROCESS] Adding version $value to stream $($psboundparameters['Stream']) in $Path\"\n        Set-Content @psboundparameters\n    } #process\n\n    End {\n        Write-Verbose \"[$((Get-Date).TimeofDay) END    ] Ending $($myinvocation.mycommand)\"\n    } #end\n\n} #close Set-VersionStream<\/code><\/pre>\n\n\n\n<p>The default stream name is versionInfo, but you can specify something else or modify the code.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-style-default\"><a href=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/set-versioninfo.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1010\" height=\"169\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/set-versioninfo.png\" alt=\"set a version information alternate data stream\" class=\"wp-image-8896\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/set-versioninfo.png 1010w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/set-versioninfo-300x50.png 300w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/set-versioninfo-768x129.png 768w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/set-versioninfo-850x142.png 850w\" sizes=\"auto, (max-width: 1010px) 100vw, 1010px\" \/><\/a><\/figure>\n\n\n\n<p>I can do something similar with author information.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">Function Set-AuthorStream {\n    [cmdletbinding(SupportsShouldProcess)]\n    Param(\n        [Parameter(\n            Position = 0,\n            Mandatory,\n            ValueFromPipeline,\n            ValueFromPipelineByPropertyName,\n            HelpMessage = \"Specify a file path.\"\n        )]\n        [ValidateScript({ (Test-Path $_) -AND ((Get-Item $_).psprovider.name -eq 'FileSystem') })]\n        [Alias(\"fullname\")]\n        [string[]]$Path,\n        [parameter(Mandatory, HelpMessage = \"Specify an author string\")]\n        [alias(\"author\")]\n        [string]$Value,\n        [Parameter(HelpMessage = \"Specify the name of the author stream. The default is 'authorInfo'\")]\n        [string]$Stream\n    )\n    Begin {\n        Write-Verbose \"[$((Get-Date).TimeofDay) BEGIN  ] Starting $($myinvocation.mycommand)\"\n        if (-Not ($psboundparameters.containskey[\"Stream\"])) {\n            $psboundparameters.Add(\"Stream\", \"authorInfo\")\n        }\n    } #begin\n\n    Process {\n        Write-Verbose \"[$((Get-Date).TimeofDay) PROCESS] Adding author $value to stream $($psboundparameters['Stream']) in $Path\"\n        Set-Content @psboundparameters\n    } #process\n\n    End {\n        Write-Verbose \"[$((Get-Date).TimeofDay) END    ] Ending $($myinvocation.mycommand)\"\n    } #end\n\n} #close Set-AuthorStream<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full is-style-default\"><a href=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/authorinfo.png\"><img loading=\"lazy\" decoding=\"async\" width=\"996\" height=\"163\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/authorinfo.png\" alt=\"set author information via alternate data stream\n\" class=\"wp-image-8897\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/authorinfo.png 996w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/authorinfo-300x49.png 300w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/authorinfo-768x126.png 768w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/authorinfo-850x139.png 850w\" sizes=\"auto, (max-width: 996px) 100vw, 996px\" \/><\/a><\/figure>\n\n\n\n<p>The function accepts pipeline input, so I can quickly set this on multiple files.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">dir *.ps1 | Set-AuthorStream -Value \"Jeff Hicks\"<\/code><\/pre>\n\n\n\n<p>Since I have a command to set the value, it might be handy to have a command to get the value and do more than giving me a simple string.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">Function Get-AuthorStream {\n    [cmdletbinding()]\n    Param(\n        [Parameter(\n            Position = 0,\n            Mandatory,\n            ValueFromPipeline,\n            ValueFromPipelineByPropertyName,\n            HelpMessage = \"Specify a file path.\"\n        )]\n        [ValidateScript({ (Test-Path $_) -AND ((Get-Item $_).psprovider.name -eq 'FileSystem') })]\n        [Alias(\"fullname\")]\n        [string[]]$Path,\n\n        [Parameter(HelpMessage = \"Specify the name of the author stream. The default is 'authorInfo'\")]\n        [string]$Stream\n    )\n    Begin {\n        Write-Verbose \"[$((Get-Date).TimeofDay) BEGIN  ] Starting $($myinvocation.mycommand)\"\n        $splat = @{\n            ErrorAction = \"Stop\"\n        }\n\n        if ($psboundparameters.containskey[\"Stream\"]) {\n            $splat.Add(\"Stream\", $psboundparameters.containskey[\"Stream\"])\n        }\n        else {\n            $splat.Add(\"Stream\", \"authorInfo\")\n        }\n    } #begin\n\n    Process {\n        foreach ($item in $path) {\n            $splat['Path'] = Convert-Path $item\n            Write-Verbose \"[$((Get-Date).TimeofDay) PROCESS] Getting author stream $($psboundparameters['Stream']) from $item\"\n            Try {\n                $info = Get-Content @splat\n                [pscustomobject]@{\n                    Path       = $splat.path\n                    AuthorInfo = $info\n                }\n            }\n            Catch {\n                Write-Warning \"The stream $Stream was not found on $path\"\n            }\n        }\n    } #process\n\n    End {\n        Write-Verbose \"[$((Get-Date).TimeofDay) END    ] Ending $($myinvocation.mycommand)\"\n    } #end\n\n} #close get-AuthorStream<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full is-style-default\"><a href=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-authorstream-object.png\"><img loading=\"lazy\" decoding=\"async\" width=\"437\" height=\"209\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-authorstream-object.png\" alt=\"\" class=\"wp-image-8898\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-authorstream-object.png 437w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-authorstream-object-300x143.png 300w\" sizes=\"auto, (max-width: 437px) 100vw, 437px\" \/><\/a><\/figure>\n\n\n\n<p>However, since there isn't a limitation to what I can store in an alternate data stream, why not store something richer? Something that looks like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-style-default\"><a href=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/author-metadata.png\"><img loading=\"lazy\" decoding=\"async\" width=\"357\" height=\"145\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/author-metadata.png\" alt=\"\" class=\"wp-image-8900\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/author-metadata.png 357w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/author-metadata-300x122.png 300w\" sizes=\"auto, (max-width: 357px) 100vw, 357px\" \/><\/a><\/figure>\n\n\n\n<p>I wrote a proof-of-concept function to store this information as a JSON in an alternate data string.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">Function Set-AuthorStreamJson {\n    [cmdletbinding(SupportsShouldProcess)]\n    Param(\n        [Parameter(\n            Position = 0,\n            Mandatory,\n            ValueFromPipeline,\n            ValueFromPipelineByPropertyName,\n            HelpMessage = \"Specify a file path.\"\n        )]\n        [ValidateScript({ (Test-Path $_) -AND ((Get-Item $_).psprovider.name -eq 'FileSystem') })]\n        [Alias(\"fullname\")]\n        [string[]]$Path,\n        [parameter(Mandatory, HelpMessage = \"Specify an author name\")]\n        [string]$Author,\n        [parameter(Mandatory)]\n        [string]$Company,\n        [parameter(Mandatory)]\n        [string]$Version,\n        [string]$Comment,\n        [Parameter(HelpMessage = \"Enter the creation tile. It will saved as a UTC formatted string\")]\n        [datetime]$Created = (Get-Date),\n        [Parameter(HelpMessage = \"Specify the name of the author stream. The default is 'authorInfo'\")]\n        [string]$Stream\n    )\n    Begin {\n        Write-Verbose \"[$((Get-Date).TimeofDay) BEGIN  ] Starting $($myinvocation.mycommand)\"\n        $splat = @{\n            ErrorAction = \"Stop\"\n        }\n\n        if ($psboundparameters.containskey[\"Stream\"]) {\n            $splat.Add(\"Stream\", $psboundparameters[\"Stream\"])\n        }\n        else {\n            $splat.Add(\"Stream\", \"authorInfo\")\n        }\n\n        $json = [PSCustomObject]@{\n            Author  = $author\n            Company = $company\n            Version = $version\n            Created = \"{0:u}\" -f $created\n            Comment = $comment\n        } | ConvertTo-Json\n\n    } #begin\n\n    Process {\n        foreach ($item in $Path) {\n            $splat[\"Path\"] = Convert-Path $item\n            $splat[\"Value\"] = $json\n            Write-Verbose \"[$((Get-Date).TimeofDay) PROCESS] Adding author information for $author to stream $($splat['Stream']) in $item\"\n            write-verbose $json\n            Set-Content @splat\n        }\n\n    } #process\n\n    End {\n        Write-Verbose \"[$((Get-Date).TimeofDay) END    ] Ending $($myinvocation.mycommand)\"\n    } #end\n\n} #close Set-AuthorStreamJson<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-large is-style-default\"><a href=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/json-author-metadata.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"517\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/json-author-metadata-1024x517.png\" alt=\"\" class=\"wp-image-8901\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/json-author-metadata-1024x517.png 1024w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/json-author-metadata-300x152.png 300w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/json-author-metadata-768x388.png 768w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/json-author-metadata-850x429.png 850w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/json-author-metadata.png 1089w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p>The stream is a JSON string that I can convert into an object. Or I can use a PowerShell function.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">\nFunction Get-AuthorStreamJson {\n    [cmdletbinding()]\n    Param(\n        [Parameter(\n            Position = 0,\n            Mandatory,\n            ValueFromPipeline,\n            ValueFromPipelineByPropertyName,\n            HelpMessage = \"Specify a file path.\"\n        )]\n        [ValidateScript({ (Test-Path $_) -AND ((Get-Item $_).psprovider.name -eq 'FileSystem') })]\n        [Alias(\"fullname\")]\n        [string[]]$Path,\n\n        [Parameter(HelpMessage = \"Specify the name of the author stream. The default is 'authorInfo'\")]\n        [string]$Stream\n    )\n    Begin {\n        Write-Verbose \"[$((Get-Date).TimeofDay) BEGIN  ] Starting $($myinvocation.mycommand)\"\n        $splat = @{\n            ErrorAction = \"Stop\"\n        }\n\n        if ($psboundparameters.containskey[\"Stream\"]) {\n            $splat.Add(\"Stream\", $psboundparameters[\"Stream\"])\n        }\n        else {\n            $splat.Add(\"Stream\", \"authorInfo\")\n        }\n    } #begin\n\n    Process {\n        foreach ($item in $path) {\n            $splat['Path'] = Convert-Path $item\n            Write-Verbose \"[$((Get-Date).TimeofDay) PROCESS] Getting author stream $($splat.stream) from $item\"\n            Try {\n                $info = Get-Content @splat\n                $out = $info | ConvertFrom-Json\n                $out | Add-Member -MemberType NoteProperty -Name Path -Value $splat.path -PassThru\n            }\n            Catch {\n                Write-Warning \"The stream $($splat.stream) was not found on $path\"\n            }\n        }\n    } #process\n\n    End {\n        Write-Verbose \"[$((Get-Date).TimeofDay) END    ] Ending $($myinvocation.mycommand)\"\n    } #end\n\n} #close get-AuthorStreamJson<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full is-style-default\"><a href=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-authorstream-json.png\"><img loading=\"lazy\" decoding=\"async\" width=\"793\" height=\"318\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-authorstream-json.png\" alt=\"\" class=\"wp-image-8902\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-authorstream-json.png 793w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-authorstream-json-300x120.png 300w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-authorstream-json-768x308.png 768w\" sizes=\"auto, (max-width: 793px) 100vw, 793px\" \/><\/a><\/figure>\n\n\n\n<p>The function will display a warning for files that lack the stream, so I'm suppressing the warning messages in this example.<\/p>\n\n\n\n<p>Finally, how about adding tags?<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">Function Add-TagStream {\n    [cmdletbinding(SupportsShouldProcess)]\n    Param(\n        [Parameter(\n            Position = 0,\n            Mandatory,\n            ValueFromPipeline,\n            ValueFromPipelineByPropertyName,\n            HelpMessage = \"Specify a file path.\"\n        )]\n        [ValidateScript({ (Test-Path $_) -AND ((Get-Item $_).psprovider.name -eq 'FileSystem') })]\n        [Alias(\"fullname\")]\n        [string[]]$Path,\n        [parameter(Mandatory, HelpMessage = \"Specify a set of tags\")]\n        [alias(\"tag\")]\n        [string[]]$Value,\n        [Parameter(HelpMessage = \"Specify the name of the Tags stream. The default is 'tags'.\")]\n        [string]$Stream\n    )\n    Begin {\n        Write-Verbose \"[$((Get-Date).TimeofDay) BEGIN  ] Starting $($myinvocation.mycommand)\"\n        if (-Not ($psboundparameters.containskey[\"Stream\"])) {\n            $psboundparameters.Add(\"Stream\", \"tags\")\n        }\n    } #begin\n\n    Process {\n        Write-Verbose \"[$((Get-Date).TimeofDay) PROCESS] Adding Tags $($value -join ',') to stream $($psboundparameters['Stream']) in $Path\"\n        Set-Content @psboundparameters\n    } #process\n\n    End {\n        Write-Verbose \"[$((Get-Date).TimeofDay) END    ] Ending $($myinvocation.mycommand)\"\n    } #end\n\n} #close Add-Tagstream<\/code><\/pre>\n\n\n\n<p>This command makes it a snap to add tags.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">dir *.csv,*.db | Add-TagStream -tag \"data\",\"company\"<\/code><\/pre>\n\n\n\n<p>Of course, I want an easy way to get the tag stream.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">Function Get-TagStream {\n    [cmdletbinding()]\n    Param(\n        [Parameter(\n            Position = 0,\n            Mandatory,\n            ValueFromPipeline,\n            ValueFromPipelineByPropertyName,\n            HelpMessage = \"Specify a file path.\"\n        )]\n        [ValidateScript({ (Test-Path $_) -AND ((Get-Item $_).psprovider.name -eq 'FileSystem') })]\n        [Alias(\"fullname\")]\n        [string[]]$Path,\n\n        [Parameter(HelpMessage = \"Specify the name of the tag stream. The default is 'tags'\")]\n        [string]$Stream\n    )\n    Begin {\n        Write-Verbose \"[$((Get-Date).TimeofDay) BEGIN  ] Starting $($myinvocation.mycommand)\"\n        $splat = @{\n            ErrorAction = \"Stop\"\n        }\n\n        if ($psboundparameters.containskey[\"Stream\"]) {\n\n            $splat.Add(\"Stream\", $psboundparameters[\"Stream\"])\n        }\n        else {\n            $splat.Add(\"Stream\", \"tags\")\n        }\n    } #begin\n\n    Process {\n        foreach ($item in $path) {\n            $splat['Path'] = Convert-Path $item\n            Write-Verbose \"[$((Get-Date).TimeofDay) PROCESS] Getting tags stream $($splat['Stream']) from $item\"\n            Try {\n                $info = Get-Content @splat\n                [pscustomobject]@{\n                    Path       = $splat.path\n                    Tags = $info\n                }\n            }\n            Catch {\n                Write-Warning \"The stream $($splat['Stream']) was not found on $path\"\n            }\n        }\n    } #process\n\n    End {\n        Write-Verbose \"[$((Get-Date).TimeofDay) END    ] Ending $($myinvocation.mycommand)\"\n    } #end\n\n} #close Get-TagStream\n<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full is-style-default\"><a href=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-tagstream.png\"><img loading=\"lazy\" decoding=\"async\" width=\"505\" height=\"347\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-tagstream.png\" alt=\"\" class=\"wp-image-8903\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-tagstream.png 505w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-tagstream-300x206.png 300w\" sizes=\"auto, (max-width: 505px) 100vw, 505px\" \/><\/a><\/figure>\n\n\n\n<p>With this command, it is easy to find files based on a tag.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-style-default\"><a href=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/find-bytag.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"273\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/find-bytag-1024x273.png\" alt=\"\" class=\"wp-image-8904\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/find-bytag-1024x273.png 1024w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/find-bytag-300x80.png 300w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/find-bytag-768x205.png 768w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/find-bytag-850x227.png 850w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/find-bytag.png 1035w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p>There's no end to how you could use alternate data streams, and you are welcome to use my code as a framework. My functions should be considered proof-of-concept and not production-ready.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"limitations\">Limitations<\/h2>\n\n\n\n<p>But before you get too excited, there are limitations to alternate data streams. This feature is only supported on Windows and NTFS formatted drives. If you copy a file with alternate data streams from one NTFS drive to another, the streams should also copy. But if you copy the file to a non-NTFS drive, you will lose the streams. <\/p>\n\n\n\n<p>If you back up or archive files, you also might lose the alternate data streams. However, it is worth looking into settings. I use WinRar, and the application has a setting to save file streams.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-style-default\"><a href=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/winrar-streams.png\"><img loading=\"lazy\" decoding=\"async\" width=\"454\" height=\"426\" src=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/winrar-streams.png\" alt=\"saving streams with winrar\" class=\"wp-image-8905\" srcset=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/winrar-streams.png 454w, https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/winrar-streams-300x281.png 300w\" sizes=\"auto, (max-width: 454px) 100vw, 454px\" \/><\/a><\/figure>\n\n\n\n<p>Finally, if you synchronize files with a cloud service like OneDrive or DropBox, expect to lose the alternate data streams. I have not been able to find any way to configure these services to include alternate data streams. If you know of a way, I'd love to hear it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"summary\">Summary<\/h2>\n\n\n\n<p>I hope you found this intriguing and got your gears churning to figure out how you can take advantage of this feature. As I hope you've seen, building PowerShell tooling around alternate data streams is not that difficult. If you find a use for ADS, I hope you'll let me know.<\/p>\n\n\n\n<p>By the way, in addition to PowerShell, you can use the streams.exe utility from SysInternals to list and delete alternate data streams.<\/p>\n\n\n\n<p>Have fun with this, and please test with non-production files. Enjoy.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I have always had a peculiar fascination with alternate data streams. This is an NTFS feature that has been around for a long time. The feature, also referred to as ADS, allows a user to write data to a hidden fork of a file. You can store practically anything in an alternate data stream without&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"New on the blog: Friday Fun with #PowerShell and Alternate Data Streams","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[8],"tags":[665,22,224,534,540],"class_list":["post-8888","post","type-post","status-publish","format-standard","hentry","category-scripting","tag-ads","tag-automation","tag-function","tag-powershell","tag-scripting"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.6 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Friday Fun with PowerShell and Alternate Data Streams &#8226; The Lonely Administrator<\/title>\n<meta name=\"description\" content=\"Here&#039;s how I use NTFS alternate data streams to do more with PowerShell. Lots of proof-of-concept code samples.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/jdhitsolutions.com\/blog\/scripting\/8888\/friday-fun-with-powershell-and-alternate-data-streams\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Friday Fun with PowerShell and Alternate Data Streams &#8226; The Lonely Administrator\" \/>\n<meta property=\"og:description\" content=\"Here&#039;s how I use NTFS alternate data streams to do more with PowerShell. Lots of proof-of-concept code samples.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/jdhitsolutions.com\/blog\/scripting\/8888\/friday-fun-with-powershell-and-alternate-data-streams\/\" \/>\n<meta property=\"og:site_name\" content=\"The Lonely Administrator\" \/>\n<meta property=\"article:published_time\" content=\"2022-02-18T17:40:11+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2022-02-18T17:40:16+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-ads.png\" \/>\n<meta name=\"author\" content=\"Jeffery Hicks\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@JeffHicks\" \/>\n<meta name=\"twitter:site\" content=\"@JeffHicks\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Jeffery Hicks\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"11 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/scripting\\\/8888\\\/friday-fun-with-powershell-and-alternate-data-streams\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/scripting\\\/8888\\\/friday-fun-with-powershell-and-alternate-data-streams\\\/\"},\"author\":{\"name\":\"Jeffery Hicks\",\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/#\\\/schema\\\/person\\\/d0258030b41f07fd745f4078bdf5b6c9\"},\"headline\":\"Friday Fun with PowerShell and Alternate Data Streams\",\"datePublished\":\"2022-02-18T17:40:11+00:00\",\"dateModified\":\"2022-02-18T17:40:16+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/scripting\\\/8888\\\/friday-fun-with-powershell-and-alternate-data-streams\\\/\"},\"wordCount\":816,\"commentCount\":6,\"publisher\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/#\\\/schema\\\/person\\\/d0258030b41f07fd745f4078bdf5b6c9\"},\"image\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/scripting\\\/8888\\\/friday-fun-with-powershell-and-alternate-data-streams\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/wp-content\\\/uploads\\\/2022\\\/02\\\/get-ads.png\",\"keywords\":[\"ADS\",\"Automation\",\"Function\",\"PowerShell\",\"Scripting\"],\"articleSection\":[\"Scripting\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/scripting\\\/8888\\\/friday-fun-with-powershell-and-alternate-data-streams\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/scripting\\\/8888\\\/friday-fun-with-powershell-and-alternate-data-streams\\\/\",\"url\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/scripting\\\/8888\\\/friday-fun-with-powershell-and-alternate-data-streams\\\/\",\"name\":\"Friday Fun with PowerShell and Alternate Data Streams &#8226; The Lonely Administrator\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/scripting\\\/8888\\\/friday-fun-with-powershell-and-alternate-data-streams\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/scripting\\\/8888\\\/friday-fun-with-powershell-and-alternate-data-streams\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/wp-content\\\/uploads\\\/2022\\\/02\\\/get-ads.png\",\"datePublished\":\"2022-02-18T17:40:11+00:00\",\"dateModified\":\"2022-02-18T17:40:16+00:00\",\"description\":\"Here's how I use NTFS alternate data streams to do more with PowerShell. Lots of proof-of-concept code samples.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/scripting\\\/8888\\\/friday-fun-with-powershell-and-alternate-data-streams\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/scripting\\\/8888\\\/friday-fun-with-powershell-and-alternate-data-streams\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/scripting\\\/8888\\\/friday-fun-with-powershell-and-alternate-data-streams\\\/#primaryimage\",\"url\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/wp-content\\\/uploads\\\/2022\\\/02\\\/get-ads.png\",\"contentUrl\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/wp-content\\\/uploads\\\/2022\\\/02\\\/get-ads.png\",\"width\":769,\"height\":233},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/scripting\\\/8888\\\/friday-fun-with-powershell-and-alternate-data-streams\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Scripting\",\"item\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/category\\\/scripting\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Friday Fun with PowerShell and Alternate Data Streams\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/\",\"name\":\"The Lonely Administrator\",\"description\":\"Practical Advice for the Automating IT Pro\",\"publisher\":{\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/#\\\/schema\\\/person\\\/d0258030b41f07fd745f4078bdf5b6c9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/jdhitsolutions.com\\\/blog\\\/#\\\/schema\\\/person\\\/d0258030b41f07fd745f4078bdf5b6c9\",\"name\":\"Jeffery Hicks\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg\",\"caption\":\"Jeffery Hicks\"},\"logo\":{\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Friday Fun with PowerShell and Alternate Data Streams &#8226; The Lonely Administrator","description":"Here's how I use NTFS alternate data streams to do more with PowerShell. Lots of proof-of-concept code samples.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/jdhitsolutions.com\/blog\/scripting\/8888\/friday-fun-with-powershell-and-alternate-data-streams\/","og_locale":"en_US","og_type":"article","og_title":"Friday Fun with PowerShell and Alternate Data Streams &#8226; The Lonely Administrator","og_description":"Here's how I use NTFS alternate data streams to do more with PowerShell. Lots of proof-of-concept code samples.","og_url":"https:\/\/jdhitsolutions.com\/blog\/scripting\/8888\/friday-fun-with-powershell-and-alternate-data-streams\/","og_site_name":"The Lonely Administrator","article_published_time":"2022-02-18T17:40:11+00:00","article_modified_time":"2022-02-18T17:40:16+00:00","og_image":[{"url":"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-ads.png","type":"","width":"","height":""}],"author":"Jeffery Hicks","twitter_card":"summary_large_image","twitter_creator":"@JeffHicks","twitter_site":"@JeffHicks","twitter_misc":{"Written by":"Jeffery Hicks","Est. reading time":"11 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/jdhitsolutions.com\/blog\/scripting\/8888\/friday-fun-with-powershell-and-alternate-data-streams\/#article","isPartOf":{"@id":"https:\/\/jdhitsolutions.com\/blog\/scripting\/8888\/friday-fun-with-powershell-and-alternate-data-streams\/"},"author":{"name":"Jeffery Hicks","@id":"https:\/\/jdhitsolutions.com\/blog\/#\/schema\/person\/d0258030b41f07fd745f4078bdf5b6c9"},"headline":"Friday Fun with PowerShell and Alternate Data Streams","datePublished":"2022-02-18T17:40:11+00:00","dateModified":"2022-02-18T17:40:16+00:00","mainEntityOfPage":{"@id":"https:\/\/jdhitsolutions.com\/blog\/scripting\/8888\/friday-fun-with-powershell-and-alternate-data-streams\/"},"wordCount":816,"commentCount":6,"publisher":{"@id":"https:\/\/jdhitsolutions.com\/blog\/#\/schema\/person\/d0258030b41f07fd745f4078bdf5b6c9"},"image":{"@id":"https:\/\/jdhitsolutions.com\/blog\/scripting\/8888\/friday-fun-with-powershell-and-alternate-data-streams\/#primaryimage"},"thumbnailUrl":"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-ads.png","keywords":["ADS","Automation","Function","PowerShell","Scripting"],"articleSection":["Scripting"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/jdhitsolutions.com\/blog\/scripting\/8888\/friday-fun-with-powershell-and-alternate-data-streams\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/jdhitsolutions.com\/blog\/scripting\/8888\/friday-fun-with-powershell-and-alternate-data-streams\/","url":"https:\/\/jdhitsolutions.com\/blog\/scripting\/8888\/friday-fun-with-powershell-and-alternate-data-streams\/","name":"Friday Fun with PowerShell and Alternate Data Streams &#8226; The Lonely Administrator","isPartOf":{"@id":"https:\/\/jdhitsolutions.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/jdhitsolutions.com\/blog\/scripting\/8888\/friday-fun-with-powershell-and-alternate-data-streams\/#primaryimage"},"image":{"@id":"https:\/\/jdhitsolutions.com\/blog\/scripting\/8888\/friday-fun-with-powershell-and-alternate-data-streams\/#primaryimage"},"thumbnailUrl":"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-ads.png","datePublished":"2022-02-18T17:40:11+00:00","dateModified":"2022-02-18T17:40:16+00:00","description":"Here's how I use NTFS alternate data streams to do more with PowerShell. Lots of proof-of-concept code samples.","breadcrumb":{"@id":"https:\/\/jdhitsolutions.com\/blog\/scripting\/8888\/friday-fun-with-powershell-and-alternate-data-streams\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/jdhitsolutions.com\/blog\/scripting\/8888\/friday-fun-with-powershell-and-alternate-data-streams\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/jdhitsolutions.com\/blog\/scripting\/8888\/friday-fun-with-powershell-and-alternate-data-streams\/#primaryimage","url":"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-ads.png","contentUrl":"https:\/\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2022\/02\/get-ads.png","width":769,"height":233},{"@type":"BreadcrumbList","@id":"https:\/\/jdhitsolutions.com\/blog\/scripting\/8888\/friday-fun-with-powershell-and-alternate-data-streams\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Scripting","item":"https:\/\/jdhitsolutions.com\/blog\/category\/scripting\/"},{"@type":"ListItem","position":2,"name":"Friday Fun with PowerShell and Alternate Data Streams"}]},{"@type":"WebSite","@id":"https:\/\/jdhitsolutions.com\/blog\/#website","url":"https:\/\/jdhitsolutions.com\/blog\/","name":"The Lonely Administrator","description":"Practical Advice for the Automating IT Pro","publisher":{"@id":"https:\/\/jdhitsolutions.com\/blog\/#\/schema\/person\/d0258030b41f07fd745f4078bdf5b6c9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/jdhitsolutions.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/jdhitsolutions.com\/blog\/#\/schema\/person\/d0258030b41f07fd745f4078bdf5b6c9","name":"Jeffery Hicks","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg","url":"https:\/\/secure.gravatar.com\/avatar\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg","caption":"Jeffery Hicks"},"logo":{"@id":"https:\/\/secure.gravatar.com\/avatar\/832ae5d438fdcfc1420d720cd1991307927de8a0b12f2342e81c30f773e21098?s=96&d=wavatar&r=pg"}}]}},"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"jetpack-related-posts":[{"id":2206,"url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/2206\/powershell-scripting-with-validateset\/","url_meta":{"origin":8888,"position":0},"title":"PowerShell Scripting with [ValidateSet]","author":"Jeffery Hicks","date":"April 16, 2012","format":false,"excerpt":"Today we'll continue our exploration of the parameter validation attributes you can use in you PowerShell scripting. We've already looked at [ValidateRange] and [ValidateScript]. Another attribute you are likely to use is [ValidateSet()]. You can use this to verify that the parameter value belongs to a pre-defined set. To use,\u2026","rel":"","context":"In &quot;PowerShell&quot;","block_context":{"text":"PowerShell","link":"https:\/\/jdhitsolutions.com\/blog\/category\/powershell\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1496,"url":"https:\/\/jdhitsolutions.com\/blog\/scripting\/1496\/get-powershell-view-definitions\/","url_meta":{"origin":8888,"position":1},"title":"Get PowerShell View Definitions","author":"Jeffery Hicks","date":"June 7, 2011","format":false,"excerpt":"When you write objects to the pipeline in Windows PowerShell, at the end of the pipeline PowerShell's formatting system handles displaying the results to the console. It accomplishes this by using a set of rules stored in XML configuration files. This is why when you run Get-Process you get a\u2026","rel":"","context":"In &quot;PowerShell v2.0&quot;","block_context":{"text":"PowerShell v2.0","link":"https:\/\/jdhitsolutions.com\/blog\/category\/powershell-v2-0\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2011\/06\/format-alternate-view-300x83.png?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":422,"url":"https:\/\/jdhitsolutions.com\/blog\/scripting\/422\/out-notepad-redux\/","url_meta":{"origin":8888,"position":2},"title":"Out-Notepad Redux","author":"Jeffery Hicks","date":"October 7, 2009","format":false,"excerpt":"I got some great comments and suggestion on my original version of Out-Notepad, which should work just find on PowerShell v1.0 or 2.0. However, because v2.0 has such terrific features I decided to rework my function into a PowerShell v2.0 only version that also incorporates a few new features. The\u2026","rel":"","context":"In &quot;PowerShell v2.0&quot;","block_context":{"text":"PowerShell v2.0","link":"https:\/\/jdhitsolutions.com\/blog\/category\/powershell-v2-0\/"},"img":{"alt_text":"captured_Image.png","src":"https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2009\/10\/captured_Image.png_thumb.png?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":1977,"url":"https:\/\/jdhitsolutions.com\/blog\/powershell\/1977\/the-powershell-morning-report\/","url_meta":{"origin":8888,"position":3},"title":"The PowerShell Morning Report","author":"Jeffery Hicks","date":"January 10, 2012","format":false,"excerpt":"I love how easy it is to manage computers with Windows PowerShell. It is a great reporting tool, but often I find people getting locked into one approach. I'm a big believer in flexibility and re-use and using objects in the pipeline wherever I can. So I put together a\u2026","rel":"","context":"In &quot;PowerShell&quot;","block_context":{"text":"PowerShell","link":"https:\/\/jdhitsolutions.com\/blog\/category\/powershell\/"},"img":{"alt_text":"Zazu","src":"https:\/\/i0.wp.com\/www.lionking.org\/imgarchive\/Clip_Art\/zazu03.gif?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":2704,"url":"https:\/\/jdhitsolutions.com\/blog\/scripting\/2704\/powershell-graphing-with-out-gridview\/","url_meta":{"origin":8888,"position":4},"title":"PowerShell Graphing with Out-Gridview","author":"Jeffery Hicks","date":"January 14, 2013","format":false,"excerpt":"I've received a lot of interest for my last few posts on graphing with the PowerShell console. But I decided I could add one more feature. Technically it might have made more sense to turn this into a separate function, but I decided to simply modify the last version of\u2026","rel":"","context":"In &quot;Powershell 3.0&quot;","block_context":{"text":"Powershell 3.0","link":"https:\/\/jdhitsolutions.com\/blog\/category\/powershell-3-0\/"},"img":{"alt_text":"out-consolegraph-gv","src":"https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2013\/01\/out-consolegraph-gv-1024x548.png?resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2013\/01\/out-consolegraph-gv-1024x548.png?resize=350%2C200 1x, https:\/\/i0.wp.com\/jdhitsolutions.com\/blog\/wp-content\/uploads\/2013\/01\/out-consolegraph-gv-1024x548.png?resize=525%2C300 1.5x"},"classes":[]},{"id":1384,"url":"https:\/\/jdhitsolutions.com\/blog\/scripting\/1384\/create-a-master-powershell-online-help-page\/","url_meta":{"origin":8888,"position":5},"title":"Create a Master PowerShell Online Help Page","author":"Jeffery Hicks","date":"April 28, 2011","format":false,"excerpt":"As I hope you know, PowerShell cmdlets can include links to online help. This is very handy because it is much easier to keep online help up to date. To see online help for a cmdlet use the -online parameter. get-help get-wmiobject -online I decided to take things to another\u2026","rel":"","context":"In &quot;Miscellaneous&quot;","block_context":{"text":"Miscellaneous","link":"https:\/\/jdhitsolutions.com\/blog\/category\/miscellaneous\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/posts\/8888","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/comments?post=8888"}],"version-history":[{"count":0,"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/posts\/8888\/revisions"}],"wp:attachment":[{"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/media?parent=8888"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/categories?post=8888"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/jdhitsolutions.com\/blog\/wp-json\/wp\/v2\/tags?post=8888"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}