How can I automate the testing of the printing functionality of my WPF application, including changing common printing properties like paper size, tray, landscape/portrait etc?
I'm thinking about using some sort of virtual printer driver like EmfPrinter, but I still have no idea how to verify, for example, that a paper tray has been set correctly.
Your testing problem may contain unit-testing aspects, but from the description it appears to (at least also) address integration testing aspects. For example you ask
I read this in the following way: You want to be sure that the printing functionality of your software achieves that on the real physical printers the paper tray has been set correctly. If this understanding is correct, then this goes beyond what you can achieve with unit-testing:
With unit-testing you try to find the bugs in small, isolated software pieces. Isolation is achieved by replacing components that the software-under-test (SUT) depends on. These replacement components are often called doubles, mocks or stubs. With such doubles, however, you can not find those bugs, which are related to misconceptions about how the interaction of the SUT with the depended-on components should work: Since you implement the doubles, you implement them based on your potential misconceptions.
For the printer functionality this means, if you double the paper tray selection function, then you can test certain aspects of the unit, but you will still not know if it interacts correctly with the real paper tray selection function. This is not a flaw of unit-testing: It is just the reason why in addition to unit-testing you also need integration and system testing.
To address all aspects of your testing problem you can make your life easier: Design your code such that you try to separate computations from interactions. You would then test the units (functions, methods, ...) that contain computations with unit-testing. Those parts that contain the interactions you would test with integration testing. There will likely remain code parts where such a separation is difficult and you are left with computations mixed with interactions. Here you may have to do the unit-testing with doubles (ensuring by design that you can inject them - see dependency injection and inversion of control). But, this strategy in general can save you a lot of effort for creation of doubles.