1
votes

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;
}
1
I assume those strings are all Wide strings, which would demand UnmanagedType.LPWStr on the struct. - rene
Thank you for your interest. I switched LPTStr to LPWStr but it is the same - user3079364
How is GetJobs defined? - NetMage
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

1 Answers

2
votes

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;
}