PowerShell codebase misuses SHEmptyRecycleBin function in Clear-RecycleBin cmdlet

Update on 28 October 2019: This issue is finally resolved in pull request #10909. (The last modified date is kept unchanged since this is not something super exciting.)

The issue is submitted to PowerShell repository as issue #6743.

Clear-RecycleBin is a cmdlet that clears your recycle bin. Internally, it calls SHEmptyRecycleBin function. It has been malfunctioning for a long time: when you run the cmdlet for the first time in a PowerShell session with Force switch on, it produces an ErrorRecord. Further investigation shows that it is detecting error status of SHEmptyRecycleBin the wrong way. To make things worse, SHEmptyRecycleBin is really bad at error handling.

If you open PowerShell, and type Clear-RecycleBin -Force, you will see the following error:

Clear-RecycleBin : The system cannot find the path specified
At line:1 char:1
+ Clear-RecycleBin -Force
+ ~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (RecycleBin:String) [Clear-RecycleBin], Win32Exception
    + FullyQualifiedErrorId : FailedToClearRecycleBin,Microsoft.PowerShell.Commands.ClearRecycleBinCommand

You recycle bin is emptied despite an ErrorRecord is produced. Interestingly, if you repeat the same command a second time in the same session, no error will be written. Moreover, if, when you are running the cmdlet for the first time in a PowerShell session, instead of switching on Force, you manually confirm the operation, no ErrorRecord will be written. This problem has been there for long. What’s wrong with Clear-RecycleBin?

Now that PowerShell is open source (a rather earlier ‘now’, and by the way, China’s law allows users to study how a piece of software is designed [clause 22 of this document], so presumably you could have used ILSpy or other things to inspect the source code), we can have a look at how this cmdlet is implemented. Let’s come to this method. On line 222, the method calls SHEmptyRecycleBin shell function, and then retrieves Win32 error code with Marshal.GetLastWin32Error method. Moreover, the comment explains the three possible cases of success:

  • The last error code is 0, which is the default success code.
  • The last error code is 203, which means the recycle bin is already emptied.
  • The last error code is 18, which means there are no more files in the given recycle bin.

However, according to the documentation of SHEmptyRecycleBin function (Windows) on MSDN, the API returns HRESULT and there’s no indication on using SetLastError to indicate error status. Now we may explain the strange behaviour of Force switch and subsequent calls. It could be that ShouldProcess calls some Win32 API that SetsLastError, and the error code was mistaken for that set by SHEmptyRecycleBin.

Now let’s see how SHEmptyRecycleBin returns its error status. The documentation says S_OK is returned when the operation succeeds, but does not specify or list common error codes the function might return. In my tests, if the recycle bin is already empty, the call returns E_UNEXPECTED (unexpected/catastrophic failure). Well, guess you’re not expected to clear an empty recycle bin, do you? In my humble opinion, returning E_UNEXPECTED is simply unacceptable when the shell API could have detected the specific error.

Perhaps Clear-RecycleBin was written when SHEmptyRecycleBin was using SetLastError? I said that in the GitHub issue, but I now know that this isn’t possible. According to documentation of Clear-RecycleBin, it was introduced in PowerShell 5.0, which comes with Windows 10 RTM, and I believe the documentation for SHEmptyRecycleBin has not changed since then. Perhaps it’s a documentation issue and SHEmptyRecycleBin does call SetLastError? Even if that were true, the implementation of Clear-RecycleBin is a big mess. If you reflect the declaration of NativeMethod.SHEmptyRecycleBin, you will see that the DllImportAttribute does not set SetLastError boolean property to true, which means Marshal.GetLastWin32Error will never return the error code set by SHEmptyRecycleBin, if it were ever set. Whether an ErrorRecord was written doesn’t depend on the call to SHEmptyRecycleBin, but some random leftover of other calls!

Microsoft, you’ve got to have quality assurance and code review.

Sidenote I wish Raymond Chen could write a blog entry on SHEmptyRecycleBin if there’s something interesting to add. He’s an expert of Windows shell programming. However, his suggestion box is currently closed and there’s no prescribed way to make a suggestion (though I could have sent him an email, but that would be too impolite), so I can only make wishes.

Please enable JavaScript to view the comments powered by Disqus.