Getting the default printer in 64-bit Vista

M

Mark Rae

Hi,

I have used the code below to return the default printer in 32-bit WinXP
Pro, but now that I am running 64-bit Vista Business I'm getting an error:

using System.Drawing.Printing;

using (PrintDocument objPrintDocument = new PrintDocument())
{
string strDefaultPrinter =
objPrintDocument.PrinterSettings.PrinterName;
}

The error message is: "No printers are installed." and which seems to come
from a method called HdevmodeInternal

However, I have several printers installed, and the default printer is an
old HP LaserJet 5.

Does the above code not work on 64-bit Vista? The app in question is
compiled for x86...

Would I be better off using WMI to return the name of the default printer?

I've tried to find a solution in Google and MSDN, but haven't had much
luck...

Any assistance gratefully received.
 
W

Willy Denoyette [MVP]

Mark Rae said:
Hi,

I have used the code below to return the default printer in 32-bit WinXP
Pro, but now that I am running 64-bit Vista Business I'm getting an error:

using System.Drawing.Printing;

using (PrintDocument objPrintDocument = new PrintDocument())
{
string strDefaultPrinter =
objPrintDocument.PrinterSettings.PrinterName;
}

The error message is: "No printers are installed." and which seems to come
from a method called HdevmodeInternal

However, I have several printers installed, and the default printer is an
old HP LaserJet 5.

Does the above code not work on 64-bit Vista? The app in question is
compiled for x86...

Would I be better off using WMI to return the name of the default printer?

I've tried to find a solution in Google and MSDN, but haven't had much
luck...

Any assistance gratefully received.



Works for me on Vista 64 and 32 bit. What happens if you make "Microsoft XPS
Document Writer" the default printer?

Willy.
 
M

Mark Rae

Willy Denoyette said:
Works for me on Vista 64 and 32 bit. What happens if you make "Microsoft
XPS Document Writer" the default printer?

Same thing... Incidentally, it works for me on 32-bit Vista... Did you try
it with a 32-bit app running on 64-bit Vista...?

Also, when I try to query objPrintDocument.PrinterSettings.PrinterName, it
returns "Default printer is not set.", though it definitely is...

The Exception generated still doesn't have any value for InnerException, and
the <Exception>.TargetSite.Name is "HdevmodeInternal" - I can't even find
that anywhere in Google!
 
W

Willy Denoyette [MVP]

Mark Rae said:
Same thing... Incidentally, it works for me on 32-bit Vista... Did you try
it with a 32-bit app running on 64-bit Vista...?

Yep, both X86 and X64 flavors work on 64-bit Vista Ultimate.

Note that I'm allways adding an application manifest to my executable
assemblies. If you don't add a manifest to an application that runs under
wow64, this application will get a virtualized view of the registry (part
off) and the file system (part off).

To prevent virtualization you have to add a manifest like this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity version="1.0.0.0"
name="MyApplication.app"></assemblyIdentity>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel
level="asInvoker"></requestedExecutionLevel>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

save above contents in a file named "MyApplication.manifest", where
"MyApplication" is the name of the assembly (not absolutely required any
name will do as manifest filename).
When done, run the following command:
mt -manifest MyApplication.manifest -outputresource:MyApplication.exe;#1

mt.exe is part of VS2005.

I'm not sure this will be of any help, but it's worth a try.

Willy.
 
M

Mark Rae

I'm not sure this will be of any help, but it's worth a try.

Didn't help, unfortunately...

However, I'm wondering if there's something more fundamental going on
here...

When I run e.g. Word 2007, it "sees" the collection of printers installed,
and selects the default one.

However, when I open a PDF in Acrobat Reader 8.0 and try to print it, I get
a MessageBox saying:

"Before you can perform print-related tasks such as page setup or printing a
document, you need to install a printer."

Maybe Word and Acrobat Reader are using different methods to interrogate the
collection if installed printers...?
 
M

Mark Rae

However, I'm wondering if there's something more fundamental going on
here...

Also, when debugging the app in VS.NET 2005 and I force it to quit as soon
as the Exception is thrown, the Output window shows:

"A first chance exception of type
'System.Drawing.Printing.InvalidPrinterException' occurred in
System.Drawing.dll"
 
W

Willy Denoyette [MVP]

Mark Rae said:
Didn't help, unfortunately...

However, I'm wondering if there's something more fundamental going on
here...

When I run e.g. Word 2007, it "sees" the collection of printers installed,
and selects the default one.

However, when I open a PDF in Acrobat Reader 8.0 and try to print it, I
get a MessageBox saying:

"Before you can perform print-related tasks such as page setup or printing
a
document, you need to install a printer."

This is basically the same error as yours.
I'm running AR 8.1.0 and everything works as expected.
Maybe Word and Acrobat Reader are using different methods to interrogate
the collection if installed printers...?

Probably they do.
Note that .NET uses PrintDlg from comdlg32.dll (SysWow64), probably Adobe
uses the same.

Could you try this?

// start of code X86 only!
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace Willys
{
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto, Pack=0x1)]
public class PRINTDLG
{
public int lStructSize;
public IntPtr hwndOwner;
public IntPtr hDevMode;
public IntPtr hDevNames;
public IntPtr hDC;
public int Flags;
public short nFromPage;
public short nToPage;
public short nMinPage;
public short nMaxPage;
public short nCopies;
public IntPtr hInstance;
public IntPtr lCustData;
public IntPtr lpfnPrintHook;
public IntPtr lpfnSetupHook;
public string lpPrintTemplateName;
public string lpSetupTemplateName;
public IntPtr hPrintTemplate;
public IntPtr hSetupTemplate;
}

class Program
{
[DllImport("comdlg32.dll", CharSet=CharSet.Auto, SetLastError=true)]
static extern int PrintDlg([In, Out] PRINTDLG lppd);
[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true,
ExactSpelling=true)]
public static extern IntPtr GlobalLock(HandleRef handle);
[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true,
ExactSpelling=true)]
public static extern bool GlobalUnlock(HandleRef handle);
[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true,
ExactSpelling=true)]
public static extern IntPtr GlobalFree(HandleRef handle);

static void Main()
{
Console.WriteLine(GetDefaultPrinterName());
}
private static PRINTDLG CreatePRINTDLG()
{
PRINTDLG printdlg = new PRINTDLG();
printdlg.lStructSize = Marshal.SizeOf(typeof(PRINTDLG));
printdlg.hwndOwner = IntPtr.Zero;
printdlg.hDevMode = IntPtr.Zero;
printdlg.hDevNames = IntPtr.Zero;
printdlg.Flags = 0x0;
printdlg.hwndOwner = IntPtr.Zero;
printdlg.hDC = IntPtr.Zero;
printdlg.nFromPage = 0x1;
printdlg.nToPage = 0x1;
printdlg.nMinPage = 0x0;
printdlg.nMaxPage = 0x270f;
printdlg.nCopies = 0x1;
printdlg.hInstance = IntPtr.Zero;
printdlg.lCustData = IntPtr.Zero;
printdlg.lpfnPrintHook = IntPtr.Zero;
printdlg.lpfnSetupHook = IntPtr.Zero;
printdlg.lpPrintTemplateName = null;
printdlg.lpSetupTemplateName = null;
printdlg.hPrintTemplate = IntPtr.Zero;
printdlg.hSetupTemplate = IntPtr.Zero;
return printdlg;
}
static string GetDefaultPrinterName()
{
PRINTDLG printdlg = CreatePRINTDLG();
printdlg.Flags = 0x400; //PD_RETURNDEFAULT
if (PrintDlg(printdlg) == 0)
return "No Default Printer Set";
IntPtr hDevNames = printdlg.hDevNames;
IntPtr handle = GlobalLock(new HandleRef(printdlg, hDevNames));
if (handle == IntPtr.Zero)
throw new Win32Exception();
// calculate offset of dev names in DEVNAMES struct allocated by
PrintDlg
// X86 platforms only!!!!!!!!
int devOffset = Marshal.SystemDefaultCharSize *
Marshal.ReadInt16((IntPtr) (((int) handle) + 2));
string name = Marshal.PtrToStringAuto((IntPtr) (((int) handle) +
devOffset));
GlobalUnlock(new HandleRef(printdlg, hDevNames));
GlobalFree(new HandleRef(printdlg, printdlg.hDevNames));
GlobalFree(new HandleRef(printdlg, printdlg.hDevMode));
return name;
}
}
}
// end of code

If this doesn't work, you have a fundamental issue, not related to .NET.
Sure you can also give WMI a try.
Willy.
 
M

Mark Rae

Could you try this?

That returns "No Default Printer Set"
If this doesn't work, you have a fundamental issue, not related to .NET.

Hmm - that's a bit worrying... :-(
Sure you can also give WMI a try.

WMI works perfectly, however...

using (ManagementObjectSearcher objMOS = new
ManagementObjectSearcher("SELECT * FROM Win32_Printer"))
{
Dictionary<string, bool> dicResults = new Dictionary<string, bool>();
using (ManagementObjectCollection objMOC = objMOS.Get())
{
foreach (ManagementObject objMO in objMOC)
{
dicResults.Add(objMO["Name"].ToString(),
Convert.ToBoolean(objMO["Default"]));
}
}
return dicResults;
}
 
W

Willy Denoyette [MVP]

Mark Rae said:
That returns "No Default Printer Set"

That means that the PrintDlg call returns 0, which means "failure" to get
the default printer..... which does not necessarily means there is no
default printer :)

If this doesn't work, you have a fundamental issue, not related to .NET.

Hmm - that's a bit worrying... :-(
Sure you can also give WMI a try.

WMI works perfectly, however...

using (ManagementObjectSearcher objMOS = new
ManagementObjectSearcher("SELECT * FROM Win32_Printer"))
{
Dictionary<string, bool> dicResults = new Dictionary<string, bool>();
using (ManagementObjectCollection objMOC = objMOS.Get())
{
foreach (ManagementObject objMO in objMOC)
{
dicResults.Add(objMO["Name"].ToString(),
Convert.ToBoolean(objMO["Default"]));
}
}
return dicResults;
}


WMI does not use comdlg32.dll, anyway, it looks like there the issue is with
the 32bit version of the common dialog component or (more probably) one of
it's dependents. Mind to check the version installed in %windir%\SysWow64? I
have 6.0.6000.16386, however I can't believe yours would be different.

Willy.
 
W

Willy Denoyette [MVP]

Mark Rae said:
Could you try this?

That returns "No Default Printer Set"
If this doesn't work, you have a fundamental issue, not related to .NET.

Hmm - that's a bit worrying... :-(
Sure you can also give WMI a try.

WMI works perfectly, however...

using (ManagementObjectSearcher objMOS = new
ManagementObjectSearcher("SELECT * FROM Win32_Printer"))
{
Dictionary<string, bool> dicResults = new Dictionary<string, bool>();
using (ManagementObjectCollection objMOC = objMOS.Get())
{
foreach (ManagementObject objMO in objMOC)
{
dicResults.Add(objMO["Name"].ToString(),
Convert.ToBoolean(objMO["Default"]));
}
}
return dicResults;
}



Note that you can simplify/optimize your query like this:

string DefaultPrinterName() {
string defaultPrinter = null;
SelectQuery q = new SelectQuery("select caption from win32_printer where
default = true");
using(ManagementObjectSearcher searcher = new
ManagementObjectSearcher(q))
{
foreach (ManagementObject printer in searcher.Get()) {
defaultPrinter = printer["Caption"].ToString();
}
}
return defaultPrinter;
}

Willy.
 
M

Mark Rae

That means that the PrintDlg call returns 0, which means "failure" to get
the default printer..... which does not necessarily means there is no
default printer :)

OK... Do I need to be concerned about this, do you think...?
WMI does not use comdlg32.dll, anyway, it looks like there the issue is
with the 32bit version of the common dialog component or (more probably)
one of it's dependents. Mind to check the version installed in
%windir%\SysWow64? I have 6.0.6000.16386, however I can't believe yours
would be different.

Yes - that's what I have...
 
M

Mark Rae

Note that you can simplify/optimize your query like this:

string DefaultPrinterName() {
string defaultPrinter = null;
SelectQuery q = new SelectQuery("select caption from win32_printer
where default = true");
using(ManagementObjectSearcher searcher = new
ManagementObjectSearcher(q))
{
foreach (ManagementObject printer in searcher.Get()) {
defaultPrinter = printer["Caption"].ToString();
}
}
return defaultPrinter;
}

Yes indeed - my method is part of a WMI base class which returns several of
these collections (OperatingSystem, ComputerSystem, BIOS, Processor,
PhysicalMemory, Printer etc), so I don't really need an extra method to
return the default printer specifically. I do appreciate, however, that that
would represent a small performance boost, especially on a system with very
many printers installed...
 
W

Willy Denoyette [MVP]

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top