WDF performs a lot of state tracking for you, from PnP to power state to the number of outstanding I/Os. In particular to this question, WDF abstracts surprise removal and graceful removal from you. WDF also handles all of the standard things that you must perform on surprise or graceful removal: disabling device interfaces, destroying symbolic links, unregistering from WMI, etc. But what if you need release your own device specific state (such a device index number that is constant for this device instance) in the context of the correct PnP IRP? Remember that you can't just clean up on the PnP remove IRP since your device can be stuck in the surprise removed state as long as there is an open handle against it. Since the difference between the removals is abstracted away, this would appear to be a difficult problem to solve by staying within the framework. I'll first the correct solution and then present 2 potential solutions which should not be used, but appear to be very attractive solutions.
The correct solution is to register for EvtDeviceSelfManagedIoFlush or IPnpCallbackSelfManagedIo::OnSelfManagedIoFlush. It is called in the correct PnP IRP context for both situations. It is called before these PnP IRPs are completed, so there is no chance of the same device instance enumerating again while this callback is running (e.g. the original device instance is stuck in the surprise removed state and the device is plugged in again). It is synchronized against the other power up/down callbacks as well.
KMDF only: one potential solution is to set a WDM IRP preprocess routine for IRP_MN_SURPRISE_REMOVAL and IRP_MN_REMOVE_DEVICE. This would require you to track your own state and you would have to remove your device specific state before KMDF processed either of these IRPs. A lot of work without a lot of gain, plus you lost out on all the WDF contracts that are provided to you.
Another potential solution is to register for EvtDeviceSurpriseRemoval or implement IPnpCallback::OnSurpriseRemoval. This provides you with half the solution, but ignores the other half of the problem, graceful removal. You could track state in the surprise removal callback and then check this state in your device's cleanup routine/destructor. This does have the benefit of working within the framework and its contracts, but it still requires you to track state. Furthermore, the surprise removal callback is not synchronized with the other power up/down callbacks which is another detractor.