I'm querying to Win32_PrintJob WMI class every time there is a change with ManagementEventWatcher, I obtained data about it, such as: Document, HostPrintQueue, JobId, JobStatus, TotalPages, etc. But TotalPages is not representing the real number of page to print, Seems at the moment to obtain these data still the printjob doesn't finished to process and devolving a number of pages to print in that moment but the real total is other number, How to get the real number of a print job when finished it to process?
Here is my code:
ManagementEventWatcher createPrintJobWatcher;
String strComputerName = ".";
// Create event query to be notified within 1 milli second of a change in a service
WqlEventQuery createPrintJobQuery = new WqlEventQuery("SELECT * FROM __InstanceCreationEvent WITHIN 0.001 WHERE TargetInstance ISA \"Win32_PrintJob\"");
createPrintJobWatcher = new ManagementEventWatcher();
createPrintJobWatcher.Scope = new ManagementScope("\\\\" + strComputerName + "\\root\\CIMV2");
createPrintJobWatcher.Query = createPrintJobQuery;
// times out watcher.WaitForNextEvent in 1 seconds
createPrintJobWatcher.Options.Timeout = new TimeSpan(0, 0, 1);
//set the print event handler
createPrintJobWatcher.EventArrived += new EventArrivedEventHandler(createPrintJobListener);
createPrintJobWatcher.Start();
Console.WriteLine("Listening...");
Console.ReadLine();
createPrintJobListener method:
static void createPrintJobListener(object sender, EventArrivedEventArgs e)
{
SelectQuery query = new SelectQuery("Win32_PrintJob");
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(query))
using (ManagementObjectCollection printJobs = searcher.Get())
foreach (ManagementObject printJob in printJobs)
{
Console.WriteLine("c1:", printJob);
Console.WriteLine("ID: {0}", printJob.GetPropertyValue("JobId").ToString());
Console.WriteLine("name: {0}", printJob.GetPropertyValue("name").ToString());
Console.WriteLine("status: {0}", printJob.GetPropertyValue("status").ToString());
if (printJob.GetPropertyValue("JobStatus") != null)
{
Console.WriteLine("JobStatus: {0}", printJob.GetPropertyValue("JobStatus").ToString());
}
else
{
Console.WriteLine("JobStatus: NULLLLLL");
}
Console.WriteLine("PC: {0}", printJob.GetPropertyValue("HostPrintQueue").ToString());
Console.WriteLine("TOTOAL PAGES: {0}", printJob.GetPropertyValue("TotalPages").ToString());
}
}
WMI is probably not sufficient to do this.
Windows doesn't reliably provide the page count (or copies etc), so the only way to get accurate info is to pause the job and parse it. This is a non-trivial task, but here's a little more info.
Related
Very new to C# here, and I'm learning a lot as I go along.
I'm creating a Winforms app that installs patches remotely. Basically, you give it a file (.msi or .exe) and a computer list, and it goes down the list until all of the patches have been installed. It seems to be working for what I want it to do. Just a click and go thing for vuln/patch management. For the record, I use PSexec and powershell to do the same task, and they're wonderful. Just fiddling around with my own, hoping to do some learning in the process.
I want to create an accurate progress bar for the application, so the admin can have a general idea of what processes are being handled at the time. There is a very wide spread in the amount of systems that may need to be patched, anywhere from 10 systems to 1K+.
I have looked at tutorials of progress bar implementation, but most are based on the code writer estimating the time of task completion for specific jobs. Being that every patch size, install time, and amount of computers are different, that doesn't seem to help me much.
private void Start_Click(object sender, EventArgs e)
{
string complist = openFileDialog2.FileName;
string patch = textBox2.Text;
string fileName = patch;
string patchfile = Path.GetFileName(fileName);
foreach (string line in File.ReadLines(complist))
{
//Checks if C:\PatchUpdates exists on remote machine. If not, a folder is created.
if (!Directory.Exists(#"\\" + line + #"\C$\PatchUpdates"))
{
Directory.CreateDirectory(#"\\" + line + #"\C$\PatchUpdates");
}
//XCOPY patch to every computer
System.Diagnostics.Process processCopy = new System.Diagnostics.Process();
ProcessStartInfo StartInfo = new ProcessStartInfo();
StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
StartInfo.FileName = "cmd";
StartInfo.Arguments = string.Format("/c xcopy " + patch + " " + #"\\{0}\C$\PatchUpdates /K /Y", line);
processCopy.StartInfo = StartInfo;
processCopy.Start();
processCopy.WaitForExit();
//Checks filename and installs according to file type.
if (patch.Contains(".msi"))
{
//Uses WMI to execute a remote command to install using msiexec.
ConnectionOptions options = new ConnectionOptions();
options.Impersonation = System.Management.ImpersonationLevel.Impersonate;
ManagementScope WMIscope = new ManagementScope(
string.Format("\\\\{0}\\root\\cimv2", line));
WMIscope.Connect();
ManagementClass WMIprocess = new ManagementClass(
WMIscope, new ManagementPath("Win32_Process"), new ObjectGetOptions());
object[] processmsi = { #"cmd.exe /c msiexec /qn /i " + #"C:\PatchUpdates\" + patchfile + #" /norestart" };
object result = WMIprocess.InvokeMethod("Create", processmsi);
}
else if (patch.Contains(".exe"))
{
//Uses WMI to execute a remote command to install using commandline.
ConnectionOptions options = new ConnectionOptions();
options.Impersonation = System.Management.ImpersonationLevel.Impersonate;
ManagementScope WMIscope = new ManagementScope(
string.Format("\\\\{0}\\root\\cimv2", line));
WMIscope.Connect();
ManagementClass WMIprocess = new ManagementClass(
WMIscope, new ManagementPath("Win32_Process"), new ObjectGetOptions());
object[] processexe = { #"cmd.exe /c C:\PatchUpdates\" + patchfile + #" /silent /norestart" };
object result = WMIprocess.InvokeMethod("Create", processexe);
}
else if (patch.Contains(".msu"))
{
//Uses WMI to execute a remote command to install using WUSA.
ConnectionOptions options = new ConnectionOptions();
options.Impersonation = System.Management.ImpersonationLevel.Impersonate;
ManagementScope WMIscope = new ManagementScope(
string.Format("\\\\{0}\\root\\cimv2", line));
WMIscope.Connect();
ManagementClass WMIprocess = new ManagementClass(
WMIscope, new ManagementPath("Win32_Process"), new ObjectGetOptions());
object[] processmsu = { #"wusa " + patchfile + " /quiet /norestart" };
object result = WMIprocess.InvokeMethod("Create", processmsu);
}
}
}
The code above is where most of the work is done. When the user clicks "Start", the patch is copied to C:\PatchUpdates on every machine and is installed using WMI.
How could I make a progress bar that is based on the calculation of time taken to do each task, and finishes at 100% when the last computers are being patched?
I'm assuming a lot of work is needed to do this.
Any help is appreciated.
You need to first get the amount of lines (you systems count).
If you are using .Net 4.0 or later you can use
var lineCount = File.ReadLines(#"C:\file.txt").Count();
else you can do first
var lineCount = 0;
using (var reader = File.OpenText(#"C:\file.txt"))
{
while (reader.ReadLine() != null)
{
lineCount++;
}
}
After that you set the progressbars minimum to 0 and the Maximum to this count of systems. In the foreach you need only to do a progressbar.PerformStep.
public void loadFiles()
{
// Sets the progress bar's minimum value to a number representing
// no operations complete -- in this case, no files read.
progressBar1.Minimum = 0;
// Sets the progress bar's maximum value to a number representing
// all operations complete -- in this case, all five files read.
progressBar1.Maximum = Convert.ToInt(lineCount); // in our case to the number of systems
// Sets the Step property to amount to increase with each iteration.
// In this case, it will increase by one with every file read.
progressBar1.Step = 1;
// Uses a for loop to iterate through the operations to be
// completed. In this case, five files are to be copied into memory,
// so the loop will execute 5 times.
for (int i = 0; i <= 4; i++)
{
// Inserts code to copy a file
progressBar1.PerformStep();
// Updates the label to show that a file was read.
label1.Text = "# of Files Read = " + progressBar1.Value.ToString();
}
}
I couldnt able to find the cached and free memory of a system using C#.Help me.......
Add Microsoft.VisualBasic.Devices assembly reference to your project then you can use following
var Available = new ComputerInfo().AvailablePhysicalMemory;
var Total = new ComputerInfo().TotalPhysicalMemory;
var Cheched = Total - Available;
Edit:
Following code works for me, also note that Available amount includes the Free Amount and also includes most of the Cached amount.
ObjectQuery wql = new ObjectQuery("SELECT * FROM Win32_OperatingSystem");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(wql);
ManagementObjectCollection results = searcher.Get();
//total amount of free physical memory in bytes
var Available = new ComputerInfo().AvailablePhysicalMemory;
//total amount of physical memory in bytes
var Total = new ComputerInfo().TotalPhysicalMemory;
var PhysicalMemoryInUse = Total - Available;
Object Free = new object();
foreach (var result in results)
{
//Free amount
Free = result["FreePhysicalMemory"];
}
var Cached = Total - PhysicalMemoryInUse - UInt64.Parse(Free.ToString());
Console.WriteLine("Available: " + ByteToGb(Available));
Console.WriteLine("Total: " + ByteToGb(Total));
Console.WriteLine("PhysicalMemoryInUse: " + ByteToGb(PhysicalMemoryInUse));
Console.WriteLine("Free: " + ByteToGb(UInt64.Parse( Free.ToString())));
Console.WriteLine("Cached: " + ByteToGb(Cached));
I get InvalidOperationException when I run this code:
static void Main(string[] args)
{
var aLog = new EventLog("Microsoft-Windows-Diagnostics-Performance/Operational");
EventLogEntry entry;
var entries = aLog.Entries;
var stack = new Stack<EventLogEntry>();
for (var i = 0; i < entries.Count; i++)
{
entry = entries[i];
stack.Push(entry);
}
entry = stack.Pop();// only display the last record
Console.WriteLine("[Index]\t" + entry.Index +
"\n[EventID]\t" + entry.InstanceId +
"\n[TimeWritten]\t" + entry.TimeWritten +
"\n[MachineName]\t" + entry.MachineName +
"\n[Source]\t" + entry.Source +
"\n[UserName]\t" + entry.UserName +
"\n[Message]\t" + entry.Message +
"\n---------------------------------------------------\n");
}
Exception says that:
Microsoft-Windows-Diagnostics-Performance/Operational doesn't exist on this computer
Why?
Updated
Since you are using EventLog class, the valid "categories" (for sure this is not the correct word to name it...) for your constructor must be Application, System or any other Log Name available under Windows Log tree, not under Applications And Services Log tree.
const string LogName = "Microsoft-Windows-Diagnostics-Performance/Operational";
var query = new EventLogQuery(LogName, PathType.LogName, "*[System/Level=2]");
using (var reader = new EventLogReader(query))
{
var currentEvent = reader.ReadEvent();
while (currentEvent != null)
{
// Do your stuff here...
// Read next event.
currentEvent = reader.ReadEvent();
}
}
This snippet code works for me.
Remember to run this under elevated privileges. If not, you'll receive an Unauthorized exception throw.
First Answer
Because you are initializing your EventLog class with a category that doesn't exists.
Typical valid categories would be Application, System, etc.
The single parameter constructor for EventLog refers to a log of the registry. [^]
I want to detect when the user plugs in or removes a USB sound card. I've managed to actually catch the event when this happens, but I can't tell what just got plugged in.
I tried an approach based on this question:
string query =
"SELECT * FROM __InstanceCreationEvent " +
"WITHIN 2 "
+ "WHERE TargetInstance ISA 'Win32_PnPEntity'";
var watcher = new ManagementEventWatcher(query);
watcher.EventArrived += new EventArrivedEventHandler(watcher_EventArrived);
watcher.Start();
While I get the notifications via the EventArrived event, I have no idea how to determine the actual name of the device that just got plugged in. I've gone through every property and couldn't make heads or tails out of it.
I also tried a different query:
var query = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent where EventType = 1 or EventType = 2");
var watcher = new ManagementEventWatcher(query);
watcher.EventArrived += watcher_EventArrived;
watcher.Stopped += watcher_Stopped;
watcher.Query = query;
watcher.Start();
but also to no avail. Is there a way to find the name of the device that got plugged in or removed.
The bottom line is that I'd like to know when a USB sound card is plugged in or removed from the system. It should work on Windows 7 and Vista (though I will settle for Win7 only).
EDIT: Based on the suggestions by the winning submitter, I've created a full solution that wraps all the functionality.
If I use your first code, I can define my event like this:
// define USB class guid (from devguid.h)
static readonly Guid GUID_DEVCLASS_USB = new Guid("{36fc9e60-c465-11cf-8056-444553540000}");
static void watcher_EventArrived(object sender, EventArrivedEventArgs e)
{
ManagementBaseObject instance = (ManagementBaseObject )e.NewEvent["TargetInstance"];
if (new Guid((string)instance["ClassGuid"]) == GUID_DEVCLASS_USB)
{
// we're only interested by USB devices, dump all props
foreach (var property in instance.Properties)
{
Console.WriteLine(property.Name + " = " + property.Value);
}
}
}
And this will dump something like this:
Availability =
Caption = USB Mass Storage Device
ClassGuid = {36fc9e60-c465-11cf-8056-444553540000}
CompatibleID = System.String[]
ConfigManagerErrorCode = 0
ConfigManagerUserConfig = False
CreationClassName = Win32_PnPEntity
Description = USB Mass Storage Device
DeviceID = USB\VID_18A5&PID_0243\07072BE66DD78609
ErrorCleared =
ErrorDescription =
HardwareID = System.String[]
InstallDate =
LastErrorCode =
Manufacturer = Compatible USB storage device
Name = USB Mass Storage Device
PNPDeviceID = USB\VID_18A5&PID_0243\07072BE66DD78609
PowerManagementCapabilities =
PowerManagementSupported =
Service = USBSTOR
Status = OK
StatusInfo =
SystemCreationClassName = Win32_ComputerSystem
SystemName = KILROY_WAS_HERE
This should contain everything you need, including the device ID that you can get with something like instance["DeviceID"].
EDIT 1: Oh is see that it is not a USB storage device but only a USB device. I will look for another solution.
Two links that describe the same problem:
http://hintdesk.com/c-catch-usb-plug-and-unplug-event/
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/37123526-83fa-4e96-a767-715fe225bf28/
if (e.NewEvent.ClassPath.ClassName == "__InstanceCreationEvent")
{
Console.WriteLine("USB was plugged in");
//Get disk letter
foreach (ManagementObject partition in new ManagementObjectSearcher(
"ASSOCIATORS OF {Win32_DiskDrive.DeviceID='" + mbo.Properties["DeviceID"].Value
+ "'} WHERE AssocClass = Win32_DiskDriveToDiskPartition").Get())
{
foreach (ManagementObject disk in new ManagementObjectSearcher(
"ASSOCIATORS OF {Win32_DiskPartition.DeviceID='"
+ partition["DeviceID"]
+ "'} WHERE AssocClass = Win32_LogicalDiskToPartition").Get())
{
Console.WriteLine("Disk=" + disk["Name"]);
}
}
}
When I tried #AngryHacker solution, I noticed that the DeviceChangedEventArgs class did not ever get called, though. I removed it and just added Console.WriteLines() in the watcher_eventArrived methods.
Besides the deletion of the DeviceChangedEventArgs, here are my changes:
(at line 46 in EstablishedWatchEvents)
// setup the query to monitor removal
const string qryRemoval = "SELECT *" + "FROM __InstanceDeletionEvent "
+ "WITHIN 2 " + "WHERE TargetInstance ISA 'Win32_PnPEntity' ";
#region Events
private void insertWatcher_EventArrived(object sender, EventArrivedEventArgs e)
{
var mbo = (ManagementBaseObject) e.NewEvent["TargetInstance"];
if (new Guid((string) mbo["ClassGuid"]) == GUID_DEVCLASS_USB)
{
var deviceName = (string) mbo["Name"];
Console.WriteLine(deviceName + " was inserted");
}
}
private void removeWatcher_EventArrived(object sender, EventArrivedEventArgs e)
{
var mbo = (ManagementBaseObject)e.NewEvent["TargetInstance"];
if (new Guid((string)mbo["ClassGuid"]) == GUID_DEVCLASS_USB)
{
var deviceName = (string)mbo["Name"];
Console.WriteLine(deviceName + " was removed");
}
}
#endregion
I'm building a form and I'm trying to use threading in order to get some results from a WMI query to display in a textbox without having the form freeze up on the user. However, when I use the code below and use Break-All when debugging, the code just sits on getPrinterThread.Join(). I know I must be missing something.
My aim is to get a thread to run the ObtainPrinterPort method to completion, then get a thread to run the InstallPrinterPort method to completion. I have the code below as inline code in another method. The code isn't in a separate class or anything and I don't have a background worker because all of the examples I've seen, up until now, have only confused me.
Here's my admittedly poor thread attempt:
Thread printThread = new Thread(ObtainPrinterPort);
printThread.Start();
while (!printThread.IsAlive) ;
Thread.Sleep(1);
printThread.Join(); // Form sits and does nothing; Break-all reveals this line as statement being executed.
Thread installThread = new Thread(InstallPrinterPort);
installThread.Start();
while (!installThread.IsAlive);
Thread.Sleep(1);
installThread.Join();
Is there a simple way I can get something to work that is safe and will allow me to display the results that occur in the methods as they happen to the user in the textbox? Hopefully there's a way to do this that will allow me to continue to use the instance variables/methods/code I've written in the form class...otherwise, I'll have to re-write a lot of code if I'm going to implement a "DoWork"-type example (where my methods are called from the DoWork method/constructor or the Worker class).
Please keep in mind that my methods need to return text from the thread to a textbox to display results to the user. I have code that I'm assuming will allow me to return the text from the thread if it works, but I just wanted to make sure that any suggestions/help kept this in mind. The code I'm using is below:
public void AppendTextBox(string value)
{
if (InvokeRequired)
{
this.Invoke(new Action<string>(AppendTextBox), new object[] { value });
return;
}
txtResults.Text += value;
}
For what it's worth, here's my ObtainPrinterPort method and the CreateNewConnection method that accompanies it...the InstallPrinterPort method is extremely similar, so posting it won't really reveal much:
private ManagementScope CreateNewConnection(string server, string userID, string password)
{
string serverString = #"\\" + server + #"\root\cimv2";
ManagementScope scope = new ManagementScope(serverString);
try
{
ConnectionOptions options = new ConnectionOptions
{
Username = userID,
Password = password,
Impersonation = ImpersonationLevel.Impersonate,
EnablePrivileges = true
};
scope.Options = options;
scope.Connect();
}
catch (ManagementException err)
{
MessageBox.Show("An error occurred while querying for WMI data: " +
err.Message);
}
catch (System.UnauthorizedAccessException unauthorizedErr)
{
MessageBox.Show("Connection error (user name or password might be incorrect): " + unauthorizedErr.Message);
}
return scope;
}
private void ObtainPrinterPort()
{
string computerName = "";
string userID = "";
string password = "";
string printerQuery = "SELECT * FROM Win32_Printer WHERE Name = ";
string portQuery = "SELECT * FROM Win32_TCPIPPrinterPort WHERE Name = ";
string search = "";
SelectQuery query;
foreach (var s in lstServer)
{
computerName = s.ServerName;
userID = s.UserID;
password = s.Password;
}
ManagementScope scope = CreateNewConnection(computerName, userID, password);
foreach (Printers p in lstPrinters)
{
AppendTextBox("Obtaining printer/port info for " + p.PrinterName + "\r\n");
search = printerQuery + "'" + p.PrinterName + "'";
query = new SelectQuery(search);
try
{
using (var searcher = new ManagementObjectSearcher(scope, query))
{
ManagementObjectCollection printers = searcher.Get();
if (printers.Count > 0)
{
AppendTextBox("\tStoring printer properties for " + p.PrinterName + "\r\n");
foreach (ManagementObject mo in printers)
{
StorePrinterProperties(p, mo);
}
}
else
{
lstPrinterExceptions.Add("Printer: " + p.PrinterName);
AppendTextBox("\t**Printer " + p.PrinterName + " not found**\r\n");
}
}
}
catch (Exception exception)
{
MessageBox.Show("Error: " + exception.Message, "Error",
MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
if (!lstPrinterExceptions.Contains("Printer: " + p.PrinterName)
&& !lstPrinterExceptions.Contains("Port: " + p.PortName))
{
search = portQuery + "'" + p.PortName + "'";
query = new SelectQuery(search);
try
{
using (var searcher = new ManagementObjectSearcher(scope, query))
{
ManagementObjectCollection ports = searcher.Get();
if (ports.Count > 0)
{
AppendTextBox("\tStoring port properties for " + p.PortName + " (" + p.PrinterName + ")\r\n");
foreach (ManagementObject mo in ports)
{
StorePortProperties(p, mo);
}
}
else
{
lstPrinterExceptions.Add("Port: " + p.PortName);
AppendTextBox("\t**Port " + p.PortName + " for " + p.PrinterName + " not found**\r\n");
}
}
}
catch (Exception exception)
{
MessageBox.Show("Error: " + exception.Message, "Error",
MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
AppendTextBox("\tSuccessfully obtained printer/port info for " + p.PrinterName + "\r\n");
}
}
}
Thanks.
Your problem is that the Invoke calls are blocking waiting for the main thread to DoEvents so they can be processed, but the main thread is blocking in Thread.Join. You have a deadlock.
This is how you run a thread without blocking the UI thread. Thread.Join blocks until the other thread finishes, so here I am blocking only for max 100ms, then calling DoEvents so the form can respond to messages (including processing your Invoke calls from the other thread), then looping until the background thread is complete.
Thread printThread = new Thread(ObtainPrinterPort);
printThread.Start();
while (printThread.IsAlive) {
Application.DoEvents();
printThread.Join(100);
}
Calling DoEvents in a loop like this is a bit hacky, but it will work.
You can also look into BackgroundWorker, which makes the whole thing a lot safer and easier.
A simple example:
var bw = new BackgroundWorker();
bw.DoWork += (worker, args) => {
ObtainPrinterPort();
};
bw.RunWorkerAsync();