Job Results From LocalPrintServer GetPrintQueue Are In Wrong Order - c#

I use the following routine to get my printer queue jobs. For some reason they seem to be in the wrong order...
Routine To Get List Of Printer Jobs:
private List<string> GetPrinterQueueJobs(string targetPrinterName)
{
List<string> jobs = new List<string>();
string unc = null;
string printerName = targetPrinterName;
if (printerName.Contains("\\"))
{
string[] printerNameParts = printerName.Split('\\');
unc = printerNameParts[0];
printerName = printerNameParts[1];
}
PrintQueue printQue = null;
if (unc == null)
{
//local printer
printQue = new LocalPrintServer().GetPrintQueue(printerName);
}
else
{
//remote printer
printQue = new PrintServer(unc).GetPrintQueue(printerName);
}
foreach (PrintSystemJobInfo queItem in printQue.GetPrintJobInfoCollection())
{
jobs.Insert(queItem.PositionInPrintQueue - 1, queItem.Name);
}
return jobs;
}
Usage Of Routine:
private void cbPrinters_SelectedIndexChanged(object sender, EventArgs e)
{
List<string> printers = GetListOfPrinters();
ComboBox cb = (ComboBox)sender;
string printerName = cb.Text;
if (printers.Contains(printerName))
{
List<string> printerQue = GetPrinterQueueJobs(printerName);
foreach (string queItem in printerQue)
{
ListViewItem lvi = new ListViewItem(queItem);
lvPrintQueue.Items.Add(lvi);
}
}
}
This is what the Windows Print Queue looks like:
This is what my results look like:
As you can see in this case they are inverted. The crazy thing is, when stepping through the routine I also check the "PositionInPrintQueue" property and it seems to think the jobs are actually in the order shown in my returned results.
Why is this happening? Does anyone have any ideas on how I can get them to be returned in the correct order?
Thanks!

It turns out that the Windows Printer Queue was not showing the items in order! By default it does not show you the order of the jobs! There is no type of "Queue Id", "Queue Index" or "Job Order" column in the Windows Printer Queue. When looking at the jobs, you can sort by job name, status, owner, pages, size, port, and submitted.
If you sort by submitted they should all match the results returned in the GetPrintJobInfoCollection() method. But in the end, don't trust the Windows Printer Queue for job order, trust the order returned in the GetPrintJobInfoCollection() method.

Related

Get process names of installed programs

How can one get the corresponding process name of the installed programs in Windows (10)? For now, I'm using this
string uninstallKey = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
using (RegistryKey rk = Registry.LocalMachine.OpenSubKey(uninstallKey))
{
foreach (string skName in rk.GetSubKeyNames())
{
using (RegistryKey sk = rk.OpenSubKey(skName))
{
//returns installed programs
}
}
}
to return the installed software. Despite not every installed program being shown, how can I get the name of the process, like it would be shown in Task Manager, that the program would start if it was started?
I want to make an application blacklist. If an application gets started it compares its process with the blacklist. If the process matches with an entry in the list, the process gets killed.
Use static method GetProcesses of Process class to create component for each running process on the local computer.
You can get their names like this:
var processNames = Process.GetProcesses().Select(x => x.ProcessName).ToList();
More about Process class here:
https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process?view=net-6.0
You should consider to use the Windows integrated feature to block applications via the registry. You can create such entries programmatically.
However, you can implement your own, but you must know that you can't prevent applications from starting using your approach. You can only kill it after it was started and after it has allocated resources.
Create your blacklist first: collect all installed application paths and let the user pick the application to blacklist (see CreateInstalledApplicationIndex method).
Use WMI to observe any process starts by registering a corresponding event handler.
In the event handler retrieve the started Process and compare its filename to your blacklisted filenames to identify and handle a forbidden process.
private List<FileInfo> InstallationInfos { get; } = new List<FileInfo>();
private List<FileInfo> BlacklistedExecutables { get; } = new List<FileInfo>();
public void ApplyBlacklist()
{
CreateInstalledApplicationIndex();
WatchProcessStarts();
}
private void CreateInstalledApplicationIndex()
{
string uninstallKey = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
using RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(uninstallKey);
foreach (string subKeyName in registryKey.GetSubKeyNames())
{
using RegistryKey subKey = registryKey.OpenSubKey(subKeyName);
var installationPath = subKey.GetValue("InstallLocation") as string;
if (string.IsNullOrWhiteSpace(installationPath))
{
continue;
}
IEnumerable<FileInfo> fileInfos = Enumerable.Empty<FileInfo>();
try
{
var installationDirectoryInfo = new DirectoryInfo(installationPath);
fileInfos = installationDirectoryInfo.EnumerateFiles("*.exe", new EnumerationOptions());
}
catch (IOException)
{
continue;
}
foreach (FileInfo fileInfo in fileInfos)
{
this.InstallationInfos.Add(fileInfo);
// For demo, all executables are blacklisted.
// TODO::Let user fill Blacklisted collection.
this.BlacklistedExecutables.Add(fileInfo);
}
}
}
private void WatchProcessStarts()
{
WqlEventQuery query = new WqlEventQuery("SELECT * FROM Win32_ProcessStartTrace");
ManagementEventWatcher watcher = new ManagementEventWatcher(query);
watcher.EventArrived += OnProcessStarted;
// Start listening for process start events
watcher.Start();
// Stop listening for process start events
//watcher.Stop();
}
private void OnProcessStarted(object sender, EventArrivedEventArgs e)
{
uint startedProcessId = (uint)e.NewEvent["ProcessID"];
// Note: Convert.ToInt32 will throw an OverflowException
// in case uint does not fit into an int.
// You must decide whether to handle this particular exception or to let it crash your application.
// Since it is very very unlikely that a machine runs Int32.MaxValue processes,
// I recommend not to handle this exception.
Process startedProcess = Process.GetProcessById(Convert.ToInt32(startedProcessId));
bool isProcessBlacklisted = this.BlacklistedExecutables
.Select(fileInfo => fileInfo.FullName)
.Contains(startedProcess.MainModule.FileName);
// TODO::Handle blacklisted process e.g., by killing it
if (isProcessBlacklisted)
{
startedProcess.Kill(entireProcessTree: true);
}
}
It is possible that you have to run your application as administrator in order to observe process starts and to kill them. In this case ensure to prompt the user to elevate your application's rights by restarting it with administrator permissions.
I got a solution which looks like this:
First I get all installed programs based on this
public static void LoadInstalledPrograms()
{
var FOLDERID_AppsFolder = new Guid("{1e87508d-89c2-42f0-8a7e-645a0f50ca58}");
ShellObject appsFolder = (ShellObject)KnownFolderHelper.FromKnownFolderId(FOLDERID_AppsFolder);
foreach (var app in (IKnownFolder)appsFolder)
{
//regular installed programs
if (app.Properties.System.Link.TargetParsingPath.Value != null)
{
AddToInstalledProgramsList(app.Name, app.Properties.System.Link.TargetParsingPath.Value, "reg");
}
//Windows apps/Microsoft store apps
/*else
{
AddToInstalledProgramsList(app.Name, app.Properties.GetProperty("System.AppUserModel.PackageInstallPath").ValueAsObject.ToString(), "win");
}*/
}
}
and then write them to a dictionary which is observed by a BackgroundWorker who kills every process from the list
static Dictionary<String, String> programs = new Dictionary<String, String>();
public static void AddToInstalledProgramsList(string programName, string programPath, string programType)
{
string processName = "";
if (programType == "reg")
{
programPath = programPath.Replace("/", "\\");
processName = programPath.Split("\\").Last();
if (!programs.ContainsKey(programName))
{
programs.Add(programName, processName);
}
else
{
AddDuplicateEntry(programName, processName, 1);
}
}
else if (programType == "win")
{
//...
}
Debug.WriteLine(programName + ": " + processName);
}
If I stumble across problems with this approach I will update this thread.

CPU Usage% NotifyIcon Using WMI

I've been trying to create a taskbar tray icon that displays the CPU usage (pulled from wbemtest if possible) when hovered over or clicked on using C#. I used the PercentProcessorTime Name from the ManagementClass Win32_PerfFormattedData_Counters_ProcessorInformation to pull the data. I haven't been able to find what data type the Name is even meant to return. Is there somewhere else I may be able to get the data from?
public void CPUactivitythread()
{
//Create a management object to open wbemtest
ManagementClass CPUdataclass = new ManagementClass("Win32_PerfFormattedData_Counters_ProcessorInformation");
try
{
//While Loop to pull consistent data from the CPU
while (true)
{
//Connect to the CPU Performance Instances in wbemtest
ManagementObjectCollection CPUobjectCollection = CPUdataclass.GetInstances();
foreach (ManagementObject obj in CPUobjectCollection) {
//Check that the "PercentProcessorTime" instance is there
if (obj["Name"].ToString() == "PercentProcessorTime")
{
if (Convert.ToUInt64(obj["PercentProcessorTime"]) > 0)
{
cPUUsageToolStripMenuItem.Text = (obj["PercentProcessorTime"]).ToString();
CPUoutputLabel.Text = (obj["PercentProcessorTime"]).ToString();
}
else
{
}
}
}
Thread.Sleep(1000);
}
}
The objects in the Collection correspond to the Task Manager CPU Information, one for each CPU, one for Total named "_Total". The "PercentProcessorTime" is a property of each performance object. Since you are getting the Formatted data, it has already been calculated ("cooked") according to its performance formula and can be used directly. LINQPad is a really useful tool for exploring objects if you don't like to read documentation :)
Try this:
ManagementClass CPUdataclass = new ManagementClass("Win32_PerfFormattedData_Counters_ProcessorInformation");
try {
//While Loop to pull consistent data from the CPU
while (true) {
//Connect to the CPU Performance Instances in wbemtest
ManagementObjectCollection CPUobjectCollection = CPUdataclass.GetInstances();
foreach (ManagementObject obj in CPUobjectCollection) {
//Check that the "PercentProcessorTime" instance is there
if (obj["Name"].ToString() == "_Total") {
var PPT = Convert.ToUInt64(obj.GetPropertyValue("PercentProcessorTime"));
if (PPT > 0) {
cPUUsageToolStripMenuItem.Text = PPT.ToString();
CPUoutputLabel.Text = PPT.ToString();
}
}
else {
}
}
}

How to contentiously loop through in order to get printer Status

I have coded a simple console application that checks the status of a printer. When status of a printer changes to a "printing status" the console app simply writes out a message saying "The Printer is now Printing".
Now what i'm having difficulties with is making this program keep checking the status of printer .. I'm not so sure what loop i have to use and how i can apply it. Please see below for more information:
public static void getPrintJob()
{
string printerName = "Some Printer Name";
string query = string.Format("SELECT * from Win32_Printer WHERE Name LIKE '%{0}'", printerName);
ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
ManagementObjectCollection coll = searcher.Get();
foreach (ManagementObject printer in coll)
{
if (Convert.ToInt32(printer.Properties["PrinterStatus"].Value) == 4)
{
Console.Write("Printer is Printing");
}
What kind of loop could i put above for this program to continuously keep on checking the the printer status? and whenever the status changes to 4 (printing status on the printer i'm targeting ).
If you want this to run forever, a while loop will work:
public static void getPrintJob()
{
string printerName = "Some Printer Name";
string query = string.Format("SELECT * from Win32_Printer WHERE Name LIKE '%{0}'", printerName);
ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
while(true)
{
ManagementObjectCollection coll = searcher.Get();
var alreadyPrinting = false;
foreach (ManagementObject printer in coll)
{
if (Convert.ToInt32(printer.Properties["PrinterStatus"].Value) == 4 && !alreadyPrinting)
{
Console.Write("Printer is Printing");
alreadyPrinting = true;
}
else
{
alreadyPrinting = false;
}
}
Thread.Sleep(1000);
}
}
As RB pointed out in the comments, Thread.Sleep(1000) will pause the loop for 1 second and stop it consuming all the CPU resource.
EDIT:
updated after comments.
Added a bool to track if the printer was already printing. This stops the code writing to the console for as long as the printer is printing. Multiple printers can start and will write to the console. But only once per print job.
Moved the coll variable assignment inside the while loop.

Retrieve port name for excel.ActivePrinter

Hi for the print method to work on excel i need the printername and the port name like this:
"printer On NE3" just as an example. The problem is, is that I use a virtual printer and the port is different, pretty much every time i need to use it.
The program needs to run in the background, so open dialog boxes to choose is not an option.
I have also tried to retrieve the port name from WMI with this:
protected string FindPrinterWithPort(string printerName)
{
StringBuilder query = new StringBuilder();
query.Append("SELECT * FROM Win32_Printer ");
query.Append("WHERE DeviceID = \""+printerName+"\"");
ObjectQuery objectQuery = new ObjectQuery(query.ToString());
var searcher = new ManagementObjectSearcher(objectQuery);
foreach (ManagementObject mo in searcher.Get())
{
return printerName +" On "+ mo["PortName"];
}
return string.Empty;
}
The portname i receive doing this gives me the full path to the virtual printer program.
I previously used the methods below to change printer for excel, and i alway knew they should be changed eventually, the code is neither fast nor good, but it worked for a while, until I made it multi-threaded and its just a big blockade. Because i need to lock these methods not to change the default printer in windows.
private bool SetPrinterForExcel(string printerName){
string[] ports = new string[]{"Ne00:", "Ne01:", "Ne02:", "Ne03:", "Ne04:",
"Ne05:", "Ne06:", "Ne07:", "Ne08:",
"Ne09:", "Ne10:", "Ne11:", "Ne12:",
"Ne13:", "Ne14:", "Ne15:", "Ne16:",
"LPT1:", "LPT2:", "File:", "SMC100:"};
foreach (string port in ports)
{
string printerWithPort = printerName + " On " + port;
bool success = SetAndTestPrinter(printerWithPort);
if(success)
return true;
}
return false;
}
private bool SetAndTestPrinter(string printerWithPort)
{
try
{
excel.ActivePrinter = printerWithPort;
return true;
}
catch
{
return false;
}
}
Any Idea how to retrieve the port, or is there a way from WMI query i can get the correct port.
Thanks in advance
Edit:
I have not gotten it work getting, what i have done to print now is change the default printer on windows and then just do the print method on my excel object. I Changed it by using:
[DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool SetDefaultPrinter(string Name);
And i lock it method and print. I have thought about trying to do a lock and save the ActivePrinter, even tho i believe performance will properly just as bad. Because the lock will stay until the default printer is changed, which takes sometimes up to 1 second, in a multi-threaded envirorment thats is a very long lock.
string printernamewithPort = string.Empty;
lock (ActivePrinterLock)
{
SetDefaultPrinter(printername);
printernamewithPort = excel.ActivePrinter;
}
foreach (Worksheet worksheet in workbook.Worksheets)
{
worksheet.PageSetup.PaperSize = XlPaperSize.xlPaperA4;
worksheet.PageSetup.Orientation = XlPageOrientation.xlPortrait;
worksheet.PageSetup.FitToPagesWide = 1;
worksheet.PageSetup.FitToPagesTall = false;
worksheet.PrintOutEx(ActivePrinter: printernamewithPort,Collate: true, Preview: false, PrintToFile: false);
}
What i dunno is that, if the default printer changes right away from another thread, if it looses the port given to the virtual printer. I have not done proper testing of this. In normal circumstances it seems that my Method FindPrinterWithPort would work.

Is there a way to check if a printing process was successful?

I have an application where I need to print a ticket. Each ticket must be unique. The application is windows forms and written entirely in c#. For our application we're using Samsung ML- 2525 laser monochromatic printers.
The flow is basically the following, the operator picks a product/ticket (which is unique) and then it presses a button that does 2 things:
Connects to a database and updates the product as used
Prints the ticket (this is done using System.Drawing and GDI+)
For some reason, every once in a while, the image that needs to be printed is not sent to the printer. It's a rare case, but it happens.
I tried to connect to the printer using Win32_Printer ( http://msdn.microsoft.com/en-us/library/Aa394363 ) but I can't get to get the current printer's state (online, offline, low toner, paper jam, etc). I can only check if the printer exists and that the paper size is installed correctly. I tried code similar to the following but it didn't work
private string MonitorPrintJobWmi()
{
var jobMessage = String.Empty;
var scope = new ManagementScope(ManagementPath.DefaultPath);
scope.Connect();
var selectQuery = new SelectQuery { QueryString = #"select * from Win32_PrintJob" };
var objSearcher = new ManagementObjectSearcher(scope, selectQuery);
var objCollection = objSearcher.Get();
foreach (var job in objCollection)
{
if (job != null)
{
jobMessage += String.Format("{0} \r\n", job["Name"].ToString());
jobMessage += String.Format("{0} \r\n", job["JobId"].ToString());
_jobId = Convert.ToInt32(job["JobId"]);
jobMessage += String.Format("{0} \r\n", job["JobStatus"].ToString());
jobMessage += String.Format("{0} \r\n", job["Status"].ToString());
}
}
return jobMessage;
}
I tried to get an API for the printer but I couldn't get a hold of it. By the way, the printer's software do indicate different errors in the windows toolbar.
My question is if anyone can lead me in the right direction as to how to connect to a printer and check if printing was successful.
Also, it would be helpful if someone know of some other specific printer in which I may accomplish this ie, changing hardware.
Thanks,
To get a list of print queues on the local machine, try PrintServer's GetPrintQueues method.
Once you have an instance of the PrintQueue object associated with the relevant printer, you can use it to access the printer's status (IsOffline, IsPaperOut, etc.). Also, you can use it to get a list of the jobs in the given queue (GetPrintJobInfoCollection) which then will allow you to get job-specific status information (IsInError, IsCompleted, IsBlocked, etc.).
Hope this helps!
After try to print your PrintDocument (System.Drawing.Printing), try to check status of printjobs.
First step: Initialize your printDocument.
Second step: Get your printer Name From System.Drawing.Printing.PrinterSettings.InstalledPrinters.Cast<string>();
And copy it into your printerDocument.PrinterSettings.PrinterName
Third step: Try to print and dispose.
printerDocument.Print();
printerDocument.Dispose();
Last step: Run the check in a Task (do NOT block UI thread).
Task.Run(()=>{
if (!IsPrinterOk(printerDocument.PrinterSettings.PrinterName,checkTimeInMillisec))
{
// failed printing, do something...
}
});
Here is the implementation:
private bool IsPrinterOk(string name,int checkTimeInMillisec)
{
System.Collections.IList value = null;
do
{
//checkTimeInMillisec should be between 2000 and 5000
System.Threading.Thread.Sleep(checkTimeInMillisec);
// or use Timer with Threading.Monitor instead of thread sleep
using (System.Management.ManagementObjectSearcher searcher = new System.Management.ManagementObjectSearcher("SELECT * FROM Win32_PrintJob WHERE Name like '%" + name + "%'"))
{
value = null;
if (searcher.Get().Count == 0) // Number of pending document.
return true; // return because we haven't got any pending document.
else
{
foreach (System.Management.ManagementObject printer in searcher.Get())
{
value = printer.Properties.Cast<System.Management.PropertyData>().Where(p => p.Name.Equals("Status")).Select(p => p.Value).ToList();
break;
}
}
}
}
while (value.Contains("Printing") || value.Contains("UNKNOWN") || value.Contains("OK"));
return value.Contains("Error") ? false : true;
}
Good luck.

Categories

Resources