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
ddanbe commented: Nice tip! +15

One question is IIF not the same as the existing iif function?

VBscript does not have an IIF (a stupid omission IMO) so I had to roll my own. The project is in vb.net but cads.vbs is vbscript.

If you are wondering about the extra wide lines int he header of cads.vbs (trailing blanks and a closing tic), for vb.net and vbscript I set the syntax highlighting for comments to grey bg and black fg so that comments are visibly distinct from code. I got into that habit years ago so that commented-out code would be obvious (more important in languages that support /* and */ block comments). Having the blanks and closing tic makes it look a lot less raggedy.

Yes, I'm kinda anal.

Got DeleteFile (kernel32) to work. Streams.exe is no longer required. Updated project is attached.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.