Set default printer

Method 1

Based on an idea by Richard Gordon.

The question was: how to get a list of available printers and set a new default printer. Here is a solution that uses the win.ini file.
It is recommended for a 32-bit application to use the EnumPrinters procedure to get a list of available printers.
One might think it's not modern to change the default printer by writing in win.ini but this is still the recommended way according to MS documentation.

{windows.i}
 
DEFINE VARIABLE list-of-printers AS CHARACTER.
DEFINE VARIABLE newdefault AS CHARACTER.
DEFINE VARIABLE driver-and-port AS CHARACTER.
 
RUN getkey
  (INPUT  "devices",    /* The section name */
   INPUT  "",           /* The key name  */
   INPUT  "win.ini",    /* Name of ini file */
   OUTPUT list-of-printers).     /* Returned stuff */
 
/* you now have a comma separated list of printer names. Check it: */
/* message list-of-printers view-as alert-box. */
/* allow the user to pick one, suppose he picks the third entry: */
 
newdefault = ENTRY(3, list-of-printers).
 
/* read driver and port for the new default printer */
RUN getkey
  (INPUT  "devices",        /* The section name */
   INPUT  newdefault,       /* The key name  */
   INPUT  "win.ini",        /* Name of ini file */
   OUTPUT driver-and-port). /* Returned stuff */
 
/* and write it back */
RUN putkey 
  (INPUT "windows",
   INPUT "device",
   INPUT "win.ini",
   INPUT newdefault + "," + driver-and-port).
 
/* check it: 
message session:printer-name view-as alert-box.
*/
 
RETURN.
 
/* ------------- internal procedures ------------ */
 
PROCEDURE getkey :
 
DEFINE INPUT PARAMETER i-section AS CHARACTER.
DEFINE INPUT PARAMETER i-key AS CHARACTER.
DEFINE INPUT PARAMETER i-filename AS CHARACTER.
DEFINE OUTPUT PARAMETER o-value AS CHARACTER.
 
DEFINE VARIABLE EntryPointer AS INTEGER NO-UNDO.
DEFINE VARIABLE mem1 AS MEMPTR NO-UNDO.
DEFINE VARIABLE mem2 AS MEMPTR NO-UNDO.
DEFINE VARIABLE mem1size AS INTEGER NO-UNDO.
DEFINE VARIABLE mem2size AS INTEGER NO-UNDO.
DEFINE VARIABLE i       AS INTEGER    NO-UNDO.
DEFINE VARIABLE cbReturnSize  AS INTEGER    NO-UNDO.
 
ASSIGN
  SET-SIZE(mem1)  = 4000
  mem1size = 4000.
 
IF i-key = "" THEN EntryPointer = 0.
 
ELSE DO:
  /* Must fill memory with desired key name and EntryPointer must point to it */
 
  ASSIGN
  SET-SIZE(mem2) = 128
  mem2size = 128
  EntryPointer = GET-POINTER-VALUE(mem2)
  PUT-STRING(mem2,1) = i-key.
END.
 
RUN getprivateprofilestring{&A} IN hpApi
                              (i-section, 
                               EntryPointer, 
                               "",
                               GET-POINTER-VALUE(mem1),
                               INPUT mem1size, 
                               i-filename,
                               OUTPUT cbReturnSize).
 
/* if i-key was "", Windows will return a list of all keys in i-section.
   This list is not comma-separated but separated by CHR(0). Progress
   can not handle that easily so we'll now replace every 0 by a comma: */ 
 
DO i = 1 TO cbReturnSize:
  /* If this is a list convert null character into a comma to generate a csv
     type variable */
  o-value = IF (GET-BYTE(mem1, i) = 0 AND i NE cbReturnSize) 
               THEN o-value + ","
               ELSE o-value + CHR(GET-BYTE(mem1, i)).
END.
 
  SET-SIZE(mem1) = 0.
  SET-SIZE(mem2) = 0.
 
END PROCEDURE.
 
 
PROCEDURE putkey :
DEFINE INPUT PARAMETER i-section AS CHARACTER.
DEFINE INPUT PARAMETER i-key AS CHARACTER.
DEFINE INPUT PARAMETER i-filename AS CHARACTER.
DEFINE INPUT PARAMETER i-value AS CHARACTER.
 
DEFINE VARIABLE cbReturnSize AS INTEGER.
 
RUN writeprivateprofilestring{&A} IN hpApi
                               (i-section, 
                                i-key, 
                                i-value,
                                i-filename, 
                                OUTPUT cbReturnSize ).
 
END PROCEDURE.

Method 2

By Nenad Orlovic

Nenad Orlovic writes: "Here is another example for same purpose that uses SetPrinter API function. I don't know which way is better. Both have the same problem, they can't change SESSION:PRINTER-NAME when it was already changed by SYSTEM-DIALOG PRINTER-SETUP command."
usage:

   run SetDefaultPrinter("HP LaserJet 4L").

Additional note from Jurjen: Microsoft knowledgebase article Q140560 says this SetPrinter() call does not work on Windows NT4. Probably because NT4 does not support the PRINTER_INFO_5 structure. The good news is: Windows 2000 finally has the new API function we have all been waiting for: "SetDefaultPrinterA". The usage of this new API function is exactly similiar to the SetDefaultPrinter procedure by Nenad.
I did not try but it may be a good idea to call SendNotifyMessage(HWND_BROADCAST,WM_SETTINGCHANGE,0,"windows") to solve the SESSION:PRINTER-NAME problem.

/* Nenad Orlovic */
/* Change default printer example */
 
FUNCTION CheckBit RETURNS LOGICAL
  ( INPUT ip_dword AS INT,
    INPUT ip_bit AS INTEGER ) :
/*------------------------------------------------------------------------------
  Purpose:  Checks if ip_bit in ip_dword is set
    Notes:  ip_bit = 0,1,...,31
------------------------------------------------------------------------------*/
DEFINE VARIABLE iBit AS INTEGER EXTENT 32 INIT 
    [
     1       ,2       ,4       ,8        ,16       ,32       ,64        ,128,
     256     ,512     ,1024    ,2048     ,4096     ,8192     ,16384     ,32768,
     65536   ,131072  ,262144  ,524288   ,1048576  ,2097152  ,4194304   ,8388608,
     16777216,33554432,67108864,134217728,268435456,536870912,1073741824,-2147483648
    ] NO-UNDO.
 
    IF ip_bit > 31 OR ip_bit < 0 then return ?.
 
    ip_dword = TRUNC(ip_dword / iBit[ip_bit + 1],0).
    RETURN (ip_dword MOD 2 = 1).
 
END FUNCTION.
 
PROCEDURE SetDefaultPrinter:
/*------------------------------------------------------------------------------
  Purpose:     Sets ip_printer to be windows default printer 
  Parameters:  input - Printer name
  Notes:       
------------------------------------------------------------------------------*/
DEFINE INPUT PARAMETER ip_printer AS CHARACTER NO-UNDO.
 
DEFINE VARIABLE iRet AS INTEGER NO-UNDO.
DEFINE VARIABLE pPrinter AS MEMPTR NO-UNDO.
DEFINE VARIABLE hPrinter AS INTEGER NO-UNDO.
DEFINE VARIABLE iSize AS INTEGER NO-UNDO.
DEFINE VARIABLE iAttr AS INTEGER NO-UNDO.
 
DEFINE VARIABLE cMessage AS CHARACTER NO-UNDO.
 
  RUN OpenPrinterA (ip_printer,OUTPUT hPrinter,0, OUTPUT iRet).
  IF iRet = 0 THEN cMessage = "ERROR: OpenPrinterA " + ip_printer.
  ELSE DO:
    SET-SIZE(pPrinter) = 1. 
    /* First call is only to get needed size for pPrinter */
    RUN GetPrinterA(hPrinter,5,pPrinter,1,OUTPUT iSize,OUTPUT iRet). 
    SET-SIZE(pPrinter) = 0.
    SET-SIZE(pPrinter) = iSize. 
    RUN GetPrinterA(hPrinter,5,pPrinter,iSize,OUTPUT iSize,OUTPUT iRet).
    /* pPrinter points to PRINTER_INFO_5 */
 
    IF iRet = 0 THEN cMessage = "ERROR: GetPrinterA " + ip_printer.
    ELSE DO:
        iAttr = GET-LONG(pPrinter,9).
 
        IF NOT CheckBit(iAttr,2) THEN DO:
            PUT-LONG(pPrinter,9) = iAttr + 4. /* 4 = PRINTER_ATTRIBUTE_DEFAULT */
            RUN SetPrinterA(hPrinter,5,pPrinter,0,OUTPUT iRet).
            IF iRet = 0 THEN cMessage = "ERROR: SetPrinterA " + ip_printer.
        END.
 
        RUN ClosePrinter(hPrinter,OUTPUT iRet).
        IF iRet = 0 THEN cMessage = cMessage + "ERROR: ClosePrinter " + ip_printer.
    END.        
  END.
  SET-SIZE(pPrinter) = 0.
  RETURN cMessage.
END PROCEDURE.
 
PROCEDURE OpenPrinterA EXTERNAL "WINSPOOL.DLL":
   DEFINE INPUT PARAMETER pPrinterName AS CHARACTER.
   DEFINE OUTPUT PARAMETER phPrinter AS LONG.
   DEFINE INPUT PARAMETER pDefault AS LONG.
   DEFINE RETURN PARAMETER X AS LONG.
END PROCEDURE.
 
PROCEDURE ClosePrinter EXTERNAL "WINSPOOL.DLL":
   DEFINE INPUT PARAMETER hPrinter AS LONG.
   DEFINE RETURN PARAMETER X AS LONG.
END PROCEDURE.
 
PROCEDURE SetPrinterA EXTERNAL "WINSPOOL.DLL":
    DEFINE INPUT PARAMETER hPrinter AS LONG.
    DEFINE INPUT PARAMETER Level AS LONG.
    DEFINE INPUT PARAMETER pPrinter AS MEMPTR.
    DEFINE INPUT PARAMETER COMMAND AS LONG.
    DEFINE RETURN PARAMETER X AS LONG.
END PROCEDURE.
 
PROCEDURE GetPrinterA EXTERNAL "WINSPOOL.DLL":
    DEFINE INPUT PARAMETER hPrinter AS LONG.
    DEFINE INPUT PARAMETER Level AS LONG.
    DEFINE INPUT PARAMETER pPrinter AS MEMPTR.
    DEFINE INPUT PARAMETER cbBuf AS LONG.
    DEFINE OUTPUT PARAMETER pcbNeeded AS LONG.
    DEFINE RETURN PARAMETER X AS LONG.
END PROCEDURE.

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
manuelml's picture

An example with SetDefaultPrinterA

DEFINE VARIABLE l-printer-name AS CHARACTER.
DEFINE VARIABLE l-ireturn AS INTEGER.

l-printer-name = "HP LaserJet P2015":U.

RUN SetDefaultPrinterA(INPUT l-printer-name, OUTPUT l-ireturn).

PROCEDURE SetDefaultPrinterA EXTERNAL "WINSPOOL.drv":U :
DEFINE INPUT PARAMETER pszPrinter AS CHARACTER.
DEFINE RETURN PARAMETER ireturn AS LONG.
END PROCEDURE.