C# Delete printer port in Windows Printer Management

1

I'm currently writing a helper class (wrapper) for the Windows printer management.

At the moment I'm able to create and remove printer objects.

In one of my next steps I want to do the same for the printer ports.

At the moment, I am using the following code to create a new printer port in the Windows printer management.

1.) DLL-Import

[DllImport("winspool.drv")]
private static extern bool OpenPrinter(string printerName, out IntPtr phPrinter, ref PrinterDefaults printerDefaults);
[DllImport("winspool.drv")]
private static extern bool ClosePrinter(IntPtr phPrinter);
[DllImport("winspool.drv", CharSet = CharSet.Unicode)]
private static extern bool XcvDataW(IntPtr hXcv, string pszDataName, IntPtr pInputData, UInt32 cbInputData, out IntPtr pOutputData, UInt32 cbOutputData, out UInt32 pcbOutputNeeded, out UInt32 pdwStatus);

2.) Structures

/// <summary>
/// Defines the printer default settings like the access rights
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct PrinterDefaults
{
    public IntPtr pDataType;
    public IntPtr pDevMode;
    public PrinterAccess DesiredAccess;
}

/// <summary>
/// Stores the port data for adding a new printer port
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct CreatePortData
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
    public string sztPortName;
    public UInt32 dwVersion;
    public UInt32 dwProtocol;
    public UInt32 cbSize;
    public UInt32 dwReserved;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 49)]
    public string sztHostAddress;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)]
    public string sztSNMPCommunity;
    public UInt32 dwDoubleSpool;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)]
    public string sztQueue;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
    public string sztIPAddress;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 540)]
    public byte[] Reserved;
    public UInt32 dwPortNumber;
    public UInt32 dwSNMPEnabled;
    public UInt32 dwSNMPDevIndex;
}

/// <summary>
/// Specifies identifiers to indicate the printer access
/// </summary>
internal enum PrinterAccess
{
    ServerAdmin = 0x01,
    ServerEnum = 0x02,
    PrinterAdmin = 0x04,
    PrinterUse = 0x08,
    JobAdmin = 0x10,
    JobRead = 0x20,
    StandardRightsRequired = 0x000f0000,
    PrinterAllAccess = (StandardRightsRequired | PrinterAdmin | PrinterUse)
}

3.) Method

/// <summary>
/// Adds a new printer port to the windows print management
/// </summary>
/// <param name="configurationType">The configuration type of the port. For example Standard TCP/IP Port.</param>
/// <param name="portName"></param>
/// <param name="portType"></param>
/// <param name="endpoint"></param>
/// <exception cref="ArgumentNullException">Occurs when a parameter is null or empty</exception>
/// <exception cref="PrinterManagementHelperException">Occurs when adding a new TCP printer port to the printer management failed</exception>
public static void CreatePrinterPort(string configurationType, string portName, PrinterPortType portType, string endpoint)
{
    // Validation
    if (String.IsNullOrEmpty(configurationType))
        throw new ArgumentNullException(nameof(configurationType));
    if (String.IsNullOrEmpty(portName))
        throw new ArgumentNullException(nameof(portName));
    if (String.IsNullOrEmpty(endpoint))
        throw new ArgumentNullException(nameof(endpoint));

    // Opens the printer management
    PrinterDefaults defaults = new PrinterDefaults { DesiredAccess = PrinterAccess.ServerAdmin };
    if (!OpenPrinter(",XcvMonitor " + configurationType, out IntPtr printerHandle, ref defaults))
    {
        string message = String.Format(Resources.FailedToOpenPrinterManagement, configurationType);
        throw new PrinterManagementHelperException(message);
    }

    try
    {
        // Defines the port properties
        CreatePortData portData = new CreatePortData
        {
            dwVersion = 1,
            dwProtocol = (uint)portType,
            dwPortNumber = portType == PrinterPortType.Raw ? 9100u : 515u,
            dwReserved = 0,
            sztPortName = portName,
            sztIPAddress = endpoint,
            sztHostAddress = endpoint,
            sztSNMPCommunity = "public",
            dwSNMPEnabled = 1,
            dwSNMPDevIndex = 1
        };

        // Sets the port properties into the pointer
        uint size = (uint)Marshal.SizeOf(portData);
        portData.cbSize = size;
        IntPtr pointer = Marshal.AllocHGlobal((int)size);
        Marshal.StructureToPtr(portData, pointer, true);

        try
        {
            // Adds the port to the printer management
            if (!XcvDataW(printerHandle, "AddPort", pointer, size, out IntPtr outputData, 0, out uint outputNeeded, out uint status))
                throw new PrinterManagementHelperException(Resources.FailedToAddTcpPrinterPortToPrinterManagement);
        }
        catch (Exception)
        {
            throw;
        }
        finally
        {
            Marshal.FreeHGlobal(pointer);
        }
    }
    catch (Exception exception)
    {
        string message = String.Format(Resources.FailedToAddTcpPrinterPort,
            configurationType, portName, portType, endpoint);
        throw new PrinterManagementHelperException(message, exception);
    }
    finally
    {
        ClosePrinter(printerHandle);
    }
}

I've found the following topic on MSDN. Based on the topic I have to change the 'AddPort' Parameter to 'DeletePort'. Changing the parameter value doesn't remove the printer port from the printer management. The status that the method returns is 13 (HEX: 0x0000000d). Regarding the Win32 Error Codes, the data seems to be invalid. Did anyone know how to set the data when I want to delete a printer port?

Update 1

I figured out that the port data structure I used in the above code sample is only for adding printer ports. I found a topic on MSDN for this. There is also another topic about the structure to remove a printer port. I tried to rebuild the model based on the CreatePortData structure sample. I've created the following structure

/// <summary>
/// Stores the port data for deleting an existing printer port
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct DeletePortData
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
    public string sztPortName;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 49)]
    public string sztName;
    public UInt32 dwVersion;
    public UInt32 dwReserved;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 540)]
    public byte[] Reserved;
}

Now the whole source code to remove the printer port looks the following:

/// <summary>
/// Deletes an existing printer port from the printer management
/// </summary>
/// <param name="configurationType">The configuration type of the port. For example Standard TCP/IP Port.</param>
/// <param name="portName"></param>
public static void DeletePrinterPort(string configurationType, string portName)
{
    // Validation
    if (String.IsNullOrEmpty(configurationType))
        throw new ArgumentNullException(nameof(configurationType));
    if (String.IsNullOrEmpty(portName))
        throw new ArgumentNullException(nameof(portName));

    // Opens the printer management
    PrinterDefaults defaults = new PrinterDefaults { DesiredAccess = PrinterAccess.ServerAdmin };
    if (!OpenPrinter(",XcvMonitor " + configurationType, out IntPtr printerHandle, ref defaults))
    {
        string message = String.Format(Resources.FailedToOpenPrinterManagement, configurationType);
        throw new PrinterManagementHelperException(message);
    }

    try
    {
        // Defines the port properties
        DeletePortData portData = new DeletePortData
        {
            dwVersion = 1,
            dwReserved = 0,
            sztPortName = portName
        };

        // Sets the port properties into the pointer
        uint size = (uint)Marshal.SizeOf(portData);
        IntPtr pointer = Marshal.AllocHGlobal((int)size);
        Marshal.StructureToPtr(portData, pointer, true);

        try
        {
            // Deletes the port from the printer management
            if (!XcvDataW(printerHandle, "DeletePort", pointer, size, out IntPtr outputData, 0, out uint outputNeeded, out uint status))
                throw new PrinterManagementHelperException(Resources.FailedToDeletePrinterPortFromPrinterManagement);
        }
        catch (Exception)
        {
            throw;
        }
        finally
        {
            Marshal.FreeHGlobal(pointer);
        }
    }
    catch (Exception exception)
    {
        string message = string.Format(Resources.FailedToDeletePrinterPort, configurationType, portName);
        throw new PrinterManagementHelperException(message, exception);
    }
    finally
    {
        ClosePrinter(printerHandle);
    }
}

The method is still returning the status 13. The data is still invalid. Do anyone know what I did wrong?

I would appreciate if someone could help me.

Thanks in advance!

c#
.net
printing
asked on Stack Overflow Jan 12, 2018 by Sago • edited Jan 13, 2018 by Sago

1 Answer

1

I've found the solution for my problem. I had to remove the Reserved property from the DeletePortData structure.

Summary:

To remove an existing printer port from the printer management you have to do the following steps:

1.) DLL-Import

[DllImport("winspool.drv")]
private static extern bool OpenPrinter(string printerName, out IntPtr phPrinter, ref PrinterDefaults printerDefaults);
[DllImport("winspool.drv")]
private static extern bool ClosePrinter(IntPtr phPrinter);
[DllImport("winspool.drv", CharSet = CharSet.Unicode)]
private static extern bool XcvDataW(IntPtr hXcv, string pszDataName, IntPtr pInputData, UInt32 cbInputData, out IntPtr pOutputData, UInt32 cbOutputData, out UInt32 pcbOutputNeeded, out UInt32 pdwStatus);

2.) Define the needed structures:

/// <summary>
/// Defines the printer default settings like the access rights
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct PrinterDefaults
{
    public IntPtr pDataType;
    public IntPtr pDevMode;
    public PrinterAccess DesiredAccess;
}

/// <summary>
/// Stores the port data for deleting an existing printer port
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct DeletePortData
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
    public string sztPortName;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 49)]
    public string sztName;
    public UInt32 dwVersion;
    public UInt32 dwReserved;
}

/// <summary>
/// Specifies identifiers to indicate the printer access
/// </summary>
internal enum PrinterAccess
{
    ServerAdmin = 0x01,
    ServerEnum = 0x02,
    PrinterAdmin = 0x04,
    PrinterUse = 0x08,
    JobAdmin = 0x10,
    JobRead = 0x20,
    StandardRightsRequired = 0x000f0000,
    PrinterAllAccess = (StandardRightsRequired | PrinterAdmin | PrinterUse)
}

2.) Implement the following method

/// <summary>
/// Deletes an existing printer port from the printer management
/// </summary>
/// <param name="configurationType">The configuration type of the port. For example Standard TCP/IP Port.</param>
/// <param name="portName"></param>
public static void DeletePrinterPort(string configurationType, string portName)
{
    // Validation
    if (String.IsNullOrEmpty(configurationType))
        throw new ArgumentNullException(nameof(configurationType));
    if (String.IsNullOrEmpty(portName))
        throw new ArgumentNullException(nameof(portName));

    // Opens the printer management
    PrinterDefaults defaults = new PrinterDefaults { DesiredAccess = PrinterAccess.ServerAdmin };
    if (!OpenPrinter(",XcvMonitor " + configurationType, out IntPtr printerHandle, ref defaults))
    {
        string message = String.Format(Resources.FailedToOpenPrinterManagement, configurationType);
        throw new PrinterManagementHelperException(message);
    }

    try
    {
        // Defines the port properties
        DeletePortData portData = new DeletePortData
        {
            dwVersion = 1,
            dwReserved = 0,
            sztPortName = portName
        };

        // Sets the port properties into the pointer
        uint size = (uint)Marshal.SizeOf(portData);
        IntPtr pointer = Marshal.AllocHGlobal((int)size);
        Marshal.StructureToPtr(portData, pointer, true);

        try
        {
            // Deletes the port from the printer management
            if (!XcvDataW(printerHandle, "DeletePort", pointer, size, out IntPtr outputData, 0, out uint outputNeeded, out uint status))
                throw new PrinterManagementHelperException(Resources.FailedToDeletePrinterPortFromPrinterManagement);
        }
        catch (Exception)
        {
            throw;
        }
        finally
        {
            Marshal.FreeHGlobal(pointer);
        }
    }
    catch (Exception exception)
    {
        string message = string.Format(Resources.FailedToDeletePrinterPort, configurationType, portName);
        throw new PrinterManagementHelperException(message, exception);
    }
    finally
    {
        ClosePrinter(printerHandle);
    }
}
answered on Stack Overflow Jan 13, 2018 by Sago • edited Jan 13, 2018 by Sago

User contributions licensed under CC BY-SA 3.0