Win-API documentation wrote:When specified from the Windows shell command line, the full name of a stream is "filename:stream name:stream type", as in the following example: "myfile.dat:stream1:$DATA".
Where the ":$DATA" part may be omitted. A file may have several streams. Working with ADS's in the command line is very simple, as shown below:
- To create an additional stream in a file, just use the apropriate stream name in an output redirection:
Code: Select all
echo This is Textfile.txt > Textfile.txt
echo This is Textfile.txt:Stream > Textfile.txt:Stream - Conversely, you must use an input redirection to access the alternate stream:
Code: Select all
type Textfile.txt
more < Textfile.txt:Stream - The existing ADS's of a file may be displayed via /R switch of DIR command (from Windows Vista and above).
- There is no native way to delete streams from a file. You may download Sysinternals Streams utility to do so (although not on individual basis), and to list ADS's in Windows before Vista.
When I knew about Alternate Data Streams some time ago I completed some tests, and after that I considered this feature just a curiosity until I discovered the keypoint that becomes ADS's much more useable:


Previous point makes the stream recognizable by several programs and commands, including the Windows Notepad editor and WSH's support cscript.exe command!

Code: Select all
notepad fileName:streamName.ext
Because Alternate Data Streams is a not widely known feature they may also be used to protect/hide file names, at least from the less experienced users. For example:
Code: Select all
echo Something > .\C:filename.txt
Previous line send data to alternate stream "filename.txt" of file "C" (with no extension) in current directory! If you want to make stream names inaccessible, you may also make good use of the following feature:
MS Documentation wrote:You may use... these characters for file names, except for the following:
Characters whose integer representations are in the range from 1 through 31, except for alternate data streams where these characters are allowed.
That is, control characters are allowed in stream names! The Batch file below is an example of this point that adds two alternate streams to itself: the first one is named: "HelloX"+BS+"World1.txt" and the second one is: "HelloXY"+BS+BS+"World2.txt". I think these names are difficult enough to be broken even for experienced users!
Code: Select all
@echo off
setlocal EnableDelayedExpansion
rem ADSnames.bat: Create ADS's with names hard to break
for /F %%a in ('echo prompt $H ^| cmd') do set "BS=%%a"
echo Special ADS created on %date% @ %time% > "%0:HelloX!BS!World1.txt"
echo The second ADS with special name > "%0:HelloXY!BS!!BS!World2.txt"
cls
echo dir /R "%0"
dir /R "%0"
echo/
echo more ^< "%0:HelloX!BS!World1.txt"
more < "%0:HelloX!BS!World1.txt"
echo/
echo more ^< "%0:HelloXY!BS!!BS!World2.txt"
more < "%0:HelloXY!BS!!BS!World2.txt"
echo/
echo Could you repeat previous commands?
An application very well suited to Alternate Data Streams is a Batch-file based hybrid script that include source code for other languages. If the additional language have provisions to achieve this mix (like JScript), the script is very simple; otherwise the hybridization requires convoluted tricks as described in Hybrids and chimeras in cmd/bat thread.
This way, an hybrid script may be created with a base Batch file and several streams comprised of JScript, VBS, PowerShell, or whichever parts you want that are "pure files" each, so they don't require any trick in order to be executed individually. The small example below show how simple is to use this capability:
Code: Select all
C:\ dir /R Base.bat
El volumen de la unidad C no tiene etiqueta.
El número de serie del volumen es: 0895-160E
Directorio de C:\Users\Antonio\Documents\ASMB\Modern Batch File Programming\Alternate Data Streams
15/01/2015 11:06 p. m. 194 Base.bat
47 Base.bat:JScript.js:$DATA
46 Base.bat:PowerShell.ps1:$DATA
41 Base.bat:VBS.vbs:$DATA
1 archivos 194 bytes
0 dirs 424,516,263,936 bytes libres
C:\ type Base.bat
@echo off
echo Produced from Batch
CScript //nologo "%~F0:JScript.js"
CScript //nologo "%~F0:VBS.vbs"
PowerShell -NoProfile -Command "Get-Content '%~F0:PowerShell.ps1' | Invoke-Expression"
C:\ more < Base.bat:JScript.js
WScript.Echo("Produced from JScript in ADS");
C:\ more < Base.bat:VBS.vbs
WScript.Echo "Produced from VBS in ADS"
C:\ more < Base.bat:PowerShell.ps1
Write-Host "Produced from PowerShell in ADS"
C:\ Base.bat
Produced from Batch
Produced from JScript in ADS
Produced from VBS in ADS
Produced from PowerShell in ADS
Previous example deserve some explanations:
- Both the JScript and VBS engines used by cscript.exe correctly open the given filename, even if it includes a stream name, and takes the last part as the extension that indicate the type of file. However:
- The base cscript.exe command does NOT do the same thing when a stream name ends in .WSF like in "cscript //nologo Base.bat:WSF.wsf"; in this case, "File not found" error is issued. If ":$DATA" part is added, like in "cscript //nologo Base.bat:WSF.wsf:$DATA", then the error change to "There is not script engine for extension .wsf:$DATA". I did several tests and can not found a method to execute a .wsf stream.
- PowerShell documentation specify that version 3.0 "manage streams", so I think at first that "PowerShell -NoProfile -ExecutionPolicy Bypass -File Base.bat:PowerShell.ps1" would run the .ps1 stream, but "Invalid format in path" error was issued. I read the documentation and discovered that streams are managed in just 6 cmdlets being Get-Content one of them (via -Stream parameter). This point requires to use "-Command Invoke-Expression" instead of "-File", so the right way is this: "PowerShell -NoProfile -Command "Get-Content -Path Base.bat -Stream PowerShell.ps1 | Invoke-Expression". However, I discovered that you may include the stream in the filename in the standard way as long as the filename include the drive, like in "PowerShell -NoProfile -Command "Get-Content C:Base.bat:PowerShell.ps1 | Invoke-Expression".
- I did not tested streams with other languages/applications, like the MSHTA or Perl, but I would like to know in which ones this feature works!
Unfortunately, ADS is a feature of Windows NTFS disks, so the Alternate Data Streams are ignored when the file is copied or moved to another file system without ADS support, attached to an e-mail, or uploaded to a website. However, it is very easy to write a Batch file that create the base file and all its alternate streams, similar to an "installer". The example below create the Base.bat file of previous example and all its ADS's using a trick similar to Unix's heredoc (http://stackoverflow.com/questions/1015163/heredoc-for-windows-batch/23051423#23051423), so it may be taken as base to create your own files.
Code: Select all
@echo off
rem MakeBase.bat: Example on using Alternate Data Streams to create an hybrid script
rem Antonio Perez Ayala
rem This program create Base.bat file with JScript.js, VBS.vbs and PowerShell.ps1 ADS's
rem Definition of heredoc macro
setlocal DisableDelayedExpansion
set LF=^
::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
set heredoc=for %%n in (1 2) do if %%n==2 (%\n%
call :heredoc !argv!%\n%
goto !argv!%\n%
) else set argv=
setlocal EnableDelayedExpansion
set "filename="
rem Definition of Base.bat file and its alternate data streams
%heredoc% :Base.bat
@echo off
echo Produced from Batch
CScript //nologo "%~F0:JScript.js"
CScript //nologo "%~F0:VBS.vbs"
PowerShell -NoProfile -Command "Get-Content '%~F0:PowerShell.ps1' | Invoke-Expression"
:Base.bat
%heredoc% :JScript.js
WScript.Echo("Produced from JScript in ADS");
:JScript.js
%heredoc% :VBS.vbs
WScript.Echo "Produced from VBS in ADS"
:VBS.vbs
%heredoc% :PowerShell.ps1
Write-Host "Produced from PowerShell in ADS"
:PowerShell.ps1
echo Base.bat file created:
dir /R Base.bat
goto :EOF
rem Definition of heredoc subroutine
:heredoc ID
if not defined filename (
set "filename=%1"
set "filename=!filename:~1!"
set "output=!filename!"
) else (
set "output=%filename%%1"
)
set "skip="
for /F "delims=:" %%a in ('findstr /N /C:" %1" "%~F0"') do (
if not defined skip (set skip=%%a) else set /A lines=%%a-skip-1
)
(for /F "skip=%skip% delims=" %%a in ('findstr /N "^" "%~F0"') do (
set "line=%%a"
echo(!line:*:=!
set /A lines-=1
if !lines! == 0 exit /B
)) > "%output%"
exit /B
I found this feature extremely useful to create JScript/VBS hybrid scripts and other applications where a base file have several companion files. You are invited to test this feature and report any additional use you may devise for it.

Antonio