MetaFS (Metadata File System for Windows)

FileSystems are the storage backbone for any computer system, in Today’s operating systems most of them provide a common functionality set like journaling, security and metadata among others. In my humble opinion, metadata is one of the most important ones, reason being is that they can tell users about the contents of the file without even opening it. Therefore, the information entered has to be accurate in order to make metadata important, otherwise it just doesn’t make sense to have it at all.

Starting with the principle of “allowing users capture the information the consider important in a flexible and customizable way” I started working on this project called “MetaFS”  a few months back (currently available on codeplex.com). I can humbly say that’s 85% percent complete, and I’m confident that the remaining bits can be completed with the help from developers in the community.

Unlike WinFS, MetaFS can have any database as metadata repository, but more importantly any given organization can create templates to capture the information that’s relevant to them, thus providing a personal touch to the solution. These templates can easily be customized per file types and even per user by extending the template which is based in XML.

The solution is comprised of the following elements:

image

  • MetaFsDriver: It’s a minifilter driver that monitors changes to the filesystem (similar to the way Antivirus and scanning software work) and if changes are detected, an event is raised to notify Windows Service which in turn interacts with Data Access layer (built with Entity Framework) to update metadata information in the database. Every file in windows has its own identifier FltQueryInformationFile function from the mini driver and then use the ObjectId member that contains this unique value. In MetaFS that has not been implemented yet but it’s the plan to have it, therefore it’s easier to update the metadata of a given file instead of using filenames (which is the current implementation). 

    image

  • MetaFSAgent:  It’s a Windows Service that starts the mini driver but more importantly allows kernel mode notifications to be raised to user mode. I explain this on this post and this communication channel is enabled through a symbolic link. The service is also responsible for calling the data access layer to update metadata information in the database.

    image

  • Shell Extension (Dynamic Property Page):  it’s a “dynamic” property  page (or sheet) that renders template definition at runtime. It’s dynamic in nature because the UI varies based on the template.  It’s an implementation of IShellPropSheetExt interface in the form of an ATL COM Library. This shell extension is loaded by the shell of the OS (Windows Explorer).

    image

  • Managed Code: This component is responsible for data access and rendering the UI based on template definition in XML. So, what effectively happens is that Windows Explorer loads the custom shell extension (property page) that in turns creates an instance and embeds a .NET user control in it and in doing so Windows Explorer creates an AppDomain thus expecting a config file (explorer.exe.config) that contains the connection string to connect to our database.   The images below depict our template rendered at runtime, and the beauty is that if changes are made to the template definition those changes are reflected the next time I’ll right click on the file to see its properties.

    image

If we start Spy++ we can inspect all the visual elements that comprise our custom template, and as you can see they’re all standard Windows Form controls. The purpose of this project was to enable and help users capture metadata through a customizable template approach, but this also opens a few opportunities to integrate with other systems (e.g.: To upload a file directly to SharePoint, for instance).

image

There are a few technical aspects in the solution, but there’s one however, that allows to intercept and replace the Dialog procedure so enabling calling .NET code from any of the existing defined buttons of the dialog (OK, Cancel and Apply). This code is shown below

/////////////////

// C++ Code    // 

/////////////////

 

 

#import "..\..\MetaFSPropPage\bin\x64\Debug\MetaFSPropPage.tlb" 

 

WNDPROC pWndProc;

 

LRESULT CALLBACK CustomWndProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) {

    FindChild* findChildWnd;

    auto retval = CallWindowProc(pWndProc, hwndDlg, msg, wParam, lParam);

 

    switch (msg) {

    case WM_COMMAND:

        auto selectedButton = LOWORD(wParam);

        if (selectedButton == IDOK || selectedButton == IDAPPLY || selectedButton == IDCANCEL) {

            auto enumChildProc = [](HWND hwnd, LPARAM lParam)-> BOOL {

                auto retval = TRUE;

                wchar_t buffer[MAX_PATH];

                auto lParamValues(reinterpret_cast<FindChild*>(lParam));

 

                if (GetClassName(hwnd, buffer, wcslen(buffer)) > 0) {

                    wstring className(buffer);

                    if (className.find(lParamValues->windowName) != wstring::npos) {

                        lParamValues->nMatchCount++;

 

                        if (lParamValues->FoundTargetIndex()) {

                            lParamValues->hFound = hwnd;

                            retval = FALSE;

                        }

                    }

                }

                return retval;

            };

 

            EnumChildWindows(hwndDlg, enumChildProc, reinterpret_cast<LPARAM>((findChildWnd = new FindChild)));

 

            switch (selectedButton) {

            case IDOK:

                SendMessage(findChildWnd->hFound, WM_APP, IDOK, NULL);

                break;

            case IDAPPLY:

                SendMessage(findChildWnd->hFound, WM_APP, IDAPPLY, NULL);

                break;

            case IDCANCEL:

                SendMessage(findChildWnd->hFound, WM_APP, IDCANCEL, NULL);

                break;

            }

 

            delete findChildWnd;

        }

        break;

    }

 

    return retval;

}

/////////////

// C# Code //

/////////////

 

 

protected override void WndProc(ref Message m) {

            switch (m.Msg) {

                case WM_IME_SETCONTEXT:

                    if (Controls[0].Controls.Count == 0)

                        UpdateOrRetrieveMetadata(SelectedFile);

                    break;

                case WM_APP:

                    if (m.WParam.ToInt32().Equals(IDAPPLY)) {

                        if (HasChanges)

                            UpdateOrRetrieveMetadata();

 

                    } else if (m.WParam.ToInt32().Equals(IDOK)) {

                        UpdateOrRetrieveMetadata();

                    }

                    break;

            }

            base.WndProc(ref m);

        }

Another important point to mention is the Windows Service registration, in .NET  Visual Studio does all of the scaffolding for us hence it provides the Service Installer but in MetaFS case,  our service is native so how can we install and register it? Fear not… That’s very easy to do Smile

//////////////////////////////////////

//// Service Creation/Registration ///

//////////////////////////////////////

 

sc create "MetaFSAgent" binPath="C:\Code\Community\VisualC\MetaFS\x64\Debug\MetaFsService.exe" DisplayName="MetaFS Agent" obj="MyPC\MetaFSAgentUser" password="MyPassword"

Sc description "MetaFSAgent" "Metadata File System Agent - Kernel Mode/User Mode Interaction"

 

//////////////////////////////////////

//// Service Deletion/Uninstall    ///

//////////////////////////////////////

 

Sc delete "MetaFSAgent"

and that’s pretty much it. Please feel free to contribute to the project here http://metafs.codeplex.com

Happy coding!

Angel

One thought on “MetaFS (Metadata File System for Windows)”

Leave a Reply

Your email address will not be published. Required fields are marked *