拦截 MS Windows 'SendTo' 菜单调用?

Intercept MS Windows 'SendTo' menu calls?

场景

我白天管理和整理许多文件,SendTo 是我在 Windows.

上使用最多的功能

问题

默认情况下,当用户单击上下文菜单的 item/link 发送文件时,O.S 不会显示任何类型的 advise/notifier 表示文件正在复制到所选目标。

我认为这是一个非常错误的设计问题,因为对于大文件它没问题...会显示一个进度条,但如果文件太小,它不会显示任何 progressbar/visual 指示器,所以这是不可能的以确保文件被复制(无需手动操作),因为我是人类,我可以在 SendTo 上下文菜单外单击错误。

所以,我想开发一个个人迷你工具,它可以帮助我优化我的时间,当我使用 SendTo 功能来自上下文菜单,只有 SendTo 功能。

问题

简单来说,我想检测来自 SendTo 菜单的 copy/send 操作,以确保在菜单项(而不是外部)上正确完成点击菜单),还提供其他基本信息,例如源文件夹、目标文件夹以及文件数量或文件路径。

有什么想法可以在正确的方向上开始开发这个工具吗?

我将感谢 C#VB.Net 中的代码示例,最好是最后一个。

方法

因为我不知道如何开始这样做我的意思是这可能是拦截那些 SendTo 调用的最简单或最有效的方法,首先我想挂钩 CopyFile or CopyFileEx API 函数,但它们没有提供我需要的信息,因为该函数将在任何类型的复制操作中调用,而不仅仅是当我使用SendTo 功能,所以我迷路了。

我不确定我是否应该调查更多有关内部调用的信息,或者可能调查更多有关 windows 上下文菜单本身的信息,而不是弄乱函数挂钩和我可以避免的丑陋事情。

我的主要想法是开发一个隐藏的 WinForms(或者 windows 服务),当我使用 SendTo 功能时(当我单击 SendTo 菜单中的一项),然后在屏幕上显示任何一种视觉指示器,以确保我正确单击了该菜单项,并可能告知我要发送的文件数量正在移动以及我将它们移动到哪里。

研究

这是一个代码示例,我认为它演示了如何实例化 SendTo com 对象以创建您自己的对象?但它是用 C++ 编写的,我不确定是否该示例很有用,因为我的目的不是替换 SendTo 菜单,但我会在此处保留此有用信息,它用于其他用途:

How to add(enable) standard "Send To" context menu option in a namespace extension

KNOWNFOLDERID 常量文档提供了一些关于 SendTo 文件夹的有用信息,我再次不确定这是否可能对 read/access 监控方法有帮助吗?我只是把信息放在这里:

GUID: {8983036C-27C0-404B-8F08-102D10DCFD74}

Default Path: %APPDATA%\Microsoft\Windows\SendTo

Legacy Default Path: %USERPROFILE%\SendTo

Shell Extension Handlers docs there is a Copy hook handler里面不知道跟SendTo的[=86有没有关系=]COM 组件,如果这能以某种方式帮助我, 对 IContextMenu::InvokeCommand 方法引用同样无知,也许我可以拦截它来识别 SendTo 调用?

那一刻我觉得自己像瞎了一样。

我最近发现了这个 A managed "Send To" menu class 但它又是一个用 C/C++ 编写的示例(我认为之前的来源相同)我不知道一次又一次地完全不理解我不确定这是否可以帮助我,因为我重复替换 SendTo 不是我想要的(只是因为我不知道如何正确地避免所有可能的风险,我更愿意让 Windows 逻辑 copy/send 文件,我只想检测复制操作以检索信息)

预期结果和使用

第 1 步:

Select 一个随机文件并使用 SendTo 菜单(在我的语言中,西班牙语,命令名称是 'Enviar a')

第 2 步:

让 .net 应用程序的逻辑(在后台工作)拦截 SendTo 操作以检索信息。

(我只需要这一步的帮助)

第 3 步:

在屏幕某处显示信息以确保执行了 SendTo 操作,以确保我正确单击了 SendTo 项目(我的Link).

(那个弹出窗口只是一个模拟,我不知道有什么方法可以检索所有这些信息)

一旦您了解了 SendTo 的真正作用,它就非常简单,而且它根本不涉及 COM 或 shell 扩展。基本上,发送到菜单填充了用户配置文件的 SendTo 文件夹的内容(C:\Users\\AppData\Roaming\Microsoft\Windows\SendTo 默认情况下 Windows 6.x)。

单击时,如果选项是文件夹的快捷方式,它将把文件复制到那里,但如果有程序(或程序可执行文件本身)的快捷方式,它将 运行 该程序,将所选文件的路径作为命令行参数传递。

从那里开始,制作一些仅将路径作为参数、提供某种通知然后复制文件或对文件执行任何操作的程序真的很简单。

一个简单粗暴的例子如下(在 C# 中,但实际上可以用其他任何东西来完成):

private static void Main(string[] args)
{
    if(MessageBox.Show("Are you sure you want to copy files?", "Copy files", MessageBoxButtons.YesNo) == DialogResult.No) return;

    foreach (string file in args)
        File.Copy(file, Path.Combine("c:\temp", Path.GetFileName(file));
}

这只是要求确认复制一堆文件。请注意,这实际上不是 "intercepts" 发送到菜单,而是完全处理它,因此程序负责执行任何有意义的操作。更严肃的实现可以使用 the built-in Windows copy dialog 并显示一些屏幕进度或其他任何内容,这取决于您的需要。

它还可以在命令行上使用更多参数。当您在 SendTo 文件夹中放置快捷方式时,目的地可以添加更多参数,这些参数将作为第一个参数(在文件名之前)传递。例如,快捷方式的目标可以读取 c:\program files\copyfiles.exe c:\temp 以传递目标文件夹而不是硬编码。然后被调用的程序必须将第一个参数解释为目标路径,将后续参数解释为源文件。

我以前不得不做这样的事情。你甚至不必拦截 SendTo() 函数,你只需要确保文件已经到达。如果在同一台计算机上,FileSystemWatcher 怎么样?

你可以在发送之前使用一个watcher来watch,然后,如果文件成功到达目的地,你可以显示一个成功的信息,然后kill watcher。

代码示例

// Create a FileSystemWatcher property.
FileSystemWatcher fsw { get; set; }
// So we can set the FileToWatch within WatchFilesBeforeTransfer().
private string FileToWatch { get; set; }

private void WatchFilesBeforeTransfer(string FileName, string DestinationFolder)
{
    fsw = new FileSystemWatcher();
    fsw.Path = DestinationFolder;
    FileToWatch = FileName;

    // Only if you need support for multiple directories. Code example note included.
    fsw.InclueSubdirectories = true; 

    // We'll be searching for the file name and directory.
    fsw.NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName 

    // If it's simply moving the file to another location on the computer.
    fsw.Renamed += new RenamedEventHandler(FileRenamed); 

    // If it was copied, not moved or renamed. 
    fsw.Created += new FileSystemEventHandler(FileCreated);
    fsw.EnableRaisingEvents = true;
}

// If the file is just renamed. (Move/Rename)
private void FileRenamed(Object source, RenamedEventArgs e)
{
    // Do something.
    // Note that the full filename is accessed by e.FullPath.

    if (e.Name == FileToWatch) 
    {
         DisplaySuccessfulMessage(e.Name);
         KillFileWatcher();
    }
}

// If creating a new file. (Copied)
private void FileCreated(Object source, FileSystemEventArgs e)
{
    // Do something.
    // Note that the full filename is accessed by e.FullPath.

    if (e.Name == FileToWatch) 
    {
         DisplaySuccessfulMessage(e.Name);
         KillFileWatcher();
    }
}

private void KillFileWatcher()
{
   fsw.Dispose();
}

您可以通过以下方式访问所需的 属性 信息(例如在弹出的 gif 中):

  • 文件夹名称Path.GetDirectory(e.FullPath);(如"C:\yo\")
  • 完整文件名e.FullPath(如"C:\yo\hey.exe")
  • 文件名e.Name(如"hey.exe")

非代码执行过程:

  1. 在启动 SendTo() 之前,创建 FileSystemWatcher 属性 的实例,并让它监视特定的 Folder/File 名称组合,应该出现在监视文件夹中:WatchFilesBeforeTransfer(FileName, DestinationFolder).
  2. 发起SendTo().
  3. 收到文件了吗? DisplaySuccessfulSendToMessage()KillFileWatcher();
  4. ???
  5. 利润。

更新:

我刚刚意识到那只是一个文件。如果要检查多个文件,可以创建多个 FileWatcher 实例(不推荐),或者使用 List<string> 对象,如下所示:

private void SendTo(List<string> FileCollection)
{
    // Clear your previous FileList.
    FileList.Clear();

    foreach (string file in FileCollection)
    {
        FileList.Add(file); 
    }
    // Rest of the code. 
}

List<string> FileList { get; set; }
private void WatchFilesBeforeTransfer(string DestinationFolder)
{
    // Same code as before, but delete FileToWatch.
}

private void FileRenamed(Object source, RenamedEventArgs e)
{
    foreach (string file in FileList)
    {
        if (e.Name == file)
        {
            // Do stuff.
        }
    }
}

private void FileCreated(Object source, FileSystemEventArgs e)
{
    foreach (string file in FileList)
    {
        if (e.Name == file)
        {
            // Do stuff.
        }
    }
}

希望对您有所帮助!

这恐怕没那么容易。 我正在尝试 FileSystemWatcher,但只取得了部分成功。

绝对应该有用的东西是 File system drivers 但这看起来也很像,好吧,看看它...

最后,最简单的方法可能是编写您自己的 shell 扩展程序来访问 SendTo 文件夹,并使用它代替 SentTo 命令,这样您就可以完全控制。

这些可能是开场白:

Windows shell extensions

Shell Context Menus

可能我来晚了点回答我自己2015年发表的问题,但是直到前几天我才对这个问题重新产生了兴趣,有了更多的经验和知识这些年在.NET中从头开始,尝试理解我过去不理解的一切。


我刚刚发现,正如@Ňɏssa Pøngjǣrdenlarp 已经在评论框中评论的那样,显然最可行的方法是实现我自己的 SendTo 上下文菜单并使用 IFileOperationProgressSink interface to report progress, which for this firstly I need to depend on the usage of IFileOperation 界面。

之所以使用 IFileOperation 接口是因为它似乎是 Windows API 中提供的让开发人员执行多个文件操作的唯一方法(复制、移动、重命名、创建或删除)在同一个进度对话框中同时进行 UI。这可能是系统在用户选择多个文件或目录时使用的界面(通过 SendTo 菜单或只是 CTRL+CCTRL+V) 一次移动或复制它们,因为它只显示一个进度对话框,所有复制操作都在其中排队...

很明显 IFileOperationIFileOperationProgressSink 接口是我需要的。但据我所知,.NET 框架命名空间中没有这些接口的托管实现(微软的 WindowsAPICodePack 库中也没有),考虑到这些接口自从Windows VISTA 甚至更早的时代,这是一个无可争辩的改进,它与您可以想到的任何 built-in 成员完全相反,可以执行复制、移动、重命名、创建或删除操作,例如例如 System.IO.File.CopyMicrosoft.VisualBasic.FileIO.FileSystem.CopyFile 方法。它们都只支持一次操作,并且一次只支持一个进度对话框。


所以我专注于研究建议的解决方案,最终我找到了 IFileOperationIFileOperationProgressSink 接口的良好实现存储库:

这是我在微软的一篇旧文章中找到的原始实现,它还附带了一个很好的 PDF 阅读来学习一些东西:


实际上,对于自 2015 年以来我在该线程中讨论要做的事情,没有必要深入研究使用 IFileOperationProgressSink 接口来报告进度(仅当我真的想要一个深度的进度信息),因为它足以确定复制已启动/对 IFileOperation.PerformOperations 函数的调用已执行,并且在执行过程中是否出现任何问题复制然后它会出现在进度对话框中 UI.

但是当然仍然需要开发一个 shell-extension 的自定义 SendTo 菜单来替换 Windows 中内置的菜单。 Shell-extensions 可以用 SharpShell 库开发。

我用 VB.NET 语言开发。我设法扩展并记录了他们的 IFileOperation 实现和包装器,并更新了枚举,添加了更新的、为 Windows 7 和 Windows 8 添加的缺失值。 =17=]

如果对某人有帮助,我将在新答案中附加 IFIleOperation 和 Vb.NET 中的包装器(因为它超过post).

允许的最大字符限制

请注意,在我实现的 IFileOperation 接口和名称为 FileSystemOperation 的包装器 class 中,您会发现一些 return 类型和缺失的 classes 或方法(如 HRESULT return 类型或 NativeMethods class),我无法在一个地方共享所有内容,但这些缺失的东西是任何有经验的程序员都会知道如何解决(例如,将 HRESULT 更改为 UInteger 类型,然后转到 Pinvoke.net 从我的 NativeMethods class 中查找任何缺少的方法)。

更新

似乎 Whosebug 不允许我添加另一个答案,所以我将在 PasteBin 上上传我的实现:

Class FileSystemOperation
https://pastebin.com/nvgLWEXu

Interface IFileOperation
https://pastebin.com/GzammHtu

Interface IFileOperationProgressSink
https://pastebin.com/jf9JjzyH

Class ComObjectDisposer(Of T As Class)
https://pastebin.com/7mPeawWr

Enum TransferSourceFlags
https://pastebin.com/V7wSSEvv

Enum FileOperationFlags
https://pastebin.com/A223w9XY

就这些了。