C#: Multithreaded (task) PowerShell cmdlet - c#

Per the subject, I'm trying to put together a multithreaded PowerShell cmdlet and am struggling. I can get the code to run successfully as a console app, and I can get the single threaded cmdlet to work, but haven't been able to merge the two as of yet.
Also, please understand that I am new to C# and have very little clue about what I'm doing, so apologies in advance for the atrocity that is my code. Tried to simplify as much as I could before posting.
Here's what I've got so far:
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Management.Automation;
using System.DirectoryServices;
using System.Diagnostics;
using System.Threading;
namespace Sample
{
[Cmdlet(VerbsCommon.Get,"LDAPObject")]
[OutputType(typeof(List<SearchResult>))]
public class GetLDAPObjectCmdlet: Cmdlet
{
[Parameter(ValueFromPipeline = true)]
public string searchRoot { get; set; }
[Parameter]
public TimeSpan maxWaitTime { get; set; }
private List<Task<searchResults>> taskList = new List<Task<searchResults>>();
protected override void BeginProcessing()
{
base.BeginProcessing();
}
protected override void ProcessRecord()
{
base.ProcessRecord();
var ts = new CancellationTokenSource();
CancellationToken ct = ts.Token;
Task<searchResults> searchTask = Task<searchResults>.Factory.StartNew(() => Search(ct, searchRoot));
taskList.Add(searchTask);
Stopwatch sw = Stopwatch.StartNew();
bool tasksCompleted = new bool();
try
{
tasksCompleted = Task.WaitAll(taskList.ToArray(), maxWaitTime);
}
catch (OperationCanceledException ex)
{
WriteWarning("Task cancelled.");
}
int resultCount = 0;
foreach (Task<searchResults> taskItem in taskList)
{
if ((taskItem.Status == TaskStatus.RanToCompletion) && (taskItem.Result.src != null))
{
resultCount += taskItem.Result.src.Count;
}
foreach (string message in taskItem.Result.warningMessages)
{
WriteWarning(message);
}
}
sw.Stop();
if (tasksCompleted)
{
WriteVerbose("The search returned `'" + resultCount.ToString() + "' results in '" + (sw.Elapsed.TotalSeconds).ToString() + "' seconds.");
}
else
{
WriteVerbose("The search did not complete before the deadline.");
}
}
protected override void EndProcessing()
{
base.EndProcessing();
for (int i = 0; i < taskList.Count; i++)
{
taskList[i].Dispose();
}
}
protected override void StopProcessing()
{
base.StopProcessing();
}
private static searchResults Search(CancellationToken ct, string searchRootDn)
{
if (ct.IsCancellationRequested)
{
ct.ThrowIfCancellationRequested();
}
searchResults results = new searchResults();
results.warningMessages = new List<string>();
results.verboseMessages = new List<string>();
results.warningMessages.Add("Search root: '" + searchRootDn + "'.");
DirectoryEntry searchRoot = new DirectoryEntry("LDAP://" + searchRootDn);
try
{
Guid guid = searchRoot.Guid;
}
catch (Exception ex)
{
throw new Exception("Invalid search root");
}
string searchFilter = "some LDAP filter here";
results.warningMessages.Add("Search filter: '" + searchFilter + "'.");
DirectorySearcher directorySearcher = new DirectorySearcher();
directorySearcher.SearchRoot = searchRoot;
directorySearcher.SearchScope = SearchScope.Subtree;
directorySearcher.PageSize = 1000;
directorySearcher.Filter = searchFilter;
results.src = directorySearcher.FindAll();
directorySearcher.Dispose();
return results;
}
}
class searchResults
{
public SearchResultCollection src { get; set; }
public List<string> warningMessages { get; set; }
public List<string> verboseMessages { get; set; }
}
}
This gives me:
System.InvalidCastException
HResult=0x80004002
Message=Unable to cast COM object of type 'System.__ComObject' to interface type 'IDirectorySearch'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{109BA8EC-92F0-11D0-A790-00C04FD8D5A8}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).
Source=System.DirectoryServices
StackTrace:
at System.DirectoryServices.SearchResultCollection.get_SearchObject()
at System.DirectoryServices.SearchResultCollection.ResultsEnumerator.MoveNext()
at System.DirectoryServices.SearchResultCollection.get_InnerList()
at System.DirectoryServices.SearchResultCollection.get_Count()
at MultiThreadedSearch.GetLDAPObjectCmdlet.ProcessRecord() in C:\Users\obfuscated\GETLDAPObjectCmdlet.cs:line 73
at System.Management.Automation.CommandProcessor.ProcessRecord()
on the resultCount += taskItem.Result.src.Count; line.
Note that I didn't actually need the searchResult class; I just introduced it for want of a means by which to allow the Search function to be able to contribute content to WriteWarning, WriteVerbose, etc.
Thoughts are most appreciated!

Looks like I solved my own problem. Unexpectedly, the problem appears to stem from how the DirectorySearcher interface is implemented. Specifically, the results from the LDAP search are not cached until the first call to enumerate the SearchResultsCollection - specifically, when I attempted to access the Count property. This is nicely described here:
We tend to use the foreach statement to enumerate the results. Unlike some other collection classes in the .NET Framework, SearchResultCollection internally implements its own private IEnumerator object that pulls the results from the directory. Any access to the SearchResultCollection properties, such as inspecting the Count property, will cause the entire result set to be retrieved from the directory and then enumerated. This can have a large impact, especially when returning many results, for a couple of reasons. First, using a foreach loop would allow us to break off the search if necessary, without enumerating all the results completely. This is more efficient, as we break off the search early and do not force the server to return all of the results first. Second, we can process each result as it comes from the server using a foreach loop without waiting for the entire search to complete. Using a for loop in conjunction with the Count property prevents both of these scenarios. As such, we generally recommend using the foreach loop when enumerating our results.

Related

Revit API - Wrong full class name

I'm very new to C# and coding. If possible I'm after some assistance figuring out how to fix this piece of code up to work.
They work individually. I can create a new button on the ribbon and execute the standard hello world. i also have a macro which works where I can remove all my sheets and views successfully but trying to combine the two is causing big difficulties! The code builds OK but I'm getting this error inside revit:
'Failed to initialize the add-in "Delete Views" because the class
"DeleteViews" cannot be found in the add-in assembly.The FullClassName
provides the enrty point for Revit to call add-in application. For
Revit to run the add-in, you must ensure this class implements the
"Autodesk.Revit.UI.ExternalCommand" interface.'
I'm pretty sure my problem is referencing in this 2nd bit of code. i know I'm not calling it up correctly but haven't been able to find any solutions.
Apologies if this is a dumb question but would really appreciate any help to help me learn!
Thanks for any help you can give me
The code:
namespace BGPanel
{
public class CsBGPanel : IExternalApplication
{
public UIDocument ActiveUIDocument { get; private set; }
public Result OnStartup(UIControlledApplication application)
{
RibbonPanel ribbonPanel = application.CreateRibbonPanel("Tools");
string thisAssemblyPath = Assembly.GetExecutingAssembly().Location;
PushButtonData buttonData = new PushButtonData("cmdDeleteViews",
"Delete Views", thisAssemblyPath, "BGPanel.DeleteViews");
PushButton pushButton = ribbonPanel.AddItem(buttonData) as PushButton;
pushButton.ToolTip = "Delete all sheets, schedules & views except structural plans";
Uri uriImage = new Uri(#"C:\Revit_API\Revit_2015\32px-Broom.png");
BitmapImage largeImage = new BitmapImage(uriImage);
pushButton.LargeImage = largeImage;
return Result.Succeeded;
}
public void DeleteViews()
{
UIDocument uidoc = this.ActiveUIDocument;
Document doc = uidoc.Document;
FilteredElementCollector collector = new FilteredElementCollector(doc);
ICollection<Element> collection = collector.OfClass(typeof(View)).ToElements();
using (Transaction t = new Transaction(doc, "Delete Views"))
{
t.Start();
int x = 0;
foreach (Element e in collection)
{
try
{
View view = e as View;
switch (view.ViewType)
{
case ViewType.FloorPlan:
break;
case ViewType.EngineeringPlan:
break;
case ViewType.ThreeD:
break;
default:
doc.Delete(e.Id);
x += 1;
break;
}
}
catch (Exception ex)
{
View view = e as View;
TaskDialog.Show("Error", e.Name + "\n" + "\n" + ex.Message);
TaskDialog.Show("Error", ex.Message);
}
}
t.Commit();
TaskDialog.Show("BG_API DeleteViews", "Views Deleted: " + x.ToString());
}
}
public Result OnShutdown(UIControlledApplication application)
{
return Result.Succeeded;
}
}
}
First, instead typing the class name yourself, consider using Reflection like:
typeof(YourClassName).FullName
Second, every command in Revit requires its own class that implements IExternalCommand. I haven't tested, but your code should be something like the following:
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Windows.Media.Imaging;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
namespace BGPanel
{
public class CsBGPanel : IExternalApplication
{
public Result OnStartup(UIControlledApplication application)
{
RibbonPanel ribbonPanel = application.CreateRibbonPanel("Tools");
string thisAssemblyPath = Assembly.GetExecutingAssembly().Location;
PushButtonData buttonData = new PushButtonData("cmdDeleteViews",
"Delete Views", thisAssemblyPath, typeof(DeleteViews).FullName);
PushButton pushButton = ribbonPanel.AddItem(buttonData) as PushButton;
pushButton.ToolTip = "Delete all sheets, schedules & views except structural plans";
Uri uriImage = new Uri(#"C:\Revit_API\Revit_2015\32px-Broom.png");
BitmapImage largeImage = new BitmapImage(uriImage);
pushButton.LargeImage = largeImage;
return Result.Succeeded;
}
public Result OnShutdown(UIControlledApplication application)
{
return Result.Succeeded;
}
}
public class DeleteViews : IExternalCommand
{
// this will not work...
//public UIDocument ActiveUIDocument { get; private set; }
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
UIDocument uidoc = commandData.Application.ActiveUIDocument; //this.ActiveUIDocument;
Document doc = uidoc.Document;
FilteredElementCollector collector = new FilteredElementCollector(doc);
ICollection<Element> collection = collector.OfClass(typeof(View)).ToElements();
using (Transaction t = new Transaction(doc, "Delete Views"))
{
t.Start();
int x = 0;
foreach (Element e in collection)
{
try
{
View view = e as View;
switch (view.ViewType)
{
case ViewType.FloorPlan:
break;
case ViewType.EngineeringPlan:
break;
case ViewType.ThreeD:
break;
default:
doc.Delete(e.Id);
x += 1;
break;
}
}
catch (Exception ex)
{
View view = e as View;
TaskDialog.Show("Error", e.Name + "\n" + "\n" + ex.Message);
TaskDialog.Show("Error", ex.Message);
}
}
t.Commit();
TaskDialog.Show("BG_API DeleteViews", "Views Deleted: " + x.ToString());
}
return Result.Succeeded; // must return here
}
}
}
You should work through the Revit API getting started material before doing anything else at all, especially the DevTV and My First Revit Plugin video tutorials:
http://thebuildingcoder.typepad.com/blog/about-the-author.html#2
Then this question and many other fundamental issues will be answered up front, and you will save yourself and others some effort and head-scratching.
I needed to add a line before the IExternalCommand because of transaction errors when I clicked my button.
[Transaction(TransactionMode.Manual)]
The first answer to this question suggests using the reflection "typeof(YourClassName).FullName" When the sample code is tried it returns a error, understandably as Augusto says he did not test his example. Just adding for anyone down the road... If you use "typeof(DeleteViews).FullName" you would need to cast it into a string variable before the code in his example would work.

C# - Visual Studio - 'object' does not contain a definition for 'ExecuteQuery'

I am trying to add code to an existing project that will check for existence of a device in SCCM and delete it if it does exist. I seem to be missing something, in that particular block of code. I get an error - 'object' does not contain a definition for 'ExecuteQuery' and no extension method 'ExecuteQuery' accepting a first argument of type 'object' could be found.
Here is the C# code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Net.NetworkInformation;
using SupportToolkit.Models;
using SupportToolkit.WindowsAutomationServices;
using NLog;
using System.Text;
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
using System.Management;
namespace SupportToolkit.Controllers
{
public class TPOSRecordDeletionController : Controller
{
private static Logger recordDeletionLogger = LogManager.GetCurrentClassLogger();
[Authorize(Roles = #"nor\NST_RIT_Users,nor\NST_STM_Users,nor\NST_Admin_Users,nor\NST_CORP_Users")]
// GET: TPOSRecordDeletion
public ActionResult Index(TPOSRecordDeletionModel model)
{
if (model.ComputerName != null)
{
}
if (ModelState.IsValid)
{
if (!(string.IsNullOrEmpty(model.btnDeleteRecord)))
{
InvokeRecordDeletion(model);
}
}
return View(model);
}
[Authorize(Roles = #"nor\NST_RIT_Users,nor\NST_STM_Users,nor\NST_Admin_Users,nor\NST_CORP_Users")]
public string InvokeRecordDeletion(TPOSRecordDeletionModel model)
{
model.Status = "Running Service";
var windowsAutomationService = new WindowsAutomationServicesClient();
string shortServiceOutput;
var serviceAction = "Remove-TPOSRecords";
var SCCMServer = "server.nor.net";
var siteCode = "PO60";
string[] recordDeletionArguments = new string[1];
recordDeletionArguments[0] = model.ComputerName;
model.Status = "Processing" + model.ComputerName;
Ping pingSender = new Ping();
PingOptions options = new PingOptions();
// Use the default Ttl value which is 128,
// but change the fragmentation behavior.
options.DontFragment = true;
// Create a buffer of 32 bytes of data to be transmitted.
string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
byte[] buffer = Encoding.ASCII.GetBytes(data);
int timeout = 120;
PingReply reply = pingSender.Send(model.ComputerName, timeout, buffer, options);
if (reply.Status == IPStatus.Success)
{
model.Status = model.ComputerName + "is currently online and will not be removed!";
}
else
{
// set up domain context
using (var ctx = new System.DirectoryServices.AccountManagement.PrincipalContext(System.DirectoryServices.AccountManagement.ContextType.Domain))
{
// find a computer
ComputerPrincipal computer = ComputerPrincipal.FindByIdentity(ctx, model.ComputerName);
if (computer == null)
{
model.Status = model.ComputerName + "does not exist in Active Directory.";
}
else
{
computer.Delete();
model.Status = model.ComputerName + "successfully removed from Active Directory!";
}
//insert code here for checking for existence of computer in SCCM and removing from SCCM if exists
SmsNamedValuesDictionary namedValues = new SmsNamedValuesDictionary();
WqlConnectionManager connection = new WqlConnectionManager(namedValues);
connection.Connect("s0319p60.nordstrom.net");
foreach (IResultObject computerobject in connection.QueryProcessor.ExecuteQuery("Select ResourceID From SMS_R_System Where Name ='" + model.ComputerName + "'"))
{
if (computerobject == null)
{
model.Status = model.ComputerName + "does not exist in SCCM.";
}
else
{
computerobject.Delete();
model.Status = model.ComputerName + "successfully removed from SCCM!";
}
}
}
var userName = User.Identity.Name;
var serviceOutput = windowsAutomationService.RunAutomationService(serviceAction, userName, recordDeletionArguments);
recordDeletionLogger.Info(userName + " is attempting to remove the record " + model.ComputerName);
if (serviceOutput.Length >= 7)
{
shortServiceOutput = serviceOutput.Substring(0, 7);
shortServiceOutput = shortServiceOutput.ToLower();
}
else
{
shortServiceOutput = serviceOutput;
shortServiceOutput = shortServiceOutput.ToLower();
}
if (shortServiceOutput == "success")
{
model.Status = "Successfully removed " + model.ComputerName + " from SCCM and Active Directory";
recordDeletionLogger.Info(userName + " successfully removed " + model.ComputerName + " from SCCM and Active Directory");
return "Success";
}
else
{
model.Status = "Failure removing " + model.ComputerName + " from SCCM and Active Directory. Unknown Error";
recordDeletionLogger.Info(userName + " failed to remove " + model.ComputerName + " from SCCM and Active Directory");
return "Failure";
}
}
}
}
internal interface IResultObject
{
//void Delete();
void Delete();
}
internal class WqlConnectionManager
{
private SmsNamedValuesDictionary namedValues;
public WqlConnectionManager(SmsNamedValuesDictionary namedValues)
{
this.namedValues = namedValues;
}
public object QueryProcessor { get; internal set; }
internal void Connect(string v)
{
throw new NotImplementedException();
}
public object ExecuteQuery { get; internal set; }
}
internal class SmsNamedValuesDictionary
{
public SmsNamedValuesDictionary()
{
}
}
Well - after a few days of searching, I FINALLY figured out the issue.
The entire block of code at the end, was not necessary. The issue was that there were missing assembly references and using statements in the code. Specifically:
using Microsoft.ConfigurationManagement.ManagementProvider;
using Microsoft.ConfigurationManagement.ManagementProvider.WqlQueryEngine;
The corresponding DLLs also needed to be added to the project as references.
I hope this helps someone who runs into similar issues and is not versed in C# coding. I myself am only versed in PowerShell, so figuring this out took a lot of work for me. Thanks.
You create a custom "connection" object:
WqlConnectionManager connection = new WqlConnectionManager(namedValues);
Then call a method on one of its properties:
connection.QueryProcessor.ExecuteQuery("...")
But what is that QueryProcessor property?...
public object QueryProcessor { get; internal set; }
It's an object. As the error states, object doesn't have a method called ExecuteQuery. (It doesn't have very many methods or properties at all, really.)
I can't really tell from this code (maybe I'm missing something?) what specific type you're expecting QueryProcessor to be, but it should definitely be something more specific than object. Something analogous to a SQLCommand object, perhaps? Essentially, whatever type would have that ExecuteQuery method.
If there's a compelling reason in the existing codebase for this to be of type object, you'd need to determine what that reason is. There seems to be a lot of use of object here, which smells of some bad design choices from before you got there.

What is the relationship between Process.GetProcesses(); and what is shown in Task Manager?

I'm trying to do something which should be (and probably is) very simply. I want a user to be able to define a process (Almost certainly taken from Task Manager) and then my application will perform differently depending on which processes are running.
I've been playing with Process.GetProcesses() to get this information, but I'm struggling to understand the data I'm getting and how it relates to what Task Manager displays.
What I really want is a list of process names which are identical to the "Name" field in Task Manager. I can get this using Path.GetFileName(theprocess.MainModule.FileName); but I get a lot of Exceptions when enumerating certain processes. This appears (from Googling) to be expected especially in cross 64bit/32bit platforms, and while I can easily trap and ignore these it becomes an exceptionally slow operation.
So, I'm hoping to use something simply like process.ProcessName. At first glance this seems to be identical to Task Manager, but there are the odd one or two tasks that it pulls back which don't show up in Task Manager.
Should I be doing something else, or should process.ProcessName be sufficient?
By the way, I'm only interesting in enumerating processes for the current user/session.
Here's one of my code examples:
foreach (theprocess in processList)
{
try
{
string fileName = theprocess.MainModule.FileName;
currentProcessList.Add(fileName.ToLower());
}
catch (Exception e)
{
}
}
I have never used your method, but in my application I use WMI to iterate over processes as follows:
List<ManagementObject> processInfo = processWmi.CreateRequest("SELECT * FROM Win32_Process");
processWmi is a class I use for all of my WMI queries that has extra functionality to kill the query if it is hanging (which WMI seems to do on some servers). The heart of this class is shown below.
private static string _query;
private static string _scope;
private static List<ManagementObject> _data;
private static bool _queryComplete;
private int _timeout = 300;
private static readonly object Locker = new Object();
public List<ManagementObject> CreateRequest(string query, bool eatErrors = false, string scope = null)
{
try
{
lock (Locker)
{
_queryComplete = false;
AscertainObject.ErrorHandler.WriteToLog("Running WMI Query: " + query + " Timeout:" + _timeout, true);
_query = query;
_scope = scope;
Thread serviceThread = new Thread(RunQuery) { Priority = ThreadPriority.Lowest, IsBackground = true };
serviceThread.Start();
int timeLeft = _timeout * 10;
while (timeLeft > 0)
{
if (_queryComplete)
return _data;
timeLeft--;
Thread.Sleep(100);
}
if (eatErrors == false)
AscertainObject.ErrorHandler.WriteToLog("WMI query timeout: " + query, true, "");
serviceThread.Abort();
}
}
catch (Exception ex)
{
if (eatErrors == false)
AscertainObject.ErrorHandler.WriteToLog("Error Running WMI Query", true, ex.ToString());
}
return null;
}
public void SetRequestTimeout(int timeoutSeconds)
{
_timeout = timeoutSeconds;
AscertainObject.ErrorHandler.WriteToLog("WMI query timeout changed to " + timeoutSeconds + " seconds", true);
}
private void RunQuery()
{
try
{
ManagementObjectSearcher searcher = _scope != null ? new ManagementObjectSearcher(_scope, _query) : new ManagementObjectSearcher(_query);
List<ManagementObject> innerData = searcher.Get().Cast<ManagementObject>().ToList();
_data = innerData;
}
catch (Exception ex)
{
AscertainObject.ErrorHandler.WriteToLog("WMI query failed, may have invalid namespace", true, null, true);
_data = null;
}
_queryComplete = true;
}
You can pull the data you want out of the WMI results as follows (type conversions in place to match my Process class):
foreach (ManagementObject item in processInfo)
{
Process tempProcess = new Process
{
id = Convert.ToInt32((UInt32)item["ProcessID"]),
name = (String)item["Name"],
path = (String)item["ExecutablePath"],
parentID = Convert.ToInt32((UInt32)item["ParentProcessID"]),
handleCount = Convert.ToInt32((UInt32)item["HandleCount"]),
priority = Convert.ToInt16((UInt32)item["Priority"]),
threadCount = Convert.ToInt32((UInt32)item["ThreadCount"]),
workingSetMB = Convert.ToInt64((UInt64)item["WorkingSetSize"]) / 1048576,
peakWorkingSetMB = Convert.ToInt64((UInt32)item["PeakWorkingSetSize"]) / 1024,
pageFileUsageMB = Convert.ToInt64((UInt32)item["PageFileUsage"]) / 1024,
peakPageFileUsage = Convert.ToInt64((UInt32)item["PeakPageFileUsage"]) / 1024
};
try
{
//get owner info
object[] ownerInfo = new object[2];
item.InvokeMethod("GetOwner", ownerInfo);
tempProcess.processOwner = (string)ownerInfo[0];
}
catch
{
}
}
WMI results are usually returned very quickly with little to no overhead on the system. They also act similar to SQL queries where you can filter the results with proper WHERE clauses.
Here is the link to all the info you can get back from Win32_Process:
http://msdn.microsoft.com/en-us/library/aa394372(v=vs.85).aspx

Detecting if internet connection is busy

We are developing an application that will install on PC and it will perform some background upload and download to/from our server. One of the requirement is to detect if the internet connection is currently busy (say above 50% utilization) and if it is, it needs to back-off and try another time. The main reason is to ensure the app does not interfere with user experience if they are in the middle of gaming, watching online movie or aggressively downloading files
After much thinking and research on google and of course SO, I still haven't found a good way on how to implement this, so decided to throw this out here. The application is implemented in C#, .NET 4.0 and I am looking for all forms of responses - either implementation in C# or other languages, pseudo-logic or approach on how to achieve - measuring of internet traffic utilization on local PC with good enough accuracy.
To avoid duplication of effort, so far I have tried these (and why they aren't suitable)
Use WMI to get network statistic. Most SO posts and solutions out there since to refer to this as the approach but it doesn't meet our requirement as measuring of bytes sent/received against network interface capacity (e.g. 1GB Ethernet Card) for utilisation will yield a good measure for LAN traffic but not for internet traffic (where the actual internet bandwidth might only be say 8Mbps)
Use of .NET Network Information Statistics or performance counter - yield similar readings to the above hence have the same shortcomings
Use ICMP (Ping) and measure RTT. It was suggested that 400ms RTT is considered as slow and good indication for busy network, however I was told that user with modem (yes we have to support that), use of reverse proxy or microwave link often get ping above that hence not a good measure
Start downloading a known file and measure the speed - this itself generate traffic which we are trying to avoid, also if this check is done often enough, our application will end up creating a lot of internet traffic - which again not ideal
MOD: Using BITS - this service can be disabled on user pc, requires group policy changes and assume server to be IIS (with custom configuration) and in our case our server is not IIS
So here it is, I'm all confuse and looking for some advice. I highlighted the question text so that you guys don't get lost reading this and wondering what the question is. Thanks.
You could use UPnP to query the router, and retrive the number of bytes sent and received over the network. You could keep checking this value on the router to determine what the activity is. Unfortunately this functionality doesn't seem to be well-documented, but it is possible to implement UPnP communication functionality within a C# application. You will need to use UDP to query for the router (UPnP discover), and once you have found the device, query its functionality, and then query the number of packets sent and received for the Internet Gateway Device using a WebClient (TCP).
Code for a UPnP library:
using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Xml;
using System.IO;
namespace UPNPLib
{
public class RouterElement
{
public RouterElement()
{
}
public override string ToString()
{
return Name;
}
public List children = new List();
public RouterElement parent;
public string Name;
public string Value;
public RouterElement this[string name] {
get
{
foreach (RouterElement et in children)
{
if (et.Name.ToLower().Contains(name.ToLower()))
{
return et;
}
}
foreach (RouterElement et in children)
{
Console.WriteLine(et.Name);
}
throw new KeyNotFoundException("Unable to find the specified entry");
}
}
public RouterElement(XmlNode node, RouterElement _parent)
{
Name = node.Name;
if (node.ChildNodes.Count
/// Gets the root URL of the device
///
///
public static string GetRootUrl()
{
StringBuilder mbuilder = new StringBuilder();
mbuilder.Append("M-SEARCH * HTTP/1.1\r\n");
mbuilder.Append("HOST: 239.255.255.250:1900\r\n");
mbuilder.Append("ST:upnp:rootdevice\r\n");
mbuilder.Append("MAN:\"ssdp:discover\"\r\n");
mbuilder.Append("MX:3\r\n\r\n");
UdpClient mclient = new UdpClient();
byte[] dgram = Encoding.ASCII.GetBytes(mbuilder.ToString());
mclient.Send(dgram,dgram.Length,new IPEndPoint(IPAddress.Broadcast,1900));
IPEndPoint mpoint = new IPEndPoint(IPAddress.Any, 0);
rootsearch:
dgram = mclient.Receive(ref mpoint);
string mret = Encoding.ASCII.GetString(dgram);
string orig = mret;
mret = mret.ToLower();
string url = orig.Substring(mret.IndexOf("location:") + "location:".Length, mret.IndexOf("\r", mret.IndexOf("location:")) - (mret.IndexOf("location:") + "location:".Length));
WebClient wclient = new WebClient();
try
{
Console.WriteLine("POLL:" + url);
string reply = wclient.DownloadString(url);
if (!reply.ToLower().Contains("router"))
{
goto rootsearch;
}
}
catch (Exception)
{
goto rootsearch;
}
return url;
}
public static RouterElement enumRouterFunctions(string url)
{
XmlReader mreader = XmlReader.Create(url);
XmlDocument md = new XmlDocument();
md.Load(mreader);
XmlNodeList rootnodes = md.GetElementsByTagName("serviceList");
RouterElement elem = new RouterElement();
foreach (XmlNode et in rootnodes)
{
RouterElement el = new RouterElement(et, null);
elem.children.Add(el);
}
return elem;
}
public static RouterElement getRouterInformation(string url)
{
XmlReader mreader = XmlReader.Create(url);
XmlDocument md = new XmlDocument();
md.Load(mreader);
XmlNodeList rootnodes = md.GetElementsByTagName("device");
return new RouterElement(rootnodes[0], null);
}
}
public class RouterMethod
{
string url;
public string MethodName;
string parentname;
string MakeRequest(string URL, byte[] data, string[] headers)
{
Uri mri = new Uri(URL);
TcpClient mclient = new TcpClient();
mclient.Connect(mri.Host, mri.Port);
Stream mstream = mclient.GetStream();
StreamWriter textwriter = new StreamWriter(mstream);
textwriter.Write("POST "+mri.PathAndQuery+" HTTP/1.1\r\n");
textwriter.Write("Connection: Close\r\n");
textwriter.Write("Content-Type: text/xml; charset=\"utf-8\"\r\n");
foreach (string et in headers)
{
textwriter.Write(et + "\r\n");
}
textwriter.Write("Content-Length: " + (data.Length).ToString()+"\r\n");
textwriter.Write("Host: " + mri.Host+":"+mri.Port+"\r\n");
textwriter.Write("\r\n");
textwriter.Flush();
Stream reqstream = mstream;
reqstream.Write(data, 0, data.Length);
reqstream.Flush();
StreamReader reader = new StreamReader(mstream);
while (reader.ReadLine().Length > 2)
{
}
return reader.ReadToEnd();
}
public RouterElement Invoke(string[] args)
{
MemoryStream mstream = new MemoryStream();
StreamWriter mwriter = new StreamWriter(mstream);
//TODO: Implement argument list
string arglist = "";
mwriter.Write("" + "" + "");
mwriter.Write("");//" + arglist + "");
mwriter.Write("");
mwriter.Flush();
List headers = new List();
headers.Add("SOAPAction: \"" + parentschema + "#" + MethodName + "\"");
mstream.Position = 0;
byte[] dgram = new byte[mstream.Length];
mstream.Read(dgram, 0, dgram.Length);
XmlDocument mdoc = new XmlDocument();
string txt = MakeRequest(url, dgram, headers.ToArray());
mdoc.LoadXml(txt);
try
{
RouterElement elem = new RouterElement(mdoc.ChildNodes[0], null);
return elem["Body"].children[0];
}
catch (Exception er)
{
RouterElement elem = new RouterElement(mdoc.ChildNodes[1], null);
return elem["Body"].children[0];
}
}
public List parameters = new List();
string baseurl;
string parentschema;
public RouterMethod(string svcurl, RouterElement element,string pname, string baseURL, string svcpdsc)
{
parentschema = svcpdsc;
baseurl = baseURL;
parentname = pname;
url = svcurl;
MethodName = element["name"].Value;
try
{
foreach (RouterElement et in element["argumentList"].children)
{
parameters.Add(et.children[0].Value);
}
}
catch (KeyNotFoundException)
{
}
}
}
public class RouterService
{
string url;
public string ServiceName;
public List methods = new List();
public RouterMethod GetMethodByNonCaseSensitiveName(string name)
{
foreach (RouterMethod et in methods)
{
if (et.MethodName.ToLower() == name.ToLower())
{
return et;
}
}
throw new KeyNotFoundException();
}
public RouterService(RouterElement element, string baseurl)
{
ServiceName = element["serviceId"].Value;
url = element["controlURL"].Value;
WebClient mclient = new WebClient();
string turtle = element["SCPDURL"].Value;
if (!turtle.ToLower().Contains("http"))
{
turtle = baseurl + turtle;
}
Console.WriteLine("service URL " + turtle);
string axml = mclient.DownloadString(turtle);
XmlDocument mdoc = new XmlDocument();
if (!url.ToLower().Contains("http"))
{
url = baseurl + url;
}
mdoc.LoadXml(axml);
XmlNode mainnode = mdoc.GetElementsByTagName("actionList")[0];
RouterElement actions = new RouterElement(mainnode, null);
foreach (RouterElement et in actions.children)
{
RouterMethod method = new RouterMethod(url, et,ServiceName,baseurl,element["serviceType"].Value);
methods.Add(method);
}
}
}
}
Code for a bandwidth meter:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using UPNPLib;
using System.IO;
namespace bandwidthmeter
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
BinaryReader mreader = new BinaryReader(File.Open("bandwidthlog.txt", FileMode.OpenOrCreate));
if (mreader.BaseStream.Length > 0)
{
prevsent = mreader.ReadInt64();
prevrecv = mreader.ReadInt64();
}
mreader.Close();
List services = new List();
string fullurl = UPNP.GetRootUrl();
RouterElement router = UPNP.enumRouterFunctions(fullurl);
Console.WriteLine("Router feature enumeration complete");
foreach (RouterElement et in router.children)
{
services.Add(new RouterService(et.children[0], fullurl.Substring(0, fullurl.IndexOf("/", "http://".Length+1))));
}
getReceiveDelegate = services[1].GetMethodByNonCaseSensitiveName("GetTotalBytesReceived");
getSentDelegate = services[1].GetMethodByNonCaseSensitiveName("GetTotalBytesSent");
Console.WriteLine("Invoking " + getReceiveDelegate.MethodName);
//Console.WriteLine(services[1].GetMethodByNonCaseSensitiveName("GetTotalPacketsSent").Invoke(null));
Timer mymer = new Timer();
mymer.Tick += new EventHandler(mymer_Tick);
mymer.Interval = 1000;
mymer.Start();
FormClosed += new FormClosedEventHandler(Form1_FormClosed);
}
long prevsent = 0;
long prevrecv = 0;
void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
BinaryWriter mwriter = new BinaryWriter(File.Open("bandwidthlog.txt", FileMode.OpenOrCreate));
mwriter.Write(getsent());
mwriter.Write(getreceived());
mwriter.Flush();
mwriter.Close();
}
long getsent()
{
long retval = Convert.ToInt64(getSentDelegate.Invoke(null).children[0].Value);
if (prevsent > retval)
{
retval = prevsent + retval;
}
return retval;
}
long getreceived()
{
long retval = Convert.ToInt64(getReceiveDelegate.Invoke(null).children[0].Value);
if (prevrecv > retval)
{
retval = prevrecv + retval;
}
return retval;
}
void mymer_Tick(object sender, EventArgs e)
{
label1.Text = "Sent: "+(getsent()/1024/1024).ToString()+"MB\nReceived: "+(getreceived()/1024/1024).ToString()+"MB";
}
RouterMethod getSentDelegate;
RouterMethod getReceiveDelegate;
}
}
Have you considered using Background Intelligent Transfer Service (BITS). It's designed to do this job already:
Background Intelligent Transfer Service (BITS) transfers files (downloads or uploads) between a client and server and provides progress information related to the transfers. You can also download files from a peer.
and,
Preserve the responsiveness of other network applications.
I'm not sure if there's a managed interface to it (I can see reference to Powershell Cmdlets), so you might have to use COM interop to use it.
Making the assumption that you are targetting Windows PC's (as you said you were developing in C#), have you looked at BITS, the Background Intelligent Transfer Service?
There's examples of how to hook into it using C# on MSDN and elsewhere, e.g. http://msdn.microsoft.com/en-us/magazine/cc188766.aspx

Is there a way to check how many messages are in a MSMQ Queue?

I was wondering if there is a way to programmatically check how many messages are in a private or public MSMQ using C#? I have code that checks if a queue is empty or not using the peek method wrapped in a try/catch, but I've never seen anything about showing the number of messages in the queue. This would be very helpful for monitoring if a queue is getting backed up.
You can read the Performance Counter value for the queue directly from .NET:
using System.Diagnostics;
// ...
var queueCounter = new PerformanceCounter(
"MSMQ Queue",
"Messages in Queue",
#"machinename\private$\testqueue2");
Console.WriteLine( "Queue contains {0} messages",
queueCounter.NextValue().ToString());
There is no API available, but you can use GetMessageEnumerator2 which is fast enough. Sample:
MessageQueue q = new MessageQueue(...);
int count = q.Count();
Implementation
public static class MsmqEx
{
public static int Count(this MessageQueue queue)
{
int count = 0;
var enumerator = queue.GetMessageEnumerator2();
while (enumerator.MoveNext())
count++;
return count;
}
}
I also tried other options, but each has some downsides
Performance counter may throw exception "Instance '...' does not exist in the specified Category."
Reading all messages and then taking count is really slow, it also removes the messages from queue
There seems to be a problem with Peek method which throws an exception
If you need a fast method (25k calls/second on my box), I recommend Ayende's version based on MQMgmtGetInfo() and PROPID_MGMT_QUEUE_MESSAGE_COUNT:
for C#
https://github.com/hibernating-rhinos/rhino-esb/blob/master/Rhino.ServiceBus/Msmq/MsmqExtensions.cs
for VB
https://gist.github.com/Lercher/5e1af6a2ba193b38be29
The origin was probably http://functionalflow.co.uk/blog/2008/08/27/counting-the-number-of-messages-in-a-message-queue-in/ but I'm not convinced that this implementation from 2008 works any more.
We use the MSMQ Interop. Depending on your needs you can probably simplify this:
public int? CountQueue(MessageQueue queue, bool isPrivate)
{
int? Result = null;
try
{
//MSMQ.MSMQManagement mgmt = new MSMQ.MSMQManagement();
var mgmt = new MSMQ.MSMQManagementClass();
try
{
String host = queue.MachineName;
Object hostObject = (Object)host;
String pathName = (isPrivate) ? queue.FormatName : null;
Object pathNameObject = (Object)pathName;
String formatName = (isPrivate) ? null : queue.Path;
Object formatNameObject = (Object)formatName;
mgmt.Init(ref hostObject, ref formatNameObject, ref pathNameObject);
Result = mgmt.MessageCount;
}
finally
{
mgmt = null;
}
}
catch (Exception exc)
{
if (!exc.Message.Equals("Exception from HRESULT: 0xC00E0004", StringComparison.InvariantCultureIgnoreCase))
{
if (log.IsErrorEnabled) { log.Error("Error in CountQueue(). Queue was [" + queue.MachineName + "\\" + queue.QueueName + "]", exc); }
}
Result = null;
}
return Result;
}
//here queue is msmq queue which you have to find count.
int index = 0;
MSMQManagement msmq = new MSMQManagement() ;
object machine = queue.MachineName;
object path = null;
object formate=queue.FormatName;
msmq.Init(ref machine, ref path,ref formate);
long count = msmq.MessageCount();
This is faster than you selected one.
You get MSMQManagement class refferance inside "C:\Program Files (x86)\Microsoft SDKs\Windows" just brows in this address you will get it. for more details you can visit http://msdn.microsoft.com/en-us/library/ms711378%28VS.85%29.aspx.
I had real trouble getting the accepted answer working because of the xxx does not exist in the specified Category error. None of the solutions above worked for me.
However, simply specifying the machine name as below seems to fix it.
private long GetQueueCount()
{
try
{
var queueCounter = new PerformanceCounter("MSMQ Queue", "Messages in Queue", #"machineName\private$\stream")
{
MachineName = "machineName"
};
return (long)queueCounter.NextValue();
}
catch (Exception e)
{
return 0;
}
}
The fastest method I have found to retrieve a message queue count is to use the peek method from the following site:
protected Message PeekWithoutTimeout(MessageQueue q, Cursor cursor, PeekAction action)
{
Message ret = null;
try
{
ret = q.Peek(new TimeSpan(1), cursor, action);
}
catch (MessageQueueException mqe)
{
if (!mqe.Message.ToLower().Contains("timeout"))
{
throw;
}
}
return ret;
}
protected int GetMessageCount(MessageQueue q)
{
int count = 0;
Cursor cursor = q.CreateCursor();
Message m = PeekWithoutTimeout(q, cursor, PeekAction.Current);
{
count = 1;
while ((m = PeekWithoutTimeout(q, cursor, PeekAction.Next)) != null)
{
count++;
}
}
return count;
}
This worked for me. Using a Enumarator to make sure the queue is empty first.
Dim qMsg As Message ' instance of the message to be picked
Dim privateQ As New MessageQueue(svrName & "\Private$\" & svrQName) 'variable svrnme = server name ; svrQName = Server Queue Name
privateQ.Formatter = New XmlMessageFormatter(New Type() {GetType(String)}) 'Formating the message to be readable the body tyep
Dim t As MessageEnumerator 'declared a enumarater to enable to count the queue
t = privateQ.GetMessageEnumerator2() 'counts the queues
If t.MoveNext() = True Then 'check whether the queue is empty before reading message. otherwise it will wait forever
qMsg = privateQ.Receive
Return qMsg.Body.ToString
End If
If you want a Count of a private queue, you can do this using WMI.
This is the code for this:
// You can change this query to a more specific queue name or to get all queues
private const string WmiQuery = #"SELECT Name,MessagesinQueue FROM Win32_PerfRawdata_MSMQ_MSMQQueue WHERE Name LIKE 'private%myqueue'";
public int GetCount()
{
using (ManagementObjectSearcher wmiSearch = new ManagementObjectSearcher(WmiQuery))
{
ManagementObjectCollection wmiCollection = wmiSearch.Get();
foreach (ManagementBaseObject wmiObject in wmiCollection)
{
foreach (PropertyData wmiProperty in wmiObject.Properties)
{
if (wmiProperty.Name.Equals("MessagesinQueue", StringComparison.InvariantCultureIgnoreCase))
{
return int.Parse(wmiProperty.Value.ToString());
}
}
}
}
}
Thanks to the Microsoft.Windows.Compatibility package this also works in netcore/netstandard.
The message count in the queue can be found using the following code.
MessageQueue messageQueue = new MessageQueue(".\\private$\\TestQueue");
var noOFMessages = messageQueue.GetAllMessages().LongCount();

Categories