title: Monitoring a print job using Print Spooler API
body: <p>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)</p>
  
  <pre><code>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 &gt; 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);
  }
  }
  </code></pre>
  
  <p>(taken from <a href="https://stackoverflow.com/a/3283918/3079364">https://stackoverflow.com/a/3283918/3079364</a>)</p>
  
  <p>To parse the pointer(printerData) returns from GetJobs, I use the following class.</p>
  
  <pre><code>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;
  }
  </code></pre>
  
  <p>The line <strong>(PrinterInfo2)Marshal.PtrToStructure(printerData, typeof(PrinterInfo2));</strong> 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 ?</p>
  
  <p><a href="https://i.stack.imgur.com/r3T39.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/r3T39.png" alt="same as stackoverflow GetJobs"></a></p>
  
  <p>EDIT: GetJobs definition</p>
  
  <pre><code>[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);
  </code></pre>
  
  <p>EDIT 2:</p>
  
  <p>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.</p>
  
  <pre><code>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;
  }
  </code></pre>