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.
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