How the Clipboard and Drag & Drop Work — Handling OLE Data Transfer Correctly in Business Apps

· · Windows, Clipboard, Drag and Drop, OLE, COM, Windows Development, WinForms, WPF

“When we paste a table copied from Excel, the formatting falls apart. We want it to paste as a table.” “Content we copy in our app turns into something weird when we paste it into Word.” “We want to be able to take files in by drag and drop.” — In consulting conversations about business-app changes, requests around copy-and-paste and drag and drop (D&D) are a staple.

Precisely because these are “features everyone takes for granted”, how they actually work is surprisingly little known. If you think of the clipboard as “a box you put one piece of data into”, you cannot explain why the same copy produces different results depending on where you paste, or why paste stops working after you close the source app. The real clipboard is a mechanism that places the same content in multiple formats at once, and lets the paste side pick a format it understands.

And drag and drop, at bottom, is OLE data transfer that hands across exactly the same data representation as the clipboard (IDataObject), through COM interfaces. In other words, copy-and-paste and D&D are siblings: understand one correctly and the other is right there.

This article is aimed at IT staff at small and midsize companies and at Windows app developers. It ties together, in a single picture, how clipboard formats work, the practices on the paste side and the copy side, the correct way to watch the clipboard, the administrative policies for clipboard history, cloud sync, and RDP, and the structure and pitfalls of OLE drag and drop.

1. The Bottom Line First

  • The clipboard is a single area shared by the apps on the same desktop (window station), and what sits there is not “one piece of data” but the same content in multiple formats at once. A different session, such as RDP, originally has a different clipboard; the redirection feature is what bridges the two. Because the destination picks a format it understands, the same copy produces different results depending on where you paste.12
  • For text, use CF_UNICODETEXT. CF_TEXT is ANSI and code-page dependent, and on Japanese systems it is a breeding ground for mojibake. The system converts between the two implicitly, but the canonical side is Unicode.3
  • Files travel as CF_HDROP (a double-NUL-terminated array of paths), and formatted text uses the registered format “HTML Format”. HTML Format has an unusual structure: UTF-8 text with a header of byte offsets.45
  • The real cause of “I closed the source app and could no longer paste” is delayed rendering. It is a mechanism that places not the payload but only a promise to “produce it when asked”; if you skip materializing it at exit (responding to WM_RENDERALLFORMATS, or OleFlushClipboard for OLE), paste stops working.26
  • Treat pasted data as untrusted input from outside. Microsoft itself states plainly that “clipboard data is not trusted. Parse it carefully”.7
  • For watching the clipboard, AddClipboardFormatListener + WM_CLIPBOARDUPDATE is the only option. Do not use polling, and do not use the old SetClipboardViewer (the viewer chain). Registered formats that keep secrets out of history and sync (ExcludeClipboardContentFromMonitorProcessing and friends) are also provided.81
  • Clipboard history (Win+V) and cloud sync are an IT-management concern. You can control them with AllowClipboardHistory and AllowCrossDeviceClipboard via GPO / Intune (Policy CSP), and RDP clipboard redirection has a dedicated policy of its own.91011
  • Drag and drop is COM. The same IDataObject as the clipboard is handed between IDropSource (the drag source) and IDropTarget (the drop target) through the DoDragDrop loop. RegisterDragDrop requires initialization with OleInitialize (STA).1213
  • You cannot drop from ordinary-privilege File Explorer onto an elevated app. UIPI (message blocking by integrity level) is the cause, and it is a constraint you should know at design time.14

Below we walk through this from the clipboard foundations up.

2. What the Clipboard Really Is — Not “One Piece of Data” but “the Same Content in Multiple Formats”

The clipboard is a common data-sharing mechanism that every app sharing the same desktop can reach (more precisely, it is per window station: a different user session or RDP session each has its own clipboard. Copy-and-paste works over RDP because the redirection feature bridges the two — Chapter 7). The first principle is that it is user-driven: the official design stance is that you do not put data in or take data out behind the user’s back.1

The important point is that a copy does not place “one piece of data”. The window that copies empties the clipboard and then places several formats in a row, expressing the same content from the more capable format down to the less capable one.2 For example, when you copy a table in a spreadsheet, conceptually something like the following is on the clipboard at the same time.

Priority Format Contents
1 App-private format A complete internal representation, including formulas and formatting (for paste back into the same app)
2 HTML Format An HTML fragment that keeps the table structure and formatting
3 CSV Cell-delimited text
4 CF_UNICODETEXT Tab-separated plain text
5 Image format A bitmap of how the table looks

The paste side picks a format it understands from this list and extracts it. Paste into Word and you get a formatted table; paste into Notepad and you get tab-separated text — because the two chose different formats. “The result depends on where you paste” is not a bug; it is the normal consequence of this design.

Why the same copy produces different results depending on where you pasteThe copy side places the same content on the clipboard in multiple formats, and the paste side picks a format it understands, so Word gets a formatted table and Notepad gets tab-separated textWordNotepadCopy: spreadsheetClipboard (many formats)Richer formatsPlainer formatsApp-privateHTML FormatCSVCF_UNICODETEXTFormatted tableTab-separated text

Put the other way around, the opening complaints — “the formatting falls apart”, “something weird gets pasted” — almost all reduce to a problem of how one side chooses formats, or how the other side offers them. Chapter 4 covers the paste side; Chapter 5 covers the copy side.

3. Standard Formats and Registered Formats — CF_UNICODETEXT, CF_HDROP, HTML Format

3.1. Standard Formats — Use the Unicode Side for Text

Formats the OS defines up front are called standard formats. The ones that show up constantly in business apps are the following.3

Format Value Contents
CF_TEXT 1 ANSI text (code-page dependent)
CF_UNICODETEXT 13 Unicode text. This is the canonical format for text
CF_HDROP 15 A list of file paths (an HDROP handle)
CF_DIB 8 A device-independent bitmap
CF_LOCALE 16 The locale identifier associated with the text

CF_TEXT and CF_UNICODETEXT are converted into each other implicitly by the system (synthesized formats). The character-code conversion uses the code page associated with CF_LOCALE.3 Relying on that conversion drops characters that ANSI cannot represent (for example Unicode-only symbols and combining characters), so the rule is unify what the app reads and writes on CF_UNICODETEXT (DataFormats.UnicodeText in .NET).

Implicit conversion between CF_UNICODETEXT and CF_TEXTThe app reads and writes only CF_UNICODETEXT; the system synthesizes CF_TEXT by implicit conversion with the CF_LOCALE code page. Characters that ANSI cannot represent are dropped in that conversionCF_LOCALE conversionApp reads and writesCF_UNICODETEXTCF_TEXT (ANSI)Unrepresentable chars are dropped

3.2. CF_HDROP — Files Travel as “a List of Paths”

CF_HDROP is what is used when you copy files in File Explorer, or when you drag and drop files. The payload is not the files themselves; it is a memory block that lays out a “double-NUL-terminated” array: after a DROPFILES structure header, full-path strings separated by NUL characters, and an empty string at the end. The header’s pFiles is the start offset of the path list, and fWide says whether the strings are Unicode.4

[DROPFILES header: pFiles=start offset of the path list, fWide=1(Unicode)]
C:\data\a.txt(NUL)C:\data\b.txt(NUL)(NUL)

In native code you pull them out one at a time with DragQueryFile; in .NET you receive them as a string[] via DataFormats.FileDrop. The fact that “what travels is only the paths, not the files themselves” will matter again in the D&D of Chapters 8 and 9.

Memory-block layout of CF_HDROPA DROPFILES structure sits at the start of the global memory; pFiles is the start offset of the path list and fWide says whether it is Unicode. Full paths then follow, NUL-separated, and the block ends with an empty string (double NUL). What travels is only the paths, not the files themselvesDROPFILES (pFiles / fWide)C:\\data\\a.txt + NULC:\\data\\b.txt + NULEmpty string (double NUL)Only paths travel, not files

3.3. Registered Formats — RegisterClipboardFormat and “HTML Format”

For data that standard formats cannot express, an app can pick a name and register its own format. Pass a name to RegisterClipboardFormat and you get a format ID back; registering under the same name from a different app returns the same ID, so once you agree on the name you can share data between apps.1 When you pass structured data among your own suite of apps, use a name that will not collide, such as KomuraSoft.Report.RowData.

The representative registered format is “HTML Format”, for formatted text (together with RTF, one of the two major rich-text formats). The payload is UTF-8 text, but it has an unusual structure: a header that lists byte offsets is attached at the front.5

Version:0.9
StartHTML:<byte offset of the start of the whole HTML>
EndHTML:<byte offset of the end of the whole HTML>
StartFragment:<byte offset of the start of the fragment>
EndFragment:<byte offset of the end of the fragment>
<html><body>
<!--StartFragment--><b>bold</b> fragment text<!--EndFragment-->
</body></html>

Each offset is a byte position from the start of the data, including the header itself; the usual practice is to reserve a fixed width (for example 10 digits) and write the measured values back after you have built the body. StartFragment/EndFragment mark the start and end of “the fragment the user actually selected” in bytes (not in characters). In UTF-8 that includes Japanese, the character count and the byte count diverge, so if you get this offset calculation wrong, paste into another app drops the beginning or the end. If you generate HTML Format yourself, you must fill the header with byte positions measured after encoding to UTF-8.5

How the HTML Format header relates to the offsetsThe header's StartHTML and EndHTML point at the whole HTML, and StartFragment and EndFragment point at the fragment the user selected, both as byte positions from the start of the data. Because character count and byte count diverge in UTF-8, fill the header with byte positions measured after encodingHeader (byte offsets)Whole HTMLSelected fragmentOffsets are bytes after UTF-8

CSV (DataFormats.CommaSeparatedValue in .NET) is also commonly used for tabular data. For interop with Excel, offering HTML Format (with formatting), CSV (values only), and CF_UNICODETEXT (tab-separated) together means you do not have to pick a single paste destination.

4. Practices on the Paste Side — Format Priority and Validation

4.1. Look from Rich Formats Down

The formats on the clipboard are lined up in the order the copy side placed them (that is, from more expressive to less). The paste side’s baseline is to look, among the formats you can handle, starting from the one with the most information. In Win32 you either enumerate with EnumClipboardFormats and use the first format you recognize, or you pass your own priority list to GetPriorityClipboardFormat and let it choose.2

In .NET the branch looks something like the following.

// Pasting a table: look from rich to plain
var data = Clipboard.GetDataObject();
if (data is null) return;

// Advertising a format does not guarantee the payload is a string. Use this
// branch only when the type also checks out; otherwise fall through to the next candidate
if (data.GetDataPresent(DataFormats.Html)
    && data.GetData(DataFormats.Html) is string html)
{
    // Validate the HTML Format header, then import as a table
}
else if (data.GetDataPresent(DataFormats.CommaSeparatedValue))
{
    // Import as CSV
}
else if (data.GetDataPresent(DataFormats.UnicodeText))
{
    // Import as tab-separated text
}

That is the answer to the opening complaint, “pasting an Excel table falls apart”. An app that reads only plain text never receives the table structure. How far down the format list you accept is a design decision on the paste side.

Paste branching that looks from rich formats downIf HTML Format is present and the payload is also a string, import as a table; otherwise try CSV; if that is missing too, fall through to tab-separated text. If none of the candidates is present, refuseyesnoyesnoyesnoStart pasteHTML Format + string?Validate header → tableCSV present?Import as CSVUnicodeText?Tab-separated textRefuse

4.2. Pasted Data Is External Input

It is easy to miss, but clipboard contents are data from outside, and you do not know which app placed them. Microsoft also warns, in the OLE clipboard documentation, that “clipboard data is not trusted. Parse it carefully before you use it in the app”.7

  • Validate that HTML Format header offsets do not point outside the buffer (apps that emit broken headers do exist).
  • Values you import as numbers, dates, or codes should go through the same validation as on-screen input.
  • Put in a defense against huge data. Even if someone pastes a hundreds-of-megabytes image or millions of lines of text, do not block the UI, and refuse once a limit is exceeded. A caveat: .NET’s GetData, at the moment you call it, materializes the whole payload into a managed string (and delayed rendering runs as part of that), so placing a size check after GetData is not a defense. In Win32, checking GlobalSize on the HGLOBAL that GetClipboardData returns does give you a defense at the stage of “do not proceed into conversion and parsing as a managed string”, but for delayed-rendering formats GetClipboardData itself kicks off rendering, so you still cannot prevent materialization on the copy-source side. To keep the UI from freezing, move the fetch off the UI thread (and even then, because .NET’s Clipboard requires STA, do it on a dedicated thread set to STA, not on the Task.Run thread-pool thread (MTA) — Section 5.1).

The idea that “a value that arrives from outside, whatever the path, is validated before you use it” is the same one laid out in “Never Use a QR Code’s Decoded Value As-Is”. The assumption that paste is safe because it is a user action is how accidents start.

Validate pasted data before you use itData taken from the clipboard goes through format-present, payload-type, size-limit, and content validation in that order; fail any of them and you refuse or fall through to the next candidate formatwrong typetoo largeinvalidFormat present?Payload type OK?Size within limit?Validate contentsImportRefuse / next format

5. Practices on the Copy Side — Offering Multiple Formats at Once, and Delayed Rendering

5.1. Place Multiple Formats at Once

The copy side’s practice is the reverse of 4.1: offer a rich format and a plain format at the same time. With the WinForms/WPF DataObject you can write it in a few lines.15

// WinForms (System.Windows.Forms). WPF is the same shape with System.Windows DataObject/Clipboard
var data = new DataObject();
data.SetData(DataFormats.Html, htmlFormatText);       // HTML Format string including the header
data.SetData(DataFormats.CommaSeparatedValue, csv);   // CSV
data.SetData(DataFormats.UnicodeText, plainText);     // Plain text
Clipboard.SetDataObject(data, copy: true);            // copy:true = keep after the app exits

Two notes. First, .NET’s Clipboard class can only be used from an STA thread.15 The WinForms/WPF UI thread is STA because of [STAThread], so this is normally not a problem, but touching it from a background thread fails (the STA/MTA fundamentals are in “COM STA/MTA Fundamentals”). Second, what copy: true means is tied to delayed rendering in the next subsection.

5.2. Delayed Rendering — Why “Close the Source and You Cannot Paste”

Building a large payload in many formats every time is wasteful, so the clipboard has a mechanism called delayed rendering. Pass NULL as the data handle to SetClipboardData and, instead of the payload, only a promise to “produce it when asked” is registered; when someone requests that format, WM_RENDERFORMAT arrives at the copy source, and only then is the data generated.2

The consequence of this design is the opening “I closed the source app and could no longer paste”. Before it exits, the copy source receives WM_RENDERALLFORMATS and is responsible for materializing every format that has not yet been rendered; exit without doing that and the format is lost.2

Delayed rendering and why close-then-paste failsThe copy source registers only a promise with a NULL handle, and materializes on demand via WM_RENDERFORMAT. At exit it is responsible for materializing every format with WM_RENDERALLFORMATS; skip that and the format is lostRENDERALLFORMATSSkip materializeSetClipboardData NULL = promisePaste side requests itWM_RENDERFORMAT → build nowCopy source about to exitPaste works after exitFormat lost after close

On the OLE clipboard (the style that places an IDataObject with OleSetClipboard), this relationship is even clearer. All the clipboard holds is a pointer to the data object, and calling OleFlushClipboard at app exit materializes the data onto the clipboard, so paste still works after exit.6 .NET’s Clipboard.SetDataObject(data, copy: true) is what specifies this “keep it after exit” behavior.

When you copy a large range in Excel and try to quit, the prompt “There is a large amount of information on the Clipboard. Do you want to be able to paste this information into another program later?” is exactly the confirmation of whether to run this materialization (the flush). If you use delayed rendering in your own app, remember that the exit-time materialization is part of the same set. Delayed rendering is a performance optimization, and because the render request runs synchronously inside message processing, data that takes a long time to generate has the trade-off of freezing the UI.2

6. Practices for Watching the Clipboard — Listener, Retry, and History Exclusion

6.1. Use AddClipboardFormatListener

Requirements such as “we want to detect a barcode-reader value or a copy from the line-of-business system and import it automatically” need you to watch clipboard changes. Historically there are three methods; today the right answer is one.8

Method Assessment
Read on a timer (polling) Wasteful, and you can miss updates. Do not use
SetClipboardViewer (viewer chain) A bug in one app in the chain breaks the whole chain. Kept only for backward compatibility
AddClipboardFormatListener Recommended. WM_CLIPBOARDUPDATE arrives at the registered window
Flow of clipboard watchingRegister with AddClipboardFormatListener when the handle is created, and WM_CLIPBOARDUPDATE arrives no matter which app copied. Read with a retry, and unregister symmetrically with RemoveClipboardFormatListener when the handle is destroyedunregisterAddClipboardFormatListenerWaitSome app copiesWM_CLIPBOARDUPDATERead with retry (6.2)RemoveClipboardFormatListener
// Minimal WinForms implementation
public partial class MainForm : Form
{
    [DllImport("user32.dll", SetLastError = true)]
    static extern bool AddClipboardFormatListener(IntPtr hwnd);
    [DllImport("user32.dll", SetLastError = true)]
    static extern bool RemoveClipboardFormatListener(IntPtr hwnd);
    const int WM_CLIPBOARDUPDATE = 0x031D;

    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);
        AddClipboardFormatListener(Handle);
    }

    protected override void OnHandleDestroyed(EventArgs e)
    {
        // Unregister symmetrically to match handle destruction / recreation
        RemoveClipboardFormatListener(Handle);
        base.OnHandleDestroyed(e);
    }

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_CLIPBOARDUPDATE)
        {
            // Read Clipboard.GetDataObject() here and import if the format is one you need
        }
        base.WndProc(ref m);
    }
}

6.2. Retry When You Cannot Open It

Only one window at a time can open the clipboard; while another process has it open, OpenClipboard fails.2 Immediately after WM_CLIPBOARDUPDATE, the copy source or another watcher is often still operating, so a temporary read failure is a normal event. Always put in a few retries with a short wait (tens of milliseconds) in between. Note that the .NET Clipboard overloads that let you specify a retry count and interval exist only on the write side, SetDataObject. There is no equivalent on the read side (GetDataObject and friends), so you write the catch-wait-retry yourself — ExternalException on WinForms, COMException on WPF.

Clipboard-read retry flowOnly one window at a time can open the clipboard, so a read right after the change notification can fail by racing with another process. On an exception, wait tens of milliseconds and retry; if you hit the limit, give up this time and pick it up on the next updatesuccessin useretrylimitWM_CLIPBOARDUPDATEAttempt a readImport (Ch. 4 checks)Wait tens of msGive up this time

6.3. Keep It Out of History and Sync — Care for Copy Features That Handle Secrets

Windows has clipboard history (Win+V) and cross-device sync (the cloud clipboard), and data an app places is in scope for both by default. An app that puts secrets such as passwords or account numbers on a copy feature also places a registered format that excludes the content from history and sync.1

  • ExcludeClipboardContentFromMonitorProcessing: Place this and that copy’s contents are included in neither history nor sync.
  • CanIncludeInClipboardHistory (DWORD 0): Suppress history only.
  • CanUploadToCloudClipboard (DWORD 0): Suppress cross-device sync only.

The reason a password copied by a password manager does not remain on Win+V is this mechanism. You get a format ID by passing the name to RegisterClipboardFormat and set it alongside the ordinary data, so it is worth implementing in any business app that handles secrets.

7. The Clipboard from an IT Perspective — History, Cloud Sync, and RDP Controls

Stepping a little away from development, here are the points that matter to an administrator. Clipboard history accumulates recent copies, and the cloud clipboard syncs copies across devices signed in with the same Microsoft account / Microsoft Entra account.10 Convenient as that is, it also produces residue and spillover: personal information copied from a line-of-business system accumulates in history, and content copied on a work PC syncs to a personal PC.

The two policies you use to control this in an organization are the following.

What you control GPO (Computer Configuration > Administrative Templates > System > OS Policies) Policy CSP (Intune) Default
Clipboard history Allow Clipboard History Experience/AllowClipboardHistory Allowed
Cross-device sync Allow Clipboard synchronization across devices Privacy/AllowCrossDeviceClipboard Allowed

Both are available from Windows 10 version 1809 onward; disable them and the corresponding items in the Settings app are grayed out, and the policy takes effect immediately.910

The other staple is RDP (Remote Desktop) clipboard redirection. By default, copy-and-paste works between the local PC and the remote session, so it can become a path for taking secrets off a server. The “Do not allow clipboard redirection” policy (registry value fDisableClip) can block both directions.11 Recent Windows Server / Windows 11 releases have also added finer-grained policies, such as restricting the server-to-client direction to text only. Whether you ban it outright or restrict it in stages is a balance of operations and security.

Paths clipboard contents can spread along, and the control pointsCopied contents are in scope for history and cloud sync by default, and on RDP they travel to another session via redirection. Each path can be controlled by policy, and the app side can exclude itself from history and sync with the exclusion formatsClipboardHistory (Win+V)Cloud syncRDP redirectionAllowClipboardHistoryAllowCrossDeviceClipboardfDisableClipApp exclude formats (6.3)

8. Drag and Drop Is COM — IDataObject + IDropSource + IDropTarget

8.1. The Same Data as the Clipboard, a Different Way of Carrying It

OLE drag and drop runs with the following three roles.12

Role Who implements it Job
IDataObject Drag source The payload being carried. The same multi-format data object as the clipboard
IDropSource Drag source Deciding whether the drag continues or is cancelled, and cursor feedback
IDropTarget Drop target Declaring accept/reject in DragEnter/DragOver/DragLeave/Drop, and receiving the drop

The drag source calls DoDragDrop, the drag loop starts, and when the mouse enters a drop-target window that IDropTarget is notified; on drop, the IDataObject is handed over. Official documentation also says that “D&D provides exactly the same functionality as clipboard copy-and-paste. If an app already implements copy-and-paste, the addition is small”.12 In other words, the multi-format DataObject you built in Chapters 2 through 5 becomes the D&D payload as-is.

Flow of OLE drag and dropThe drag source puts an IDataObject in the payload and calls DoDragDrop to start the drag loop; the drop target's IDropTarget declares accept/reject in DragEnter and DragOver, and on Drop picks a format from the IDataObject and extracts itDoDragDropmouse entersbutton upIDataObject + IDropSourceDrag loopDragEnter/Over: EffectIDropTarget.DropPick a format and extract

8.2. OleInitialize (STA) Is Required

A window that will be a drop target registers with RegisterDragDrop, and there is a classic pitfall here. If you initialized COM with CoInitialize/CoInitializeEx, RegisterDragDrop always fails with E_OUTOFMEMORY; you must initialize with OleInitialize.13 OleInitialize initializes COM as STA, because D&D is a feature rooted in the STA world of windows and a message pump. The calling thread must also be running a message pump; skip that and other apps hang during the drag.13 The background here is exactly the threading-model discussion in “COM STA/MTA Fundamentals”.

In a WinForms/WPF app the framework takes care of OLE initialization and the interface implementations, so the developer only has to write the events.

// WinForms: accept dropped files
listView1.AllowDrop = true;
listView1.DragEnter += (s, e) =>
{
    // Also check that the source allows Copy (some sources only allow Move/Link)
    e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop)
            && (e.AllowedEffect & DragDropEffects.Copy) == DragDropEffects.Copy
        ? DragDropEffects.Copy      // Accept: receive as a copy
        : DragDropEffects.None;     // Do not accept
};
listView1.DragDrop += (s, e) =>
{
    // Drag data is also untrusted input. Even if it advertises FileDrop, the payload
    // can be null or a different type, and GetData itself can fail
    object data;
    try { data = e.Data.GetData(DataFormats.FileDrop); }
    catch (COMException) { return; }
    if (data is not string[] paths) return;
    foreach (var path in paths)
    {
        // Validate the path before importing (Section 9.3)
    }
};

The shape is the same in WPF: you receive with AllowDrop="True" and the DragOver/Drop events on the element, and you extract the path array with e.Data.GetData(DataFormats.FileDrop). Declaring accept/reject (Effect) on every DragEnter/DragOver is the IDropTarget convention; skip it and you get the bug where the cursor stays on “not allowed” and never changes.

9. D&D Pitfalls — Elevation, Move, and Path Validation

9.1. You Cannot Drop onto an App Elevated as Administrator

Drop a file from File Explorer onto an app launched with “Run as administrator” and nothing happens — this is not an implementation bug, it is OS behavior. UIPI (User Interface Privilege Isolation) blocks messages from a lower-integrity process to a higher-integrity window by default, so drop notifications from ordinary-privilege (medium-integrity) File Explorer never reach an elevated app.14

How UIPI blocks drops onto an elevated appDrop notifications from medium-integrity File Explorer to a high-integrity elevated app are blocked by UIPI by default and never arrive. Keep the UI at ordinary privilege and isolate privileged work, and the drop arrivesdrop notifyblockedpassesdelegate privileged workExplorer (medium)UIPIElevated app: no dropOrdinary UI: drop arrivesIsolated elevated process

A workaround that individually allows specific messages such as WM_DROPFILES with ChangeWindowMessageFilterEx is well known,14 but what that lets through is the older (WM_DROPFILES) drop notification; it does not solve OLE D&D as a whole. The practical guidance is clear: stop designing the app to run elevated all the time. Isolate only the work that needs elevation into a separate process, and the UI itself can stay at ordinary privilege and receive D&D (the isolation design is covered in detail in “How to Concretely Isolate “Only the Operations That Need Administrator Privileges” in a Windows App”).

9.2. What DragDropEffects Means — Move Is a Contract That “the Original Goes Away”

Copy/Move/Link on DragDropEffects are not decoration; they are a contract between the drag source and the drop target. The drag source declares the set of effects it allows in DoDragDrop, the drop target picks the actual effect, and when Move succeeds, the drag source deletes the data (the file) — that is the convention. If the receiving side thoughtlessly returns Move, you get the accident “I dropped it and the original file disappeared”. For a business app’s import use, the receiving side stating Copy is the safe default.

The DragDropEffects contract — Move deletes the originalThe drag source declares the set of allowed effects in DoDragDrop, and the drop target picks the actual effect. When Move succeeds the drag source deletes the file, so for import the receiving side should state CopyCopyMoveSource: allowed effectsTarget: pick EffectOriginal remains (import)Source deletes the file

9.3. Validating a Dropped Path

What travels in CF_HDROP/FileDrop is only the path (Section 3.2). Before you import, put it through the same untrusted-input validation as paste.

  • File or folder: Decide as a spec what happens when a whole folder is dropped (recurse and import, or refuse).
  • OneDrive placeholders: The path may exist while the file body is not local — an on-demand file. The moment you open it a download starts, and offline it fails. Behavior and countermeasures are in “OneDrive “Files On-Demand” and Business Apps”.
  • Long paths and unusual paths: Paths over MAX_PATH, network (UNC) paths, and paths on removable media should be accepted only after you have confirmed that the downstream processing can handle them.
  • Count and total size: So that dropping thousands of files does not freeze the UI, make the import asynchronous and put in a limit and a progress display.

10. Summary

  • The clipboard is a mechanism that places the same content in multiple formats at once in a single area shared within the same desktop (window station). The paste side picks the format, so the same copy produces different results.
  • Text is CF_UNICODETEXT, files are CF_HDROP, and formatted text is the registered format HTML Format (a byte-offset header + UTF-8).
  • The paste side looks from rich to plain and treats the payload as external input. The copy side offers multiple formats at once, and if it uses delayed rendering it implements exit-time materialization (WM_RENDERALLFORMATS / OleFlushClipboard) as well.
  • Watching is AddClipboardFormatListener + WM_CLIPBOARDUPDATE. Prepare for OpenClipboard races with a retry, and keep secrets out of history and sync with ExcludeClipboardContentFromMonitorProcessing and friends.
  • IT can control clipboard history, cloud sync, and RDP redirection with GPO / Intune. The default is allowed for all of them, so decide deliberately in environments that handle secrets.
  • D&D is COM: IDropSource/IDropTarget hand across the same IDataObject as the clipboard. RegisterDragDrop requires OleInitialize (STA).
  • Drops onto an elevated app are blocked by UIPI. Move on DragDropEffects is a contract that “the original goes away”; validate a dropped path before you import it.

Copy-and-paste and D&D are, to the user, features that should feel like air. That is exactly why “I cannot paste”, “it falls apart”, and “it disappeared” hurt the experience so much — and why an app that offers multiple formats and handles drops properly makes everyday operations smoother by itself. I hope this is useful material when you decide what to fix first.

KomuraSoft LLC handles the design and implementation of copy-and-paste and drag-and-drop support in business apps (offering multiple formats, Excel interop, importing dropped files), root-cause investigation of problems such as “it falls apart when I paste” or “the copy disappears”, input automation that watches the clipboard, and implementations that keep confidential data out of history and sync. Cases that involve the lower layers of COM and OLE are welcome even if you start from isolating the symptom.

References

  1. Microsoft Learn, Clipboard Formats. On a window being able to place the same information in multiple clipboard formats; registered formats via RegisterClipboardFormat (registering the same name returns the same value, so apps can share it); synthesized formats; and excluding content from clipboard history / cloud sync with ExcludeClipboardContentFromMonitorProcessing, CanIncludeInClipboardHistory, and CanUploadToCloudClipboard.  2 3 4 5

  2. Microsoft Learn, Clipboard Operations. On only one window at a time being able to open the clipboard; placing formats from more expressive to less expressive at copy time; format selection at paste time with EnumClipboardFormats / GetPriorityClipboardFormat; delayed rendering by passing NULL to SetClipboardData and the WM_RENDERFORMAT / WM_RENDERALLFORMATS responsibilities; and the trade-offs of delayed rendering.  2 3 4 5 6 7 8

  3. Microsoft Learn, Standard Clipboard Formats. On the definitions of the standard formats CF_TEXT (ANSI), CF_UNICODETEXT, CF_HDROP, CF_DIB, and CF_LOCALE, and on the system implicitly converting CF_TEXT and CF_UNICODETEXT using the code page associated with CF_LOCALE.  2 3

  4. Microsoft Learn, Shell Clipboard Formats. On CF_HDROP being composed of a DROPFILES structure plus a double-NUL-terminated array of full-path strings; retrieving individual paths with DragQueryFile; and CFSTR_ shell formats requiring registration via RegisterClipboardFormat.  2

  5. Microsoft Learn, HTML Clipboard Format. On the registered name being “HTML Format”; the header structure with byte offsets such as Version, StartHTML, EndHTML, StartFragment, and EndFragment; the encoding always being UTF-8; and the StartFragment/EndFragment comment convention.  2 3

  6. Microsoft Learn, OleFlushClipboard function (ole2.h). On OleSetClipboard making the clipboard hold only a pointer to the data object; OleFlushClipboard materializing the data onto the clipboard so paste still works after the app exits; and emptying the clipboard with OleSetClipboard(NULL) when you do not need to keep it at exit.  2

  7. Microsoft Learn, OleGetClipboard function (ole2.h). On how to obtain an IDataObject from the clipboard, and the warning that clipboard data is not trusted and should be parsed carefully before the app uses it.  2

  8. Microsoft Learn, Using the clipboard. On comparing the three ways of watching the clipboard (viewer windows, sequence numbers, and format listeners); new programs being expected to use a listener via AddClipboardFormatListener; the viewer chain being fragile when chain maintenance is incomplete; and sequence numbers not being something you should poll.  2

  9. Microsoft Learn, Policy CSP - Experience. On allowing or denying clipboard history with the Experience/AllowClipboardHistory policy; availability from Windows 10 version 1809 onward; the default being allowed; and the GPO mapping under “System > OS Policies” with changes taking effect immediately.  2

  10. Microsoft Learn, Policy CSP - Privacy. On allowing or denying cross-device clipboard sync with the Privacy/AllowCrossDeviceClipboard policy; sync happening between devices signed in with the same Microsoft account / Microsoft Entra account; and the default being allowed.  2 3

  11. Microsoft Learn, Policy CSP - ADMX_TerminalServer. On TS_CLIENT_CLIPBOARD (“Do not allow clipboard redirection”, registry value fDisableClip) being able to forbid clipboard sharing between local and remote in a Remote Desktop session, and on redirection being allowed by default.  2

  12. Microsoft Learn, Drag and Drop (COM). On OLE drag and drop running with the three of IDropSource (drag source), IDropTarget (drop target), and DoDragDrop (the loop OLE provides); providing the same functionality as clipboard copy-and-paste, so that an app which already implements copy-and-paste needs only a small addition; and the kinds of feedback.  2 3

  13. Microsoft Learn, RegisterDragDrop function (ole2.h). On registering a drop-target window with an IDropTarget; always failing with E_OUTOFMEMORY if COM was initialized with CoInitialize/CoInitializeEx, so that OleInitialize is required; and the drag-source app hanging if the calling thread is not running a message pump.  2 3

  14. Microsoft Learn, ChangeWindowMessageFilterEx function (winuser.h). On UIPI being a security mechanism that by default blocks receiving messages from a lower-integrity sender, and on allowing specific messages per window with a message filter (MSGFLT_ALLOW).  2 3

  15. Microsoft Learn, How to add data to the Clipboard (Windows Forms). On placing data in multiple formats at once with DataObject and Clipboard.SetDataObject; adding in multiple formats so that other apps can recognize it; and the Clipboard class being usable only from an STA thread, so that [STAThread] is required.  2

Recent articles sharing the same tags. Deepen your understanding with closely related topics.

These topic pages place the article in a broader service and decision context.

This article connects naturally to the following service pages.

Frequently Asked Questions

Common questions about the topic of this article.

Why does the formatting of a table copied from Excel fall apart when I paste it into my app?
The clipboard does not hold "one piece of data". The same content is placed in several formats at once (the source app's private format, HTML Format, CSV, Unicode text, and so on), and the destination app picks a format it understands and extracts that. When formatting falls apart, the typical cause is that the destination is reading only plain text (CF_UNICODETEXT). If you want the table structure as well, implement the paste side so that it prefers HTML Format or CSV. Conversely, if you want other apps to paste correctly from a copy made in your own app, offer both a rich format and a plain format at copy time.
Why can I no longer paste after I close the app I copied from?
Because the source is using delayed rendering. Apps that handle large data do not place the payload at copy time; they register only a promise on the clipboard that they will "produce it when asked". If the source then exits without materializing the data in response to WM_RENDERALLFORMATS at shutdown, any format that has not yet been rendered is lost. An app that uses the OLE clipboard (IDataObject) can keep paste working after exit by calling OleFlushClipboard at shutdown to materialize the data.
How can my own app watch the clipboard for changes?
The currently recommended method is to register your window as a listener with AddClipboardFormatListener and handle the WM_CLIPBOARDUPDATE message that arrives every time the contents change. Polling the contents on a timer wastes work and can miss updates, and the old viewer chain based on SetClipboardViewer is kept only for backward compatibility, because a bug in one app in the chain breaks the whole chain. Note also that OpenClipboard on a read can fail because another process holds the clipboard, so implement a retry with a short wait if you want the read to be stable.
Is there a way to keep secrets such as passwords out of clipboard history (Win+V)?
There are two levers, one on the app side and one on the policy side. On the app side, if you also place the registered format ExcludeClipboardContentFromMonitorProcessing when you copy, that content is included in neither history nor cross-device sync. You can also control each independently with CanIncludeInClipboardHistory (history only) and CanUploadToCloudClipboard (sync only). This is the mechanism password managers use. If you want to turn it off for the whole organization, you can disable history and cloud sync themselves with AllowClipboardHistory and AllowCrossDeviceClipboard via Group Policy or Intune (Policy CSP).
Why can I not drag and drop a file onto an app that is running as administrator?
Because a security mechanism called UIPI (User Interface Privilege Isolation) blocks message delivery from a lower-integrity process to a higher-integrity window. File Explorer runs at ordinary privilege (medium integrity), so drag-and-drop notifications never reach the window of an elevated app. A workaround that individually allows messages such as WM_DROPFILES with ChangeWindowMessageFilterEx is well known, but it applies only to the older drop notification. The real fix is to stop designing the app to run elevated all the time, and to isolate only the work that needs elevation into a separate process.

Author Profile

Profile page for the article author.

Go Komura

Representative of KomuraSoft LLC

Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.

Back to the Blog