I've never been a fan of readme files. They are fine for project files where you will likely need note of building an application, or for distribution packages where you might need extra information on installing a package. But sometimes you want to add a comment to a file (perhaps you want to make a note of where the file came from). Some picture formats (jpg, for example) support an internal comment. And sure, Windows supports comments (sorta). But you have to
Right mouse click -> properties -> details -> comment
and even then you only get to see the first few characters. Also, there is no similar support for folder comments.
That's why I decided to write CommentShell. With a couple of minor registry changes (additions) you can now add a comment of any size to a file or folder. This is done using a feature of NTFS called Alternate Data Streams. You may not be familiar with ADS but you have probably seen their influence. When you download a program and try to run it you will have seen the warning that pops up. The warning is displayed because of the presence of an ADS named Zone.Identifier. The contents of this ADS on a downloaded jpg on my computer is
D:\Downloads>cat 1085.jpg:Zone.Identifier
[ZoneTransfer]
ZoneId=3
HostUrl=http://media.twnmm.com/storage/29705514/1085
Note that the ADS is fully named by prefixing it with a colon and adding it to the file name. You can create an ADS with just about any name you want and it can contain any type of data. For my application I use an ADS named comment and the format is plain text.
The registry changes are minimal. A Show Comment subkey is created in two places (one for files and one for directories).
HKEY_CLASSES_ROOT
*
shell
Show Comment
Command
HKEY_CLASSES_ROOT
Directory
shell
Show Comment
Command
The command that gets added is the same for both.
"D:\Apps\CommentShell\CommentShell\bin\Release\CommentShell.exe" "%1"
The actual path will vary depending on where you put the executable. The entries can be added manually but I included the ability to install/uninstall the registry entries in the application itself. Just open a command shell as Administrator, go to the folder containing the executable and type
CommentShell -install
CommentShell -uninstall
to add or remove the registry entries. Once you have the extension installed you can right click on any NTFS file or folder (except for drive roots like C:, D:, etc.) then select "Show Comment" from the pop-up menu. If a comment exists it will be shown in a non-modal dialog box. If not then you will be asked if you want to create one. Once a comment is displayed you can choose to close, edit or delete it. The comment window can be resized and moved and it will remember the last settings when reopened.
As already stated, ADS are a feature of NTFS. If you copy a commented file or folder from one NTFS location to another, the comment will be preserved, however, if you copy it to a non-NTFS file system then the comment will not be copied.
Access to ADS is not possible through vb.net, however it is possible through the Windows Scripting FileSystem Object. Oddly enough, although it is possible to create, retrieve and modify ADS through the scripting object, it is not possible to delete an ADS. According to the Microsoft documentation it should be possible to delete an ADS through a kernel32 API call to DeleteFileA
Declare Function DeleteFile Lib "kernel32" Alias "DeleteFileA" (ByVal filename As String) As Long
I have been unable to determine why this does not work. So I resorted to Plan B which was to do the delete using a free program from the excellent SysInternals Suite by Mark Russinovich available through Microsoft. The particular utility is Streams.exe. If I can get DeleteA to work I will post an update.
The project file is attached.
If you start using CommentShell, you may want a way to see which files contain a comment. Save the following code in a file named cads.vbs. When you run it, files with a comment ADS will be tagged with a * between the file size and file name.
' '
' Name: '
' '
' cads.vbs '
' '
' Description: '
' '
' Recursive directory listing that also shows which files and folders have a '
' comment in an alternate data stream. '
' '
' Usage: '
' '
' cads [folder] [/s | -s] '
' '
' /s or -s enumerates all sub-folders as well '
' '
' Audit: '
' '
' 2014-01-28 rj added recursive switch '
' 2014-01-27 rj original code '
' '
set fso = CreateObject("Scripting.FileSystemObject")
'use either given folder name or current folder
folder = "."
recurse = False
for each arg in Wscript.Arguments
select case lcase(arg)
case "-s"
recurse = True
case "/s"
recurse = True
case else
folder = arg
end select
next
EnumFolder fso.GetAbsolutePathName(folder)
'do a recursive enumeration of all files and folders
Function EnumFolder (folder)
on error Resume Next
If SystemFolder(folder) Then Exit Function
Out ""
Out "Directory of " & folder
Out ""
'list all folders
For Each fld In fso.GetFolder(folder).SubFolders
If Not SystemFolder(fld.Name) Then
tag = IIF(HasComment(fld)," * ", " ")
Out fld.DateLastModified & " <DIR> " & tag & fld.Name
End If
Next
'list all files
size = 0
nfil = 0
For Each fil In fso.GetFolder(folder).Files
tag = IIF(HasComment(fil), " * ", " ")
Out fil.DateLastModified & FmtNum(fil.Size,20) & tag & fil.Name
size = size + fil.Size
nfil = nfil+ 1
Next
Out Space(20) & nfil & " File(s) " & FmtNum(size,-1) & " bytes"
'enumerate all subfolders
if recurse then
For Each fld In fso.GetFolder(folder).SubFolders
EnumFolder fso.BuildPath(folder, fld.Name)
Next
end if
End Function
Function SystemFolder (name)
SystemFolder = True
if fso.GetFileName(name) = "$RECYCLE.BIN" Then Exit Function
if fso.GetFileName(name) = "System Volume Information" Then Exit Function
SystemFolder = False
End Function
'returns true if the file or folder has an ADS named :comment
Function HasComment (obj)
HasComment = fso.FileExists(fso.GetAbsolutePathName(obj) & ":comment")
End Function
'formats an integer with grouping in the given width
Function FmtNum (num, width)
FmtNum = FormatNumber(num,0,True,False,True)
if width > Len(FmtNum) then
FmtNum = Right(Space(width)&FmtNum,width)
end if
End Function
'one-line if-then-else for simple evaluation
Function IIF (cond, tval, fval)
if cond then
IIF = tval
else
IIF = fval
end if
End Function
'output the string (shorter than Wscript.Echo)
Function Out (text)
wscript.echo text
End Function