Microsoft Dynamics 365 Blog

Periodically we receive support requests with questions about available printers list in NAV. Especially about situations when this printers list includes or not includes some printers unexpectedly and issues especially comes when there is used Microsoft Terminal Servers or Citrix.

Situation changed when NAV developing team replaced previous used GetProfileString to winspool function EnumPrinters. This was replaced in Microsoft Dynamics NAV 5.0 Sp1 with fix by KB 2592845 and in Microsoft Dynamics NAV 2009 SP1 with fix by KB 2575152.

About function EnumPrinters you can read at http://msdn.microsoft.com/en-us/library/dd162692(v=VS.85).aspx. NAV is using flags PRINTER_ENUM_LOCAL and PRINTER_ENUM_CONNECTIONS and level 2 for printers list. However we still sometimes receive the same issues:
printers list is not correct.

So I made little C# application which shows printers list the same way as NAV does and I would like you run it and compare results with list in NAV or list in Windows devices whenever you met any problems.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

internal class clPrintersList
{
[DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool EnumPrintersA(
PrinterEnumFlags Flags,
string Name,
uint Level,
IntPtr pPrinterEnum,
uint cbBuf,
ref uint pcbNeeded,
ref uint pcReturned);


public static string[] GetPrinterNames(PrinterEnumFlags PEF)
{
List<string> returnVal = new List<string>();
foreach (PRINTER_INFO_2 info in EnumPrinters(PEF))
{
returnVal.Add(info.pName);
}
return returnVal.ToArray();
}



private static PRINTER_INFO_2[] EnumPrinters(PrinterEnumFlags Flags)
{
uint cbNeeded = 0;
uint cReturned = 0;
if (EnumPrintersA(Flags, null, 2, IntPtr.Zero, 0, ref cbNeeded, ref cReturned))
{
return null;
}
int lastWin32Error = Marshal.GetLastWin32Error();
if (lastWin32Error == 122)
{
IntPtr pAddr = Marshal.AllocHGlobal((int)cbNeeded);
if (EnumPrintersA(Flags, null, 2, pAddr, cbNeeded, ref cbNeeded, ref cReturned))
{
PRINTER_INFO_2[] printerInfo1 = new PRINTER_INFO_2[cReturned];
int offset = pAddr.ToInt32();
Type type = typeof(PRINTER_INFO_2);
int increment = Marshal.SizeOf(type);
for (int i = 0; i < cReturned; i++)
{
printerInfo1[i] = (PRINTER_INFO_2)Marshal.PtrToStructure(new IntPtr(offset), type);
offset += increment;
}
Marshal.FreeHGlobal(pAddr);
return printerInfo1;
}
lastWin32Error = Marshal.GetLastWin32Error();
}
throw new System.ComponentModel.Win32Exception(lastWin32Error);
}


private struct PRINTER_INFO_2
{
[MarshalAs(UnmanagedType.LPStr)] public string pServerName;
[MarshalAs(UnmanagedType.LPStr)] public string pName;
[MarshalAs(UnmanagedType.LPStr)]
public string pShareName;
[MarshalAs(UnmanagedType.LPStr)]
public string pPortName;
[MarshalAs(UnmanagedType.LPStr)]
public string pDriverName;
[MarshalAs(UnmanagedType.LPStr)]
public string pComment;
[MarshalAs(UnmanagedType.LPStr)]
public string pLocation;
public IntPtr pDevMode;
[MarshalAs(UnmanagedType.LPStr)]
public string pSepFile;
[MarshalAs(UnmanagedType.LPStr)]
public string pPrintProcessor;
[MarshalAs(UnmanagedType.LPStr)]
public string pDatatype;
[MarshalAs(UnmanagedType.LPStr)]
public string pParameters;
public IntPtr pSecurityDescriptor;
public Int32 Attributes;
public Int32 Priority;
public Int32 DefaultPriority;
public Int32 StartTime;
public Int32 UntilTime;
public Int32 Status;
public Int32 cJobs;
public Int32 AveragePPM;
}


[FlagsAttribute]
internal enum PrinterEnumFlags
{
PRINTER_ENUM_DEFAULT = 0x00000001,
PRINTER_ENUM_LOCAL = 0x00000002,
PRINTER_ENUM_CONNECTIONS = 0x00000004,
PRINTER_ENUM_FAVORITE = 0x00000004,
PRINTER_ENUM_NAME = 0x00000008,
PRINTER_ENUM_REMOTE = 0x00000010,
PRINTER_ENUM_SHARED = 0x00000020,
PRINTER_ENUM_NETWORK = 0x00000040,
PRINTER_ENUM_EXPAND = 0x00004000,
PRINTER_ENUM_CONTAINER = 0x00008000,
PRINTER_ENUM_ICONMASK = 0x00ff0000,
PRINTER_ENUM_ICON1 = 0x00010000,
PRINTER_ENUM_ICON2 = 0x00020000,
PRINTER_ENUM_ICON3 = 0x00040000,
PRINTER_ENUM_ICON4 = 0x00080000,
PRINTER_ENUM_ICON5 = 0x00100000,
PRINTER_ENUM_ICON6 = 0x00200000,
PRINTER_ENUM_ICON7 = 0x00400000,
PRINTER_ENUM_ICON8 = 0x00800000,
PRINTER_ENUM_HIDE = 0x01000000
}

}

namespace PrintersList
{
class Program
{
static void Main(string[] args)
{

//>> Local printers list
Console.WriteLine("Local printers list:");

string[] ListLocal;
ListLocal = clPrintersList.GetPrinterNames(clPrintersList.PrinterEnumFlags.PRINTER_ENUM_LOCAL);
int count = 1;

foreach (string name in ListLocal)
{
Console.WriteLine("Device {0} local - {1}", count, name);
count += 1;
}


//>> "Connected" printers list
Console.WriteLine("\nConnected printers list:");

string[] ListCon;
ListCon = clPrintersList.GetPrinterNames(clPrintersList.PrinterEnumFlags.PRINTER_ENUM_CONNECTIONS);
int count2 = 1;

foreach (string name in ListCon)
{
Console.WriteLine("Device {0} connected - {1}", count2, name);
count2 += 1;
}

Console.WriteLine("\nEnd Of List... \nIf you want to save to file, use something like:\nPrinterslist.exe >> file.txt");
Console.WriteLine("\nPress any key...");
Console.ReadKey();
}
}
}

You can create new C# console project in Visual Studio, copy/paste code from my file and compile project. Received exe file run from “cmd”, it shows 2 lists: local printers list (included redirected printers) and connected printers list (printers connected to PC remotely). If you run application like PrintersList.exe >> list.txt then you will have list saved to txt file.

There are few points I want you to pay attention:

  1. Level 2 in function EnumPrinters means: function performs an OpenPrinter call on each remote connection. If a remote connection is down, or the remote server no longer exists, or the remote printer no longer exists, the function must wait for RPC to time out and consequently fail the OpenPrinter call. You can modify application to use level 1 or 4 which, according documentation, are faster.
  2. Run application as “normal user” and run it “as administrator” and compare lists. I’ve seen cases where these lists have many differences – it means some printers are not available for normal users.
  3. There can be issues with printer names. NAV has limited total memory size for printer’s names, if there are hundreds of printers with “long names”, it can be last printers are not in the NAV list.

 

Any questions/comments are welcome 🙂

 

These postings are provided “AS IS” with no warranties and confer no rights. You assume all risk for your use.

Best Regards

Gedas Busniauskas

Microsoft Lithuania

Microsoft Customer Service and Support (CSS) EMEA

Program.cs

We're always looking for feedback and would like to hear from you. Please head to the Dynamics 365 Community to start a discussion, ask questions, and tell us what you think!