1

I am trying to get some data of a print job using Print Spooler API in C#. To do that, I used following code snippet. (error handlings are removed)

public static int GetJobInfo(string printerUncName, int jobId)
{
    var printerInfo2 = new PrinterInfo2();
    var pHandle = new IntPtr();
    var defaults = new PrinterDefaults();
    try
    {
        //Open a handle to the printer
        bool ok = OpenPrinter(printerUncName, out pHandle, IntPtr.Zero);
        //Here we determine the size of the data we to be returned
        //Passing in 0 for the size will force the function to return the size of the data requested
        int actualDataSize = 0;
        GetJobs(pHandle, jobId, 2, IntPtr.Zero, 0, ref actualDataSize);
        int err = Marshal.GetLastWin32Error();
        if (err == 122)// ERROR_INSUFFICIENT_BUFFER 
        {
            if (actualDataSize > 0)
            {
                //Allocate memory to the size of the data requested
                IntPtr printerData = Marshal.AllocHGlobal(actualDataSize);
                //Retrieve the actual information this time
                GetJobs(pHandle, jobId, 2, printerData, actualDataSize, ref actualDataSize);
                //Marshal to our structure
                printerInfo2 = (PrinterInfo2)Marshal.PtrToStructure(printerData, typeof(PrinterInfo2));
                //We've made the conversion, now free up that memory
                Marshal.FreeHGlobal(printerData);
            }
        }
}
finally
{
    //Always close the handle to the printer
    ClosePrinter(pHandle);
}
}

(taken from https://stackoverflow.com/a/3283918/3079364)

To parse the pointer(printerData) returns from GetJobs, I use the following class.

public struct PrinterInfo2
{
    public uint JobID;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string ServerName;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string PrinterName;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string ShareName;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string PortName;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string DriverName;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string Comment;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string Location;
    public IntPtr DevMode;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string SepFile;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string PrintProcessor;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string Datatype;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string Parameters;
    public IntPtr SecurityDescriptor;
    public uint Attributes;
    public uint Priority;
    public uint DefaultPriority;
    public uint StartTime;
    public uint UntilTime;
    public uint Status;
    public uint Jobs;
    public uint AveragePpm;
}

The line (PrinterInfo2)Marshal.PtrToStructure(printerData, typeof(PrinterInfo2)); returns following object. I am not familiar with Windows apis ands dlls. Probably I am doing something wrong at creating parser class. How can I return a meaningful info from PtrToStructure method ?

same as stackoverflow GetJobs

EDIT: GetJobs definition

[DllImport("winspool.drv", SetLastError = true, EntryPoint = "GetJobA", CharSet = CharSet.Auto)]
public static extern bool GetJobs(
                IntPtr printerHandle,
                int jobId,
                int Level,
                IntPtr printerData,
                int bufferSize,
                ref int printerDataSize);

EDIT 2:

I applied NetMage's solution but it did not returned the correct number of PagesPrinted and TotalPages too. (It returned PagesPrinted = 2, TotalPages = 0 where it supposed to be PagesPrinted = 1, TotalPages = 2) Then I realized that WMI gives the same numbers when I run the following code snippet.

string searchQuery = "SELECT * FROM Win32_PrintJob";
ManagementObjectSearcher searchPrintJobs = new ManagementObjectSearcher(searchQuery);
ManagementObjectCollection prntJobCollection = searchPrintJobs.Get();
foreach (ManagementObject prntJob in prntJobCollection)
{
char[] splitArr = new char[1];
splitArr[0] = Convert.ToChar(",");
string prnterName = jobName.Split(splitArr)[0];
int prntJobID = Convert.ToInt32(jobName.Split(splitArr)[1]);
string documentName = prntJob.Properties["Document"].Value.ToString();
UInt32 jobSatus = (UInt32)prntJob.Properties["StatusMask"].Value;
UInt32 pagesPrinted = (UInt32)prntJob.Properties["PagesPrinted"].Value;
UInt32 totalPages = (UInt32)prntJob.Properties["TotalPages"].Value;
}
user3079364
  • 171
  • 1
  • 19
  • I assume those strings are all Wide strings, which would demand `UnmanagedType.LPWStr` on the struct. – rene Jan 02 '18 at 21:31
  • Thank you for your interest. I switched LPTStr to LPWStr but it is the same – user3079364 Jan 02 '18 at 21:58
  • How is `GetJobs` defined? – NetMage Jan 02 '18 at 22:42
  • I think I would call it `GetJob` like Microsoft does in that case... it only returns information about a single job. I would also suggest only calling `ClosePrinter` when you get a valid value from `OpenPrinter`. – NetMage Jan 02 '18 at 23:38

1 Answers1

2

Your problem is you copied the PRINTER_INFO_2 structure to create the JOB_INFO_2 structure, but they are not that alike at all. You need the following structure for JOB_INFO_2:

[StructLayout(LayoutKind.Sequential)]
public struct SystemTime {
    [MarshalAs(UnmanagedType.U2)] public short Year;
    [MarshalAs(UnmanagedType.U2)] public short Month;
    [MarshalAs(UnmanagedType.U2)] public short DayOfWeek;
    [MarshalAs(UnmanagedType.U2)] public short Day;
    [MarshalAs(UnmanagedType.U2)] public short Hour;
    [MarshalAs(UnmanagedType.U2)] public short Minute;
    [MarshalAs(UnmanagedType.U2)] public short Second;
    [MarshalAs(UnmanagedType.U2)] public short Milliseconds;

    public SystemTime(DateTime dt) {
        dt = dt.ToUniversalTime();  // SetSystemTime expects the SYSTEMTIME in UTC
        Year = (short)dt.Year;
        Month = (short)dt.Month;
        DayOfWeek = (short)dt.DayOfWeek;
        Day = (short)dt.Day;
        Hour = (short)dt.Hour;
        Minute = (short)dt.Minute;
        Second = (short)dt.Second;
        Milliseconds = (short)dt.Millisecond;
    }
}

public struct JobInfo2 {
    public uint JobID;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string PrinterName;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string ServerName;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string UserName;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string DocumentName;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string NotifyName;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string DataType;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string PrintProcessor;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string Parameters;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string DriverName;
    public IntPtr DevMode;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string strStatus;
    public IntPtr SecurityDescriptor;
    public uint Status;
    public uint Priority;
    public uint Position;
    public uint StartTime;
    public uint UntilTime;
    public uint TotalPages;
    public uint Size;
    public SystemTime Submitted;
    public uint Time;
    public uint PagesPrinted;
}
NetMage
  • 26,163
  • 3
  • 34
  • 55
  • Thank you for the answer. In this case I am getting meaningful info from Submitted Time field at least. However for me, important thing is totalPages and PagesPrinted fields. Obviously they are not correct. When I check printing spool using WMI, I see the same wrong quantities too. I am going to edit my question regarding these new progress https://imgur.com/a/a0lRF – user3079364 Jan 03 '18 at 16:16
  • Unfortunately that information is provided by the printer driver, and not all drivers provide it correctly. What driver are you using? What page information do you see when you open the spooler printer queue from P{rint Manager for the printer? – NetMage Jan 03 '18 at 16:43
  • I am using HP OfficejetPro 8100 printer and driver name is the same as the printer name. I am going to test this with another driver (HP Universal printing pcl 6). If this fix the problem, I will edit the question. Thanks – user3079364 Jan 03 '18 at 16:47
  • Interesting - none of my stuck jobs have anything besides TotalPages. Unfortunately, it seems like your issue isn't uncommon: [PagesPrinted field of job_info_2 not reliable for hp printers on windows 7](https://stackoverflow.com/q/14836627/2557128) Note: You apparently need to look in DevMode to get the number of copies. – NetMage Jan 03 '18 at 18:16
  • I appreciate for your effort on helping me in this manner. As you said it is very weird that I get incorrect info from the win32_printjob api while windows printer queue shows it correctly. I may need to open up another question. – user3079364 Jan 03 '18 at 20:18