Function to Set Default Printer

Environment: Windows NT4 SP3; Developed with Visual C++ 6 SP3

SetPrinter is a simple DLL COM that enables you to programmatically set the default printer:

  • The problem that I faced is that if you attempt to specify the default printer from an application, that printer is not actually realized as the default printer by Windows immediately.

You can resolve this problem by doing the following:

  • First, install the driver printer on your machine;
  • If You work on Windows 2000, you must use 'SetDefaultPrinter' followed by the printer's name.
  • If You work on Windows 98 or Windows NT 4 you must first call the OpenPrinter function in order to retrieve the printer handle. Then you need to call the GetPrinter function twice in order to obtain the printer informations. You then need to call the WriteProfileString function (Windows NT)
  • Finally, you must notify all windows of this new Windows setting as follows:
    lResult = SendMessageTimeout(HWND_BROADCAST, 
                                 WM_SETTINGCHANGE, 
                                 0L, 0L,
                                 SMTO_NORMAL, 
                                 1000, 
                                 NULL);
    
    	

Or instead of going through all of that, you can use my COM component and its DPSetDefaultPrinter method. Here is the code for that method so that you can see all that needs to take place to perform a seemingly very simple task.

STDMETHODIMP CCustomPrinter::DPSetDefaultPrinter(BSTR pPrinterName, int *Result)
{
USES_CONVERSION;

  BOOL bFlag;
  OSVERSIONINFO osv;
  DWORD dwNeeded = 0;
  HANDLE hPrinter = NULL;
  PRINTER_INFO_2 *ppi2 = NULL;
  LPTSTR pBuffer = NULL;
  LONG lResult;
  
  // What version of Windows are you running?
  osv.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
  GetVersionEx(&osv);
  
  if (!pPrinterName)
	*Result = 0;
	return S_FALSE;  
  // If Windows 95 or 98, use SetPrinter...
  if (osv.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
  {
    // Open this printer so you can get information about it...
    bFlag = OpenPrinter((char*)OLE2CA(pPrinterName), &hPrinter, NULL);
    if (!bFlag || !hPrinter)
		*Result = 0;
		return S_FALSE;    
    // The first GetPrinter() tells you how big our buffer should
    // be in order to hold ALL of PRINTER_INFO_2. Note that this will
    // usually return FALSE. This only means that the buffer (the 3rd
    // parameter) was not filled in. You don't want it filled in here...
    GetPrinter(hPrinter, 2, 0, 0, &dwNeeded);
    if (dwNeeded == 0)
    {
      ClosePrinter(hPrinter);
      *Result = 0;
	  return S_FALSE;
    }
    
    // Allocate enough space for PRINTER_INFO_2...
    ppi2 = (PRINTER_INFO_2 *)GlobalAlloc(GPTR, dwNeeded);
    if (!ppi2)
    {
      ClosePrinter(hPrinter);
      *Result = 0;
	  return S_FALSE;
    }
    
    // The second GetPrinter() will fill in all the current information
    // so that all you need to do is modify what you're interested in...
    bFlag = GetPrinter(hPrinter, 2, (LPBYTE)ppi2, dwNeeded, &dwNeeded);
    if (!bFlag)
    {
      ClosePrinter(hPrinter);
      GlobalFree(ppi2);
      *Result = 0;
	  return S_FALSE;
    }
    
    // Set default printer attribute for this printer...
    ppi2->Attributes |= PRINTER_ATTRIBUTE_DEFAULT;
    bFlag = SetPrinter(hPrinter, 2, (LPBYTE)ppi2, 0);
    if (!bFlag)
    {
      ClosePrinter(hPrinter);
      GlobalFree(ppi2);
      *Result = 0;
	  return S_FALSE;
    }
    
    // Tell all open applications that this change occurred. 
    // Allow each application 1 second to handle this message.
    lResult = SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0L,
      (LPARAM)(LPCTSTR)"windows", SMTO_NORMAL, 1000, NULL);
  }
  
  // If Windows NT, use the SetDefaultPrinter API for Windows 2000,
  // or WriteProfileString for version 4.0 and earlier...
  else if (osv.dwPlatformId == VER_PLATFORM_WIN32_NT)
  {
#if(WINVER >= 0x0500)
    if (osv.dwMajorVersion >= 5) // Windows 2000 or later...
    {
      bFlag = SetDefaultPrinter(pPrinterName);
      if (!bFlag)
        *Result = 0;
		return S_FALSE;
    }
    
    else // NT4.0 or earlier...
#endif
    {
      // Open this printer so you can get information about it...
      bFlag = OpenPrinter((char*)OLE2CA(pPrinterName), &hPrinter, NULL);
      if (!bFlag || !hPrinter)
        *Result = 0;
		return S_FALSE;
      
      // The first GetPrinter() tells you how big our buffer should
      // be in order to hold ALL of PRINTER_INFO_2. Note that this will
      // usually return FALSE. This only means that the buffer (the 3rd
      // parameter) was not filled in. You don't want it filled in here...
      GetPrinter(hPrinter, 2, 0, 0, &dwNeeded);
      if (dwNeeded == 0)
      {
        ClosePrinter(hPrinter);
        *Result = 0;
		return S_FALSE;
      }
      
      // Allocate enough space for PRINTER_INFO_2...
      ppi2 = (PRINTER_INFO_2 *)GlobalAlloc(GPTR, dwNeeded);
      if (!ppi2)
      {
        ClosePrinter(hPrinter);
        *Result = 0;
		return S_FALSE;
      }
      
      // The second GetPrinter() fills in all the current
// information... bFlag = GetPrinter(hPrinter, 2, (LPBYTE)ppi2, dwNeeded, &dwNeeded); if ((!bFlag) || (!ppi2->pDriverName) || (!ppi2->pPortName)) { ClosePrinter(hPrinter); GlobalFree(ppi2); *Result = 0; return S_FALSE; } // Allocate buffer big enough for concatenated string. // String will be in form "printername,drivername,portname"... pBuffer = (LPTSTR)GlobalAlloc(GPTR, lstrlen((char*)OLE2CA(pPrinterName)) + lstrlen(ppi2->pDriverName) + lstrlen(ppi2->pPortName) + 3); if (!pBuffer) { ClosePrinter(hPrinter); GlobalFree(ppi2); *Result = 0; return S_FALSE; } // Build string in form "printername,drivername,portname"... lstrcpy(pBuffer, (char*)OLE2CA(pPrinterName)); lstrcat(pBuffer, ","); lstrcat(pBuffer, ppi2->pDriverName); lstrcat(pBuffer, ","); lstrcat(pBuffer, ppi2->pPortName); // Set the default printer in Win.ini and registry... bFlag = WriteProfileString("windows", "device", pBuffer); if (!bFlag) { ClosePrinter(hPrinter); GlobalFree(ppi2); GlobalFree(pBuffer); *Result = 0; return S_FALSE; } } // Tell all open applications that this change occurred. // Allow each app 1 second to handle this message. lResult = SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0L, 0L, SMTO_NORMAL, 1000, NULL); } // Cleanup... if (hPrinter) ClosePrinter(hPrinter); if (ppi2) GlobalFree(ppi2); if (pBuffer) GlobalFree(pBuffer); *Result = 1; return S_OK; }

Installation

  • On each machine where you want to use the SetPrinter component, you must register the DLL as follows:
    regsvr32 <path>SetPrinter.dll;

Downloads

Download SetPrinter project code - 40 Kb


Comments

  • An example usage would be helpful.

    Posted by mcgahanfl on 03/18/2004 10:51am

    I am a little rusty on my C++ and would appreciate an example of how to use your dll.
    1.  I used the C++ Wizard to create a new class based on
    your tlb file.
    2.  I wrote the testing code below.
    3.  The default printer returned is always blank. Please advise.
    
    	ICustomPrinter *  p = new ICustomPrinter();
    	CString strPrinter;
    	long iLen = 100;
    	strPrinter.GetBufferSetLength(iLen);
    	BSTR bPrinter = strPrinter.AllocSysString();
    	int iResult = 	p->DPGetDefaultPrinter(&bPrinter, &iLen);
    	CString strReturn(bPrinter);
            AfxMessageBox(strReturn);
    	delete p;

    Reply
  • What a SOB ? Copied code from MSDN SDK and made your own!

    Posted by Legacy on 08/27/2003 12:00am

    Originally posted by: Imran

    This following class and entire technique already demonstrated in MSDN SDK docoment nothing new except few MACRO and turn into COM class. Any novice programmer can do this.I wonder character like this exist, who make out some one else hard work their own! Specially in this case from Microsoft.
    

    Reply
  • Getting the defualt Printer in a client computer

    Posted by Legacy on 04/28/2003 12:00am

    Originally posted by: JonJon Bulalaque

    If you were able to set a default printer, can you also get the default printer of a client pc? Can you show me how?

    Reply
  • Related KB posting, bug fixes

    Posted by Legacy on 07/26/2002 12:00am

    Originally posted by: Scott Cameron

    There's a related microsoft knowledge base article on changing the default printer at:

    http://support.microsoft.com/default.aspx?scid=KB;EN-US;q246772&

    KB246772 I think it is.

    There are a few bugs with the code in the project I found. For example in my build it didn't find the GetDefaultPrinter and mis-identified that I was running windows 2000 (I am, it didn't think I was, hence the logic was wrong. - I don't think WINVER define is reliable as used in this context, at least not from a COM app, or there may be some other reason, but the logic was wrong). Manually changing the WINVER - to fix this - caused a build error with GetDefaultPrinter not defined, again, from COM I think you have to manually map this function in as in the Knowledge Base article which uses a standard DLL map-in via a mechanism like below (quoting from the article)


    // You are explicitly linking to GetDefaultPrinter because
    //linking
    // implicitly on Windows 95/98 or NT4 results in a runtime
    //error.
    // This block specifies which text version you explicitly
    //link to.
    #ifdef UNICODE
    #define GETDEFAULTPRINTER "GetDefaultPrinterW"
    #else
    #define GETDEFAULTPRINTER "GetDefaultPrinterA"
    #endif

    // and later in program
    ...

    fnGetDefaultPrinter = GetProcAddress(hWinSpool,
    GETDEFAULTPRINTER);
    if (!fnGetDefaultPrinter)
    {
    FreeLibrary(hWinSpool);
    return FALSE;
    }

    bFlag = fnGetDefaultPrinter(pPrinterName, pdwBufferSize);
    FreeLibrary(hWinSpool);
    ...


    The KB article appears to cover the different cases of windows versions also.

    Perhaps some of the bugs are related to this was a port from MFC based app and wasn't fully rewritten to be a happy COM object? Anyway the KB article should be helpful to people having trouble with the example.

    Scott

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • The latest release of SugarCRM's flagship product gives users new tools to build extraordinary customer relationships. Read an in-depth analysis of SugarCRM's enhanced ability to help companies execute their customer-facing initiatives from Ovum, a leading technology research firm.

  • When it comes to desktops – physical or virtual – it's all about the applications. Cloud-hosted virtual desktops are growing fast because you get local data center-class security and 24x7 access with the complete personalization and flexibility of your own desktop. Organizations make five common mistakes when it comes to planning and implementing their application management strategy. This eBook tells you what they are and how to avoid them, and offers real-life case studies on customers who didn't …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds