📜 ⬆️ ⬇️

Working with printing devices in C # on the example of implementing a virtual printer

Greetings to all. In today's article, I’ll talk about how you can implement your own high-level API in managed code to work with printing devices, from installing a new print monitor in the system to getting the document processed by the print device driver from the printer port.

Like last time , the article will be useful for familiarizing developers of junior and middle managers. In the process of studying the material, you will learn how to access the low-level WinAPI DLLs in C # using P / Invoke , how to install, configure and remove print monitors from the system, the printer driver, the print device itself, open and link the port to redirect input data from the device print on the monitor, get acquainted with the key points of marshalling . We will also use a practical example to figure out how using our API you can conveniently manipulate printing devices in the system, find out how you can intercept the processed data after printing from a printer and, for example, send them to a server.

Formulation of the problem


First, I propose to outline an approximate list of tasks that we will need to solve in this article. Let it look like this:


In this article, we will use the monitor monitor mfilemon.dll , already implemented by the Italian developer Lorenzo Monti, and use the official Microsoft PostScript Printer Driver as the printer driver. In fact, the monitor and the driver can be any, depending on the requirements of your task.
')

Some theory


I will not consider in this article the theoretical basis for printing documents, the differences between printers with PCL / PostScript and GDI printers, and other bases. All the necessary information on this topic can be found in abundance in the vast network. But to know something is still necessary in order to better understand how to realize the tasks set before us.

Let's start with the most important thing - the process of printing in Windows is managed by the Spooler service. The C: / Windows / System32 / directory has its own low-level winspool.drv driver, which has many entry points for accessing the print service and performing a variety of actions, from getting the system directory of print drivers and the name of the default printer in the system to manipulation of the print queue. For those who want to write their own monitor (about which, I may sometime also tell in one of the future articles), besides winspool.h, winsplp.h from the DDK is also needed, representing additional functionality for assembling a driver in accordance with the Spooler specification .

Further, a full-featured printing device in Windows, in simple terms, consists of a print monitor, a port open on the monitor, the actual printing device (printer) and a pre-installed driver for it (Fig. 1). Multiple ports can be open on one monitor at once, and multiple printers can be linked to one port at once. This is important to consider when removing a component from the system, if, for example, you want to remove a specific port, you will first need to pull down and all printing devices that are tied to it.

Picture 1

P / Invoke


Platform Invocation Services (PInvoke) is a platform for accessing DLL entry points (functions) written in unmanaged code (C / C ++ and WinAPI) from managed code (C # / VB and .NET). In order to access the low-level code from .NET, we need to describe the external methods from the DLL, as well as the structures that are used in them. Consider each of the problems in order in the examples.

Example 1 Calling the GetPrinterDriverDirectory method from the winspool.drv driver.
First of all, we need to know what the method returns and what it takes in the arguments when called. To do this, we climb into the documentation and read the description of the method signature. Please note that in the future we will have to turn to the documentation of the low-level API quite often, then in the course of the article I will no longer indicate the need for this action when implementing certain methods / structures, since they are required by default.

BOOL GetPrinterDriverDirectory( _In_ LPTSTR pName, _In_ LPTSTR pEnvironment, _In_ DWORD Level, _Out_ LPBYTE pDriverDirectory, _In_ DWORD cbBuf, _Out_ LPDWORD pcbNeeded ); 

A description of each function parameter can also be found in the documentation. It is important to understand here that parameters can only be input (In), only output (Out), both input and output simultaneously (In / Out), as well as optional (either input or output, depending on other parameters) . We also need to know which type of data in .NET needs to be mapped to a WDT type (here, in most cases, the rule “The size of the allocated memory to the relevant .NET types corresponds to the size of the allocated memory to the basic C ++ types, the rest have IntPtr ”).

Now, based on this information, we will describe the method in managed C # code:

 [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)] extern bool GetPrinterDriverDirectory(string serverName, string environment, uint level, [Out] StringBuilder driverDirectory, uint bufferSize, ref uint bytesNeeded); 

The DllImportAttribute attribute allows us to specify the parameters for accessing the low-level entry point. In WinAPI, most of the functions are written taking into account the two main encodings - Unicode and ANSI, the functions have the terminations W and A, respectively. If we need to refer to a specific method in the encoding, but we don’t want to refactor the main name of the described method, we can specify the name of the entry point explicitly, passing it to the corresponding attribute argument (for example, GetPrinterDriverDirectoryW). Also, do not forget to specify the argument CharSet = CharSet.Unicode (in our case, the encoding is determined automatically). For all other useful attributes you can find information in the official documentation .

The attribute InAttribute in most cases can be omitted, because Arguments in C # methods are by default passed by value. We specify the OutAttribute attribute in cases where the type of the argument passed is a reference, but the data must be output. For the output of the arguments of significant types, we specify ref , i.e. pass the argument by reference.

Example 2 Calling the AddMonitor method from the winspool.drv driver.

This example uses the data structure MONITOR_INFO_2 , which we will need to first describe in C # code.

 typedef struct _MONITOR_INFO_2 { LPTSTR pName; LPTSTR pEnvironment; LPTSTR pDLLName; } MONITOR_INFO_2, *PMONITOR_INFO_2; BOOL AddMonitor( _In_ LPTSTR pName, _In_ DWORD Level, _In_ LPBYTE pMonitors ); 

Here it is important to understand that structures, speaking in "popular" language, are a set of allocated memory sections of a certain size that store values. Each of these sections represents a data field of a certain type. There is no talk about field names and their attributes, no metadata inherent in the variable types (classes), only the size of the allocated memory for storing data. This means that the names of the members of the structure, as well as the very name of the structure, can be any, it is important to observe the correspondence of the allocated memory sizes to each member. For such purposes, there is the StructLayoutAttribute attribute that allows you to control the physical layout of the data fields of a class or structure in memory. There are many ways to control the placement of these fields in memory, you can explicitly set the offset of the fields, specify the absolute size of the structure, set the encoding for the marshaling method, package, place the field segments in the order of succession, etc. Examples of implementations of these methods can be found here . Specifically, the last method that we specify through LayoutKind.Sequential is perfect for our task.

 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] struct MonitorInfo { [MarshalAs(UnmanagedType.LPTStr)] public string Name; [MarshalAs(UnmanagedType.LPTStr)] public string Environment; [MarshalAs(UnmanagedType.LPTStr)] public string DllName; } [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)] static extern bool AddMonitor(string serverName, uint level, ref MonitorInfo monitor); 

How it works: we declared the structure, indicated in the attribute the placement of fields in memory using LayoutKind.Sequential, indicated the data types of the fields, for WinAPI the data types in the structures are significant, which means we know their size, in unmanaged code this is sizeof () , in the managed Marshal.SizeOf () . Everything.

Marshaling


The term "marshaling" (it is also "marshaling", it is also "Marshalling") describes the process of converting data stored in memory from one view to another. In the case of .NET in general and P / Invoke in particular, this is a type conversion from unmanaged code to CLR. Marshaling makes it easier to work with memory in managed code. For these purposes, there are two main classes - Marshal and MarshalAsAttribute . The MarshalAsAttribute attribute allows you to explicitly specify the type mapping for conversion from unmanaged to managed code (like mapping during type serialization). It can be applied only to type fields, to method parameters with the specification specified via param:, as well as to the return value via return:. The Marshal class contains many useful static methods for working with pointers, allocating memory, sizing, shifting, and more. We also need the FlagsAttribute attribute, which allows you to customize the conversion of low-level bit flags to enum 's C #.

Future API Architecture


With the theory figured out, it's time to think about the architecture of our future API. There is no specific or optimal philosophy here; everyone chooses the necessary code design patterns depending on the conditions of the problem being solved. For our case, I decided to do the following: all the code of the future library will consist of two main modules - the “factory” of classes and the interfaces that implement these classes. A public implementation will provide an opportunity to get a list of all installed components in the system, install / remove a component and so on. The internal implementation will work with marshaling and P / Invoke. For specific cases we will be able to create instances of our classes and call their methods, for basic cases we will contact our "factory". Visually, all this can be represented approximately as follows (Fig. 2):



To solve the problem in the framework of the article, we will need the IMonitor, IPort, IDriver and IPrinter implementations, the actual PrintingApi class itself, as well as auxiliary flags. The rest is still omitted.

Code base


First of all, let's write a basic interface for all of our print components:

 /// <summary> ///      ,    ( ,  , ). /// </summary> public interface IPrintableDevice { /// <summary> ///   . /// </summary> string Name { get; } /// <summary> ///      . /// </summary> /// <param name="serverName"> .</param> void Install(string serverName); /// <summary> ///      . /// </summary> /// <param name="serverName"> .</param> void Uninstall(string serverName); } 

Everything is simple here, each component will have a name and two methods for installing / removing in the system, with the ability to work with the remote machine.

Now we will write the basis for our factory:

class PrintingApi
 /// <summary> ///  API     . /// </summary> public class PrintingApi { /// <summary> ///       P/Invoke. /// </summary> /// <param name="serverName"> .</param> /// <param name="level">  .</param> /// <param name="structs">   .</param> /// <param name="bufferSize"> .</param> /// <param name="bytesNeeded">     .</param> /// <param name="bufferReturnedLength">   .</param> /// <returns></returns> internal delegate bool EnumInfo(string serverName, uint level, IntPtr structs, uint bufferSize, ref uint bytesNeeded, ref uint bufferReturnedLength); /// <summary> ///       P/Invoke. /// </summary> /// <param name="serverName"> .</param> /// <param name="environment">.</param> /// <param name="level">  .</param> /// <param name="structs">   .</param> /// <param name="bufferSize"> .</param> /// <param name="bytesNeeded">     .</param> /// <param name="bufferReturnedLength">   .</param> /// <returns></returns> internal delegate bool EnumInfo2(string serverName, string environment, uint level, IntPtr structs, uint bufferSize, ref uint bytesNeeded, ref uint bufferReturnedLength); /// <summary> ///    <see cref="PrintingApi"/>. /// </summary> public static PrintingApi Factory { get; protected set; } /// <summary> ///    <see cref="PrintingApi"/>. /// </summary> static PrintingApi() { Factory = new PrintingApi(); } /// <summary> ///     Spooler API.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="serverName"> .</param> /// <param name="level"> .</param> /// <returns>   Spooler API.</returns> internal static T[] GetInfo<T>(EnumInfo handler, string serverName, uint level) where T : struct { uint bytesNeeded = 0; uint bufferReturnedLength = 0; if (handler(serverName, level, IntPtr.Zero, 0, ref bytesNeeded, ref bufferReturnedLength)) return null; int lastWin32Error = Marshal.GetLastWin32Error(); if (lastWin32Error != PrintingException.ErrorInsufficientBuffer) throw new PrintingException(lastWin32Error); IntPtr pointer = Marshal.AllocHGlobal((int)bytesNeeded); try { if (handler(serverName, level, pointer, bytesNeeded, ref bytesNeeded, ref bufferReturnedLength)) { IntPtr currentPointer = pointer; T[] dataCollection = new T[bufferReturnedLength]; Type type = typeof(T); for (int i = 0; i < bufferReturnedLength; i++) { dataCollection[i] = (T)Marshal.PtrToStructure(currentPointer, type); currentPointer = (IntPtr)(currentPointer.ToInt64() + Marshal.SizeOf(type)); } return dataCollection; } throw new PrintingException(Marshal.GetLastWin32Error()); } catch (Exception e) { throw new PrintingException(e.Message, e); } finally { Marshal.FreeHGlobal(pointer); } } /// <summary> ///     Spooler API.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="serverName"> .</param> /// <returns>   Spooler API.</returns> internal static T[] GetInfo<T>(EnumInfo handler, string serverName) where T : struct => GetInfo<T>(handler, serverName, 2); /// <summary> ///     Spooler API.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="level"> .</param> /// <returns>   Spooler API.</returns> internal static T[] GetInfo<T>(EnumInfo handler, uint level) where T : struct => GetInfo<T>(handler, null, level); /// <summary> ///     Spooler API.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <returns>   Spooler API.</returns> internal static T[] GetInfo<T>(EnumInfo handler) where T : struct => GetInfo<T>(handler, null); /// <summary> ///     Spooler API.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="serverName"> .</param> /// <param name="arg">  .</param> /// <param name="level"> .</param> /// <returns>   Spooler API.</returns> internal static T[] GetInfo<T>(EnumInfo2 handler, string serverName, string arg, uint level) where T : struct { uint bytesNeeded = 0; uint bufferReturnedLength = 0; if (handler(serverName, arg, level, IntPtr.Zero, 0, ref bytesNeeded, ref bufferReturnedLength)) return null; int lastWin32Error = Marshal.GetLastWin32Error(); if (lastWin32Error != PrintingException.ErrorInsufficientBuffer) throw new PrintingException(lastWin32Error); IntPtr pointer = Marshal.AllocHGlobal((int)bytesNeeded); try { if (handler(serverName, arg, level, pointer, bytesNeeded, ref bytesNeeded, ref bufferReturnedLength)) { IntPtr currentPointer = pointer; T[] dataCollection = new T[bufferReturnedLength]; Type type = typeof(T); for (int i = 0; i < bufferReturnedLength; i++) { dataCollection[i] = (T)Marshal.PtrToStructure(currentPointer, type); currentPointer = (IntPtr)(currentPointer.ToInt64() + Marshal.SizeOf(type)); } return dataCollection; } throw new PrintingException(Marshal.GetLastWin32Error()); } catch (Exception e) { throw new PrintingException(e.Message, e); } finally { Marshal.FreeHGlobal(pointer); } } /// <summary> ///     Spooler API.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="serverName"> .</param> /// <param name="arg">  .</param> /// <returns>   Spooler API.</returns> internal static T[] GetInfo<T>(EnumInfo2 handler, string serverName, string arg) where T : struct => GetInfo<T>(handler, serverName, arg, 2); /// <summary> ///     Spooler API.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="arg">  .</param> /// <param name="level"> .</param> /// <returns>   Spooler API.</returns> internal static T[] GetInfo<T>(EnumInfo2 handler, string arg, uint level) where T : struct => GetInfo<T>(handler, null, arg, level); /// <summary> ///     Spooler API.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="arg">  .</param> /// <returns>   Spooler API.</returns> internal static T[] GetInfo<T>(EnumInfo2 handler, string arg) where T : struct => GetInfo<T>(handler, null, arg); /// <summary> ///     Spooler API.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="level"> .</param> /// <returns>   Spooler API.</returns> internal static T[] GetInfo<T>(EnumInfo2 handler, uint level) where T : struct => GetInfo<T>(handler, null, level); /// <summary> ///     Spooler API.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <returns>   Spooler API.</returns> internal static T[] GetInfo<T>(EnumInfo2 handler) where T : struct => GetInfo<T>(handler, null); /// <summary> ///     Spooler API      True.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="serverName"> .</param> /// <param name="level"> .</param> /// <param name="dataCollection">   Spooler API.</param> /// <param name="e">,    .</param> /// <returns>True,    ,  False.</returns> internal static bool TryGetInfo<T>(EnumInfo handler, string serverName, uint level, out T[] dataCollection, out PrintingException e) where T : struct { dataCollection = null; e = null; try { dataCollection = GetInfo<T>(handler, serverName, level); return true; } catch (PrintingException ex) { e = ex; } return false; } /// <summary> ///     Spooler API      True.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="serverName"> .</param> /// <param name="level"> .</param> /// <param name="dataCollection">   Spooler API.</param> /// <returns>True,    ,  False.</returns> internal static bool TryGetInfo<T>(EnumInfo handler, string serverName, uint level, out T[] dataCollection) where T : struct => TryGetInfo(handler, serverName, level, out dataCollection, out PrintingException e); /// <summary> ///     Spooler API      True.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="serverName"> .</param> /// <param name="dataCollection">   Spooler API.</param> /// <param name="e">,    .</param> /// <returns>True,    ,  False.</returns> internal static bool TryGetInfo<T>(EnumInfo handler, string serverName, out T[] dataCollection, out PrintingException e) where T : struct => TryGetInfo(handler, serverName, 2, out dataCollection, out e); /// <summary> ///     Spooler API      True.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="serverName"> .</param> /// <param name="dataCollection">   Spooler API.</param> /// <returns>True,    ,  False.</returns> internal static bool TryGetInfo<T>(EnumInfo handler, string serverName, out T[] dataCollection) where T : struct => TryGetInfo(handler, serverName, 2, out dataCollection, out PrintingException e); /// <summary> ///     Spooler API      True.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="level"> .</param> /// <param name="dataCollection">   Spooler API.</param> /// <param name="e">,    .</param> /// <returns>True,    ,  False.</returns> internal static bool TryGetInfo<T>(EnumInfo handler, uint level, out T[] dataCollection, out PrintingException e) where T : struct => TryGetInfo(handler, null, level, out dataCollection, out e); /// <summary> ///     Spooler API      True.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="level"> .</param> /// <param name="dataCollection">   Spooler API.</param> /// <returns>True,    ,  False.</returns> internal static bool TryGetInfo<T>(EnumInfo handler, uint level, out T[] dataCollection) where T : struct => TryGetInfo(handler, null, level, out dataCollection, out PrintingException e); /// <summary> ///     Spooler API      True.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="dataCollection">   Spooler API.</param> /// <param name="e">,    .</param> /// <returns>True,    ,  False.</returns> internal static bool TryGetInfo<T>(EnumInfo handler, out T[] dataCollection, out PrintingException e) where T : struct => TryGetInfo(handler, null, out dataCollection, out e); /// <summary> ///     Spooler API      True.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="dataCollection">   Spooler API.</param> /// <returns>True,    ,  False.</returns> internal static bool TryGetInfo<T>(EnumInfo handler, out T[] dataCollection) where T : struct => TryGetInfo(handler, null, out dataCollection, out PrintingException e); /// <summary> ///     Spooler API      True.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="serverName"> .</param> /// <param name="arg">  .</param> /// <param name="level"> .</param> /// <param name="dataCollection">   Spooler API.</param> /// <param name="e">,    .</param> /// <returns>True,    ,  False.</returns> internal static bool TryGetInfo<T>(EnumInfo2 handler, string serverName, string arg, uint level, out T[] dataCollection, out PrintingException e) where T : struct { dataCollection = null; e = null; try { dataCollection = GetInfo<T>(handler, serverName, arg, level); return true; } catch (PrintingException ex) { e = ex; } return false; } /// <summary> ///     Spooler API      True.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="serverName"> .</param> /// <param name="arg">  .</param> /// <param name="level"> .</param> /// <param name="dataCollection">   Spooler API.</param> /// <returns>True,    ,  False.</returns> internal static bool TryGetInfo<T>(EnumInfo2 handler, string serverName, string arg, uint level, out T[] dataCollection) where T : struct => TryGetInfo(handler, serverName, arg, level, out dataCollection, out PrintingException e); /// <summary> ///     Spooler API      True.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="serverName"> .</param> /// <param name="arg">  .</param> /// <param name="dataCollection">   Spooler API.</param> /// <param name="e">,    .</param> /// <returns>True,    ,  False.</returns> internal static bool TryGetInfo<T>(EnumInfo2 handler, string serverName, string arg, out T[] dataCollection, out PrintingException e) where T : struct => TryGetInfo(handler, serverName, arg, 2, out dataCollection, out e); /// <summary> ///     Spooler API      True.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="serverName"> .</param> /// <param name="arg">  .</param> /// <param name="dataCollection">   Spooler API.</param> /// <returns>True,    ,  False.</returns> internal static bool TryGetInfo<T>(EnumInfo2 handler, string serverName, string arg, out T[] dataCollection) where T : struct => TryGetInfo(handler, serverName, arg, 2, out dataCollection, out PrintingException e); /// <summary> ///     Spooler API      True.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="arg">  .</param> /// <param name="level"> .</param> /// <param name="dataCollection">   Spooler API.</param> /// <param name="e">,    .</param> /// <returns>True,    ,  False.</returns> internal static bool TryGetInfo<T>(EnumInfo2 handler, string arg, uint level, out T[] dataCollection, out PrintingException e) where T : struct => TryGetInfo(handler, null, arg, level, out dataCollection, out e); /// <summary> ///     Spooler API      True.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="arg">  .</param> /// <param name="level"> .</param> /// <param name="dataCollection">   Spooler API.</param> /// <returns>True,    ,  False.</returns> internal static bool TryGetInfo<T>(EnumInfo2 handler, string arg, uint level, out T[] dataCollection) where T : struct => TryGetInfo(handler, null, arg, level, out dataCollection, out PrintingException e); /// <summary> ///     Spooler API      True.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="arg">  .</param> /// <param name="dataCollection">   Spooler API.</param> /// <param name="e">,    .</param> /// <returns>True,    ,  False.</returns> internal static bool TryGetInfo<T>(EnumInfo2 handler, string arg, out T[] dataCollection, out PrintingException e) where T : struct => TryGetInfo(handler, null, arg, out dataCollection, out e); /// <summary> ///     Spooler API      True.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="arg">  .</param> /// <param name="dataCollection">   Spooler API.</param> /// <returns>True,    ,  False.</returns> internal static bool TryGetInfo<T>(EnumInfo2 handler, string arg, out T[] dataCollection) where T : struct => TryGetInfo(handler, null, arg, out dataCollection, out PrintingException e); /// <summary> ///     Spooler API      True.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="level"> .</param> /// <param name="dataCollection">   Spooler API.</param> /// <param name="e">,    .</param> /// <returns>True,    ,  False.</returns> internal static bool TryGetInfo<T>(EnumInfo2 handler, uint level, out T[] dataCollection, out PrintingException e) where T : struct => TryGetInfo(handler, null, level, out dataCollection, out e); /// <summary> ///     Spooler API      True.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="level"> .</param> /// <param name="dataCollection">   Spooler API.</param> /// <returns>True,    ,  False.</returns> internal static bool TryGetInfo<T>(EnumInfo2 handler, uint level, out T[] dataCollection) where T : struct => TryGetInfo(handler, null, level, out dataCollection, out PrintingException e); /// <summary> ///     Spooler API      True.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="dataCollection">   Spooler API.</param> /// <param name="e">,    .</param> /// <returns>True,    ,  False.</returns> internal static bool TryGetInfo<T>(EnumInfo2 handler, out T[] dataCollection, out PrintingException e) where T : struct => TryGetInfo(handler, null, out dataCollection, out e); /// <summary> ///     Spooler API      True.       P/Invoke. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="handler">-    Spooler API.</param> /// <param name="dataCollection">   Spooler API.</param> /// <returns>True,    ,  False.</returns> internal static bool TryGetInfo<T>(EnumInfo2 handler, out T[] dataCollection) where T : struct => TryGetInfo(handler, null, out dataCollection, out PrintingException e); } 


I think from the documented comments it is clear what is what. Singlton is creating a new static instance of the class in the static constructor, describing two delegates EnumInfo and EnumInfo2 to call native methods for obtaining data in our future classes, describing helper methods over native methods.

In most cases, the whole process of working with native methods will be reduced to the following sequence of actions:


To work with char ** buffers (arrays of strings), I recommend using StringBuilder . It has ready-made overloads working with pointers, as well as all the necessary marshalling functionality is implemented.

To intercept and generate exceptions in our API, we will provide a separate class:

class PrintingException
 /// <summary> ///    . /// </summary> [Serializable] public class PrintingException : Win32Exception { #region Error Codes /// <summary> ///   "  ". /// </summary> public const int ErrorFileNotFound = 2; /// <summary> ///   " ". /// </summary> public const int ErrorInsufficientBuffer = 122; /// <summary> ///   "  ". /// </summary> public const int ErrorModuleNotFound = 126; /// <summary> ///   "   ". /// </summary> public const int ErrorInvalidPrinterName = 1801; /// <summary> ///   "   ". /// </summary> public const int ErrorMonitorUnknown = 3000; /// <summary> ///   "   ". /// </summary> public const int ErrorPrinterDriverIsReadyUsed = 3001; /// <summary> ///   "    ". /// </summary> public const int ErrorPrinterJobFileNotFound = 3002; /// <summary> ///   "    StartDocPrinter". /// </summary> public const int ErrorStartDocPrinterNotCalling = 3003; /// <summary> ///   "    AddJob". /// </summary> public const int ErrorAddJobNotCalling = 3004; /// <summary> ///   "    ". /// </summary> public const int ErrorPrinterProcessorAlreadyInstalled = 3005; /// <summary> ///   "    ". /// </summary> public const int ErrorMonitorAlreadyInstalled = 3006; /// <summary> ///   "      ". /// </summary> public const int ErrorInvalidMonitor = 3007; /// <summary> ///   "     ". /// </summary> public const int ErrorMonitorIsReadyUsed = 3008; #endregion /// <summary> ///     <see cref="PrintingException"/>. /// </summary> public PrintingException() : base() { } /// <summary> ///     <see cref="PrintingException"/>. /// </summary> /// <param name="nativeErrorCode">  Win32.</param> public PrintingException(int nativeErrorCode) : base(nativeErrorCode) { } /// <summary> ///     <see cref="PrintingException"/>. /// </summary> /// <param name="message">  .</param> public PrintingException(string message) : base(message) { } /// <summary> ///     <see cref="PrintingException"/>. /// </summary> /// <param name="nativeErrorCode">  Win32.</param> /// <param name="message">  .</param> public PrintingException(int nativeErrorCode, string message) : base(nativeErrorCode, message) { } /// <summary> ///     <see cref="PrintingException"/>. /// </summary> /// <param name="message">  .</param> /// <param name="innerException"></param> public PrintingException(string message, Exception innerException) : base(message, innerException) { } /// <summary> ///     <see cref="PrintingException"/>. /// </summary> /// <param name="info">  .</param> /// <param name="context">  .</param> public PrintingException(SerializationInfo info, StreamingContext context) : base(info, context) { } } 


For convenience, we immediately registered the main codes of native errors when working with the printing service.

Now we need to implement a couple of enumerated types to more conveniently work with the code and minimize the transfer of invalid arguments to native methods:

 /// <summary> ///  . /// </summary> public enum Environment { /// <summary> ///   . /// </summary> Current, /// <summary> /// Windows NT x86. /// </summary> X86, /// <summary> /// Windows x64. /// </summary> X64, /// <summary> /// Windows IA64. /// </summary> IA64, } /// <summary> ///     . /// </summary> [Flags] public enum PortType { /// <summary> ///  . /// </summary> Write = 0x1, /// <summary> ///  . /// </summary> Read = 0x2, /// <summary> ///  . /// </summary> Redirected = 0x4, /// <summary> ///    . /// </summary> NetAttached = 0x8, } /// <summary> ///   . /// </summary> public enum DataType : uint { RAW = 1, LPR = 2, } 

To convert the Environment to a string and vice versa, we implement two extension methods:

 /// <summary> ///     . /// </summary> public static class PrintingExtensions { /// <summary> ///    ,   WinAPI. /// </summary> /// <param name="environment"> .</param> /// <returns>    .</returns> internal static string GetEnvironmentName(this Environment environment) { switch (environment) { default: return null; case Environment.X86: return "Windows x86"; case Environment.X64: return "Windows x64"; case Environment.IA64: return "Windows IA64"; } } /// <summary> ///  <see cref="Environment"/>,     . /// </summary> /// <param name="environmentString">   .</param> /// <returns><see cref="Environment"/>,     .</returns> internal static Environment GetEnvironment(this string environmentString) { environmentString = environmentString.ToLower(); if (environmentString.Contains("x86")) return Environment.X86; if (environmentString.Contains("x64")) return Environment.X64; if (environmentString.Contains("ia64")) return Environment.IA64; return Environment.Current; } } 

enum Environment , , , .

, :

abstract class PrintableDevice
 /// <summary> ///        . /// </summary> public abstract class PrintableDevice : IPrintableDevice { /// <summary> ///    . /// </summary> public virtual string Name { get; protected set; } /// <summary> ///     <see cref="PrintableDevice"/>. /// </summary> /// <param name="name"></param> /// <exception cref="ArgumentNullException"/> public PrintableDevice(string name) { if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name"); Name = name; } /// <summary> ///       . /// </summary> /// <param name="serverName"> .</param> /// <exception cref="FileNotFoundException" /> /// <exception cref="PrintingException" /> public abstract void Install(string serverName); /// <summary> ///       . /// </summary> /// <exception cref="FileNotFoundException" /> /// <exception cref="PrintingException" /> public void Install() => Install(null); /// <summary> ///       . /// </summary> /// <param name="serverName"> .</param> /// <param name="e">,    .</param> /// <returns>True,     ,  False.</returns> public bool TryInstall(string serverName, out PrintingException e) { e = null; try { Install(serverName); } catch (PrintingException ex) { e = ex; return false; } return true; } /// <summary> ///       . /// </summary> /// <param name="serverName"> .</param> /// <returns>True,     ,  False.</returns> public bool TryInstall(string serverName) => TryInstall(serverName, out PrintingException e); /// <summary> ///       . /// </summary> /// <param name="e">,    .</param> /// <returns>True,     ,  False.</returns> public bool TryInstall(out PrintingException e) => TryInstall(null, out e); /// <summary> ///       . /// </summary> /// <returns>True,     ,  False.</returns> public bool TryInstall() => TryInstall(out PrintingException e); /// <summary> ///       . /// </summary> /// <param name="serverName"> .</param> /// <exception cref="PrintingException" /> public abstract void Uninstall(string serverName); /// <summary> ///       . /// </summary> /// <exception cref="PrintingException" /> public void Uninstall() => Uninstall(null); /// <summary> ///       . /// </summary> /// <param name="serverName"> .</param> /// <param name="e">,    .</param> /// <returns>True,     ,  False.</returns> public bool TryUninstall(string serverName, out PrintingException e) { e = null; try { Uninstall(serverName); } catch (PrintingException ex) { e = ex; return false; } return true; } /// <summary> ///       . /// </summary> /// <param name="serverName"> .</param> /// <returns>True,     ,  False.</returns> public bool TryUninstall(string serverName) => TryUninstall(serverName, out PrintingException e); /// <summary> ///       . /// </summary> /// <param name="e">,    .</param> /// <returns>True,     ,  False.</returns> public bool TryUninstall(out PrintingException e) => TryUninstall(null, out e); /// <summary> ///       . /// </summary> /// <returns>True,     ,  False.</returns> public bool TryUninstall() => TryUninstall(out PrintingException e); } 



. , :

struct MonitorInfo
 /// <summary> ///        . /// </summary> [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] internal struct MonitorInfo { /// <summary> ///  . /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string Name; /// <summary> /// ,      (, Windows NT x86, Windows IA64  Windows x64). /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string Environment; /// <summary> ///   *.dll . /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string DllName; } 


:

interface IMonitor
 /// <summary> ///       . /// </summary> public interface IMonitor : IPrintableDevice { /// <summary> /// ,      (, Windows NT x86, Windows IA64  Windows x64). /// </summary> Environment Environment { get; } /// <summary> ///   *.dll . /// </summary> string Dll { get; } } 


. , , .

, , :

class Monitor
 /// <summary> ///      . /// </summary> public class Monitor : PrintableDevice, IMonitor { /// <summary> /// ,      (, Windows NT x86, Windows IA64  Windows x64). /// </summary> public virtual Environment Environment { get; protected set; } /// <summary> ///   *.dll . /// </summary> public virtual string Dll { get; protected set; } /// <summary> ///        . /// </summary> public static Monitor[] All { get { if (!PrintingApi.TryGetInfo(EnumMonitors, out MonitorInfo[] monitorInfo)) return null; Monitor[] monitors = new Monitor[monitorInfo.Length]; for (int i = 0; i < monitorInfo.Length; i++) monitors[i] = new Monitor(monitorInfo[i].Name, monitorInfo[i].DllName, monitorInfo[i].Environment.GetEnvironment()); return monitors; } } /// <summary> ///     <see cref="Monitor"/>. /// </summary> /// <param name="name">  .</param> /// <param name="dll">  *.dll .</param> /// <param name="environment">,      (, Windows NT x86, Windows IA64  Windows x64).</param> /// <exception cref="ArgumentNullException" /> public Monitor(string name, string dll, Environment environment) : base(name) { if (string.IsNullOrEmpty(dll)) throw new ArgumentNullException("dll"); Environment = environment; Dll = dll; } /// <summary> ///     <see cref="Monitor"/>. /// </summary> /// <param name="name">  .</param> /// <param name="dll">  *.dll .</param> /// <exception cref="ArgumentNullException" /> public Monitor(string name, string dll) : this(name, dll, Environment.Current) { } /// <summary> ///      . /// </summary> /// <param name="serverName"> .</param> /// <exception cref="FileNotFoundException"/> /// <exception cref="PrintingException"/> public override void Install(string serverName) { try { if (!File.Exists(Dll)) throw new FileNotFoundException("     ", Dll); string dllName = Path.GetFileName(Dll); string dllPath = Path.Combine(System.Environment.SystemDirectory, dllName); File.Copy(Dll, dllPath, true); MonitorInfo monitorInfo = new MonitorInfo { Name = Name, Environment = Environment.GetEnvironmentName(), DllName = File.Exists(dllPath) ? dllName : Dll, }; if (AddMonitor(serverName, 2, ref monitorInfo)) return; if (Marshal.GetLastWin32Error() == PrintingException.ErrorMonitorAlreadyInstalled && TryUninstall(serverName) && AddMonitor(serverName, 2, ref monitorInfo)) return; else throw new PrintingException(Marshal.GetLastWin32Error()); } catch (Exception e) { throw new PrintingException(e.Message, e); } } /// <summary> ///      . /// </summary> /// <param name="serverName"> .</param> /// <exception cref="PrintingException"/> public override void Uninstall(string serverName) { try { if (!All.Select(m => m.Name).Contains(Name)) return; /// TODO:      . if (DeleteMonitor(serverName, Environment.GetEnvironmentName(), Name)) return; if (Marshal.GetLastWin32Error() == PrintingException.ErrorMonitorUnknown) return; if (DeleteMonitor(serverName, Environment.GetEnvironmentName(), Name)) return; throw new PrintingException(Marshal.GetLastWin32Error()); } catch (Exception e) { throw new PrintingException(e.Message, e); } } #region Native /// <summary> ///      . /// </summary> /// <param name="serverName"> ,     .   null -    .</param> /// <param name="level">  .    2.</param> /// <param name="monitor">  <see cref="MonitorInfo"/>.</param> /// <returns>True,    ,  False.</returns> [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)] internal static extern bool AddMonitor(string serverName, uint level, ref MonitorInfo monitor); /// <summary> ///       <see cref="MonitorInfo"/>. /// </summary> /// <param name="serverName"> ,      .   null -    .</param> /// <param name="level">  .    1  2.</param> /// <param name="monitors">     <see cref="MonitorInfo"/>.</param> /// <param name="bufferSize">    <see cref="MonitorInfo"/> ( ).</param> /// <param name="bytesNeeded">    .</param> /// <param name="bufferReturnedLength">  .</param> /// <returns>True,    ,  False.</returns> [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)] internal static extern bool EnumMonitors(string serverName, uint level, IntPtr monitors, uint bufferSize, ref uint bytesNeeded, ref uint bufferReturnedLength); /// <summary> ///      . /// </summary> /// <param name="serverName"> ,     .   null -    .</param> /// <param name="environment">,      (, Windows x86, Windows IA64  Windows x64).</param> /// <param name="monitorName">  .</param> /// <returns>True,    ,  False.</returns> [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)] internal static extern bool DeleteMonitor(string serverName, string environment, string monitorName); #endregion } 


. :

PrintingApi
 /// <summary> ///       . /// </summary> public static Monitor[] Monitors => Monitor.All; /// <summary> ///      . /// </summary> /// <param name="name">  .</param> /// <param name="dll">   dll  .</param> /// <param name="environment">,      .</param> /// <param name="serverName"> ,      .</param> /// <returns>  .</returns> public Monitor CreateMonitor(string name, string dll, Environment environment, string serverName) { Monitor monitor = new Monitor(name, dll, environment); monitor.TryInstall(serverName); return monitor; } /// <summary> ///      . /// </summary> /// <param name="name">  .</param> /// <param name="dll">   dll  .</param> /// <param name="environment">,      .</param> /// <returns>  .</returns> public Monitor CreateMonitor(string name, string dll, Environment environment) => CreateMonitor(name, dll, environment, null); /// <summary> ///      . /// </summary> /// <param name="name">  .</param> /// <param name="dll">   dll  .</param> /// <param name="serverName"> ,      .</param> /// <returns>  .</returns> public Monitor CreateMonitor(string name, string dll, string serverName) => CreateMonitor(name, dll, Environment.Current, null); /// <summary> ///      . /// </summary> /// <param name="name">  .</param> /// <param name="dll">   dll  .</param> /// <returns>  .</returns> public Monitor CreateMonitor(string name, string dll) => CreateMonitor(name, dll, null); 


. Unit-, dll , , :

Unit-
 /// <summary> ///     <see cref="Monitor"/>. /// </summary> [TestClass] public class MonitorTests { /// <summary> ///  . /// </summary> protected const string MonitorName = "Test Monitor"; /// <summary> ///   dll . /// </summary> protected const string MonitorDll = "D:/Printing Tests/mfilemon.dll"; /// <summary> ///    dll . /// </summary> protected const string FailedMonitorDll = "noexist.dll"; /// <summary> ///    . /// </summary> [TestMethod] public void InstallTest() { Monitor monitor = new Monitor(MonitorName, MonitorDll); monitor.Install(); Assert.IsTrue(Monitor.All.Select(m => m.Name).Contains(MonitorName)); } /// <summary> ///    . /// </summary> [TestMethod] public void UninstallTest() { Monitor monitor = new Monitor(MonitorName, MonitorDll); monitor.Uninstall(); Assert.IsFalse(Monitor.All.Select(m => m.Name).Contains(MonitorName)); } /// <summary> ///        . /// </summary> [TestMethod] public void TryInstallTest() { Monitor monitor = new Monitor(MonitorName, MonitorDll); bool f = monitor.TryInstall(); Assert.IsTrue(f); Assert.IsTrue(Monitor.All.Select(m => m.Name).Contains(MonitorName)); } /// <summary> ///        . /// </summary> [TestMethod] public void TryUninstallTest() { Monitor monitor = new Monitor(MonitorName, MonitorDll); bool f = monitor.TryUninstall(); Assert.IsTrue(f); Assert.IsFalse(Monitor.All.Select(m => m.Name).Contains(MonitorName)); } /// <summary> ///     . /// </summary> [TestMethod] [ExpectedException(typeof(PrintingException))] public void InstallFailedTest() { Monitor monitor = new Monitor(MonitorName, FailedMonitorDll); monitor.Install(); Assert.IsFalse(Monitor.All.Select(m => m.Name).Contains(MonitorName)); } /// <summary> ///         . /// </summary> [TestMethod] public void TryInstallFailedTest() { Monitor monitor = new Monitor(MonitorName, FailedMonitorDll); bool f = monitor.TryInstall(); Assert.IsFalse(f); Assert.IsFalse(Monitor.All.Select(m => m.Name).Contains(MonitorName)); } 


mfilemon.dll .


. IPort:

interface IPort
 /// <summary> ///       . /// </summary> public interface IPort : IPrintableDevice { /// <summary> /// ,    . /// </summary> IMonitor Monitor { get; } /// <summary> ///  . /// </summary> string Description { get; } /// <summary> ///  . /// </summary> PortType Type { get; } } 


:

struct PortInfo
 /// <summary> ///      . /// </summary> [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct PortInfo { /// <summary> ///    (, "LPT1:"). /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string PortName; /// <summary> ///     (, "PJL monitor").    null. /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string MonitorName; /// <summary> ///   (,  <see cref="PortName"/>  "LPT1:", <see cref="Description"/>   "printer port").    null. /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string Description; /// <summary> ///  . /// </summary> public PortType Type; /// <summary> /// .    0. /// </summary> internal uint Reserved; } 


Spooler : — AddPort/DeletePort, — XcvData . , .. , . XCV :

enum PrinterAccess -
 /// <summary> ///    . /// </summary> internal enum PrinterAccess { /// <summary> ///     . /// </summary> ServerAdmin = 0x01, /// <summary> ///     . /// </summary> ServerEnum = 0x02, /// <summary> ///     . /// </summary> PrinterAdmin = 0x04, /// <summary> ///     . /// </summary> PrinterUse = 0x08, /// <summary> ///      . /// </summary> JobAdmin = 0x10, /// <summary> ///    . /// </summary> JobRead = 0x20, /// <summary> ///   . /// </summary> StandardRightsRequired = 0x000F0000, /// <summary> ///   . /// </summary> PrinterAllAccess = (StandardRightsRequired | PrinterAdmin | PrinterUse), } 


struct PrinterDefaults - XcvData
 /// <summary> ///     <see cref="Port.XcvData(IntPtr, string, IntPtr, uint, IntPtr, uint, out uint, out uint)"/>. /// </summary> [StructLayout(LayoutKind.Sequential)] internal struct PrinterDefaults { /// <summary> ///   (   null). /// </summary> public IntPtr DataType; /// <summary> ///   (   null). /// </summary> public IntPtr DevMode; /// <summary> ///    . /// </summary> public PrinterAccess DesiredAccess; } 


struct PortData - XcvData
 /// <summary> ///     <see cref="Port.XcvData(System.IntPtr, string, System.IntPtr, uint, System.IntPtr, uint, out uint, out uint)"/>. /// </summary> [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] internal struct PortData { /// <summary> ///  . /// </summary> [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] public string PortName; /// <summary> ///   (   1). /// </summary> public uint Version; /// <summary> /// . /// </summary> public DataType Protocol; /// <summary> ///   . /// </summary> public uint BufferSize; /// <summary> ///   . /// </summary> public uint ReservedSize; /// <summary> ///  . /// </summary> [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 49)] public string HostAddress; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)] public string SNMPCommunity; public uint DoubleSpool; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)] public string Queue; /// <summary> /// IP-. /// </summary> [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)] public string IPAddress; /// <summary> ///  . /// </summary> [MarshalAs(UnmanagedType.ByValArray, SizeConst = 540)] public byte[] Reserved; /// <summary> ///  . /// </summary> public uint PortNumber; public uint SNMPEnabled; public uint SNMPDevIndex; } 


enum XcvDataType - XcvData
 /// <summary> ///    <see cref="Port.XcvData(IntPtr, string, IntPtr, uint, IntPtr, uint, out uint, out uint)"/>. /// </summary> internal enum XcvDataType { /// <summary> ///   . /// </summary> AddPort, /// <summary> ///   . /// </summary> DeletePort, } 


, :

class Port
 /// <summary> ///     . /// </summary> public class Port : PrintableDevice, IPort { /// <summary> /// ,    . /// </summary> public virtual IMonitor Monitor { get; protected set; } /// <summary> ///  . /// </summary> public virtual string Description { get; protected set; } /// <summary> ///  . /// </summary> public virtual PortType Type { get; protected set; } /// <summary> ///        . /// </summary> public static Port[] All { get { if (!PrintingApi.TryGetInfo(EnumPorts, out PortInfo[] portInfo)) return null; Port[] ports = new Port[portInfo.Length]; for (int i = 0; i < portInfo.Length; i++) ports[i] = new Port(portInfo[i].PortName, portInfo[i].Description, portInfo[i].Type, portInfo[i].MonitorName); return ports; } } /// <summary> ///     <see cref="Port"/>. /// </summary> /// <param name="name"> .</param> /// <param name="description"> .</param> /// <param name="type"> .</param> /// <param name="monitorName">  ,    .</param> /// <exception cref="ArgumentNullException"/> /// <exception cref="PrintingException"/> public Port(string name, string description, PortType type, string monitorName) : base(name) { Description = description; Type = type; Monitor[] monitors = PrintingApi.Monitors; if (monitors.Select(m => m.Name).Contains(monitorName)) Monitor = monitors.Where(m => m.Name == monitorName).FirstOrDefault(); } /// <summary> ///     <see cref="Port"/>. /// </summary> /// <param name="name"> .</param> /// <param name="description"> .</param> /// <param name="type"> .</param> /// <param name="monitor"> ,    .</param> /// <exception cref="ArgumentNullException"/> /// <exception cref="PrintingException"/> public Port(string name, string description, PortType type, IMonitor monitor) : this(name, description, type, monitor?.Name) { } /// <summary> ///     <see cref="Port"/>. /// </summary> /// <param name="name"> .</param> /// <param name="description"> .</param> /// <param name="type"> .</param> /// <exception cref="ArgumentNullException"/> /// <exception cref="PrintingException"/> public Port(string name, string description, PortType type) : this(name, description, type, null as string) { } /// <summary> ///     <see cref="Port"/>. /// </summary> /// <param name="name"> .</param> /// <param name="description"> .</param> /// <param name="monitorName">  ,    .</param> /// <exception cref="ArgumentNullException"/> /// <exception cref="PrintingException"/> public Port(string name, string description, string monitorName) : this(name, description, PortType.Redirected, monitorName) { } /// <summary> ///     <see cref="Port"/>. /// </summary> /// <param name="name"> .</param> /// <param name="description"> .</param> /// <param name="monitor"> ,    .</param> /// <exception cref="ArgumentNullException"/> /// <exception cref="PrintingException"/> public Port(string name, string description, IMonitor monitor) : this(name, description, PortType.Redirected, monitor) { } /// <summary> ///     <see cref="Port"/>. /// </summary> /// <param name="name"> .</param> /// <param name="description"> .</param> /// <exception cref="ArgumentNullException"/> /// <exception cref="PrintingException"/> public Port(string name, string description) : this(name, description, null as string) { } /// <summary> ///     <see cref="Port"/>. /// </summary> /// <param name="name"> .</param> /// <param name="monitor"> ,    .</param> /// <exception cref="ArgumentNullException"/> /// <exception cref="PrintingException"/> public Port(string name, IMonitor monitor) : this(name, null, monitor) { } /// <summary> ///     <see cref="Port"/>. /// </summary> /// <param name="name"> .</param> /// <param name="type"> .</param> /// <exception cref="ArgumentNullException"/> /// <exception cref="PrintingException"/> public Port(string name, PortType type) : this(name, null, type) { } /// <summary> ///     <see cref="Port"/>. /// </summary> /// <param name="name"> .</param> /// <exception cref="ArgumentNullException"/> /// <exception cref="PrintingException"/> public Port(string name) : this(name, null as string) { } /// <summary> ///     . /// </summary> /// <param name="serverName"> .</param> /// <exception cref="FileNotFoundException"/> /// <exception cref="PrintingException"/> public override void Install(string serverName) { try { if (All.Select(p => p.Name).Contains(Name)) Uninstall(serverName); PrinterDefaults defaults = new PrinterDefaults { DesiredAccess = PrinterAccess.ServerAdmin }; if (!OpenPrinter($",XcvMonitor {Monitor.Name}", out IntPtr printerHandle, ref defaults)) throw new PrintingException(Marshal.GetLastWin32Error()); PortData portData = new PortData { Version = 1, Protocol = DataType.RAW, PortNumber = 9100, // 9100 = RAW, 515 = LPR. ReservedSize = 0, PortName = Name, IPAddress = serverName, SNMPCommunity = "public", SNMPEnabled = 1, SNMPDevIndex = 1, }; uint size = (uint)Marshal.SizeOf(portData); portData.BufferSize = size; IntPtr pointer = Marshal.AllocHGlobal((int)size); Marshal.StructureToPtr(portData, pointer, true); try { IntPtr outputData = IntPtr.Zero; uint outputDataSize = 0; if (!XcvData(printerHandle, Enum.GetName(typeof(XcvDataType), XcvDataType.AddPort), pointer, size, outputData, outputDataSize, out uint outputNeeded, out uint status)) throw new PrintingException(Marshal.GetLastWin32Error()); } catch (Exception e) { throw new PrintingException(e.Message, e); } finally { Marshal.FreeHGlobal(pointer); ClosePrinter(printerHandle); } } catch (Exception e) { throw new PrintingException(e.Message, e); } } /// <summary> ///     . /// </summary> /// <param name="serverName"> .</param> /// <exception cref="FileNotFoundException"/> /// <exception cref="PrintingException"/> public override void Uninstall(string serverName) { try { if (!All.Select(p => p.Name).Contains(Name)) return; /// TODO:   ,   . PrinterDefaults defaults = new PrinterDefaults { DesiredAccess = PrinterAccess.ServerAdmin }; if (!OpenPrinter($",XcvPort {Name}", out IntPtr printerHandle, ref defaults)) throw new PrintingException(Marshal.GetLastWin32Error()); PortData portData = new PortData { Version = 1, Protocol = DataType.RAW, PortNumber = 9100, ReservedSize = 0, PortName = Name, IPAddress = serverName, SNMPCommunity = "public", SNMPEnabled = 1, SNMPDevIndex = 1, }; uint size = (uint)Marshal.SizeOf(portData); portData.BufferSize = size; IntPtr pointer = Marshal.AllocHGlobal((int)size); Marshal.StructureToPtr(portData, pointer, true); try { IntPtr outputData = IntPtr.Zero; uint outputDataSize = 0; if (!XcvData(printerHandle, Enum.GetName(typeof(XcvDataType), XcvDataType.DeletePort), pointer, size, outputData, outputDataSize, out uint outputNeeded, out uint status)) throw new PrintingException(Marshal.GetLastWin32Error()); } catch (Exception e) { throw new PrintingException(e.Message, e); } finally { Marshal.FreeHGlobal(pointer); ClosePrinter(printerHandle); } } catch (Exception e) { throw new PrintingException(e.Message, e); } } #region Native /// <summary> ///    . /// </summary> /// <param name="printerName"> .</param> /// <param name="printer">  .</param> /// <param name="printerDefaults">  <see cref="XcvData(IntPtr, string, IntPtr, uint, IntPtr, uint, out uint, out uint)"/>.</param> /// <returns>True,    ,  False.</returns> [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)] internal static extern bool OpenPrinter(string printerName, out IntPtr printer, ref PrinterDefaults printerDefaults); /// <summary> ///   . /// </summary> /// <param name="printer"> .</param> /// <returns>True,    ,  False.</returns> [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)] internal static extern bool ClosePrinter(IntPtr printer); /// <summary> ///    . /// </summary> /// <param name="printer">  .</param> /// <param name="dataType"> .</param> /// <param name="inputData"> .</param> /// <param name="inputDataSize">   .</param> /// <param name="outputData"> .</param> /// <param name="outputDataSize">   .</param> /// <param name="outputNeeded">    .</param> /// <param name="status"> .</param> /// <returns>True,    ,  False.</returns> [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)] internal static extern bool XcvData(IntPtr printer, string dataType, IntPtr inputData, uint inputDataSize, IntPtr outputData, uint outputDataSize, out uint outputNeeded, out uint status); /// <summary> ///       <see cref="PortInfo"/>. /// </summary> /// <param name="serverName"> ,      .   null -    .</param> /// <param name="level">  .    1  2.</param> /// <param name="ports">     <see cref="PortInfo"/>.</param> /// <param name="bufferSize">    <see cref="PortInfo"/> ( ).</param> /// <param name="bytesNeeded">    .</param> /// <param name="bufferReturnedLength">  .</param> /// <returns>True,    ,  False.</returns> [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)] internal static extern bool EnumPorts(string serverName, uint level, IntPtr ports, uint bufferSize, ref uint bytesNeeded, ref uint bufferReturnedLength); #endregion } 


PrintingApi :

PrintingApi
 /// <summary> ///        . /// </summary> public static Port[] Ports => Port.All; /// <summary> ///      . /// </summary> /// <param name="name"> .</param> /// <param name="description"> .</param> /// <param name="type"> .</param> /// <param name="monitor"> ,    .</param> /// <param name="serverName"> .</param> /// <returns>  .</returns> public Port OpenPort(string name, string description, PortType type, Monitor monitor, string serverName) { Port port = new Port(name, description, type, monitor); monitor.TryInstall(serverName); return port; } /// <summary> ///      . /// </summary> /// <param name="name"> .</param> /// <param name="description"> .</param> /// <param name="type"> .</param> /// <param name="monitor"> ,    .</param> /// <returns>  .</returns> public Port OpenPort(string name, string description, PortType type, Monitor monitor) => OpenPort(name, description, type, monitor, null); /// <summary> ///      . /// </summary> /// <param name="name"> .</param> /// <param name="description"> .</param> /// <param name="type"> .</param> /// <param name="serverName"> .</param> /// <returns>  .</returns> public Port OpenPort(string name, string description, PortType type, string serverName) => OpenPort(name, description, type, null, serverName); /// <summary> ///      . /// </summary> /// <param name="name"> .</param> /// <param name="description"> .</param> /// <param name="type"> .</param> /// <returns>  .</returns> public Port OpenPort(string name, string description, PortType type) => OpenPort(name, description, type, null as string); /// <summary> ///      . /// </summary> /// <param name="name"> .</param> /// <param name="description"> .</param> /// <param name="monitor"> ,    .</param> /// <param name="serverName"> .</param> /// <returns>  .</returns> public Port OpenPort(string name, string description, Monitor monitor, string serverName) => OpenPort(name, description, PortType.Redirected, monitor, serverName); /// <summary> ///      . /// </summary> /// <param name="name"> .</param> /// <param name="description"> .</param> /// <param name="monitor"> ,    .</param> /// <returns>  .</returns> public Port OpenPort(string name, string description, Monitor monitor) => OpenPort(name, description, monitor, null); /// <summary> ///      . /// </summary> /// <param name="name"> .</param> /// <param name="monitor"> ,    .</param> /// <param name="serverName"> .</param> /// <returns>  .</returns> public Port OpenPort(string name, Monitor monitor, string serverName) => OpenPort(name, null, monitor, serverName); /// <summary> ///      . /// </summary> /// <param name="name"> .</param> /// <param name="monitor"> ,    .</param> /// <returns>  .</returns> public Port OpenPort(string name, Monitor monitor) => OpenPort(name, monitor, null); /// <summary> ///      . /// </summary> /// <param name="name"> .</param> /// <param name="type"> .</param> /// <param name="serverName"> .</param> /// <returns>  .</returns> public Port OpenPort(string name, PortType type, string serverName) => OpenPort(name, null, type, serverName); /// <summary> ///      . /// </summary> /// <param name="name"> .</param> /// <param name="type"> .</param> /// <returns>  .</returns> public Port OpenPort(string name, PortType type) => OpenPort(name, type, null); /// <summary> ///      . /// </summary> /// <param name="name"> .</param> /// <param name="description"> .</param> /// <param name="serverName"> .</param> /// <returns>  .</returns> public Port OpenPort(string name, string description, string serverName) => OpenPort(name, description, null, serverName); /// <summary> ///      . /// </summary> /// <param name="name"> .</param> /// <param name="description"> .</param> /// <returns>  .</returns> public Port OpenPort(string name, string description) => OpenPort(name, description, null as string); /// <summary> ///      . /// </summary> /// <param name="name"> .</param> /// <returns>  .</returns> public Port OpenPort(string name) => OpenPort(name, null as string); 


TODO:

 IEnumerable<Port> openPorts = Port.All.Where(p => p.Monitor?.Name == Name); foreach (Port openPort in openPorts) openPort.Uninstall(serverName); 

:

Unit-
 /// <summary> ///     <see cref="Port"/>. /// </summary> [TestClass] public class PortTests { /// <summary> ///  . /// </summary> protected const string PortName = "TESTPORT:"; /// <summary> ///  . /// </summary> protected const string PortDescription = "Description for " + PortName; /// <summary> ///  . /// </summary> protected const string MonitorName = "mfilemon"; /// <summary> ///   . /// </summary> protected const string FailedMonitorName = "noexist"; /// <summary> ///    . /// </summary> [TestMethod] public void InstallTest() { Port port = new Port(PortName, PortDescription, MonitorName); port.Install(); Assert.IsTrue(Port.All.Select(p => p.Name).Contains(PortName)); } /// <summary> ///    . /// </summary> [TestMethod] public void UninstallTest() { Port port = new Port(PortName, PortDescription, MonitorName); port.Uninstall(); Assert.IsFalse(Port.All.Select(p => p.Name).Contains(PortName)); } /// <summary> ///        . /// </summary> [TestMethod] public void TryInstallTest() { Port port = new Port(PortName, PortDescription, MonitorName); bool f = port.TryInstall(); Assert.IsTrue(f); Assert.IsTrue(Port.All.Select(p => p.Name).Contains(PortName)); } /// <summary> ///        . /// </summary> [TestMethod] public void TryUninstallTest() { Port port = new Port(PortName, PortDescription, MonitorName); bool f = port.TryUninstall(); Assert.IsTrue(f); Assert.IsFalse(Port.All.Select(p => p.Name).Contains(PortName)); } /// <summary> ///     . /// </summary> [TestMethod] [ExpectedException(typeof(PrintingException))] public void InstallFailedTest() { Port port = new Port(PortName, PortDescription, MonitorName); port.Install(); Assert.IsFalse(Port.All.Select(p => p.Name).Contains(PortName)); } /// <summary> ///         . /// </summary> [TestMethod] public void TryInstallFailedTest() { Port port = new Port(PortName, PortDescription, FailedMonitorName); bool f = port.TryUninstall(); Assert.IsTrue(f); Assert.IsFalse(Port.All.Select(p => p.Name).Contains(PortName)); } } 



, . , , , API , :

struct DriverInfo
 /// <summary> ///         . /// </summary> [StructLayout(LayoutKind.Sequential)] public struct DriverInfo { /// <summary> ///    ,     .   - 3  4 (V3  V4    ). /// </summary> public uint Version; /// <summary> ///   (, "QMS 810"). /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string Name; /// <summary> /// ,      (, Windows x86, Windows IA64  Windows x64). /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string Environment; /// <summary> ///         (, "C:\DRIVERS\Pscript.dll"). /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string DriverPath; /// <summary> ///         (, "C:\DRIVERS\Qms810.ppd"). /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string DataFile; /// <summary> ///      dll    (, "C:\DRIVERS\Pscriptui.dll"). /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string ConfigFile; /// <summary> ///      dll  HLP-  (, "C:\DRIVERS\Pscript.hlp"). /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string HelpFile; /// <summary> ///  . /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string DependentFiles; /// <summary> ///  . /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string MonitorName; /// <summary> ///     . /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string DefaultDataType; } 


interface IDriver
 /// <summary> ///       . /// </summary> public interface IDriver : IPrintableDevice { /// <summary> ///  . /// </summary> IMonitor Monitor { get; } /// <summary> ///    ,     .   - 3  4 (V3  V4    ). /// </summary> uint Version { get; } /// <summary> /// ,      (, Windows x86, Windows IA64  Windows x64). /// </summary> Environment Environment { get; } /// <summary> ///         (, "C:\DRIVERS\Pscript.dll"). /// </summary> string Dll { get; } /// <summary> ///         (, "C:\DRIVERS\Qms810.ppd"). /// </summary> string DataFile { get; } /// <summary> ///      dll    (, "C:\DRIVERS\Pscriptui.dll"). /// </summary> string ConfigFile { get; } /// <summary> ///      dll  HLP-  (, "C:\DRIVERS\Pscript.hlp"). /// </summary> string HelpFile { get; } /// <summary> ///  . /// </summary> string DependentFiles { get; } /// <summary> ///     . /// </summary> DataType DefaultDataType { get; } } 


class Driver
 /// <summary> ///       . /// </summary> public class Driver : PrintableDevice, IDriver { /// <summary> ///  . /// </summary> public virtual IMonitor Monitor { get; protected set; } /// <summary> ///    ,     .   - 3  4 (V3  V4    ). /// </summary> public virtual uint Version { get; protected set; } /// <summary> /// ,     . /// </summary> public virtual Environment Environment { get; protected set; } /// <summary> ///        . /// </summary> public virtual string Dll { get; protected set; } /// <summary> ///        . /// </summary> public virtual string DataFile { get; protected set; } /// <summary> ///      dll   . /// </summary> public virtual string ConfigFile { get; protected set; } /// <summary> ///      dll  HLP- . /// </summary> public virtual string HelpFile { get; protected set; } /// <summary> ///  . /// </summary> public virtual string DependentFiles { get; protected set; } /// <summary> ///     . /// </summary> public virtual DataType DefaultDataType { get; protected set; } /// <summary> ///      . /// </summary> public static string Directory { get; protected set; } /// <summary> ///        . /// </summary> public static Driver[] All { get { if (!PrintingApi.TryGetInfo(EnumPrinterDrivers, 3, out DriverInfo[] driverInfo)) return null; Driver[] drivers = new Driver[driverInfo.Length]; for (int i = 0; i < driverInfo.Length; i++) drivers[i] = new Driver(driverInfo[i].Name, driverInfo[i].DriverPath, driverInfo[i].DataFile, driverInfo[i].ConfigFile, driverInfo[i].HelpFile, driverInfo[i].Version, driverInfo[i].Environment.GetEnvironment(), (DataType)Enum.Parse(typeof(DataType), driverInfo[i].DefaultDataType ?? "RAW", true), driverInfo[i].DependentFiles, driverInfo[i].MonitorName); return drivers; } } /// <summary> ///     <see cref="Driver"/>. /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <param name="helpFile">     dll  HLP-  (, "C:\DRIVERS\Pscript.hlp").</param> /// <param name="version">   ,     .   - 3  4 (V3  V4    ).</param> /// <param name="environment">,      (, Windows x86, Windows IA64  Windows x64).</param> /// <param name="defaultDataType">    .</param> /// <param name="dependentFiles"> .</param> /// <param name="monitorName">  .</param> public Driver(string name, string dll, string dataFile, string configFile, string helpFile, uint version, Environment environment, DataType defaultDataType, string dependentFiles, string monitorName) : base(name) { Dll = dll; DataFile = dataFile; ConfigFile = configFile; HelpFile = helpFile; Version = version; Environment = environment; DefaultDataType = defaultDataType; DependentFiles = dependentFiles; Monitor[] monitors = PrintingApi.Monitors; if (monitors.Select(m => m.Name).Contains(monitorName)) Monitor = monitors.Where(m => m.Name == monitorName).FirstOrDefault(); } /// <summary> ///     <see cref="Driver"/>. /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <param name="helpFile">     dll  HLP-  (, "C:\DRIVERS\Pscript.hlp").</param> /// <param name="version">   ,     .   - 3  4 (V3  V4    ).</param> /// <param name="environment">,      (, Windows x86, Windows IA64  Windows x64).</param> /// <param name="defaultDataType">    .</param> /// <param name="dependentFiles"> .</param> /// <param name="monitor"> .</param> public Driver(string name, string dll, string dataFile, string configFile, string helpFile, uint version, Environment environment, DataType defaultDataType, string dependentFiles, IMonitor monitor) : this(name, dll, dataFile, configFile, helpFile, version, environment, defaultDataType, dependentFiles, monitor?.Name) { } /// <summary> ///     <see cref="Driver"/>. /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <param name="helpFile">     dll  HLP-  (, "C:\DRIVERS\Pscript.hlp").</param> /// <param name="version">   ,     .   - 3  4 (V3  V4    ).</param> /// <param name="environment">,      (, Windows x86, Windows IA64  Windows x64).</param> /// <param name="defaultDataType">    .</param> /// <param name="monitorName">  .</param> public Driver(string name, string dll, string dataFile, string configFile, string helpFile, uint version, Environment environment, DataType defaultDataType, string monitorName) : this(name, dll, dataFile, configFile, helpFile, version, environment, defaultDataType, null, monitorName) { } /// <summary> ///     <see cref="Driver"/>. /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <param name="helpFile">     dll  HLP-  (, "C:\DRIVERS\Pscript.hlp").</param> /// <param name="version">   ,     .   - 3  4 (V3  V4    ).</param> /// <param name="environment">,      (, Windows x86, Windows IA64  Windows x64).</param> /// <param name="defaultDataType">    .</param> /// <param name="monitor"> .</param> public Driver(string name, string dll, string dataFile, string configFile, string helpFile, uint version, Environment environment, DataType defaultDataType, IMonitor monitor) : this(name, dll, dataFile, configFile, helpFile, version, environment, defaultDataType, monitor?.Name) { } /// <summary> ///     <see cref="Driver"/>. /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <param name="helpFile">     dll  HLP-  (, "C:\DRIVERS\Pscript.hlp").</param> /// <param name="version">   ,     .   - 3  4 (V3  V4    ).</param> /// <param name="environment">,      (, Windows x86, Windows IA64  Windows x64).</param> /// <param name="monitorName">  .</param> public Driver(string name, string dll, string dataFile, string configFile, string helpFile, uint version, Environment environment, string monitorName) : this(name, dll, dataFile, configFile, helpFile, version, environment, DataType.RAW, monitorName) { } /// <summary> ///     <see cref="Driver"/>. /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <param name="helpFile">     dll  HLP-  (, "C:\DRIVERS\Pscript.hlp").</param> /// <param name="version">   ,     .   - 3  4 (V3  V4    ).</param> /// <param name="environment">,      (, Windows x86, Windows IA64  Windows x64).</param> /// <param name="monitor"> .</param> public Driver(string name, string dll, string dataFile, string configFile, string helpFile, uint version, Environment environment, IMonitor monitor) : this(name, dll, dataFile, configFile, helpFile, version, environment, monitor?.Name) { } /// <summary> ///     <see cref="Driver"/>. /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <param name="helpFile">     dll  HLP-  (, "C:\DRIVERS\Pscript.hlp").</param> /// <param name="version">   ,     .   - 3  4 (V3  V4    ).</param> /// <param name="monitorName">  .</param> public Driver(string name, string dll, string dataFile, string configFile, string helpFile, uint version, string monitorName) : this(name, dll, dataFile, configFile, helpFile, version, Environment.Current, monitorName) { } /// <summary> ///     <see cref="Driver"/>. /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <param name="helpFile">     dll  HLP-  (, "C:\DRIVERS\Pscript.hlp").</param> /// <param name="version">   ,     .   - 3  4 (V3  V4    ).</param> /// <param name="monitor"> .</param> public Driver(string name, string dll, string dataFile, string configFile, string helpFile, uint version, IMonitor monitor) : this(name, dll, dataFile, configFile, helpFile, version, monitor?.Name) { } /// <summary> ///     <see cref="Driver"/>. /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <param name="helpFile">     dll  HLP-  (, "C:\DRIVERS\Pscript.hlp").</param> /// <param name="monitorName">  .</param> public Driver(string name, string dll, string dataFile, string configFile, string helpFile, string monitorName) : this(name, dll, dataFile, configFile, helpFile, 3, monitorName) { } /// <summary> ///     <see cref="Driver"/>. /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <param name="helpFile">     dll  HLP-  (, "C:\DRIVERS\Pscript.hlp").</param> /// <param name="monitor"> .</param> public Driver(string name, string dll, string dataFile, string configFile, string helpFile, IMonitor monitor) : this(name, dll, dataFile, configFile, helpFile, monitor?.Name) { } /// <summary> ///     <see cref="Driver"/>. /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <param name="monitorName">  .</param> public Driver(string name, string dll, string dataFile, string configFile, string monitorName) : this(name, dll, dataFile, configFile, null, monitorName) { } /// <summary> ///     <see cref="Driver"/>. /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <param name="monitor"> .</param> public Driver(string name, string dll, string dataFile, string configFile, IMonitor monitor) : this(name, dll, dataFile, configFile, monitor?.Name) { } /// <summary> ///     <see cref="Driver"/>. /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> public Driver(string name, string dll, string dataFile, string configFile) : this(name, dll, dataFile, configFile, null as string) { } /// <summary> ///    <see cref="Driver"/>. /// </summary> static Driver() { uint length = 1024; StringBuilder driverDirectory = new StringBuilder((int)length); uint bytesNeeded = 0; if (!GetPrinterDriverDirectory(null, null, 1, driverDirectory, length, ref bytesNeeded)) throw new PrintingException(Marshal.GetLastWin32Error()); Directory = driverDirectory.ToString(); } /// <summary> ///     . /// </summary> /// <param name="serverName"> .</param> public override void Install(string serverName) { try { if (!File.Exists(Dll)) throw new PrintingException($"     '{Dll}'"); if (!File.Exists(DataFile)) throw new PrintingException($"     '{DataFile}'"); if (!File.Exists(ConfigFile)) throw new PrintingException($"     '{ConfigFile}'"); if (All.Select(d => d.Name).Contains(Name)) Uninstall(serverName); string systemDriverPath = Path.Combine(Directory, Path.GetFileName(Dll)); string systemDataPath = Path.Combine(Directory, Path.GetFileName(DataFile)); string systemConfigPath = Path.Combine(Directory, Path.GetFileName(ConfigFile)); string systemHelpPath = Path.Combine(Directory, Path.GetFileName(HelpFile)); File.Copy(Dll, systemDriverPath, true); File.Copy(DataFile, systemDataPath, true); File.Copy(ConfigFile, systemConfigPath, true); if (File.Exists(HelpFile)) File.Copy(HelpFile, systemHelpPath, true); DriverInfo driverInfo = new DriverInfo { Version = Version, Name = Name, Environment = Environment.GetEnvironmentName(), DriverPath = File.Exists(systemDriverPath) ? systemDriverPath : Dll, DataFile = File.Exists(systemDataPath) ? systemDataPath : DataFile, ConfigFile = File.Exists(systemConfigPath) ? systemConfigPath : ConfigFile, HelpFile = File.Exists(systemHelpPath) ? systemHelpPath : HelpFile, DependentFiles = DependentFiles, MonitorName = Monitor?.Name, DefaultDataType = Enum.GetName(typeof(DataType), DefaultDataType), }; if (AddPrinterDriver(serverName, Version, ref driverInfo)) return; int lastWin32ErrorCode = Marshal.GetLastWin32Error(); if (lastWin32ErrorCode == 0) return; throw new PrintingException(lastWin32ErrorCode); } catch (Exception e) { throw new PrintingException(e.Message, e); } } /// <summary> ///     . /// </summary> /// <param name="serverName"> .</param> public override void Uninstall(string serverName) { try { if (!All.Select(d => d.Name).Contains(Name)) return; /// TODO:   ,  . if (DeletePrinterDriver(serverName, Environment.GetEnvironmentName(), Name)) return; throw new PrintingException(Marshal.GetLastWin32Error()); } catch (Exception e) { throw new PrintingException(e.Message, e); } } #region Native /// <summary> ///         . /// </summary> /// <param name="serverName"> ,         .   null -   .</param> /// <param name="environment">,      (, Windows x86, Windows IA64  Windows x64).</param> /// <param name="level">  .    1.</param> /// <param name="driverDirectory">   .</param> /// <param name="bufferSize">    .</param> /// <param name="bytesNeeded">   .</param> /// <returns>True,    ,  False.</returns> [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)] internal static extern bool GetPrinterDriverDirectory(string serverName, string environment, uint level, [Out] StringBuilder driverDirectory, uint bufferSize, ref uint bytesNeeded); /// <summary> ///    . /// </summary> /// <param name="serverName"> .</param> /// <param name="level">  .    1, 2, 3, 4, 5, 6  8.</param> /// <param name="driverInfo"> .</param> /// <returns>True,    ,  False.</returns> [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)] internal static extern bool AddPrinterDriver(string serverName, uint level, ref DriverInfo driverInfo); /// <summary> ///       <see cref="DriverInfo"/>. /// </summary> /// <param name="serverName"> ,      .   null -    .</param> /// <param name="environment"> (, Windows x86, Windows IA64, Windows x64,  Windows NT R4000).    null, ///     ( ).    "all",       , ///       .</param> /// <param name="level">  .    1, 2, 3, 4, 5, 6  8.</param> /// <param name="drivers">     <see cref="DriverInfo"/>.</param> /// <param name="bufferSize">    <see cref="DriverInfo"/> ( ).</param> /// <param name="bytesNeeded">    .</param> /// <param name="bufferReturnedLength">  .</param> /// <returns>True,    ,  False.</returns> [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)] internal static extern bool EnumPrinterDrivers(string serverName, string environment, uint level, IntPtr drivers, uint bufferSize, ref uint bytesNeeded, ref uint bufferReturnedLength); /// <summary> ///    . /// </summary> /// <param name="serverName"> .</param> /// <param name="environment"> (, Windows x86, Windows IA64, Windows x64,  Windows NT R4000).    null, ///     ( ).    "all",       , ///       .</param> /// <param name="driverName"> .</param> /// <returns>True,    ,  False.</returns> [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)] internal static extern bool DeletePrinterDriver(string serverName, string environment, string driverName); #endregion } 


PrintingApi
 /// <summary> ///        . /// </summary> public static Driver[] Drivers => Driver.All; /// <summary> ///     . /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <param name="helpFile">     dll  HLP-  (, "C:\DRIVERS\Pscript.hlp").</param> /// <param name="version">   ,     .   - 3  4 (V3  V4    ).</param> /// <param name="environment">,      (, Windows x86, Windows IA64  Windows x64).</param> /// <param name="defaultDataType">    .</param> /// <param name="dependentFiles"> .</param> /// <param name="monitor"> .</param> /// <param name="serverName"> .</param> /// <returns>  .</returns> public Driver InstallDriver(string name, string dll, string dataFile, string configFile, string helpFile, uint version, Environment environment, DataType defaultDataType, string dependentFiles, Monitor monitor, string serverName) { Driver driver = new Driver(name, dll, dataFile, configFile, helpFile, version, environment, defaultDataType, dependentFiles, monitor); driver.TryInstall(serverName); return driver; } /// <summary> ///     . /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <param name="helpFile">     dll  HLP-  (, "C:\DRIVERS\Pscript.hlp").</param> /// <param name="version">   ,     .   - 3  4 (V3  V4    ).</param> /// <param name="environment">,      (, Windows x86, Windows IA64  Windows x64).</param> /// <param name="defaultDataType">    .</param> /// <param name="dependentFiles"> .</param> /// <param name="monitor"> .</param> /// <returns>  .</returns> public Driver InstallDriver(string name, string dll, string dataFile, string configFile, string helpFile, uint version, Environment environment, DataType defaultDataType, string dependentFiles, Monitor monitor) => InstallDriver(name, dll, dataFile, configFile, helpFile, version, environment, defaultDataType, dependentFiles, monitor, null); /// <summary> ///     . /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <param name="helpFile">     dll  HLP-  (, "C:\DRIVERS\Pscript.hlp").</param> /// <param name="version">   ,     .   - 3  4 (V3  V4    ).</param> /// <param name="environment">,      (, Windows x86, Windows IA64  Windows x64).</param> /// <param name="defaultDataType">    .</param> /// <param name="dependentFiles"> .</param> /// <param name="serverName"> .</param> /// <returns>  .</returns> public Driver InstallDriver(string name, string dll, string dataFile, string configFile, string helpFile, uint version, Environment environment, DataType defaultDataType, string dependentFiles, string serverName) => InstallDriver(name, dll, dataFile, configFile, helpFile, version, environment, defaultDataType, dependentFiles, null, serverName); /// <summary> ///     . /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <param name="helpFile">     dll  HLP-  (, "C:\DRIVERS\Pscript.hlp").</param> /// <param name="version">   ,     .   - 3  4 (V3  V4    ).</param> /// <param name="environment">,      (, Windows x86, Windows IA64  Windows x64).</param> /// <param name="defaultDataType">    .</param> /// <param name="dependentFiles"> .</param> /// <returns>  .</returns> public Driver InstallDriver(string name, string dll, string dataFile, string configFile, string helpFile, uint version, Environment environment, DataType defaultDataType, string dependentFiles) => InstallDriver(name, dll, dataFile, configFile, helpFile, version, environment, defaultDataType, dependentFiles, null as string); /// <summary> ///     . /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <param name="helpFile">     dll  HLP-  (, "C:\DRIVERS\Pscript.hlp").</param> /// <param name="version">   ,     .   - 3  4 (V3  V4    ).</param> /// <param name="environment">,      (, Windows x86, Windows IA64  Windows x64).</param> /// <param name="defaultDataType">    .</param> /// <returns>  .</returns> public Driver InstallDriver(string name, string dll, string dataFile, string configFile, string helpFile, uint version, Environment environment, DataType defaultDataType) => InstallDriver(name, dll, dataFile, configFile, helpFile, version, environment, defaultDataType, null); /// <summary> ///     . /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <param name="helpFile">     dll  HLP-  (, "C:\DRIVERS\Pscript.hlp").</param> /// <param name="version">   ,     .   - 3  4 (V3  V4    ).</param> /// <param name="environment">,      (, Windows x86, Windows IA64  Windows x64).</param> /// <param name="serverName"> .</param> /// <returns>  .</returns> public Driver InstallDriver(string name, string dll, string dataFile, string configFile, string helpFile, uint version, Environment environment, string serverName) => InstallDriver(name, dll, dataFile, configFile, helpFile, version, environment, DataType.RAW, serverName); /// <summary> ///     . /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <param name="helpFile">     dll  HLP-  (, "C:\DRIVERS\Pscript.hlp").</param> /// <param name="version">   ,     .   - 3  4 (V3  V4    ).</param> /// <param name="environment">,      (, Windows x86, Windows IA64  Windows x64).</param> /// <returns>  .</returns> public Driver InstallDriver(string name, string dll, string dataFile, string configFile, string helpFile, uint version, Environment environment) => InstallDriver(name, dll, dataFile, configFile, helpFile, version, environment, null); /// <summary> ///     . /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <param name="helpFile">     dll  HLP-  (, "C:\DRIVERS\Pscript.hlp").</param> /// <param name="version">   ,     .   - 3  4 (V3  V4    ).</param> /// <param name="serverName"> .</param> /// <returns>  .</returns> public Driver InstallDriver(string name, string dll, string dataFile, string configFile, string helpFile, uint version, string serverName) => InstallDriver(name, dll, dataFile, configFile, helpFile, version, Environment.Current, serverName); /// <summary> ///     . /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <param name="helpFile">     dll  HLP-  (, "C:\DRIVERS\Pscript.hlp").</param> /// <param name="version">   ,     .   - 3  4 (V3  V4    ).</param> /// <returns>  .</returns> public Driver InstallDriver(string name, string dll, string dataFile, string configFile, string helpFile, uint version) => InstallDriver(name, dll, dataFile, configFile, helpFile, version, null); /// <summary> ///     . /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <param name="helpFile">     dll  HLP-  (, "C:\DRIVERS\Pscript.hlp").</param> /// <param name="serverName"> .</param> /// <returns>  .</returns> public Driver InstallDriver(string name, string dll, string dataFile, string configFile, string helpFile, string serverName) => InstallDriver(name, dll, dataFile, configFile, helpFile, 3, serverName); /// <summary> ///     . /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <param name="helpFile">     dll  HLP-  (, "C:\DRIVERS\Pscript.hlp").</param> /// <returns>  .</returns> public Driver InstallDriver(string name, string dll, string dataFile, string configFile, string helpFile) => InstallDriver(name, dll, dataFile, configFile, helpFile, null); /// <summary> ///     . /// </summary> /// <param name="name"> .</param> /// <param name="dll">        (, "C:\DRIVERS\Pscript.dll").</param> /// <param name="dataFile">        (, "C:\DRIVERS\Qms810.ppd").</param> /// <param name="configFile">     dll    (, "C:\DRIVERS\Pscriptui.dll").</param> /// <returns>  .</returns> public Driver InstallDriver(string name, string dll, string dataFile, string configFile) => InstallDriver(name, dll, dataFile, configFile, null); 


Add the removal of drivers attached to the print monitor in the monitor removal method
 IEnumerable<Driver> drivers = Driver.All.Where(d => d.Monitor?.Name == Name); foreach (Driver driver in drivers) driver.Uninstall(serverName); 


We test with the help of Unit tests
 /// <summary> ///     <see cref="Driver"/>. /// </summary> [TestClass] public class DriverTests { /// <summary> ///  . /// </summary> protected const string DriverName = "Test Driver"; /// <summary> ///  . /// </summary> protected const string MonitorName = "mfilemon"; /// <summary> ///   . /// </summary> protected const string FailedMonitorName = "noexist"; protected const string DllPath = "D:/Printing Tests/pscript.dll"; protected const string DataPath = "D:/Printing Tests/testprinter.ppd"; protected const string ConfigPath = "D:/Printing Tests/pscriptui.dll"; protected const string HelpPath = "D:/Printing Tests/pscript.hlp"; /// <summary> ///    . /// </summary> [TestMethod] public void InstallTest() { Driver driver = new Driver(DriverName, DllPath, DataPath, ConfigPath, HelpPath, MonitorName); driver.Install(); Assert.IsTrue(Driver.All.Select(d => d.Name).Contains(DriverName)); } /// <summary> ///    . /// </summary> [TestMethod] public void UninstallTest() { Driver driver = new Driver(DriverName, DllPath, DataPath, ConfigPath, HelpPath, MonitorName); driver.Uninstall(); Assert.IsFalse(Driver.All.Select(d => d.Name).Contains(DriverName)); } /// <summary> ///        . /// </summary> [TestMethod] public void TryInstallTest() { Driver driver = new Driver(DriverName, DllPath, DataPath, ConfigPath, HelpPath, MonitorName); bool f = driver.TryInstall(); Assert.IsTrue(f); Assert.IsTrue(Driver.All.Select(d => d.Name).Contains(DriverName)); } /// <summary> ///        . /// </summary> [TestMethod] public void TryUninstallTest() { Driver driver = new Driver(DriverName, DllPath, DataPath, ConfigPath, HelpPath, MonitorName); bool f = driver.TryUninstall(); Assert.IsTrue(f); Assert.IsFalse(Driver.All.Select(d => d.Name).Contains(DriverName)); } /// <summary> ///     . /// </summary> [TestMethod] [ExpectedException(typeof(PrintingException))] public void InstallFailedTest() { Driver driver = new Driver(DriverName, DllPath + "failed", DataPath, ConfigPath, HelpPath, FailedMonitorName); driver.Install(); Assert.IsFalse(Driver.All.Select(d => d.Name).Contains(DriverName)); } /// <summary> ///         . /// </summary> [TestMethod] public void TryInstallFailedTest() { Driver driver = new Driver(DriverName, DllPath + "failed", DataPath, ConfigPath, HelpPath, FailedMonitorName); bool f = driver.TryInstall(); Assert.IsTrue(f); Assert.IsFalse(Driver.All.Select(d => d.Name).Contains(DriverName)); } } 


Printing device


And now it's time to implement, perhaps, the most important component that provides the relationship between the UI and the print monitor - the printer. Here, too, there are few differences from previous manipulations:

struct PrinterInfo
 /// <summary> ///    . /// </summary> [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct PrinterInfo { /// <summary> ///  . /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string ServerName; /// <summary> ///  . /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string PrinterName; /// <summary> ///   . /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string ShareName; /// <summary> ///  ,   . /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string PortName; /// <summary> ///   . /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string DriverName; /// <summary> ///  . /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string Comment; /// <summary> ///  . /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string Location; public IntPtr DevMode; [MarshalAs(UnmanagedType.LPTStr)] public string SepFile; /// <summary> ///  ,   . /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string PrintProcessor; /// <summary> ///    . /// </summary> [MarshalAs(UnmanagedType.LPTStr)] public string DataType; [MarshalAs(UnmanagedType.LPTStr)] public string Parameters; public IntPtr SecurityDescriptor; public uint Attributes; public uint Priority; public uint DefaultPriority; public uint StartTime; public uint UntilTime; public uint Status; public uint cJobs; public uint AveragePPM; } 


To get a list of installed printers, we also need a flag:

enum PrinterEnumFlag
 /// <summary> ///       . /// </summary> [Flags] internal enum PrinterEnumFlag { Default = 0x00000001, Local = 0x00000002, Connections = 0x00000004, Favorite = 0x00000004, Name = 0x00000008, Remote = 0x00000010, Shared = 0x00000020, Network = 0x00000040, Expand = 0x00004000, Container = 0x00008000, IconMask = 0x00ff0000, Icon1 = 0x00010000, Icon2 = 0x00020000, Icon3 = 0x00040000, Icon4 = 0x00080000, Icon5 = 0x00100000, Icon6 = 0x00200000, Icon7 = 0x00400000, Icon8 = 0x00800000, Hide = 0x01000000, All = 0x02000000, Category3D = 0x04000000, } 


interface IPrinter
 /// <summary> ///     . /// </summary> public interface IPrinter : IPrintableDevice { /// <summary> /// ,    . /// </summary> IPort Port { get; } /// <summary> /// ,    . /// </summary> IDriver Driver { get; } /// <summary> ///   . /// </summary> string ShareName { get; } /// <summary> ///  ,    . /// </summary> string ServerName { get; } /// <summary> ///   . /// </summary> string Description { get; } /// <summary> ///  . /// </summary> string Location { get; } string SepFile { get; } /// <summary> ///  . /// </summary> string Parameters { get; } /// <summary> ///   . /// </summary> DataType DataType { get; } } 


class Printer
 /// <summary> ///   . /// </summary> public class Printer : PrintableDevice, IPrinter { /// <summary> /// ,    . /// </summary> public virtual IPort Port { get; protected set; } /// <summary> /// ,    . /// </summary> public virtual IDriver Driver { get; protected set; } /// <summary> ///   . /// </summary> public virtual string ShareName { get; protected set; } /// <summary> ///   . /// </summary> public virtual string Description { get; protected set; } /// <summary> ///   . /// </summary> public virtual DataType DataType { get; protected set; } /// <summary> ///   . /// </summary> public virtual string Processor { get; protected set; } /// <summary> ///  ,    . /// </summary> public virtual string ServerName { get; protected set; } /// <summary> ///  . /// </summary> public virtual string Location { get; protected set; } /// <summary> ///  . /// </summary> public virtual string Parameters { get; protected set; } public virtual string SepFile { get; protected set; } /// <summary> ///      . /// </summary> public static Printer Default { get { uint length = 0; if (GetDefaultPrinter(null, ref length)) return null; int lastWin32Error = Marshal.GetLastWin32Error(); if (lastWin32Error != PrintingException.ErrorInsufficientBuffer) throw new PrintingException(lastWin32Error); StringBuilder printerName = new StringBuilder((int)length); if (!GetDefaultPrinter(printerName, ref length)) throw new PrintingException(Marshal.GetLastWin32Error()); string name = printerName.ToString(); return All.Where(p => p.Name == name).FirstOrDefault(); } set { if (!SetDefaultPrinter(value?.Name)) throw new PrintingException(Marshal.GetLastWin32Error()); } } /// <summary> ///      . /// </summary> public static Printer[] All { get { uint bytesNeeded = 0; uint bufferReturnedLength = 0; uint level = 2; PrinterEnumFlag flags = PrinterEnumFlag.Local | PrinterEnumFlag.Network; if (EnumPrinters(flags, null, level, IntPtr.Zero, 0, ref bytesNeeded, ref bufferReturnedLength)) return null; int lastWin32Error = Marshal.GetLastWin32Error(); if (lastWin32Error != PrintingException.ErrorInsufficientBuffer) throw new PrintingException(lastWin32Error); IntPtr printersPtr = Marshal.AllocHGlobal((int)bytesNeeded); try { if (EnumPrinters(flags, null, level, printersPtr, bytesNeeded, ref bytesNeeded, ref bufferReturnedLength)) { IntPtr currentPrinterPtr = printersPtr; PrinterInfo[] printerInfo = new PrinterInfo[bufferReturnedLength]; Printer[] printers = new Printer[bufferReturnedLength]; Type type = typeof(PrinterInfo); for (int i = 0; i < bufferReturnedLength; i++) { printerInfo[i] = (PrinterInfo)Marshal.PtrToStructure(currentPrinterPtr, type); currentPrinterPtr = (IntPtr)(currentPrinterPtr.ToInt64() + Marshal.SizeOf(type)); printers[i] = new Printer(printerInfo[i].PrinterName, printerInfo[i].PortName, printerInfo[i].DriverName, printerInfo[i].PrintProcessor, printerInfo[i].ShareName, printerInfo[i].ServerName, printerInfo[i].Comment, (DataType)Enum.Parse(typeof(DataType), printerInfo[i].DataType), printerInfo[i].Location, printerInfo[i].Parameters, printerInfo[i].SepFile); } return printers; } throw new PrintingException(Marshal.GetLastWin32Error()); } catch { return null; } finally { Marshal.FreeHGlobal(printersPtr); } } } /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="portName"> .</param> /// <param name="driverName"> .</param> /// <param name="processorName">  .</param> /// <param name="shareName">  .</param> /// <param name="serverName"> .</param> /// <param name="description"> .</param> /// <param name="dataType">  .</param> /// <param name="location"> .</param> /// <param name="parameters"> .</param> /// <param name="sepFile"></param> public Printer(string name, string portName, string driverName, string processorName, string shareName, string serverName, string description, DataType dataType, string location, string parameters, string sepFile) : base(name) { Port[] ports = PrintingApi.Ports; Driver[] drivers = PrintingApi.Drivers; if (ports.Select(p => p.Name).Contains(portName)) Port = ports.Where(p => p.Name == portName).FirstOrDefault(); if (drivers.Select(d => d.Name).Contains(driverName)) Driver = drivers.Where(d => d.Name == driverName).FirstOrDefault(); Processor = processorName; ShareName = shareName; ServerName = serverName; Description = description; DataType = dataType; Location = location; Parameters = parameters; SepFile = sepFile; } /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="port">,    .</param> /// <param name="driver">,    .</param> /// <param name="processorName">  .</param> /// <param name="shareName">  .</param> /// <param name="serverName"> .</param> /// <param name="description"> .</param> /// <param name="dataType">  .</param> /// <param name="location"> .</param> /// <param name="parameters"> .</param> /// <param name="sepFile"></param> public Printer(string name, IPort port, IDriver driver, string processorName, string shareName, string serverName, string description, DataType dataType, string location, string parameters, string sepFile) : this(name, port?.Name, driver?.Name, processorName, shareName, serverName, description, dataType, location, parameters, sepFile) { } /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="portName"> .</param> /// <param name="driverName"> .</param> /// <param name="processorName">  .</param> /// <param name="shareName">  .</param> /// <param name="serverName"> .</param> /// <param name="description"> .</param> /// <param name="dataType">  .</param> /// <param name="location"> .</param> /// <param name="parameters"> .</param> public Printer(string name, string portName, string driverName, string processorName, string shareName, string serverName, string description, DataType dataType, string location, string parameters) : this(name, portName, driverName, processorName, shareName, serverName, description, dataType, location, parameters, null) { } /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="port">,    .</param> /// <param name="driver">,    .</param> /// <param name="processorName">  .</param> /// <param name="shareName">  .</param> /// <param name="serverName"> .</param> /// <param name="description"> .</param> /// <param name="dataType">  .</param> /// <param name="location"> .</param> /// <param name="parameters"> .</param> public Printer(string name, IPort port, IDriver driver, string processorName, string shareName, string serverName, string description, DataType dataType, string location, string parameters) : this(name, port?.Name, driver?.Name, processorName, shareName, serverName, description, dataType, location, parameters) { } /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="portName"> .</param> /// <param name="driverName"> .</param> /// <param name="processorName">  .</param> /// <param name="shareName">  .</param> /// <param name="serverName"> .</param> /// <param name="description"> .</param> /// <param name="dataType">  .</param> /// <param name="location"> .</param> public Printer(string name, string portName, string driverName, string processorName, string shareName, string serverName, string description, DataType dataType, string location) : this(name, portName, driverName, processorName, shareName, serverName, description, dataType, location, null) { } /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="port">,    .</param> /// <param name="driver">,    .</param> /// <param name="processorName">  .</param> /// <param name="shareName">  .</param> /// <param name="serverName"> .</param> /// <param name="description"> .</param> /// <param name="dataType">  .</param> /// <param name="location"> .</param> public Printer(string name, IPort port, IDriver driver, string processorName, string shareName, string serverName, string description, DataType dataType, string location) : this(name, port?.Name, driver?.Name, processorName, shareName, serverName, description, dataType, location) { } /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="portName"> .</param> /// <param name="driverName"> .</param> /// <param name="processorName">  .</param> /// <param name="shareName">  .</param> /// <param name="serverName"> .</param> /// <param name="description"> .</param> /// <param name="dataType">  .</param> public Printer(string name, string portName, string driverName, string processorName, string shareName, string serverName, string description, DataType dataType) : this(name, portName, driverName, processorName, shareName, serverName, description, dataType, null) { } /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="port">,    .</param> /// <param name="driver">,    .</param> /// <param name="processorName">  .</param> /// <param name="shareName">  .</param> /// <param name="serverName"> .</param> /// <param name="description"> .</param> /// <param name="dataType">  .</param> public Printer(string name, IPort port, IDriver driver, string processorName, string shareName, string serverName, string description, DataType dataType) : this(name, port?.Name, driver?.Name, processorName, shareName, serverName, description, dataType) { } /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="portName"> .</param> /// <param name="driverName"> .</param> /// <param name="processorName">  .</param> /// <param name="shareName">  .</param> /// <param name="serverName"> .</param> /// <param name="description"> .</param> public Printer(string name, string portName, string driverName, string processorName, string shareName, string serverName, string description) : this(name, portName, driverName, processorName, shareName, serverName, description, DataType.RAW) { } /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="port">,    .</param> /// <param name="driver">,    .</param> /// <param name="processorName">  .</param> /// <param name="shareName">  .</param> /// <param name="serverName"> .</param> /// <param name="description"> .</param> public Printer(string name, IPort port, IDriver driver, string processorName, string shareName, string serverName, string description) : this(name, port?.Name, driver?.Name, processorName, shareName, serverName, description) { } /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="portName"> .</param> /// <param name="driverName"> .</param> /// <param name="processorName">  .</param> /// <param name="shareName">  .</param> /// <param name="serverName"> .</param> public Printer(string name, string portName, string driverName, string processorName, string shareName, string serverName) : this(name, portName, driverName, processorName, shareName, serverName, null) { } /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="port">,    .</param> /// <param name="driver">,    .</param> /// <param name="processorName">  .</param> /// <param name="shareName">  .</param> /// <param name="serverName"> .</param> public Printer(string name, IPort port, IDriver driver, string processorName, string shareName, string serverName) : this(name, port?.Name, driver?.Name, processorName, shareName, serverName) { } /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="portName"> .</param> /// <param name="driverName"> .</param> /// <param name="processorName">  .</param> /// <param name="shareName">  .</param> public Printer(string name, string portName, string driverName, string processorName, string shareName) : this(name, portName, driverName, processorName, shareName, null) { } /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="port">,    .</param> /// <param name="driver">,    .</param> /// <param name="processorName">  .</param> /// <param name="shareName">  .</param> public Printer(string name, IPort port, IDriver driver, string processorName, string shareName) : this(name, port?.Name, driver?.Name, processorName, shareName) { } /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="portName"> .</param> /// <param name="driverName"> .</param> /// <param name="processorName">  .</param> public Printer(string name, string portName, string driverName, string processorName) : this(name, portName, driverName, processorName, null) { } /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="port">,    .</param> /// <param name="driver">,    .</param> /// <param name="processorName">  .</param> public Printer(string name, IPort port, IDriver driver, string processorName) : this(name, port?.Name, driver?.Name, processorName) { } /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="portName"> .</param> /// <param name="driverName"> .</param> public Printer(string name, string portName, string driverName) : this(name, portName, driverName, "WinPrint") { } /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="port">,    .</param> /// <param name="driver">,    .</param> public Printer(string name, IPort port, IDriver driver) : this(name, port?.Name, driver?.Name) { } /// <summary> ///    . /// </summary> /// <param name="serverName"> .</param> public override void Install(string serverName) { try { if (All.Select(p => p.Name).Contains(Name)) Uninstall(serverName); PrinterInfo printerInfo = new PrinterInfo { ServerName = serverName, PrinterName = Name, ShareName = ShareName, PortName = Port?.Name, DriverName = Driver?.Name, Comment = Description, Location = Location, DevMode = new IntPtr(0), SepFile = SepFile, PrintProcessor = Processor, DataType = Enum.GetName(typeof(DataType), DataType), Parameters = Parameters, SecurityDescriptor = new IntPtr(0), }; if (AddPrinter(serverName, 2, ref printerInfo)) return; int lastWin32ErrorCode = Marshal.GetLastWin32Error(); if (lastWin32ErrorCode == 0) return; throw new PrintingException(lastWin32ErrorCode); } catch (Exception e) { throw new PrintingException(e.Message, e); } } /// <summary> ///    . /// </summary> /// <param name="serverName"> .</param> public override void Uninstall(string serverName) { try { if (!All.Select(p => p.Name).Contains(Name)) return; PrinterDefaults defaults = new PrinterDefaults { DesiredAccess = PrinterAccess.PrinterAllAccess }; if (!NET.Port.OpenPrinter(Name, out IntPtr printerHandle, ref defaults)) throw new PrintingException(Marshal.GetLastWin32Error()); if (!DeletePrinter(printerHandle)) { int lastWin32ErrorCode = Marshal.GetLastWin32Error(); if (lastWin32ErrorCode == PrintingException.ErrorInvalidPrinterName) return; throw new PrintingException(lastWin32ErrorCode); } NET.Port.ClosePrinter(printerHandle); } catch (Exception e) { throw new PrintingException(e.Message, e); } } #region Native /// <summary> ///    . /// </summary> /// <param name="serverName"> .</param> /// <param name="level"> .</param> /// <param name="printerInfo"> .</param> /// <returns>True,    ,  False.</returns> [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.StdCall)] internal static extern bool AddPrinter(string serverName, uint level, [In] ref PrinterInfo printerInfo); /// <summary> ///   ,     . /// </summary> /// <param name="printerName"> .</param> /// <param name="bytesNeeded">   .</param> /// <returns>True,    ,  False.</returns> [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)] internal static extern bool GetDefaultPrinter([Out] StringBuilder printerName, ref uint bytesNeeded); /// <summary> ///     . /// </summary> /// <param name="printerName">    .</param> /// <returns>True,    ,  False.</returns> [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)] internal static extern bool SetDefaultPrinter(string printerName); /// <summary> ///      . /// </summary> /// <param name="flags">   .</param> /// <param name="serverName"> .</param> /// <param name="level"> .</param> /// <param name="printers">   .</param> /// <param name="bufferSize"> .</param> /// <param name="bytesNeeded">       .</param> /// <param name="bufferReturnedLength">  .</param> /// <returns>True,    ,  False.</returns> [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)] internal static extern bool EnumPrinters(PrinterEnumFlag flags, string serverName, uint level, IntPtr printers, uint bufferSize, ref uint bytesNeeded, ref uint bufferReturnedLength); /// <summary> ///    . /// </summary> /// <param name="printer">  .</param> /// <returns>True,    ,  False.</returns> [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.StdCall)] internal static extern bool DeletePrinter(IntPtr printer); #endregion } 


Expand PrintingApi
 /// <summary> ///        . /// </summary> public static Printer[] Printers => Printer.All; /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="port">,    .</param> /// <param name="driver">,    .</param> /// <param name="processorName">  .</param> /// <param name="shareName">  .</param> /// <param name="serverName"> .</param> /// <param name="description"> .</param> /// <param name="dataType">  .</param> /// <param name="location"> .</param> /// <param name="parameters"> .</param> /// <param name="sepFile"></param> /// <returns>  .</returns> public Printer RunPrinter(string name, Port port, Driver driver, string processorName, string shareName, string serverName, string description, DataType dataType, string location, string parameters, string sepFile) { Printer printer = new Printer(name, port, driver, processorName, shareName, serverName, description, dataType, location, parameters, sepFile); printer.TryInstall(serverName); return printer; } /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="port">,    .</param> /// <param name="driver">,    .</param> /// <param name="processorName">  .</param> /// <param name="shareName">  .</param> /// <param name="serverName"> .</param> /// <param name="description"> .</param> /// <param name="dataType">  .</param> /// <param name="location"> .</param> /// <param name="parameters"> .</param> /// <returns>  .</returns> public Printer RunPrinter(string name, Port port, Driver driver, string processorName, string shareName, string serverName, string description, DataType dataType, string location, string parameters) => RunPrinter(name, port, driver, processorName, shareName, serverName, description, dataType, location, parameters, null); /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="port">,    .</param> /// <param name="driver">,    .</param> /// <param name="processorName">  .</param> /// <param name="shareName">  .</param> /// <param name="serverName"> .</param> /// <param name="description"> .</param> /// <param name="dataType">  .</param> /// <param name="location"> .</param> /// <returns>  .</returns> public Printer RunPrinter(string name, Port port, Driver driver, string processorName, string shareName, string serverName, string description, DataType dataType, string location) => RunPrinter(name, port, driver, processorName, shareName, serverName, description, dataType, location, null); /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="port">,    .</param> /// <param name="driver">,    .</param> /// <param name="processorName">  .</param> /// <param name="shareName">  .</param> /// <param name="serverName"> .</param> /// <param name="description"> .</param> /// <param name="dataType">  .</param> /// <returns>  .</returns> public Printer RunPrinter(string name, Port port, Driver driver, string processorName, string shareName, string serverName, string description, DataType dataType) => RunPrinter(name, port, driver, processorName, shareName, serverName, description, dataType, null); /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="port">,    .</param> /// <param name="driver">,    .</param> /// <param name="processorName">  .</param> /// <param name="shareName">  .</param> /// <param name="serverName"> .</param> /// <param name="description"> .</param> /// <returns>  .</returns> public Printer RunPrinter(string name, Port port, Driver driver, string processorName, string shareName, string serverName, string description) => RunPrinter(name, port, driver, processorName, shareName, serverName, description, DataType.RAW); /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="port">,    .</param> /// <param name="driver">,    .</param> /// <param name="processorName">  .</param> /// <param name="shareName">  .</param> /// <param name="serverName"> .</param> /// <returns>  .</returns> public Printer RunPrinter(string name, Port port, Driver driver, string processorName, string shareName, string serverName) => RunPrinter(name, port, driver, processorName, shareName, serverName, null); /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="port">,    .</param> /// <param name="driver">,    .</param> /// <param name="processorName">  .</param> /// <param name="shareName">  .</param> /// <returns>  .</returns> public Printer RunPrinter(string name, Port port, Driver driver, string processorName, string shareName) => RunPrinter(name, port, driver, processorName, shareName, null); /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="port">,    .</param> /// <param name="driver">,    .</param> /// <param name="processorName">  .</param> /// <returns>  .</returns> public Printer RunPrinter(string name, Port port, Driver driver, string processorName) => RunPrinter(name, port, driver, processorName, null); /// <summary> ///     <see cref="Printer"/>. /// </summary> /// <param name="name"> .</param> /// <param name="port">,    .</param> /// <param name="driver">,    .</param> /// <returns>  .</returns> public Printer RunPrinter(string name, Port port, Driver driver) => RunPrinter(name, port, driver, "WinPrint"); 


Add removal of all attached printers to the port and driver to be deleted.
 IEnumerable<Printer> printers = Printer.All.Where(p => p.Driver?.Name == Name); foreach (Printer printer in printers) printer.Uninstall(serverName); 


We are testing
 /// <summary> ///     <see cref="Printer"/>. /// </summary> [TestClass] public class PrinterTests { /// <summary> ///  . /// </summary> protected const string PrinterName = "Test Printer"; /// <summary> ///  . /// </summary> protected const string PortName = "TESTPORT:"; /// <summary> ///  . /// </summary> protected const string DriverName = "Test Driver"; /// <summary> ///    . /// </summary> [TestMethod] public void InstallTest() { Printer printer = new Printer(PrinterName, PortName, DriverName); printer.Install(); Assert.IsTrue(Printer.All.Select(p => p.Name).Contains(PrinterName)); } /// <summary> ///    . /// </summary> [TestMethod] public void UninstallTest() { Printer printer = new Printer(PrinterName, PortName, DriverName); printer.Uninstall(); Assert.IsFalse(Printer.All.Select(p => p.Name).Contains(PrinterName)); } /// <summary> ///        . /// </summary> [TestMethod] public void TryInstallTest() { Printer printer = new Printer(PrinterName, PortName, DriverName); bool f = printer.TryInstall(); Assert.IsTrue(f); Assert.IsTrue(Printer.All.Select(p => p.Name).Contains(PrinterName)); } /// <summary> ///        . /// </summary> [TestMethod] public void TryUninstallTest() { Printer printer = new Printer(PrinterName, PortName, DriverName); bool f = printer.TryUninstall(); Assert.IsTrue(f); Assert.IsFalse(Printer.All.Select(p => p.Name).Contains(PrinterName)); } } 


In order for the changes in the system to take effect, after installing the printer we need to restart the print service manually. We will write a static method in the PrintingApi class that will start / restart Spooler. This is also true for cases when the print service on the computer was initially stopped:

 /// <summary> ///   . /// </summary> /// <returns>True,    ,  False.</returns> public static bool TryRestart() { int tryCount = 5; while (tryCount > 0) { try { ServiceController sc = new ServiceController("Spooler"); if (sc.Status != ServiceControllerStatus.Stopped || sc.Status != ServiceControllerStatus.StopPending) { sc.Stop(); sc.WaitForStatus(ServiceControllerStatus.Stopped); } sc.Start(); sc.WaitForStatus(ServiceControllerStatus.Running); return sc.Status == ServiceControllerStatus.Running; } catch { tryCount--; } } return false; } 

System.ServiceProcess.dll . : , , — , , «», (, ) .

. , API :


We make the last general test for the PrintingApi class and proceed to the final part of the article:

Unit test to check the sequential installation of all components of the printer
 [TestClass] public class PrintingApiTests { protected const string MonitorName = "mfilemon"; protected const string PortName = "TESTPORT:"; protected const string DriverName = "Test Driver"; protected const string PrinterName = "Test Printer"; protected const string MonitorFile = "D:/Printing Tests/mfilemon.dll"; protected const string DriverFile = "D:/Printing Tests/pscript5.dll"; protected const string DriverDataFile = "D:/Printing Tests/testprinter.ppd"; protected const string DriverConfigFile = "D:/Printing Tests/ps5ui.dll"; protected const string DriverHelpFile = "D:/Printing Tests/pscript.hlp"; [TestMethod] public void PrinterInstallationTest() { PrintingApi.TryRestart(); Monitor monitor = PrintingApi.Factory.CreateMonitor(MonitorName, MonitorFile); Port port = PrintingApi.Factory.OpenPort(PortName, monitor); Driver driver = PrintingApi.Factory.InstallDriver(DriverName, DriverFile, DriverDataFile, DriverConfigFile, DriverHelpFile, 3, Environment.Current, DataType.RAW, null, monitor); Printer printer = PrintingApi.Factory.RunPrinter(PrinterName, port, driver); PrintingApi.TryRestart(); Assert.IsNotNull(printer); } } 





Note that almost all native Spooler methods block the flow in which they are called, do not forget to perform operations with our API in asynchronous mode in order to avoid hanging the main STA-flow UI.

Work with data


After installing the virtual printer in the system, you need to configure the monitor. It all depends on the specification of the monitor, for this you need to study its documentation. Specifically, in our case, the mfilemon.dll is configured using the registry:

 string monitorName = "mfilemon"; string portName = "TESTPORT:"; string keyName = $"SYSTEM\\CurrentControlSet\\Control\\Print\\Monitors\\{monitorName}\\{portName}"; Registry.LocalMachine.CreateSubKey(keyName); using (RegistryKey regKey = Registry.LocalMachine.OpenSubKey(keyName, true)) { regKey.SetValue("OutputPath", "D:/Printing Tests/", RegistryValueKind.String); regKey.SetValue("FilePattern", "%r_%c_%u_%Y%m%d_%H%n%s_%j.ps", RegistryValueKind.String); regKey.SetValue("Overwrite", 0, RegistryValueKind.DWord); regKey.SetValue("UserCommand", string.Empty, RegistryValueKind.String); regKey.SetValue("ExecPath", string.Empty, RegistryValueKind.String); regKey.SetValue("WaitTermination", 0, RegistryValueKind.DWord); regKey.SetValue("PipeData", 0, RegistryValueKind.DWord); } 

«D:/Printing Tests/» PostScript- , , PDF, ( ) GhostScript , , . , System.IO FileSystemWatcher , :

 // ,        ,   ,       . FileSystemWatcher fileSystemWatcher = new FileSystemWatcher("D:/Printing Tests/", "*.ps") { NotifyFilter = NotifyFilters.DirectoryName }; fileSystemWatcher.NotifyFilter = fileSystemWatcher.NotifyFilter | NotifyFilters.FileName; fileSystemWatcher.NotifyFilter = fileSystemWatcher.NotifyFilter | NotifyFilters.Attributes; fileSystemWatcher.Created += new FileSystemEventHandler(PrinterHandler); //     . try { fileSystemWatcher.EnableRaisingEvents = true; //  . } catch (ArgumentException e) { } 

:

 void PrinterHandler(object sender, FileSystemEventArgs e) { //    . switch (e.ChangeType) { //   .          ,  . case WatcherChangeTypes.Created: try { // TODO:      ,    -   ( ). byte[] fileData = File.ReadAllBytes(e.FullPath); //        ,          . //      . File.Delete(e.FullPath); //        ,     . } catch (Exception ex) { } break; } } 

, .

Conclusion


, . « » (, — ) C#, , . , .

, Spooler.

, , , . Unit- . NuGet-package for use in their projects is available here . Download the universal PPD from here .

Thank you for attention!

Source: https://habr.com/ru/post/322322/


All Articles