HOWTO: Retrieve and Set the Default Printer in Windows

ID: Q246772


The information in this article applies to:
  • Microsoft Windows 98
  • Microsoft Windows NT 4.0
  • Microsoft Windows 2000
  • Microsoft Windows 95


SUMMARY

Depending on the version of Windows used, there are different ways to retrieve and/or set the default printer.


MORE INFORMATION

Under Windows NT 4.0 (and earlier), neither SetPrinter nor SetDefaultPrinter can be used to set the default printer. Also, neither EnumPrinters nor GetDefaultPrinter can be used to get the default printer.

Under Windows NT 4.0, you may achieve this by using GetProfileString (to get the default printer) or WriteProfileString (to set the default printer). The Device value that you get or set actually contains three elements separated by commas as follows:


      printer name,driver name,port 
For example:

      My Printer,HPPCL5MS,lpt1: 
When using this method, you must specify a valid printer, driver, and port. If you don't, the APIs will *not* fail, potentially causing other programs to set the printer back to the previous valid printer or simply become very confused. You can use the EnumPrinters API to retrieve the printer name, driver name, and port name of all available printers.
  • Windows NT maps most .ini file references to the registry. Because of this, GetProfileString and WriteProfileString still function as if they were running under 16-bit Windows (Microsoft Windows and Windows for Workgroups).
  • After setting the default printer with either GetPrinter, GetDefaultPrinter, or WriteProfileString, you should notify all other open applications of the change by broadcasting the WM_SETTINGCHANGE message. Only applications that handle this message recognize the change.
  • WM_SETTINGCHANGE and WM_WININICHANGE are identical. Win32 programs should use WM_SETTINGCHANGE.

Sample Code

The following sample code shows how to retrieve the default printer (DPGetDefaultPrinter) and set the default printer (DPSetDefaultPrinter) under Windows 95, Windows 98, Windows NT, and Windows 2000.

// Size of internal buffer used to hold "printername,drivername,portname"
// string. Increasing this may be necessary for huge strings.
#define MAXBUFFERSIZE 250

/*----------------------------------------------------------------*/ 
/* DPGetDefaultPrinter                                            */ 
/*                                                                */ 
/* Parameters:                                                    */ 
/*   pPrinterName: Buffer alloc'd by caller to hold printer name. */ 
/*   pdwBufferSize: On input, ptr to size of pPrinterName.        */ 
/*          On output, min required size of pPrinterName.         */ 
/*                                                                */ 
/* NOTE: You must include enough space for the NULL terminator!   */ 
/*                                                                */ 
/* Returns: TRUE for success, FALSE for failure.                  */ 
/*----------------------------------------------------------------*/ 
BOOL DPGetDefaultPrinter(LPTSTR pPrinterName, LPDWORD pdwBufferSize)
{
  BOOL bFlag;
  OSVERSIONINFO osv;
  TCHAR cBuffer[MAXBUFFERSIZE];
  PRINTER_INFO_2 *ppi2 = NULL;
  DWORD dwNeeded = 0;
  DWORD dwReturned = 0;
  
  // What version of Windows are you running?
  osv.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
  GetVersionEx(&osv);
  
  // If Windows 95 or 98, use EnumPrinters...
  if (osv.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
  {
    // The first EnumPrinters() 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 4th
    // parameter) was not filled in. You don't want it filled in here...
    EnumPrinters(PRINTER_ENUM_DEFAULT, NULL, 2, NULL, 0, &dwNeeded, &dwReturned);
    if (dwNeeded == 0) 
      return FALSE;
    
    // Allocate enough space for PRINTER_INFO_2...
    ppi2 = (PRINTER_INFO_2 *)GlobalAlloc(GPTR, dwNeeded);
    if (!ppi2)
      return FALSE;
    
    // The second EnumPrinters() will fill in all the current information...
    bFlag = EnumPrinters(PRINTER_ENUM_DEFAULT, NULL, 2, (LPBYTE)ppi2, dwNeeded, &dwNeeded, &dwReturned);
    if (!bFlag)
    {
      GlobalFree(ppi2);
      return FALSE;
    }
    
    // If given buffer too small, set required size and fail...
    if ((DWORD)lstrlen(ppi2->pPrinterName) >= *pdwBufferSize)
    {
      *pdwBufferSize = (DWORD)lstrlen(ppi2->pPrinterName) + 1;
      GlobalFree(ppi2);
      return FALSE;
    }
    
    // Copy printer name into passed-in buffer...
    lstrcpy(pPrinterName, ppi2->pPrinterName);
    
    // Set buffer size parameter to min required buffer size...
    *pdwBufferSize = (DWORD)lstrlen(ppi2->pPrinterName) + 1;
  }
  
  // If Windows NT, use the GetDefaultPrinter API for Windows 2000,
  // or GetProfileString 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 = GetDefaultPrinter(pPrinterName, pdwBufferSize);
      if (!bFlag)
        return FALSE;
    }
    
    else // NT4.0 or earlier
#endif
    {
      // Retrieve the default string from Win.ini (the registry).
      // String will be in form "printername,drivername,portname".
      if (GetProfileString("windows", "device", ",,,", cBuffer, MAXBUFFERSIZE) <= 0)
        return FALSE;
      
      // Printer name precedes first "," character...
      strtok(cBuffer, ",");
      
      // If given buffer too small, set required size and fail...
      if ((DWORD)lstrlen(cBuffer) >= *pdwBufferSize)
      {
        *pdwBufferSize = (DWORD)lstrlen(cBuffer) + 1;
        return FALSE;
      }
      
      // Copy printer name into passed-in buffer...
      lstrcpy(pPrinterName, cBuffer);
      
      // Set buffer size parameter to min required buffer size...
      *pdwBufferSize = (DWORD)lstrlen(cBuffer) + 1;
    }
  }
  
  // Cleanup...
  if (ppi2)
    GlobalFree(ppi2);
  
  return TRUE;
}

#undef MAXBUFFERSIZE

/*-----------------------------------------------------------------*/ 
/* DPSetDefaultPrinter                                             */ 
/*                                                                 */ 
/* Parameters:                                                     */ 
/*   pPrinterName: Valid name of existing printer to make default. */ 
/*                                                                 */ 
/* Returns: TRUE for success, FALSE for failure.                   */ 
/*-----------------------------------------------------------------*/ 
BOOL DPSetDefaultPrinter(LPTSTR pPrinterName)
{
  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)
    return 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(pPrinterName, &hPrinter, NULL);
    if (!bFlag || !hPrinter)
      return 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);
      return FALSE;
    }
    
    // Allocate enough space for PRINTER_INFO_2...
    ppi2 = (PRINTER_INFO_2 *)GlobalAlloc(GPTR, dwNeeded);
    if (!ppi2)
    {
      ClosePrinter(hPrinter);
      return 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);
      return 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);
      return 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)
        return FALSE;
    }
    
    else // NT4.0 or earlier...
#endif
    {
      // Open this printer so you can get information about it...
      bFlag = OpenPrinter(pPrinterName, &hPrinter, NULL);
      if (!bFlag || !hPrinter)
        return 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);
        return FALSE;
      }
      
      // Allocate enough space for PRINTER_INFO_2...
      ppi2 = (PRINTER_INFO_2 *)GlobalAlloc(GPTR, dwNeeded);
      if (!ppi2)
      {
        ClosePrinter(hPrinter);
        return FALSE;
      }
      
      // The second GetPrinter() fills in all the current<BR/>
      // information...
      bFlag = GetPrinter(hPrinter, 2, (LPBYTE)ppi2, dwNeeded, &dwNeeded);
      if ((!bFlag) || (!ppi2->pDriverName) || (!ppi2->pPortName))
      {
        ClosePrinter(hPrinter);
        GlobalFree(ppi2);
        return FALSE;
      }
      
      // Allocate buffer big enough for concatenated string.
      // String will be in form "printername,drivername,portname"...
      pBuffer = (LPTSTR)GlobalAlloc(GPTR,
        lstrlen(pPrinterName) +
        lstrlen(ppi2->pDriverName) +
        lstrlen(ppi2->pPortName) + 3);
      if (!pBuffer)
      {
        ClosePrinter(hPrinter);
        GlobalFree(ppi2);
        return FALSE;
      }
      
      // Build string in form "printername,drivername,portname"...
      lstrcpy(pBuffer, 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);
        return 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);
  
  return TRUE;
} 
There are a few circumstances where the code won't work (or appears not to work):
  • If you leave out the SendMessageTimeout call, no other application recognizes the change until the application is restarted.
  • If a different 32-bit application does not handle the WM_SETTINGCHANGE message, then it appears to that application as if the default printer has not been changed. You will need to exit and re-enter the application to force it to recognize the change.
  • MAXBUFFERSIZE is too small to hold the following string:
    
    printername,portname,drivername 
  • When using the WriteProfileString API in the earlier sample code (which is only necessary for Windows NT 4.0 and earlier), keep in mind that the port name passed to WriteProfileString was retrieved using the 32-bit API GetPrinter. This port name, although valid, is not readable by 16-bit applications and may cause problems for 16-bit applications. If this method is to be used under Windows NT (4.0 and earlier) and 16-bit applications need to understand the new port name, then the 16-bit alias for the port name ("NE0X:" where X is a number) should be used. The mapping between 32-bit port names and their 16-bit aliases can be found in the registry.


REFERENCES

For additional information, please see the following article(s) in the Microsoft Knowledge Base:

Q140560 How to Set the Default Printer Programmatically in Windows 95

Additional query words:

Keywords : kbGDI kbNTOS400 kbPrinting kbWinOS95 kbWinOS98 kbDSupport
Version : WINDOWS:95; winnt:4.0
Platform : WINDOWS winnt
Issue type : kbhowto


Last Reviewed: November 25, 1999
© 2000 Microsoft Corporation. All rights reserved. Terms of Use.