How hard can it be to add a printer?Under the hood, I was just using WMI to add printers and printer ports. I could easily update and delete existing printers. I could also create new printer ports without issue. My major road block was that my code to create a new printer would only fail when ran with Start-DscConfiguration. One little feature that is kind of critical to the whole project.
Win32_Printer.Put()I was creating a new Win32_Printer object and using the .Put() command to save it. The error message I got was very nondescript. I used a try/catch block to report back the inner message of the exception. Access Denied is all it said.
I tried creating WMI objects different ways but I was not making any progress. I validated all the properties and tested setting more/less of them at once. I took a closer look at the .Put() command and found it had a putOptions overload. I had to track down how to create the right object because it didn't accept the raw int values. I discovered that there was a .psbase.put() command that I was not using before. Trial and error.
VBScriptI also discovered that Powershell and VBScript use different wrappers for WMI objects. For some reason VBScript uses a .put_() command (yes, that underscore is intentional) to commit changes. I was able to use an existing VBScript in the Windows folder so I didn't have to write my own. It worked well enough with manual testing, but it also failed to work correctly when ran as a DscResource. I had hope that the different wrappers would somehow make a difference. But I was mistaken.
CIM_PrinterI would have loved to have used New-Printer but I am working with Windows 7. But that gave me an idea to look at the CIM instances. Again, a different wrapper. While the CIM_Printer class is easy to pull data out with, I don't think it was ever intended to be used for adding printers. I tried to create a new object but did not have much luck. As I explored the object, all the properties look to be read only. When trying to create a new one, it complains about the key fields missing. Yet when I tried to work that out, I didn't make any progress.
rundll32.exeI figured there had to be some way to add a printer from the command line without using WMI. I tracked down a rundll32 command that I used in the past for other things (Rundll32 printui.dll,PrintUIEntry). I had a good feeling that it would have worked. I didn't like the required parameters as much. Mostly because it wanted more details around the driver than I was using. Something I could change, but it didn't feel as clean as I wanted this part to be. But it did give me another idea.
pInvokeI started to look at system calls and found the AddPrinter that I was looking for. I required exactly what I wanted it to require. It was almost too good to be true. I was then looking at my next challenge. How do I pInvoke from Powershell? I found a little snip of code where they used C# to do the pInvoke from Powershell. That looked like as good of an approach as any.
C# and PowershellI had my C# code fleshed out and needed to get it running in Powershell. I want to avoid having an external assembly so I had to figure out how to do it more inline. The Add-Type command turned out to be exactly what I was looking for. After importing my C# code, I was off an running.
It just worksI was able to drop it into my DSC resource with minimal adjustment and it worked perfectly. That last piece fell into place and my DSC resource was working wonderfully. This project started out as one of the most basic resources that I could think of and it ended up taking me on journey across many different technologies before I was done.
The end result is a TCPPrinter DSC resource that I have added to my KevMar modules.