That's a bit unfair. The Win32 apis were built with needing to propagate errors across different language barriers (i.e. so you could create MyAwesomeProgram in VB or Pascal or Fortran rather than being forced to use C/C++ in order to interoperate with the Microsoft libraries written in C/C++).
You can't unwind an exception over someone else's code (because you don't know how they are compiled), so you can't use exceptions, heap-pointer based exceptions (like C++) are also bad because it restricts what you can throw (i.e. you can't throw an out-of-memory exception if you'd need to allocate an OOM exception to throw one)
You also can't intern stuff like Java and C# because you don't have a common runtime (because some of the code in the process is Win32 C/C++, some is MyAwesomeProgram.vb and a third party component it is using is in delphi pascal, all interacting with a lovely frontend written in flash).
So sadly Microsoft only has one thing left: A global enum of all possible errors, i.e. HRESULTs.
Basically what I'm saying is that HRESULTs are dirty, but that's because they're intended for crossing language or API boundaries. Microsoft never intended or expected you to use them internally. They expected you to write code like this:
In fact, that is pretty much exactly what C#/VB.Net is doing under the hood.
It's not the fault of HRESULTs that they are crappy. It's the fault of developers for daring to attempt writing code in anything other than the glorious language of C/C++. If all developers could agree to only ever use C++, I'm sure Microsoft would be happy to dump HRESULTs and move to std::exceptions