3
votes

Context:

I am able to copy (using CTRL-C) and paste programmatically a file from the clipboard when the file copied to the clipboard is for example a file on the desktop. This is simple enough using the following syntax:

File.Copy(Clipboard.GetFileDropList()[0], savePath)

where Clipboard.GetFileDropList()[0] returns the path of the copied file and savePath is the paste location.

However, i find that the above syntax does NOT work if the copied file (using CTRL-C) is a file attachment in an Outlook email. In that scenario, Clipboard.ContainsFileDropList() returns false and Clipboard.GetFileDropList()[0] results in the following error message:

"ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index"

This is despite the fact that pressing CTRL-V does successfully paste the file, confirming that the file was initially successfully copied to the clipboard.

Question:

Sorry if i missed something very basic. My question is how to programmatically paste/save an email attachment (PDF, Word, etc...) from the clipboard to a file location when that email attachment was copied into the clipboard using CTRL-C from within Outlook.

Note that i do understand that what i am trying to do can be solved by skipping the Clipboard and interacting programmatically with Outlook to access a selected email attachment. However, my goal here is to learn how to interact programmatically with the Clipboard under different scenario.

1
Thank you @Michael Randall for pointing to this stackoverflow past question, although i am not sure what i should take away from it. If i can try to rephrase my question: What information is sent to the clipboard when the user presses CTRL-C on an outlook email attachment, and how to access that information programmatically, considering that that specific CTRL-C event doesn't appear to toggle Clipboard.GetFileDropList to the "true" state.scorpiotomse
From quick test i did, you cannot use GetFileDropList at least with outlook files when you press copy. You can get a list of files and data using the code in the exampleTheGeneral

1 Answers

1
votes

You are using the wrong DataFormat. You can always get a list of currently present data formats by calling Clipboard.GetDataObject().GetFormats().

You need to use:

"FileGroupDescriptor" to retrieve the file names

private static async Task<List<string>> GetAttachedFileNamesFromClipboardAsync(IDataObject clipboardData)
{
  if (!clipboardData.GetDataPresent("FileGroupDescriptor"))
  {
    return new List<string>();
  }

  using (var descriptorStream = clipboardData.GetData("FileGroupDescriptor", true) as MemoryStream)
  {
    using (var streamReader = new StreamReader(descriptorStream))
    {
      var streamContent = await streamReader.ReadToEndAsync();
      string[] fileNames = streamContent.Split(new[] { '\0' }, StringSplitOptions.RemoveEmptyEntries);
      return new List<string>(fileNames.Skip(1));
    }
  }
}

"FileContents" to retrieve the raw file contents

// Returns the attachment file content as string
private static async Task<string> GetAttachmentFromClipboardAsync(IDataObject clipboardData)
{
  if (!clipboardData.GetDataPresent("FileContents"))
  {
    return string.Empty;
  }

  using (var fileContentStream = clipboardData.GetData("FileContents", true) as MemoryStream)
  {
    using (var streamReader = new StreamReader(fileContentStream))
    {
      return await streamReader.ReadToEndAsync();
    }
  }
}

// Returns the attachment file content as MemoryStream
private static MemoryStream GetAttachmentFromClipboard(IDataObject clipboardData)
{
  if (!clipboardData.GetDataPresent("FileContents"))
  {
    return null;
  }

  return clipboardData.GetData("FileContents", true) as MemoryStream;
}

Save attached file to disk

Since Windows only adds the first selected attachment to the system clipboard, this solution can only save a single attachment. Apparently the office clipboard is not accessible.

private static async Task SaveAttachmentFromClipboardToFileAsync(IDataObject clipboardData, string destinationFilePath)
{
  if (!clipboardData.GetDataPresent("FileContents"))
  {
    return;
  }

  using (var attachedFileStream = clipboardData.GetData("FileContents", true) as MemoryStream)
  {
    using (var destinationFileStream = File.Open(destinationFilePath, FileMode.OpenOrCreate))
    {
      await attachedFileStream.CopyToAsync(destinationFileStream);
    }
  }
}

Save attached files to disk using the Office API

Requires to reference Microsoft.Office.Interop.Outlook.dll. This solution does not depend on the system clipboard. It just reaads the selected attachments from the currently open message item in the Outlook explorer.
You still can monitor the system clipboard to trigger the process to save the attachments.

private static void SaveSelectedAttachementsToFolder(string destinationFolderPath)
{
  var outlookApplication = new Microsoft.Office.Interop.Outlook.Application();
  Explorer activeOutlookExplorer = outlookApplication.ActiveExplorer();
  AttachmentSelection selectedAttachments = activeOutlookExplorer.AttachmentSelection;

  foreach(Attachment attachment in selectedAttachments)
  {
    attachment.SaveAsFile(Path.Combine(destinationFolderPath, attachment.FileName));
  }
}