Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating directories during a copy using IFileOperation

Tags:

c#

com

Using Stephen Toub's IFileOperation wrapper for C# (link), which has been working well until now. Now I am trying to do a copy to gather files from network locations, each network location into its own subdirectory.

\\FOO\data into C:\gather\Foo_data
\\BAR\manage\current into C:\gather\bar\manage

And so on. The problem is in FileOperation.CopyItem. It must be because the destination directory doesn't exist yet—IFileOperation will create it during the copy, right? I used the technique from another question and changed Toub's FileOperation.CreateShellItem to this:

private static ComReleaser<IShellItem> CreateShellItem( string path )
{
    try
    {
        return new ComReleaser<IShellItem>( (IShellItem)SHCreateItemFromParsingName( path, null, ref _shellItemGuid ) );
    }
    catch ( FileNotFoundException )
    {
        IntPtr pidl = SHSimpleIDListFromPath( path );
        IShellItem isi = (IShellItem)SHCreateItemFromIDList( pidl, ref _shellItemGuid );
        Marshal.FreeCoTaskMem( pidl );
        System.Diagnostics.Debug.WriteLine( "Shell item: " + isi.GetDisplayName( SIGDN.DesktopAbsoluteParsing ) );
        return new ComReleaser<IShellItem>( isi );
    }
}

I stuck the Debug.WriteLine in there to check that it's working, and it seems to be working fine; it writes the path back out.

But IFileOperation.CopyItem throws an ArgumentException, and I can't figure out why. Am I not doing the "IShellItem for a nonexistent file" correctly? I suspect I need to get SFGAO_FOLDER in there, since I am trying to create an IShellItem for a nonexistent directory, not file, but how?

like image 802
sidbushes Avatar asked Aug 10 '12 12:08

sidbushes


1 Answers

After a lot of Googling and coding experimentation this weekend (what a wild party weekend), I found a solution.

  1. Write a COM-compatible class that implements IFileSystemBindData.
  2. Allocate a WIN32_FIND_DATA structure and set its dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY. (The other members are irrelevant here, as far as I can tell after some testing.)
  3. Create an instance of your IFileSystemBindData class.
  4. Pass your WIN32_FIND_DATA object to IFileSystemBindData::SetFindData.
  5. Create an instance of IBindCtx.
  6. Call its IBindCtx::RegisterObjectParam with arguments of STR_FILE_SYS_BIND_DATA (#defined as L"File System Bind Data") and your IFileSystemBindData object.
  7. Call SHCreateItemFromParsingName with the pathname of the directory to create, and your IBindCtx object.

It works. The binding-context object tells SHCreateItemFromParsingName not to query the filesystem, and just use what it's given. I got IFileOperation to copy a file into the new and cleverly-named directory D:\Some\Path\That\Did\Not\Previously\Exist, and it created all seven directories along the way. Presumably this same technique could be used to create an IShellItem for any non-existent filesystem object, depending on what you put in dwFileAttributes.

But it's ugly. I hope there's a better way. My coding experimentation was in C++; now to write the COM interop stuff to do it again in C#. Or maybe it will be easier to throw out the project and start over in C++.

like image 81
sidbushes Avatar answered Sep 17 '22 11:09

sidbushes