How to publish a printer in Active Directory (C#)
Hi all,
We may publish a printer attached to our machine in Active Directory. To do that on Windows 7, for instance, we can go to "Devices and Printers" console, select "Printer Properties", go to "Sharing" tab, and mark the "List in the directory" checkbox. That action will end up calling SetPrinter API to do the work.
I created a C# WinForms sample which calls SetPrinter through p/invoke.
The form of the ap contains 1 text box for the printer name and 4 buttons: Publish, Update, Unpublish and Status.
Publish, Update and Unpublish operations may fail with error ERROR_IO_PENDING as documented in MSDN, which means that the operation is taking place in the background, and has not finished yet. So we can use Status to check the exact status of those pending operations: PUBLISHED, UNPUBLISHED, PENDING PUBLISH or PENDING UNPUBLISH. If we are e.g. PENDING PUBLISH, we press Status again after a while until we get PUBLISHED if everything went ok.
Update operation will fail with error ERROR_FILE_NOT_FOUND if the printer is not already published, as documented in MSDN.
This sample works on both x86 and x64. Here is the code:
Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace AlexPublishPrinter
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void publishButton_Click(object sender, EventArgs e)
{
Publish(Win32.DSPRINT_PUBLISH, textBox1.Text);
}
private void updateButton_Click(object sender, EventArgs e)
{
Publish(Win32.DSPRINT_UPDATE, textBox1.Text);
}
private void unpublishButton_Click(object sender, EventArgs e)
{
Publish(Win32.DSPRINT_UNPUBLISH, textBox1.Text);
}
private void statusButton_Click(object sender, EventArgs e)
{
GetPublishedStatus(textBox1.Text);
}
private void Publish(uint dwAction, string printerName)
{
IntPtr hPrinter = IntPtr.Zero;
Win32.PRINTER_DEFAULTS printerDefaults = new Win32.PRINTER_DEFAULTS();
Win32.PRINTER_INFO_7 printerInfo = new Win32.PRINTER_INFO_7();
try
{
// Open printer
printerDefaults.pDatatype = IntPtr.Zero;
printerDefaults.pDevMode = IntPtr.Zero;
printerDefaults.DesiredAccess = Win32.PRINTER_ALL_ACCESS;
if (!Win32.OpenPrinter(printerName, out hPrinter, ref printerDefaults))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// Publish printer
printerInfo.pszObjectGUID = IntPtr.Zero;
printerInfo.dwAction = dwAction;
if (!Win32.SetPrinter(hPrinter, 7, ref printerInfo, 0))
{
uint error = (uint)Marshal.GetLastWin32Error();
switch (error)
{
case Win32.ERROR_IO_PENDING:
// According to MSDN, DSPRINT_PUBLISH may fail with this error and attempt to complete the action in the background
MessageBox.Show("ERROR_IO_PENDING");
break;
case Win32.ERROR_FILE_NOT_FOUND:
// According to MSDN, DSPRINT_UPDATE will fail with this error if the printer is not already published
MessageBox.Show("ERROR_FILE_NOT_FOUND");
break;
default:
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
MessageBox.Show("SUCCESS");
}
catch (Exception ex)
{
// Show errors
MessageBox.Show(ex.Message);
}
finally
{
// Close printer
if (!hPrinter.Equals(IntPtr.Zero))
{
Win32.ClosePrinter(hPrinter);
}
}
}
private void GetPublishedStatus(string printerName)
{
IntPtr hPrinter = IntPtr.Zero;
Win32.PRINTER_DEFAULTS printerDefaults = new Win32.PRINTER_DEFAULTS();
Win32.PRINTER_INFO_7 printerInfo = new Win32.PRINTER_INFO_7();
IntPtr pPrinterInfo = IntPtr.Zero;
int cbNeeded = 0;
try
{
// Open printer
printerDefaults.pDatatype = IntPtr.Zero;
printerDefaults.pDevMode = IntPtr.Zero;
printerDefaults.DesiredAccess = Win32.PRINTER_ALL_ACCESS;
if (!Win32.OpenPrinter(printerName, out hPrinter, ref printerDefaults))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// Get if printer got published
if (!Win32.GetPrinter(hPrinter, 7, IntPtr.Zero, 0, out cbNeeded))
{
int error = Marshal.GetLastWin32Error();
if (error != Win32.ERROR_INSUFFICIENT_BUFFER)
{
throw new Win32Exception(error);
}
pPrinterInfo = Marshal.AllocHGlobal(cbNeeded);
if (!Win32.GetPrinter(hPrinter, 7, pPrinterInfo, cbNeeded, out cbNeeded))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
printerInfo = (Win32.PRINTER_INFO_7)Marshal.PtrToStructure(pPrinterInfo, typeof(Win32.PRINTER_INFO_7));
}
switch (printerInfo.dwAction)
{
case Win32.DSPRINT_PUBLISH:
MessageBox.Show("PUBLISHED");
break;
case Win32.DSPRINT_UNPUBLISH:
MessageBox.Show("UNPUBLISHED");
break;
case Win32.DSPRINT_PENDING | Win32.DSPRINT_PUBLISH:
MessageBox.Show("PENDING PUBLISH");
break;
case Win32.DSPRINT_PENDING | Win32.DSPRINT_UNPUBLISH:
MessageBox.Show("PENDING UNPUBLISH");
break;
default:
MessageBox.Show("Unknown status #" + printerInfo.dwAction.ToString());
break;
}
}
catch (Exception ex)
{
// Show errors
MessageBox.Show(ex.Message);
}
finally
{
// Clean up memory
if (!pPrinterInfo.Equals(IntPtr.Zero))
{
Marshal.FreeHGlobal(pPrinterInfo);
}
// Close printer
if (!hPrinter.Equals(IntPtr.Zero))
{
Win32.ClosePrinter(hPrinter);
}
}
}
}
}
Win32.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace AlexPublishPrinter
{
class Win32
{
public const uint ERROR_INSUFFICIENT_BUFFER = 122;
public const uint ERROR_IO_PENDING = 997;
public const uint ERROR_FILE_NOT_FOUND = 0x80070002;
public const uint DSPRINT_PUBLISH = 0x00000001;
public const uint DSPRINT_UPDATE = 0x00000002;
public const uint DSPRINT_PENDING = 0x80000000;
public const uint DSPRINT_UNPUBLISH = 0x00000004;
public const uint STANDARD_RIGHTS_REQUIRED = 0x000F0000;
public const uint PRINTER_ACCESS_ADMINISTER = 0x00000004;
public const uint PRINTER_ACCESS_USE = 0x00000008;
public const uint PRINTER_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | PRINTER_ACCESS_ADMINISTER | PRINTER_ACCESS_USE;
[StructLayout(LayoutKind.Sequential)]
public struct PRINTER_DEFAULTS
{
public IntPtr pDatatype;
public IntPtr pDevMode;
public uint DesiredAccess;
}
[StructLayout(LayoutKind.Sequential)]
public struct PRINTER_INFO_7
{
public IntPtr pszObjectGUID;
public uint dwAction;
}
[DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool OpenPrinter(
string pPrinterName,
out IntPtr phPrinter,
ref PRINTER_DEFAULTS pDefault
);
[DllImport("winspool.drv", SetLastError = true)]
public static extern bool SetPrinter(
IntPtr hPrinter,
int Level,
ref PRINTER_INFO_7 pPrinter,
int Command
);
[DllImport("winspool.drv", SetLastError = true)]
public static extern bool GetPrinter(
IntPtr hPrinter,
int Level,
IntPtr pPrinter,
int cbBuf,
out int pcbNeeded
);
[DllImport("winspool.drv", SetLastError = true)]
public static extern bool ClosePrinter(
IntPtr hPrinter
);
}
}
I hope this helps.
Regards,
Alex (Alejandro Campos Magencio)