Can not get MSMQ Com to find my Queue - c#

I am trying to get a count of messages in my MSMQ. I have found this code on the internet (many times):
// setup the queue management COM stuff
MSMQManagement _queueManager = new MSMQManagement();
object machine = "MyLaptopComputer";
object path = #"DIRECT=OS:MyLaptopComputer\PRIVATE$\MyQueue";
_queueManager.Init(ref machine, ref path);
Console.WriteLine(_queueManager.MessageCount);
Marshal.ReleaseComObject(_queueManager);
Every time I get to _queueManager.Init it fails with this error:
The queue path name specified is invalid.
I have checked (and double checked) my queue name to see if that is wrong. I have tried different queues, different machines, running remote, running local... Nothing works.
I have also tried variations on the code above. For example I have tried:
_queueManager.Init("MyLaptopComputer", #"DIRECT=OS:MyLaptopComputer\PRIVATE$\MyQueue");
The queues are used with NServiceBus and function just fine when I use NServiceBus to access them.
Does anyone have an Idea on how I can get this to work?

I think the problem is the error you're getting is a little misguiding. MSMQManagement.Init takes 3 parameters. They're all optional which is why in other languages (like VB) you'll sometimes see it called with only 2 parameters.
There is a CodeProject project that shows how to do what you're doing in C#:
private int GetMessageCount(string queueName)
{
int count = 0;
try
{
MSMQ.MSMQManagement mgmt = new MSMQ.MSMQManagement();
MSMQ.MSMQOutgoingQueueManagement outgoing;
String s = "YOURPCNAME";
Object ss = (Object)s;
String pathName = queueName;
Object pn = (Object)pathName;
String format = null;
Object f = (Object)format;
mgmt.Init(ref ss , ref f, ref pn);
outgoing = (MSMQ.MSMQOutgoingQueueManagement)mgmt;
count = outgoing.MessageCount;
}
catch (Exception ee)
{
MessageBox.Show(ee.ToString());
}
return count;
}
It might provide a better starting point.

Turns out it was a combination of issues. The biggest being that I needed to use FormatName not path name.
_queueManager.Init("MyComputer", null, #"DIRECT=OS:MyComputer\PRIVATE$\MyQueue");
Also, it will throw an exception if the queue is empty...
Got to love COM interfaces. :)

Related

Event log API missing entries

I'm trying to retrieve the most recent events from an event log. I found this answer Read Event Log From Newest to Oldest but it involves loading the whole log which I wish to avoid.
So I tried going to the end of the log first likes this:
var eventLog = EventLog.GetEventLogs().OfType<EventLog>().Single(el => el.Log == "Security");
int logSize = eventLog.Entries.Count;
var lastScanTime = DateTime.Now.AddDays(-1),
for (int i = logSize - 1; i >= 0; i--)
{
var entry = eventLog.Entries[i];
if (entry.TimeWritten <= lastScanTime)
{
break;
}
entry.Dump();
}
This works for a while, but if I keep running it I start to get IndexOutOfRangeExceptions
It seems that the eventLog.Entries.Count no longer matches the number of entries, which you can check with the followiog code:
var eventLog = EventLog.GetEventLogs().OfType<EventLog>().Single(el => el.Log == "Security");
int size = eventLog.Entries.Count.Dump();
int size2 = eventLog.Entries.Cast<EventLogEntry>().ToList().Count.Dump();
The two size values return different values. Entries.Countkeeps growing but the number of entries in the list stops increasing. The count also matches what I see in the Windows Event Log viewer.
Its like something in the .net API breaks and no more events are available.
Anybody seen this before or have any fixes to get this approach to work.
Edit: I also found this A mystery IndexOutOfRange Exception recurring when reading from a full EventLog which seems like a similar problem but no solution. I can't use events because I may be looking to activity on the machine before my software is installed.
Edit: If I catch exceptions and put the results into a list I get a different number of events again:
var results = new List<EventLogEntry>();
for (int i = eventLog.Entries.Count; i >= 0; i--)
{
try
{
var entry = eventLog.Entries[i];
{
results.Add(entry);
}
}
catch (Exception ex) { }
}
It has a lower count than Entries.Count but more recent logs entries than Entries does which seems to have just stopped at a certain point.
In case anyone else has the same issue it seems to be a result of not always disposing of the EventLog object after reading from file.
Once this happens it seems to break or corrupt the file in some way any you can't get more recent data until you clear the file out.

COM+ activation on a remote server with partitions in C#

I want to access partitioned COM+ applications on a remote server.
I have tried this:
using COMAdmin
using System.Runtime.InteropServices;
_serverName = myRemoteServer;
_partionName = myPartionName;
_message = myMessage;
ICOMAdminCatalog2 catalog = new COMAdminCatalog();
catalog.Connect(_serverName);
string moniker = string.Empty;
string MsgInClassId = "E3BD1489-30DD-4380-856A-12B959502BFD";
//we are using partitions
if (!string.IsNullOrEmpty(_partitionName))
{
COMAdminCatalogCollection partitions = catalog.GetCollection("Partitions");
partitions.Populate();
string partitionId = string.Empty;
foreach (ICatalogObject item in partitions)
{
if (item.Name == _partitionName)
{
partitionId = item.Key;
break;
}
}
if (!string.IsNullOrEmpty(partitionId) )
{
moniker = $"partition:{partitionId}/new:{new Guid(MsgInClassId)}";
try
{
var M = (IMsgInManager)Marshal.BindToMoniker(moniker);
M.AddMsg(_message);
}
catch (Exception ex)
{
throw new Exception($"We can not use: {_partitionName} with Id {partitionId}. {ex.ToString()}");
}
}
else
{
throw;
}
}
else
//we don't have partitions and this will work
{
Type T = Type.GetTypeFromCLSID(new Guid(MsgInClassId), _serverName, true);
var M = (IMsgInManager)Activator.CreateInstance(T);
M.AddMsg(_message);
}
}
So when we are local on the (remote) machine, partitions are working with the moniker and Marshal.BindToMoniker.
But when I try do the same remotely from my machine, I get an error from
Marshal.BindToMoniker that Partitons is not enabled. Because on my machine partitions is not enabled.
Message = "COM+ partitions are currently disabled. (Exception from HRESULT: 0x80110824)"
How can I use Marshal.BindToMoniker to run on the remote server.
Is it something I can add to the moniker string i.e.
moniker = $"server:_server/partition:{partitionId}/new:{new Guid(MsgInClassId)}"
My questions is very simular to this:
COM+ object activation in a different partition
tl;dr
According to MS documentation there is a way to do this by setting the pServerInfo in BIND_OPTS2 structure for binding the moniker. Unfortunately this is not working for the COM class moniker.
see:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms694513(v=vs.85).aspx
where it says for *pServerInfo:
COM's new class moniker does not currently honor the pServerInfo flag.
But maybe just try your scenario and at some future time it might be supported (or already is and documentation is wrong).
also see:
http://thrysoee.dk/InsideCOM+/ch11c.htm
where it also says in the footnote it does not work for class moniker: http://thrysoee.dk/InsideCOM+/footnotes.htm#CH1104
Theory and suggested solution if it was supported in c#
Disclaimer: I couldn't test the code as I don't have a test setup. This is off the top of my head. A bit pseudo code.
To do this you would have to code the COM/Moniker calls yourself. For this you could look at the source of microsofts implementation as a starting point.
There BindToMoniker is implemented like:
public static Object BindToMoniker(String monikerName)
{
Object obj = null;
IBindCtx bindctx = null;
CreateBindCtx(0, out bindctx);
UInt32 cbEaten;
IMoniker pmoniker = null;
MkParseDisplayName(bindctx, monikerName, out cbEaten, out pmoniker);
BindMoniker(pmoniker, 0, ref IID_IUnknown, out obj);
return obj;
}
CreateBindCtx, MkParseDisplayName and BindMoniker are OLE32.dll functions.
IBindCtx has methods to change the binding context. For this you call IBindCtx.GetBindContext(out BIND_OPTS2) and change the settings to what you need. Then set the new binding context with IBindCtx.SetBindContext(BIND_OPTS2). So essentially your own version of code would look something like this (pseudo code):
public static Object BindToMoniker(String monikerName)
{
Object obj = null;
IBindCtx bindctx = null;
CreateBindCtx(0, out bindctx);
BIND_OPTS2 bindOpts;
bindOpts.cbStruct = Marshal.SizeOf(BIND_OPTS2);
bindctx.GetBindOptions(ref bindOpts);
// Make your settings that you need. For example:
bindOpts.dwClassContext = CLSCTX_REMOTE_SERVER;
// Anything else ?
bindOpts.pServerInfo = new COSERVERINFO{pwszName = "serverName"};
bindctx.SetBindOptions(ref bindOpts);
UInt32 cbEaten;
IMoniker pmoniker = null;
MkParseDisplayName(bindctx, monikerName, out cbEaten, out pmoniker);
BindMoniker(pmoniker, 0, ref IID_IUnknown, out obj);
return obj;
}
As said, unfortunately this code is not possible to write in C# out of the box. Even the OLE32.dll method declarations CreateBindCtx, MkParseDisplayName and BindMoniker are privately declared in Marshal.cs so you will have to declare them in your project again.
But we are lucky with the IBindCtx declaration using a BIND_OPTS2 and the BIND_OPTS2 structure definition itself. They are declared in Microsoft.VisualStudio.OLE.Interop (interesting declarations in this namespace anyway). So you can try using them because inside the Marshal object and marshal.cs only the BIND_OPTS structure is used. I don't know if this is part of the framework and redistributable (I doubt it) but for testing this should be good enough. If it works these things can be declared again in your own solution.
Some Info on the used functions:
BindMoniker
CreateBindCtx
MkParseDisplayName
BIND_OPTS2
The remote COM needs to be accessed by Queue or DCOM. You need to export the application proxy on the server when accessing by DCOM. And install the proxy in the client PC.
The COM activation type must be configured as "Server Application" to export application proxy.
After installing application proxy, the client can directly call
moniker = $"new:{new Guid(MsgInClassId)}";
try
{
var M = Marshal.BindToMoniker(moniker);
}
For the partition, it's designed to show each user with own application set. If the current partition is associated to the user, the partition needs not to be written in codes.

Out Of Context Variables In Visual Studio 2010 Debugger

I am having a very odd problem with local variables being out of context in the Visual Studio 2010 debugger for a C# console application targeting .NET 4.0. I've searched for other similar questions on SO, but while some have the same symptoms, none seem to apply directly to this problem (they all appear to have other root causes).
The problem is that for some variables (but not all) I do not get a tooltip with their value, they do not appear in the Locals window, and I get "The name 'xyz' does not exist in the current context" if I add them to the Watch window. It appears to affect some variables but not others, and I can't figure out a pattern (it doesn't seem to be based on member vs. local, class vs. struct, or any other differentiator). I've restarted my computer and Visual Studio, verified I'm in a clean Debug build, made sure the debugging frame is correct, made sure to refresh the variables in the watch screen, and attempted various spells and incantations.
I've included a screenshoot below (bigger version at http://i.stack.imgur.com/JTFBT.png).
Any thoughts?
EDIT:
Adding some additional information:
The problem is repeatable. The exact same variables either work or don't work, even after completely shutting down and restarting Visual Studio. This leads me to believe there's actually something systematic going wrong rather than just memory corruption or something.
I've also discovered that it appears to be related to the try-catch block. If I position the breakpoint outside the try statement I can see any of the in-scope variables normally. As soon as the execution point enters the try statement all the variables outside the try block become inaccessible and I can only access the ones inside the try statement. It's almost as though the debugger is treating the try block as a separate method (though you can see the code/compiler still does have access to in-scope variables). Has anyone seen this behavior before?
ANOTHER EDIT:
I (partially) take back what I said about the try-catch being suspect - it appears that in this portion of the code the debugger exhibits this odd taking stuff out of context for any enclosing block. For example, if I set a breakpoint directly inside the foreach statement in the screenshot I can see the "port" variable value on each iteration, but none of the variables outside the foreach statement (which disappear as soon as I enter the foreach block). Then as soon as you enter the try block, the "port" variable suddenly goes away. This is getting really weird.
Also, as requested, the code for the entire method is below.
private void ConfigureAnnouncerSockets(XDocument configDocument)
{
XElement socketsElement = configDocument.XPathSelectElement("/Configuration/Network/AnnouncerSockets");
bool useDefault = true;
if (socketsElement != null)
{
//Use the default announcers? (they will be added at the end)
XAttribute defaultAttribute = socketsElement.Attribute("useDefault");
if (defaultAttribute != null)
{
useDefault = Convert.ToBoolean(defaultAttribute);
}
//Get the default frequency
int defaultFrequency = Announcer.DefaultFrequency;
XAttribute frequencyAttribute = socketsElement.Attribute("frequency");
if (frequencyAttribute != null)
{
defaultFrequency = Convert.ToInt32(frequencyAttribute.Value);
}
//Get all sockets
foreach (XElement socketElement in socketsElement.XPathSelectElements("./Socket"))
{
//Get the address
IPAddress address = IPAddress.Broadcast;
string addressAttribute = (string)socketElement.Attribute("address");
if(!GetAddress(addressAttribute, ref address, true))
{
Intelliplex.Log.Warn("Invalid announcer socket address: " + addressAttribute);
continue;
}
//Get the local address
IPAddress localAddress = null;
string localAddressAttribute = (string)socketElement.Attribute("localAddress");
if(!GetAddress(localAddressAttribute, ref localAddress, false))
{
Intelliplex.Log.Warn("Invalid announcer socket local address: " + localAddressAttribute);
continue;
}
//Get the port(s)
List<int> ports = new List<int>();
string[] ranges = ((string)socketElement.Attribute("port")).Split(new[] { ',' });
foreach (string range in ranges)
{
string[] portPair = range.Split(new[] { '-' });
int firstPort = Convert.ToInt32(portPair[0]);
int lastPort = portPair.Length > 1 ? Convert.ToInt32(portPair[1]) : firstPort;
do
{
ports.Add(firstPort);
} while (++firstPort <= lastPort);
}
//Get the local port
int localPort = socketElement.Attribute("localPort") != null
? Convert.ToInt32((string)socketElement.Attribute("localPort")) : 0;
//Get the frequency
int frequency = socketElement.Attribute("frequency") != null
? Convert.ToInt32((string)socketElement.Attribute("frequency")) : defaultFrequency;
//Create the socket(s) and add it/them to the manager
foreach (int port in ports)
{
try
{
IPEndPoint endPoint = new IPEndPoint(address, port);
IPEndPoint localEndPoint = localAddress == null
? new IPEndPoint(IPAddress.Any, 0) : new IPEndPoint(localAddress, localPort);
Announcer socket = new Announcer(frequency, endPoint, localEndPoint);
AnnouncerSockets.Add(socket);
}
catch (Exception ex)
{
Intelliplex.Log.Warn("Could not add announcer socket: " + ex.Message);
}
}
}
}
//Add default announcement sockets?
if (useDefault)
{
ConfigureDefaultAnnouncerSockets();
}
}
So it turns out this is related to a bug in PostSharp. I had been using PostSharp but removed all aspects from my code and ensured that none were applied. I also verified with Reflector that the methods were intact in the assembly. However, it appears simply referencing PostSharp triggers some kind of manipulation of the debugging symbols that causes this problem. A (little) more information can be found here:
http://www.sharpcrafters.com/forum/Topic5794-21-1.aspx#bm7927
Also, in the release notes for the latest PostSharp hotfix states one of the fixed issues in hotfix 2.1.5.6 is "Debugging symbols: local variable symbols lost in implicit iterators."
When I installed the latest and greatest PostSharp the problem went away and the universe returned to normal. Hopefully this question/answer will help anyone else using PostSharp who stumbles on this odd behavior before the next official PostSharp release. Make sure you're on hotfix 2.1.5.6 or greater (given the severity of the bug, this probably should have been an actual release).
Thanks for all the help everyone.

is there a better way to handle RPC_E_CALL_REJECTED exceptions when doing visual studio automation?

this is what I'm currently doing:
protected void setupProject()
{
bool lbDone = false;
int liCount = 0;
while (!lbDone && liCount < pMaxRetries)
{
try
{
pProject.ProjectItems.Item("Class1.cs").Delete();
lbDone = true;
}
catch (System.Runtime.InteropServices.COMException loE)
{
liCount++;
if ((uint)loE.ErrorCode == 0x80010001)
{
// RPC_E_CALL_REJECTED - sleep half sec then try again
System.Threading.Thread.Sleep(pDelayBetweenRetry);
}
}
}
}
now I have that try catch block around most calls to the EnvDTE stuff, and it works well enough. The problem I have is when I to loop through a collection and do something to each item once.
foreach(ProjectItem pi in pProject.ProjectItems)
{
// do something to pi
}
Sometimes I get the exception in the foreach(ProjectItem pi in pProject.ProjectItems) line.
Since I don't want to start the foreach loop over if I get the RPC_E_CALL_REJECTED exception I'm not sure what I can do.
Edit to answer comment:
Yes I'm automating VS from another program and yes I usually am using VS for something else at the same time. We have an application that reads an xml file then generates around 50 VS solutions based on the xml file. This usually takes a couple of hours so I try to do other work while this is happening.
There is a solution on this MSDN page: How to: Fix 'Application is Busy' and 'Call was Rejected By Callee' Errors. It shows how to implement a COM IOleMessageFilter interface so that it will automatically retry the call.
First, Hans doesn't want to say so but the best answer to "how to do this" is "don't do this". Just use separate instances of visual studio for your automation and your other work, if at all possible.
You need to take your problem statement out somewhere you can handle the error. You can do this by using in integer index instead of foreach.
// You might also need try/catch for this!
int cProjectItems = pProject.ProjectItems.Length;
for(iProjectItem = 0; iProjectItem < cProjectItems; iProjectItem++)
{
bool bSucceeded = false;
while(!bSucceeded)
{
try{
ProjectItem pi = pProject.ProjectItems[iProjectItem];
// do something with pi
bSucceeded = true;
}catch (System.Runtime.InteropServices.COMException loE)
{
liCount++;
if ((uint)loE.ErrorCode == 0x80010001) {
// RPC_E_CALL_REJECTED - sleep half sec then try again
System.Threading.Thread.Sleep(pDelayBetweenRetry);
}
}
}
}
I didn't have much luck with the recommended way from MSDN, and it seemed rather complicated. What I have done is to wrap up the re-try logic, rather like in the original post, into a generic utility function. You call it like this:
Projects projects = Utils.call( () => (m_dteSolution.Projects) );
The 'call' function calls the function (passed in as a lambda expression) and will retry if necessary. Because it is a generic function, you can use it to call any EnvDTE properties or methods, and it will return the correct type.
Here's the code for the function:
public static T call<T>(Func<T> fn)
{
// We will try to call the function up to 100 times...
for (int i=0; i<100; ++i)
{
try
{
// We call the function passed in and return the result...
return fn();
}
catch (COMException)
{
// We've caught a COM exception, which is most likely
// a Server is Busy exception. So we sleep for a short
// while, and then try again...
Thread.Sleep(1);
}
}
throw new Exception("'call' failed to call function after 100 tries.");
}
As the original post says, foreach over EnvDTE collections can be a problem as there are implicit calls during the looping. So I use my 'call' function to get the Count proprty and then iterate using an index. It's uglier than foreach, but the 'call' function makes it not so bad, as there aren't so many try...catches around. For example:
int numProjects = Utils.call(() => (projects.Count));
for (int i = 1; i <= numProjects; ++i)
{
Project project = Utils.call(() => (projects.Item(i)));
parseProject(project);
}
I was getting the same error using C# to read/write to Excel. Oddly, it worked in debug mode but not on a deployed machine. I simply changed the Excel app to be Visible, and it works properly, albeit about twice as slow. It is annoying to have an Excel app open and close dynamically on your screen, but this seems to be the simplest work-around for Excel.
Microsoft.Office.Interop.Excel.Application oApp = new ApplicationClass();
oApp.Visible = true;
oApp.DisplayAlerts = false;

How to check if public MSMQ is empty

Is there any way to check if a public MSMQ is empty? For a private MSMQ it's easy:
private bool IsQueueEmpty(string path)
{
bool isQueueEmpty = false;
var myQueue = new MessageQueue(path);
try
{
myQueue.Peek(new TimeSpan(0));
isQueueEmpty = false;
}
catch (MessageQueueException e)
{
if (e.MessageQueueErrorCode == MessageQueueErrorCode.IOTimeout)
{
isQueueEmpty = true;
}
}
return isQueueEmpty;
}
How would I do the same check for a public MSMQ? If I try to check a public MSMQ with the code above it gives me an error on the Peak:
System.ArgumentOutOfRangeException: Length cannot be less than zero.
I just started working with Message Queues but my coworker has this nice way for checking if a queue is empty:
if (MessageQueue.Exists(fullQueuePath))
{
// FYI, GetMessageQueue() is a helper method we use to consolidate the code
using (var messageQueue = GetMessageQueue(fullQueuePath))
{
var queueEnum = messageQueue.GetMessageEnumerator2();
if (queueEnum.MoveNext())
{
// Queue not empty
}
else
{
// Queue empty
}
}
}
The benefit of using this method is that it doesn't throw an exception, and I don't think it requires you to wait for a timeout to occur.
The Peek method is only available on remote machines when you use a direct format name to access the queue. You should be able to use the same code, so long as you're not relying on directory services to get you to the queue.
Direct queue names generally look something like: DIRECT=URLAddressSpecification/QueueName
Leo, you sure about that? You can't use a path name with a remote Peek? The error returned doesn't say invalid format name, which would be expected if that was the case.
In fact the error appears to be on the "isQueueEmpty = false" line - the try/catch doesn't differentiate between the peek and the isQueueEmpty lines.
I bet the isQueueEmpty call is receiving an exception which translates to an negative number.
Now your solution, though, may be correct - a lot of remote calls in MSMQ require format names instead of path names. So if you use a format name for creating myQueue, the isQueueEmpty should work.
Cheers
John Breakwell
To check if the queue is empty the simplest way is to use the method: GetAllMessages(). If there are zero messages, then the queue is empty.
string queueName = ".\private$\testqueue";
if (MessageQueue.Exists(queueName))
{
MessageQueue mq = new MessageQueue(queueName);
var allMessages = mq.GetAllMessages();
if (allMessages.Length > 0)
{
//Queue is not empty
}
else
{
//Queue is empty
}
}
else
{
//Queue does not exist
}

Categories