Over the years, I have seen many questions being asked on newsgroups and developer forums concerning the SHFileOperation API. This API allows developers to perform file operations such as Copy, Move, Rename and Delete as well as provide rich UI for confirmation, error and progress dialogs.
The API uses a struct named SHFILEOPSTRUCT, which contains many members and possible flags/values for those members. As a consequence, there are many dark corners of the API that need better clarification. While the documentation on MSDN contains helpful nuggets of information, I wanted to address some of the more commonly asked questions in this multi-part blog entry.
Part 1: Why does SHFileOperation return incorrect error codes that have nothing to do with what actually went wrong?
The newsgroups are filled with developers asking questions such as: "SHFileOperation returned error code 117 which maps to ERROR_INVALID_CATEGORY. This doesn't make any sense!"
In the context of Win32 - that developer is correct. The description for ERROR_INVALID_CATEGORY in winerror.h is: "The IOCTL call made by the application program is not correct." What in the world does SHFileOperation have to do with IOCTL errors??? Absolutely nothing. SHFileOperation isn't returning ERROR_INVALID_CATEGORY. In this example it is actually returning DE_OPCANCELLED (0x75). Don't bother searching for what DE_OPCANCELLED is. It's not documented.
Many of the error codes returned from SHFileOperation are either old DOS codes from the pre- Win32 era, or custom error codes defined in the copy engine for the old File Manager. The SHFileOperation API was created from this code and first appeared in Windows 95/NT 4. At the time, changing the error codes was not foreseen as a priority. The problem that we see today is that these error codes overlap with those defined in winerror.h. Other Shell APIs behave the same way. For example, the ShellExecute API documentation states that any return code lower than 32 is an error. Above that, it succeeded. These are old DOS error codes as well.
For reference, below is a table of the error codes returned from SHFileOperation along with their corresponding description.
Error Code | Value | Description |
DE_SAMEFILE | 0x71 | Source and destination file are the same |
DE_MANYSRC1DEST | 0x72 | Multiple paths were specified in the source of the operation, but only one destination path |
DE_DIFFDIR | 0x73 | Rename operation was specified but the destination path is a different directory. Use move instead. |
DE_ROOTDIR | 0x74 | Source is a root directory, cannot be moved or renamed |
DE_OPCANCELLED | 0x75 | Operation was cancelled by the user (or silently cancelled if the specified flags were supplied to SHFileOperation) |
DE_DESTSUBTREE | 0x76 | The destination is a sub-tree of the source |
DE_ACCESSDENIEDSRC | 0x78 | Security problems on source |
DE_PATHTOODEEP | 0x79 | The source or destination path exceeded or would exceed MAX_PATH |
DE_MANYDEST | 0x7A | Operation involved multiple destination paths which can fail in the case of a move operation |
DE_INVALIDFILES | 0x7C | The paths in the source or destination were invalid |
DE_DESTSAMETREE | 0x7D | Source and destination have the same parent folder. |
DE_FLDDESTISFILE | 0x7E | The destination path is to an existing file |
DE_FILEDESTISFLD | 0x80 | The destination path is to an existing folder |
DE_FILENAMETOOLONG | 0x81 | The name of the file exceeds MAX_PATH |
DE_DEST_IS_CDROM | 0x82 | Destination is a Read-Only CDRom, possibly unformatted |
DE_DEST_IS_DVD | 0x83 | Destination is a Read-Only DVD, possibly unformatted |
DE_DEST_IS_CDRECORD | 0x84 | Destination is a Recordable (AudioL) CDRom, possibly unformatted |
DE_FILE_TOO_LARGE | 0x85 | The file involved in the operation is too large for the destination media or file system |
DE_SRC_IS_CDROM | 0x86 | Source is a Read-Only CDRom, possibly unformatted |
DE_SRC_IS_DVD | 0x87 | Source is a Read-Only DVD, possibly unformatted |
DE_SRC_IS_CDRECORD | 0x88 | Source is a Recordable (AudioL) CDRom, possibly unformatted |
DE_ERROR_MAX | 0xB7 | MAX_PATH was exceeded during the operation. |
Generic Error | 0x402 | An unknown error occurred. This is typically due to invalid paths in the source or destination. This error does not occur on Vista and greater. |
ERRORONDEST | 0x10000 | An unspecified error occurred on the destination. |
DE_ROOTDIR|ERRORONDEST | 0x10074 | Destination is a root directory, cannot be renamed |
Correcting this error code overlap at this point is simply not practical. Too many applications are written to expect these specific error codes. Changing SHFileOperation to return valid winerror.h defined error codes could break these applications. Please note that the above error codes are subject to change (as they have in previous versions of Windows) and I want to stress that there is NO OFFICIAL SUPPORT FOR THESE ERROR CODES. As MSDN calls out: Your code should only check if SHFileOperation returned zero or non-zero. I only call them out here to add some clarity for those developers debugging their code. If you want to know for sure why an operation failed you will have to roll your own operations engine.
The silver lining to this story is that in Vista a new interface has been written to allow developers to move away from SHFileOperation's confusing error codes to correctly mapped HRESULT values. That interface is IFileOperation and will be the topic of later posts.