Why dragging attachments out of New Outlook does nothing
New Outlook advertises the file, then refuses to hand it over. We wrote a probe to find out why, and the answer is a documented Windows handshake that almost nothing implements.
Drag an attachment out of New Outlook onto a folder and nothing happens. No error, no file, nothing to search for. Here is what is actually going on, how we proved it, and the roughly thirty lines that fix it.
The annoyance#
You drag a PDF out of New Outlook onto your desktop. The cursor shows the "no drop" symbol, or the drop is accepted and no file ever arrives. No error dialog. No entry in any log. Nothing you can paste into a search box.
That last part is what makes it maddening. A normal bug hands you a message to search for. This one hands you silence, so everybody assumes it is their machine, their profile, or their IT department.
It is none of those. It fails the same way from Microsoft Teams, Gmail in a browser tab, SharePoint and OneDrive. And Classic Outlook works fine.
That pattern is the whole clue, and it is worth sitting with for a second before reaching for a fix.
What the working case has in common#
Classic Outlook is a native Windows application. When you drag an attachment out of it, it puts a real file on the drag, in the format Windows has used since the nineties: CF_HDROP, a list of paths on disk. The receiving application reads the paths. Done.
New Outlook is Chromium in a window. So are Teams, Gmail, SharePoint and OneDrive. Every source that fails is Chromium. Every source that works is native.
Chromium cannot put a file on the drag, because at the moment you press the mouse button there is no file. The attachment is still on a server. Writing it to disk takes time, and a drag and drop operation is not allowed to block while that happens.
So Chromium uses delayed rendering. It advertises the formats it could produce, then waits to be asked properly before producing anything. "Properly" turns out to be extremely specific.
The investigation#
Rather than guess, we wrote a probe: a small program that registers a real Windows drop target with RegisterDragDrop, logs every clipboard format a drag carries, and then tries to pull the file two different ways against that same drag.
Then we dragged one attachment out of New Outlook onto it. Here is what the drag advertised:
--- RAW FORMATETC ENUMERATION ---
1. id=49327 tymed=TYMED_ISTREAM DragContext
2. id=49917 tymed=TYMED_HGLOBAL DragImageBits
3. id=50088 tymed=TYMED_HGLOBAL chromium/x-renderer-taint
4. id=15 tymed=TYMED_HGLOBAL CF_HDROP
5. id=49856 tymed=TYMED_HGLOBAL Chromium Web Custom MIME Data Format
IDataObjectAsyncCapability: PRESENT GetAsyncMode hr=0x00000000 asyncMode=TrueTwo lines in that output matter.
CF_HDROP is right there on the list, format id 15. The source is openly saying it can produce an ordinary Windows file. And IDataObjectAsyncCapability is present, reporting asyncMode=True. The source is openly saying it works asynchronously.
So we asked the ordinary way, which is how essentially every Windows application asks:
[A] CF_HDROP GetData threw: DV_E_FORMATETC (0x80040064)
[A] FileGroupDescriptorW threw: DV_E_FORMATETC (0x80040064)DV_E_FORMATETC means "that format is not available." Except it plainly is available. It was on the list three lines earlier.
This is exactly what every failing application sees. The file is advertised, then refused.
The handshake nobody implements#
The refusal is not a bug, and it is not Chromium being difficult. It is documented behaviour. `IDataObjectAsyncCapability` is a Windows interface for precisely this situation: a source that can produce data but needs time to do it.
The sequence a drop target is supposed to follow:
target: GetAsyncMode() -> source says "yes, I work asynchronously"
target: StartOperation() -> "I am going to fetch on a background thread"
target: GetData(CF_HDROP) -> now the source produces the file
target: EndOperation() -> "done"A drop target that skips this and simply calls GetData(CF_HDROP) on the UI thread gets DV_E_FORMATETC and nothing else.
Almost nothing implements that sequence. Not File Explorer, for these sources. Not the upload box on most websites. Not the average desktop application.
This is why the failure looks so total. It is not in Outlook and it is not in the destination. Two pieces of software are using different halves of the same documented protocol, and neither one raises an error when they fail to line up.
The finding#
So we completed the handshake: call GetAsyncMode, call StartOperation, marshal the data object to a background MTA thread, extract there, then call EndOperation.
StartOperation hr=0x00000000
[B/try1] CF_HDROP SUCCESS, 1 path(s):
C:\Users\...\AppData\Local\Temp\chrome_drag19360_810947597\DOC081826.pdf
(413387 bytes on disk)A real 413 KB PDF. First attempt, no retries, no polling loop that eventually got lucky.
Then look at the path. chrome_drag19360_ is Chromium's own temp directory, and 19360 is the process ID of the WebView2 host running Outlook. Chromium wrote that file itself, the instant it was asked correctly.
Nothing was fetched from Microsoft. No API was called. No credentials were involved. The bytes were always there and always local. The file just needed the right question.
That is the part worth writing down. This is not a scrape or a workaround bolted onto someone else's product. The file was sitting on disk the whole time, behind a handshake that is published, stable, and almost universally ignored.
The fix#
The insight above is maybe thirty lines of code. In full, the capture path does this:
- Register a real
IDropTargetwithRegisterDragDrop, rather than relying on a UI framework's simplified drop handling. - On drop, query the data object for
IDataObjectAsyncCapability. - If async mode is on, call
StartOperation, marshal the data object to a background MTA thread withCoMarshalInterThreadInterfaceInStream, and poll there while Chromium writes the file. - Copy the result out of Chromium's temp directory immediately, because that directory is deleted the moment the drag operation ends.
- Call
EndOperation. - Serve the saved file back out as a plain
CF_HDROPdrag.
Step six is the one that makes it useful rather than merely interesting. Once the file is on disk and an ordinary Windows application is offering it, every destination in Windows accepts it, because there is nothing unusual left to accept. File Explorer, browser upload boxes, CRMs, ERPs, chat apps. Nothing on the receiving end has to cooperate, or even know the tool exists.
There are fallbacks for other source types too: plain synchronous CF_HDROP for ordinary Explorer drags, and FileGroupDescriptorW with FileContents over TYMED_ISTREAM for classic virtual-file sources such as attachments in Classic Outlook.
The rest of the work was not the clever part. It was making it pleasant: a small always on top shelf window to drop things onto, a Ctrl+C shortcut for upload dialogs that take a paste, an installer that needs no admin rights, and an icon.
Why it is two gestures, not one#
Worth stating plainly rather than hiding in a FAQ: using DragIn1 is two gestures. Drop the attachment on the shelf, then drag it out where you actually wanted it.
One gesture is possible. It means fixing the problem at the source: getting inside the Chromium process, intercepting DoDragDrop, and performing the handshake on the application's behalf before the drag ever reaches a destination.
That approach works. It also means injecting unsigned code into Outlook, Teams and Chrome. That trips antivirus, requires a conversation with IT on a managed machine, and breaks whenever any of those applications update, which for Outlook is roughly constantly.
DragIn1 never enters another process. The cost is one extra gesture. The benefit is that it cannot break your email client, and it will still work after the next Outlook update. For something people install once and then forget about, that trade felt like the right one.
Why it is free#
The hard part here was understanding the problem, not writing the code. Once you know the handshake exists and know that Chromium is waiting to be asked, the implementation is short, unexciting, and sitting in front of you.
Charging a subscription for thirty lines of protocol compliance, aimed at people who are already frustrated and just want their attachment, did not sit right. So DragIn1 is MIT licensed and the source is on GitHub. It makes no network connections of any kind: no accounts, no licence checks, no update pings, no telemetry. You can read the whole thing, or build it yourself in about two seconds with a compiler that already ships inside Windows.
One thing to expect when you install it#
Windows will stop you with a blue "Windows protected your PC" dialog the first time you run the installer. Click More info, then Run anyway.
That dialog is SmartScreen, and it is worth being precise about what it means, because it is not what most people assume. It is not reporting that something was found in the file. It is reporting that the file has no reputation yet, which is the normal state for any free tool that has not bought a code signing certificate. It will also come back on every new release, because SmartScreen ties reputation to a specific file hash and every release is a new file.
If you would rather not take that on faith, you do not have to: every release publishes a SHA256 you can check, the source is two C# files you can read, and you can build it yourself instead of running our binary.
The full explanation of what SmartScreen actually checks, what a certificate would and would not buy, and how to verify a download is its own post: “Windows protected your PC” is not what most people think. The reasoning applies to every small free Windows tool you will ever download, not just this one.
If you are implementing this yourself#
Three things that are easy to get wrong:
- Do the extraction off the UI thread. The entire point of async mode is that the source may need time. Marshal the data object across apartments with
CoMarshalInterThreadInterfaceInStreamandCoGetInterfaceAndReleaseStream. Do not call from the drop thread and hope. - Copy the file immediately. The path you get back points inside Chromium's temp directory, which is deleted when the drag ends. A path you stored and read later will be gone.
- Call `EndOperation`, even on failure. Skipping it leaves the source believing an operation is still in flight.
The full technical write-up, including the complete probe output and the reference links, is in docs/how-it-works.md in the repo. The implementation is in DragIn1.cs, in the Grab and ShelfTarget classes. It is plain C# against the Win32 interfaces, with no dependencies.
If you just want the tool, it is on the DragIn1 product page, and the longer story of building it is in the case study. There is also a DragIn1 Chrome extension that covers the same problem from inside the browser. If Windows blocks the installer, here is what that warning actually means, and the support page has the rest of the install and troubleshooting steps.
Want the next post as it ships? Subscribe to the RSS feed.
